From 2459882d3fb6c3223186c3b50e54e0e22cc29d09 Mon Sep 17 00:00:00 2001 From: DatDang Date: Mon, 18 Nov 2024 15:59:22 +0700 Subject: [PATCH] Fix double scrolling editor --- CHANGELOG.md | 3 + example/lib/main.dart | 528 ++++++++++---------- lib/src/html_editor_controller_web.dart | 6 +- lib/src/widgets/html_editor_widget_web.dart | 104 ++-- pubspec.yaml | 2 +- 5 files changed, 307 insertions(+), 336 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a68c8167..f4e18aa1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## [3.1.2] - 2024-11-20 +* Let editor decide its own height instead of calculating html content + ## [3.1.1] - 2024-08-20 * Support `Copy & Paste` multiple images diff --git a/example/lib/main.dart b/example/lib/main.dart index 4b7ea32e..e14462da 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -64,11 +64,11 @@ class _HtmlEditorExampleState extends State { child: const Text(r'<\>', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)), ), - body: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - HtmlEditor( + body: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: HtmlEditor( controller: controller, htmlEditorOptions: const HtmlEditorOptions( hint: 'Your text here...', @@ -172,279 +172,279 @@ class _HtmlEditorExampleState extends State { }), ], ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - TextButton( - style: TextButton.styleFrom( - backgroundColor: Colors.blueGrey), - onPressed: () { - controller.undo(); - }, - child: const Text('Undo', - style: TextStyle(color: Colors.white)), - ), - const SizedBox( - width: 16, - ), - TextButton( - style: TextButton.styleFrom( - backgroundColor: Colors.blueGrey), - onPressed: () { - controller.clear(); - }, - child: const Text('Reset', - style: TextStyle(color: Colors.white)), - ), - const SizedBox( - width: 16, - ), - TextButton( - style: TextButton.styleFrom( - backgroundColor: - Theme.of(context).colorScheme.secondary), - onPressed: () async { - var txt = await controller.getText(); - if (txt.contains('src=\"data:')) { - txt = - ''; - } - setState(() { - result = txt; - }); - }, - child: const Text( - 'Submit', - style: TextStyle(color: Colors.white), - ), - ), - const SizedBox( - width: 16, + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextButton( + style: TextButton.styleFrom( + backgroundColor: Colors.blueGrey), + onPressed: () { + controller.undo(); + }, + child: const Text('Undo', + style: TextStyle(color: Colors.white)), + ), + const SizedBox( + width: 16, + ), + TextButton( + style: TextButton.styleFrom( + backgroundColor: Colors.blueGrey), + onPressed: () { + controller.clear(); + }, + child: const Text('Reset', + style: TextStyle(color: Colors.white)), + ), + const SizedBox( + width: 16, + ), + TextButton( + style: TextButton.styleFrom( + backgroundColor: + Theme.of(context).colorScheme.secondary), + onPressed: () async { + var txt = await controller.getText(); + if (txt.contains('src=\"data:')) { + txt = + ''; + } + setState(() { + result = txt; + }); + }, + child: const Text( + 'Submit', + style: TextStyle(color: Colors.white), ), - TextButton( - style: TextButton.styleFrom( - backgroundColor: - Theme.of(context).colorScheme.secondary), - onPressed: () { - controller.redo(); - }, - child: const Text( - 'Redo', - style: TextStyle(color: Colors.white), - ), + ), + const SizedBox( + width: 16, + ), + TextButton( + style: TextButton.styleFrom( + backgroundColor: + Theme.of(context).colorScheme.secondary), + onPressed: () { + controller.redo(); + }, + child: const Text( + 'Redo', + style: TextStyle(color: Colors.white), ), - ], - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text(result), + ), + ], ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - TextButton( - style: TextButton.styleFrom( - backgroundColor: Colors.blueGrey), - onPressed: () { - controller.disable(); - }, - child: const Text('Disable', - style: TextStyle(color: Colors.white)), - ), - const SizedBox( - width: 16, - ), - TextButton( - style: TextButton.styleFrom( - backgroundColor: - Theme.of(context).colorScheme.secondary), - onPressed: () async { - controller.enable(); - }, - child: const Text( - 'Enable', - style: TextStyle(color: Colors.white), - ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text(result), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextButton( + style: TextButton.styleFrom( + backgroundColor: Colors.blueGrey), + onPressed: () { + controller.disable(); + }, + child: const Text('Disable', + style: TextStyle(color: Colors.white)), + ), + const SizedBox( + width: 16, + ), + TextButton( + style: TextButton.styleFrom( + backgroundColor: + Theme.of(context).colorScheme.secondary), + onPressed: () async { + controller.enable(); + }, + child: const Text( + 'Enable', + style: TextStyle(color: Colors.white), ), - ], - ), + ), + ], ), - const SizedBox(height: 16), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - TextButton( - style: TextButton.styleFrom( - backgroundColor: - Theme.of(context).colorScheme.secondary), - onPressed: () { - controller.insertText('Google'); - }, - child: const Text('Insert Text', - style: TextStyle(color: Colors.white)), - ), - const SizedBox( - width: 16, - ), - TextButton( - style: TextButton.styleFrom( - backgroundColor: - Theme.of(context).colorScheme.secondary), - onPressed: () { - controller.insertHtml( - '''

Google in blue

'''); - }, - child: const Text('Insert HTML', - style: TextStyle(color: Colors.white)), - ), - ], - ), + ), + const SizedBox(height: 16), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextButton( + style: TextButton.styleFrom( + backgroundColor: + Theme.of(context).colorScheme.secondary), + onPressed: () { + controller.insertText('Google'); + }, + child: const Text('Insert Text', + style: TextStyle(color: Colors.white)), + ), + const SizedBox( + width: 16, + ), + TextButton( + style: TextButton.styleFrom( + backgroundColor: + Theme.of(context).colorScheme.secondary), + onPressed: () { + controller.insertHtml( + '''

Google in blue

'''); + }, + child: const Text('Insert HTML', + style: TextStyle(color: Colors.white)), + ), + ], ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - TextButton( - style: TextButton.styleFrom( - backgroundColor: - Theme.of(context).colorScheme.secondary), - onPressed: () async { - controller.insertLink( - 'Google linked', 'https://google.com', true); - }, - child: const Text( - 'Insert Link', - style: TextStyle(color: Colors.white), - ), - ), - const SizedBox( - width: 16, + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextButton( + style: TextButton.styleFrom( + backgroundColor: + Theme.of(context).colorScheme.secondary), + onPressed: () async { + controller.insertLink( + 'Google linked', 'https://google.com', true); + }, + child: const Text( + 'Insert Link', + style: TextStyle(color: Colors.white), ), - TextButton( - style: TextButton.styleFrom( - backgroundColor: - Theme.of(context).colorScheme.secondary), - onPressed: () { - controller.insertNetworkImage( - 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png', - filename: 'Google network image'); - }, - child: const Text( - 'Insert network image', - style: TextStyle(color: Colors.white), - ), + ), + const SizedBox( + width: 16, + ), + TextButton( + style: TextButton.styleFrom( + backgroundColor: + Theme.of(context).colorScheme.secondary), + onPressed: () { + controller.insertNetworkImage( + 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png', + filename: 'Google network image'); + }, + child: const Text( + 'Insert network image', + style: TextStyle(color: Colors.white), ), - ], - ), + ), + ], ), - const SizedBox(height: 16), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - TextButton( - style: TextButton.styleFrom( - backgroundColor: Colors.blueGrey), - onPressed: () { - controller.addNotification( - 'Info notification', NotificationType.info); - }, - child: const Text('Info', - style: TextStyle(color: Colors.white)), - ), - const SizedBox( - width: 16, - ), - TextButton( - style: TextButton.styleFrom( - backgroundColor: Colors.blueGrey), - onPressed: () { - controller.addNotification( - 'Warning notification', NotificationType.warning); - }, - child: const Text('Warning', - style: TextStyle(color: Colors.white)), - ), - const SizedBox( - width: 16, - ), - TextButton( - style: TextButton.styleFrom( - backgroundColor: - Theme.of(context).colorScheme.secondary), - onPressed: () async { - controller.addNotification( - 'Success notification', NotificationType.success); - }, - child: const Text( - 'Success', - style: TextStyle(color: Colors.white), - ), - ), - const SizedBox( - width: 16, + ), + const SizedBox(height: 16), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextButton( + style: TextButton.styleFrom( + backgroundColor: Colors.blueGrey), + onPressed: () { + controller.addNotification( + 'Info notification', NotificationType.info); + }, + child: const Text('Info', + style: TextStyle(color: Colors.white)), + ), + const SizedBox( + width: 16, + ), + TextButton( + style: TextButton.styleFrom( + backgroundColor: Colors.blueGrey), + onPressed: () { + controller.addNotification( + 'Warning notification', NotificationType.warning); + }, + child: const Text('Warning', + style: TextStyle(color: Colors.white)), + ), + const SizedBox( + width: 16, + ), + TextButton( + style: TextButton.styleFrom( + backgroundColor: + Theme.of(context).colorScheme.secondary), + onPressed: () async { + controller.addNotification( + 'Success notification', NotificationType.success); + }, + child: const Text( + 'Success', + style: TextStyle(color: Colors.white), ), - TextButton( - style: TextButton.styleFrom( - backgroundColor: - Theme.of(context).colorScheme.secondary), - onPressed: () { - controller.addNotification( - 'Danger notification', NotificationType.danger); - }, - child: const Text( - 'Danger', - style: TextStyle(color: Colors.white), - ), + ), + const SizedBox( + width: 16, + ), + TextButton( + style: TextButton.styleFrom( + backgroundColor: + Theme.of(context).colorScheme.secondary), + onPressed: () { + controller.addNotification( + 'Danger notification', NotificationType.danger); + }, + child: const Text( + 'Danger', + style: TextStyle(color: Colors.white), ), - ], - ), + ), + ], ), - const SizedBox(height: 16), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - TextButton( - style: TextButton.styleFrom( - backgroundColor: Colors.blueGrey), - onPressed: () { - controller.addNotification('Plaintext notification', - NotificationType.plaintext); - }, - child: const Text('Plaintext', - style: TextStyle(color: Colors.white)), + ), + const SizedBox(height: 16), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextButton( + style: TextButton.styleFrom( + backgroundColor: Colors.blueGrey), + onPressed: () { + controller.addNotification('Plaintext notification', + NotificationType.plaintext); + }, + child: const Text('Plaintext', + style: TextStyle(color: Colors.white)), + ), + const SizedBox( + width: 16, + ), + TextButton( + style: TextButton.styleFrom( + backgroundColor: + Theme.of(context).colorScheme.secondary), + onPressed: () async { + controller.removeNotification(); + }, + child: const Text( + 'Remove', + style: TextStyle(color: Colors.white), ), - const SizedBox( - width: 16, - ), - TextButton( - style: TextButton.styleFrom( - backgroundColor: - Theme.of(context).colorScheme.secondary), - onPressed: () async { - controller.removeNotification(); - }, - child: const Text( - 'Remove', - style: TextStyle(color: Colors.white), - ), - ), - ], - ), + ), + ], ), - ], - ), + ), + ], ), ), ); diff --git a/lib/src/html_editor_controller_web.dart b/lib/src/html_editor_controller_web.dart index 88e7f344..a2dcdac5 100644 --- a/lib/src/html_editor_controller_web.dart +++ b/lib/src/html_editor_controller_web.dart @@ -245,11 +245,7 @@ class HtmlEditorController extends unsupported.HtmlEditorController { /// Recalculates the height of the editor to remove any vertical scrolling. /// This method will not do anything if [autoAdjustHeight] is turned off. @override - void recalculateHeight() { - _evaluateJavascriptWeb(data: { - 'type': 'toIframe: getHeight', - }); - } + void recalculateHeight() {} /// A function to quickly call a document.execCommand function in a readable format @override diff --git a/lib/src/widgets/html_editor_widget_web.dart b/lib/src/widgets/html_editor_widget_web.dart index af5279b5..b3ba636b 100644 --- a/lib/src/widgets/html_editor_widget_web.dart +++ b/lib/src/widgets/html_editor_widget_web.dart @@ -47,9 +47,6 @@ class _HtmlEditorWidgetWebState extends State { /// The view ID for the IFrameElement. Must be unique. late String createdViewId; - /// The actual height of the editor, used to automatically set the height - late double actualHeight; - /// A Future that is observed by the [FutureBuilder]. We don't use a function /// as the Future on the [FutureBuilder] because when the widget is rebuilt, /// the function may be excessively called, hurting performance. @@ -71,7 +68,6 @@ class _HtmlEditorWidgetWebState extends State { @override void initState() { - actualHeight = widget.otherOptions.height; createdViewId = getRandString(10); widget.controller.viewId = createdViewId; initSummernote(); @@ -94,6 +90,12 @@ class _HtmlEditorWidgetWebState extends State { var headString = ''; var summernoteCallbacks = '''callbacks: { onKeydown: function(e) { + if (e.keyCode === 13) { /* ENTER */ + const editor = querySelector('.note-editable'); + editor.blur(); + editor.focus(); + } + var chars = \$(".note-editable").text(); var totalChars = chars.length; ${widget.htmlEditorOptions.characterLimit != null ? '''allowedKeys = ( @@ -250,7 +252,6 @@ class _HtmlEditorWidgetWebState extends State { \$('#summernote-2').summernote({ placeholder: "${widget.htmlEditorOptions.hint}", tabsize: 2, - height: ${widget.otherOptions.height}, disableResizeEditor: false, disableDragAndDrop: ${widget.htmlEditorOptions.disableDragAndDrop}, disableGrammar: false, @@ -282,10 +283,6 @@ class _HtmlEditorWidgetWebState extends State { var str = \$('#summernote-2').summernote('code'); window.parent.postMessage(JSON.stringify({"type": "toDart: getTextWithSignatureContent", "text": str}), "*"); } - if (data["type"].includes("getHeight")) { - var height = document.body.scrollHeight; - window.parent.postMessage(JSON.stringify({"view": "$createdViewId", "type": "toDart: htmlHeight", "height": height}), "*"); - } if (data["type"].includes("setInputType")) { document.getElementsByClassName('note-editable')[0].setAttribute('inputmode', '${widget.htmlEditorOptions.inputType.name}'); } @@ -617,9 +614,6 @@ class _HtmlEditorWidgetWebState extends State { } final iframe = html.IFrameElement() ..width = maxWidth - ..height = widget.htmlEditorOptions.autoAdjustHeight - ? actualHeight.toString() - : widget.otherOptions.height.toString() // ignore: unsafe_html, necessary to load HTML string ..srcdoc = htmlString ..style.border = 'none' @@ -635,45 +629,37 @@ class _HtmlEditorWidgetWebState extends State { @override Widget build(BuildContext context) { - final child = SizedBox( - height: widget.htmlEditorOptions.autoAdjustHeight - ? actualHeight - : widget.otherOptions.height, - child: Column( - children: [ - widget.htmlToolbarOptions.toolbarPosition == ToolbarPosition.aboveEditor - ? ToolbarWidget( - key: toolbarKey, - controller: widget.controller, - htmlToolbarOptions: widget.htmlToolbarOptions, - callbacks: widget.callbacks) - : const SizedBox(height: 0, width: 0), - Expanded( - child: Directionality( - textDirection: TextDirection.ltr, - child: FutureBuilder( - future: summernoteInit, - builder: (context, snapshot) { - if (snapshot.hasData) { - return HtmlElementView( - viewType: createdViewId, - ); - } else { - return Container( - height: widget.htmlEditorOptions.autoAdjustHeight - ? actualHeight - : widget.otherOptions.height); - } - }))), - widget.htmlToolbarOptions.toolbarPosition == ToolbarPosition.belowEditor - ? ToolbarWidget( - key: toolbarKey, - controller: widget.controller, - htmlToolbarOptions: widget.htmlToolbarOptions, - callbacks: widget.callbacks) - : const SizedBox(height: 0, width: 0), - ], - ), + final child = Column( + children: [ + widget.htmlToolbarOptions.toolbarPosition == ToolbarPosition.aboveEditor + ? ToolbarWidget( + key: toolbarKey, + controller: widget.controller, + htmlToolbarOptions: widget.htmlToolbarOptions, + callbacks: widget.callbacks) + : const SizedBox(height: 0, width: 0), + Expanded( + child: Directionality( + textDirection: TextDirection.ltr, + child: FutureBuilder( + future: summernoteInit, + builder: (context, snapshot) { + if (snapshot.hasData) { + return HtmlElementView( + viewType: createdViewId, + ); + } else { + return const SizedBox.shrink(); + } + }))), + widget.htmlToolbarOptions.toolbarPosition == ToolbarPosition.belowEditor + ? ToolbarWidget( + key: toolbarKey, + controller: widget.controller, + htmlToolbarOptions: widget.htmlToolbarOptions, + callbacks: widget.callbacks) + : const SizedBox(height: 0, width: 0), + ], ); return Focus( @@ -914,7 +900,7 @@ class _HtmlEditorWidgetWebState extends State { widget.callbacks?.onInitialTextLoadComplete?.call( widget.htmlEditorOptions.initialText!); } - var data = {'type': 'toIframe: getHeight'}; + var data = {}; data['view'] = createdViewId; var data2 = {'type': 'toIframe: setInputType'}; data2['view'] = createdViewId; @@ -922,20 +908,6 @@ class _HtmlEditorWidgetWebState extends State { var jsonStr2 = _jsonEncoder.convert(data2); _summernoteOnLoadListener = html.window.onMessage.listen((event) { var data = json.decode(event.data); - if (data['type'] != null && - data['type'].contains('toDart: htmlHeight') && - data['view'] == createdViewId && - widget.htmlEditorOptions.autoAdjustHeight) { - final docHeight = data['height'] ?? actualHeight; - if ((docHeight != null && docHeight != actualHeight) && - mounted && - docHeight > 0) { - setState(mounted, this.setState, () { - actualHeight = - docHeight + (toolbarKey.currentContext?.size?.height ?? 0); - }); - } - } if (data['type'] != null && data['type'].contains('toDart: onChangeContent') && data['view'] == createdViewId) { diff --git a/pubspec.yaml b/pubspec.yaml index 2bb52ca3..10cfc86e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: html_editor_enhanced description: HTML rich text editor for Android, iOS, and Web, using the Summernote library. Enhanced with highly customizable widget-based controls, bug fixes, callbacks, dark mode, and more. -version: 3.1.1 +version: 3.1.2 homepage: https://github.com/tneotia/html-editor-enhanced environment: