From 3f38e246ea294f6f21f8e8ef917e0fbc118c2c21 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Mon, 29 Aug 2022 15:56:33 +0800 Subject: [PATCH] feat: support customizing editor edges --- .../appflowy_editor/example/lib/main.dart | 9 +-- .../appflowy_editor/lib/appflowy_editor.dart | 1 + .../appflowy_editor/lib/src/editor_state.dart | 4 ++ .../src/render/image/image_node_widget.dart | 18 +++--- .../render/rich_text/bulleted_list_text.dart | 43 ++++++------- .../src/render/rich_text/checkbox_text.dart | 63 +++++++++---------- .../src/render/rich_text/heading_text.dart | 17 +++-- .../render/rich_text/number_list_text.dart | 39 ++++++------ .../lib/src/render/rich_text/quoted_text.dart | 43 ++++++------- .../lib/src/render/rich_text/rich_text.dart | 15 ++--- .../src/render/rich_text/rich_text_style.dart | 1 - .../lib/src/render/style/editor_style.dart | 20 ++++++ .../lib/src/service/editor_service.dart | 47 ++++++++------ .../render/image/image_node_builder_test.dart | 9 +-- 14 files changed, 172 insertions(+), 157 deletions(-) create mode 100644 frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/editor_style.dart diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart index 8f6bf64c30..4bd1cb1972 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart @@ -46,13 +46,7 @@ class _MyHomePageState extends State { Widget build(BuildContext context) { return Scaffold( extendBodyBehindAppBar: true, - body: Center( - child: Container( - width: 780, - alignment: Alignment.topCenter, - child: _buildEditor(context), - ), - ), + body: _buildEditor(context), floatingActionButton: _buildExpandableFab(), ); } @@ -100,6 +94,7 @@ class _MyHomePageState extends State { width: MediaQuery.of(context).size.width, child: AppFlowyEditor( editorState: _editorState, + editorStyle: const EditorStyle.defaultStyle(), ), ); } else { diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart b/frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart index 14826ff713..12b3a29252 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart @@ -2,6 +2,7 @@ library appflowy_editor; export 'src/infra/log.dart'; +export 'src/render/style/editor_style.dart'; export 'src/document/node.dart'; export 'src/document/path.dart'; export 'src/document/position.dart'; diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/editor_state.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/editor_state.dart index 396b428baf..2750af07a6 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/editor_state.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/editor_state.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:appflowy_editor/src/infra/log.dart'; import 'package:appflowy_editor/src/render/selection_menu/selection_menu_widget.dart'; +import 'package:appflowy_editor/src/render/style/editor_style.dart'; import 'package:appflowy_editor/src/service/service.dart'; import 'package:flutter/material.dart'; @@ -58,6 +59,9 @@ class EditorState { /// Stores the selection menu items. List selectionMenuItems = []; + /// Stores the editor style. + EditorStyle editorStyle = const EditorStyle.defaultStyle(); + final UndoManager undoManager = UndoManager(); Selection? _cursorSelection; diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/image/image_node_widget.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/image/image_node_widget.dart index 316202b1c7..a65df11541 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/image/image_node_widget.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/image/image_node_widget.dart @@ -1,10 +1,8 @@ -import 'dart:math'; - +import 'package:appflowy_editor/src/extensions/object_extensions.dart'; import 'package:appflowy_editor/src/document/node.dart'; import 'package:appflowy_editor/src/document/position.dart'; import 'package:appflowy_editor/src/document/selection.dart'; import 'package:appflowy_editor/src/infra/flowy_svg.dart'; -import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart'; import 'package:appflowy_editor/src/render/selection/selectable.dart'; import 'package:flutter/material.dart'; @@ -35,6 +33,8 @@ class ImageNodeWidget extends StatefulWidget { } class _ImageNodeWidgetState extends State with Selectable { + final _imageKey = GlobalKey(); + double? _imageWidth; double _initial = 0; double _distance = 0; @@ -50,8 +50,11 @@ class _ImageNodeWidgetState extends State with Selectable { _imageWidth = widget.width; _imageStreamListener = ImageStreamListener( (image, _) { - _imageWidth = - min(defaultMaxTextNodeWidth, image.image.width.toDouble()); + _imageWidth = _imageKey.currentContext + ?.findRenderObject() + ?.unwrapOrNull() + ?.size + .width; }, ); } @@ -65,9 +68,8 @@ class _ImageNodeWidgetState extends State with Selectable { @override Widget build(BuildContext context) { // only support network image. - return Container( - width: defaultMaxTextNodeWidth, + key: _imageKey, padding: const EdgeInsets.only(top: 8, bottom: 8), child: _buildNetworkImage(context), ); @@ -137,7 +139,7 @@ class _ImageNodeWidgetState extends State with Selectable { loadingBuilder: (context, child, loadingProgress) => loadingProgress == null ? child : _buildLoading(context), errorBuilder: (context, error, stackTrace) { - _imageWidth ??= defaultMaxTextNodeWidth; + // _imageWidth ??= defaultMaxTextNodeWidth; return _buildError(context); }, ); diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/bulleted_list_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/bulleted_list_text.dart index 5408f862d8..7f0f0363f8 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/bulleted_list_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/bulleted_list_text.dart @@ -56,30 +56,27 @@ class _BulletedListTextNodeWidgetState extends State @override Widget build(BuildContext context) { - return Container( - constraints: BoxConstraints(maxWidth: defaultMaxTextNodeWidth), - child: Padding( - padding: EdgeInsets.only(bottom: defaultLinePadding), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - FlowySvg( - key: iconKey, - width: _iconWidth, - height: _iconWidth, - padding: EdgeInsets.only(right: _iconRightPadding), - name: 'point', + return Padding( + padding: EdgeInsets.only(bottom: defaultLinePadding), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FlowySvg( + key: iconKey, + width: _iconWidth, + height: _iconWidth, + padding: EdgeInsets.only(right: _iconRightPadding), + name: 'point', + ), + Flexible( + child: FlowyRichText( + key: _richTextKey, + placeholderText: 'List', + textNode: widget.textNode, + editorState: widget.editorState, ), - Flexible( - child: FlowyRichText( - key: _richTextKey, - placeholderText: 'List', - textNode: widget.textNode, - editorState: widget.editorState, - ), - ) - ], - ), + ) + ], ), ); } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart index bfda4e3f73..ed6748a43e 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart @@ -63,41 +63,38 @@ class _CheckboxNodeWidgetState extends State Widget _buildWithSingle(BuildContext context) { final check = widget.textNode.attributes.check; - return SizedBox( - width: defaultMaxTextNodeWidth, - child: Padding( - padding: EdgeInsets.only(bottom: defaultLinePadding), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - GestureDetector( - key: iconKey, - child: FlowySvg( - width: _iconWidth, - height: _iconWidth, - padding: EdgeInsets.only(right: _iconRightPadding), - name: check ? 'check' : 'uncheck', - ), - onTap: () { - TransactionBuilder(widget.editorState) - ..updateNode(widget.textNode, { - StyleKey.checkbox: !check, - }) - ..commit(); - }, + return Padding( + padding: EdgeInsets.only(bottom: defaultLinePadding), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + GestureDetector( + key: iconKey, + child: FlowySvg( + width: _iconWidth, + height: _iconWidth, + padding: EdgeInsets.only(right: _iconRightPadding), + name: check ? 'check' : 'uncheck', ), - Flexible( - child: FlowyRichText( - key: _richTextKey, - placeholderText: 'To-do', - textNode: widget.textNode, - textSpanDecorator: _textSpanDecorator, - placeholderTextSpanDecorator: _textSpanDecorator, - editorState: widget.editorState, - ), + onTap: () { + TransactionBuilder(widget.editorState) + ..updateNode(widget.textNode, { + StyleKey.checkbox: !check, + }) + ..commit(); + }, + ), + Flexible( + child: FlowyRichText( + key: _richTextKey, + placeholderText: 'To-do', + textNode: widget.textNode, + textSpanDecorator: _textSpanDecorator, + placeholderTextSpanDecorator: _textSpanDecorator, + editorState: widget.editorState, ), - ], - ), + ), + ], ), ); } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/heading_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/heading_text.dart index 7b94783f03..fff25dd2d5 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/heading_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/heading_text.dart @@ -63,16 +63,13 @@ class _HeadingTextNodeWidgetState extends State top: _topPadding, bottom: defaultLinePadding, ), - child: Container( - constraints: BoxConstraints(maxWidth: defaultMaxTextNodeWidth), - child: FlowyRichText( - key: _richTextKey, - placeholderText: 'Heading', - placeholderTextSpanDecorator: _placeholderTextSpanDecorator, - textSpanDecorator: _textSpanDecorator, - textNode: widget.textNode, - editorState: widget.editorState, - ), + child: FlowyRichText( + key: _richTextKey, + placeholderText: 'Heading', + placeholderTextSpanDecorator: _placeholderTextSpanDecorator, + textSpanDecorator: _textSpanDecorator, + textNode: widget.textNode, + editorState: widget.editorState, ), ); } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/number_list_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/number_list_text.dart index a4d72bb011..36cf91bdce 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/number_list_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/number_list_text.dart @@ -58,28 +58,25 @@ class _NumberListTextNodeWidgetState extends State Widget build(BuildContext context) { return Padding( padding: EdgeInsets.only(bottom: defaultLinePadding), - child: SizedBox( - width: defaultMaxTextNodeWidth, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - FlowySvg( - key: iconKey, - width: _iconWidth, - height: _iconWidth, - padding: EdgeInsets.only(right: _iconRightPadding), - number: widget.textNode.attributes.number, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FlowySvg( + key: iconKey, + width: _iconWidth, + height: _iconWidth, + padding: EdgeInsets.only(right: _iconRightPadding), + number: widget.textNode.attributes.number, + ), + Flexible( + child: FlowyRichText( + key: _richTextKey, + placeholderText: 'List', + textNode: widget.textNode, + editorState: widget.editorState, ), - Flexible( - child: FlowyRichText( - key: _richTextKey, - placeholderText: 'List', - textNode: widget.textNode, - editorState: widget.editorState, - ), - ), - ], - ), + ), + ], )); } } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/quoted_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/quoted_text.dart index 04ae379799..9c2366d1cb 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/quoted_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/quoted_text.dart @@ -55,30 +55,27 @@ class _QuotedTextNodeWidgetState extends State @override Widget build(BuildContext context) { - return SizedBox( - width: defaultMaxTextNodeWidth, - child: Padding( - padding: EdgeInsets.only(bottom: defaultLinePadding), - child: IntrinsicHeight( - child: Row( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - FlowySvg( - key: iconKey, - width: _iconWidth, - padding: EdgeInsets.only(right: _iconRightPadding), - name: 'quote', + return Padding( + padding: EdgeInsets.only(bottom: defaultLinePadding), + child: IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + FlowySvg( + key: iconKey, + width: _iconWidth, + padding: EdgeInsets.only(right: _iconRightPadding), + name: 'quote', + ), + Flexible( + child: FlowyRichText( + key: _richTextKey, + placeholderText: 'Quote', + textNode: widget.textNode, + editorState: widget.editorState, ), - Flexible( - child: FlowyRichText( - key: _richTextKey, - placeholderText: 'Quote', - textNode: widget.textNode, - editorState: widget.editorState, - ), - ), - ], - ), + ), + ], ), ), ); diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/rich_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/rich_text.dart index 5fe65db4b7..b9a3e2f314 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/rich_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/rich_text.dart @@ -52,15 +52,12 @@ class _RichTextNodeWidgetState extends State @override Widget build(BuildContext context) { - return Container( - constraints: BoxConstraints(maxWidth: defaultMaxTextNodeWidth), - child: Padding( - padding: EdgeInsets.only(bottom: defaultLinePadding), - child: FlowyRichText( - key: _richTextKey, - textNode: widget.textNode, - editorState: widget.editorState, - ), + return Padding( + padding: EdgeInsets.only(bottom: defaultLinePadding), + child: FlowyRichText( + key: _richTextKey, + textNode: widget.textNode, + editorState: widget.editorState, ), ); } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/rich_text_style.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/rich_text_style.dart index efcdd3790f..6270127610 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/rich_text_style.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/rich_text_style.dart @@ -61,7 +61,6 @@ class StyleKey { } // TODO: customize -double defaultMaxTextNodeWidth = 780.0; double defaultLinePadding = 8.0; double baseFontSize = 16.0; String defaultHighlightColor = '0x6000BCF0'; diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/editor_style.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/editor_style.dart new file mode 100644 index 0000000000..e691ea689e --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/editor_style.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; + +/// Editor style configuration +class EditorStyle { + const EditorStyle({ + required this.padding, + }); + + const EditorStyle.defaultStyle() + : padding = const EdgeInsets.fromLTRB(200.0, 0.0, 200.0, 0.0); + + /// The margin of the document context from the editor. + final EdgeInsets padding; + + EditorStyle copyWith({EdgeInsets? padding}) { + return EditorStyle( + padding: padding ?? this.padding, + ); + } +} diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/editor_service.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/editor_service.dart index 2781471b46..3a8d75560b 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/editor_service.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/editor_service.dart @@ -1,5 +1,6 @@ import 'package:appflowy_editor/src/render/image/image_node_builder.dart'; import 'package:appflowy_editor/src/render/selection_menu/selection_menu_widget.dart'; +import 'package:appflowy_editor/src/render/style/editor_style.dart'; import 'package:appflowy_editor/src/service/internal_key_event_handlers/default_key_event_handlers.dart'; import 'package:flutter/material.dart'; @@ -36,6 +37,7 @@ class AppFlowyEditor extends StatefulWidget { this.customBuilders = const {}, this.keyEventHandlers = const [], this.selectionMenuItems = const [], + this.editorStyle = const EditorStyle.defaultStyle(), }) : super(key: key); final EditorState editorState; @@ -48,6 +50,8 @@ class AppFlowyEditor extends StatefulWidget { final List selectionMenuItems; + final EditorStyle editorStyle; + @override State createState() => _AppFlowyEditorState(); } @@ -60,6 +64,7 @@ class _AppFlowyEditorState extends State { super.initState(); editorState.selectionMenuItems = widget.selectionMenuItems; + editorState.editorStyle = widget.editorStyle; editorState.service.renderPluginService = _createRenderPlugin(); } @@ -68,6 +73,8 @@ class _AppFlowyEditorState extends State { super.didUpdateWidget(oldWidget); if (editorState.service != oldWidget.editorState.service) { + editorState.selectionMenuItems = widget.selectionMenuItems; + editorState.editorStyle = widget.editorStyle; editorState.service.renderPluginService = _createRenderPlugin(); } } @@ -76,27 +83,31 @@ class _AppFlowyEditorState extends State { Widget build(BuildContext context) { return AppFlowyScroll( key: editorState.service.scrollServiceKey, - child: AppFlowySelection( - key: editorState.service.selectionServiceKey, - editorState: editorState, - child: AppFlowyInput( - key: editorState.service.inputServiceKey, + child: Padding( + padding: widget.editorStyle.padding, + child: AppFlowySelection( + key: editorState.service.selectionServiceKey, editorState: editorState, - child: AppFlowyKeyboard( - key: editorState.service.keyboardServiceKey, - handlers: [ - ...defaultKeyEventHandlers, - ...widget.keyEventHandlers, - ], + child: AppFlowyInput( + key: editorState.service.inputServiceKey, editorState: editorState, - child: FlowyToolbar( - key: editorState.service.toolbarServiceKey, + child: AppFlowyKeyboard( + key: editorState.service.keyboardServiceKey, + handlers: [ + ...defaultKeyEventHandlers, + ...widget.keyEventHandlers, + ], editorState: editorState, - child: editorState.service.renderPluginService.buildPluginWidget( - NodeWidgetContext( - context: context, - node: editorState.document.root, - editorState: editorState, + child: FlowyToolbar( + key: editorState.service.toolbarServiceKey, + editorState: editorState, + child: + editorState.service.renderPluginService.buildPluginWidget( + NodeWidgetContext( + context: context, + node: editorState.document.root, + editorState: editorState, + ), ), ), ), diff --git a/frontend/app_flowy/packages/appflowy_editor/test/render/image/image_node_builder_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/render/image/image_node_builder_test.dart index 9121fa1868..a9732d8a20 100644 --- a/frontend/app_flowy/packages/appflowy_editor/test/render/image/image_node_builder_test.dart +++ b/frontend/app_flowy/packages/appflowy_editor/test/render/image/image_node_builder_test.dart @@ -49,9 +49,10 @@ void main() async { final editorRect = tester.getRect(editorFinder); final leftImageRect = tester.getRect(imageFinder.at(0)); - expect(leftImageRect.left, editorRect.left); + expect(leftImageRect.left, editor.editorState.editorStyle.padding.left); final rightImageRect = tester.getRect(imageFinder.at(2)); - expect(rightImageRect.right, editorRect.right); + expect(rightImageRect.right, + editorRect.right - editor.editorState.editorStyle.padding.right); final centerImageRect = tester.getRect(imageFinder.at(1)); expect(centerImageRect.left, (leftImageRect.left + rightImageRect.left) / 2.0); @@ -73,8 +74,8 @@ void main() async { leftImage.onAlign(Alignment.centerRight); await tester.pump(const Duration(milliseconds: 100)); expect( - tester.getRect(imageFinder.at(0)).left, - rightImageRect.left, + tester.getRect(imageFinder.at(0)).right, + rightImageRect.right, ); }); });