Browse Source

feat: support customizing editor edges

Lucas.Xu 2 years ago
parent
commit
3f38e246ea

+ 2 - 7
frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart

@@ -46,13 +46,7 @@ class _MyHomePageState extends State<MyHomePage> {
   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<MyHomePage> {
             width: MediaQuery.of(context).size.width,
             child: AppFlowyEditor(
               editorState: _editorState,
+              editorStyle: const EditorStyle.defaultStyle(),
             ),
           );
         } else {

+ 1 - 0
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';

+ 4 - 0
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<SelectionMenuItem> selectionMenuItems = [];
 
+  /// Stores the editor style.
+  EditorStyle editorStyle = const EditorStyle.defaultStyle();
+
   final UndoManager undoManager = UndoManager();
   Selection? _cursorSelection;
 

+ 10 - 8
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<ImageNodeWidget> with Selectable {
+  final _imageKey = GlobalKey();
+
   double? _imageWidth;
   double _initial = 0;
   double _distance = 0;
@@ -50,8 +50,11 @@ class _ImageNodeWidgetState extends State<ImageNodeWidget> with Selectable {
     _imageWidth = widget.width;
     _imageStreamListener = ImageStreamListener(
       (image, _) {
-        _imageWidth =
-            min(defaultMaxTextNodeWidth, image.image.width.toDouble());
+        _imageWidth = _imageKey.currentContext
+            ?.findRenderObject()
+            ?.unwrapOrNull<RenderBox>()
+            ?.size
+            .width;
       },
     );
   }
@@ -65,9 +68,8 @@ class _ImageNodeWidgetState extends State<ImageNodeWidget> 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<ImageNodeWidget> with Selectable {
       loadingBuilder: (context, child, loadingProgress) =>
           loadingProgress == null ? child : _buildLoading(context),
       errorBuilder: (context, error, stackTrace) {
-        _imageWidth ??= defaultMaxTextNodeWidth;
+        // _imageWidth ??= defaultMaxTextNodeWidth;
         return _buildError(context);
       },
     );

+ 20 - 23
frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/bulleted_list_text.dart

@@ -56,30 +56,27 @@ class _BulletedListTextNodeWidgetState extends State<BulletedListTextNodeWidget>
 
   @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,
-              ),
-            )
-          ],
-        ),
+          )
+        ],
       ),
     );
   }

+ 30 - 33
frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart

@@ -63,41 +63,38 @@ class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
 
   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,
             ),
-          ],
-        ),
+          ),
+        ],
       ),
     );
   }

+ 7 - 10
frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/heading_text.dart

@@ -63,16 +63,13 @@ class _HeadingTextNodeWidgetState extends State<HeadingTextNodeWidget>
         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,
       ),
     );
   }

+ 18 - 21
frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/number_list_text.dart

@@ -58,28 +58,25 @@ class _NumberListTextNodeWidgetState extends State<NumberListTextNodeWidget>
   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,
-                ),
-              ),
-            ],
-          ),
+            ),
+          ],
         ));
   }
 }

+ 20 - 23
frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/quoted_text.dart

@@ -55,30 +55,27 @@ class _QuotedTextNodeWidgetState extends State<QuotedTextNodeWidget>
 
   @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,
-                ),
-              ),
-            ],
-          ),
+            ),
+          ],
         ),
       ),
     );

+ 6 - 9
frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/rich_text.dart

@@ -52,15 +52,12 @@ class _RichTextNodeWidgetState extends State<RichTextNodeWidget>
 
   @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,
       ),
     );
   }

+ 0 - 1
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';

+ 20 - 0
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,
+    );
+  }
+}

+ 29 - 18
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<SelectionMenuItem> selectionMenuItems;
 
+  final EditorStyle editorStyle;
+
   @override
   State<AppFlowyEditor> createState() => _AppFlowyEditorState();
 }
@@ -60,6 +64,7 @@ class _AppFlowyEditorState extends State<AppFlowyEditor> {
     super.initState();
 
     editorState.selectionMenuItems = widget.selectionMenuItems;
+    editorState.editorStyle = widget.editorStyle;
     editorState.service.renderPluginService = _createRenderPlugin();
   }
 
@@ -68,6 +73,8 @@ class _AppFlowyEditorState extends State<AppFlowyEditor> {
     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<AppFlowyEditor> {
   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,
+                  ),
                 ),
               ),
             ),

+ 5 - 4
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,
         );
       });
     });