浏览代码

Merge pull request #798 from LucasXu0/feat/text_style

update text style and document style.
Lucas.Xu 2 年之前
父节点
当前提交
a032a0e8db

+ 5 - 1
frontend/app_flowy/packages/flowy_editor/example/assets/example.json

@@ -37,7 +37,11 @@
         "type": "text",
         "delta": [
           {
-            "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow."
+            "insert": "At "
+          },
+          { "insert": "AppFlowy", "attributes": { "code": true, "bold": true, "color": "0xFFED459C"} },
+          {
+            "insert": ", we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow."
           }
         ]
       },

+ 1 - 1
frontend/app_flowy/packages/flowy_editor/lib/render/editor/editor_entry.dart

@@ -33,7 +33,7 @@ class EditorNodeWidget extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return Column(
-      crossAxisAlignment: CrossAxisAlignment.start,
+      crossAxisAlignment: CrossAxisAlignment.center,
       children: node.children
           .map(
             (child) =>

+ 12 - 10
frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/bulleted_list_text.dart

@@ -3,6 +3,7 @@ import 'package:flowy_editor/editor_state.dart';
 import 'package:flowy_editor/infra/flowy_svg.dart';
 import 'package:flowy_editor/render/rich_text/default_selectable.dart';
 import 'package:flowy_editor/render/rich_text/flowy_rich_text.dart';
+import 'package:flowy_editor/render/rich_text/rich_text_style.dart';
 import 'package:flowy_editor/render/selection/selectable.dart';
 import 'package:flowy_editor/service/render_plugin_service.dart';
 import 'package:flutter/material.dart';
@@ -56,21 +57,22 @@ class _BulletedListTextNodeWidgetState extends State<BulletedListTextNodeWidget>
 
   @override
   Widget build(BuildContext context) {
-    return Row(
-      children: [
-        FlowySvg(
-          size: Size.square(leftPadding),
-          name: 'point',
-        ),
-        Expanded(
-          child: FlowyRichText(
+    return SizedBox(
+      width: maxTextNodeWidth,
+      child: Row(
+        children: [
+          FlowySvg(
+            size: Size.square(leftPadding),
+            name: 'point',
+          ),
+          FlowyRichText(
             key: _richTextKey,
             placeholderText: 'List',
             textNode: widget.textNode,
             editorState: widget.editorState,
           ),
-        ),
-      ],
+        ],
+      ),
     );
   }
 }

+ 21 - 20
frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/checkbox_text.dart

@@ -65,33 +65,34 @@ class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
 
   Widget _buildWithSingle(BuildContext context) {
     final check = widget.textNode.attributes.check;
-    return Row(
-      crossAxisAlignment: CrossAxisAlignment.start,
-      children: [
-        GestureDetector(
-          child: FlowySvg(
-            size: Size.square(leftPadding),
-            name: check ? 'check' : 'uncheck',
+    return SizedBox(
+      width: maxTextNodeWidth,
+      child: Row(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: [
+          GestureDetector(
+            child: FlowySvg(
+              size: Size.square(leftPadding),
+              name: check ? 'check' : 'uncheck',
+            ),
+            onTap: () {
+              debugPrint('[Checkbox] onTap...');
+              TransactionBuilder(widget.editorState)
+                ..updateNode(widget.textNode, {
+                  StyleKey.checkbox: !check,
+                })
+                ..commit();
+            },
           ),
-          onTap: () {
-            debugPrint('[Checkbox] onTap...');
-            TransactionBuilder(widget.editorState)
-              ..updateNode(widget.textNode, {
-                StyleKey.checkbox: !check,
-              })
-              ..commit();
-          },
-        ),
-        Expanded(
-          child: FlowyRichText(
+          FlowyRichText(
             key: _richTextKey,
             placeholderText: 'To-do',
             textNode: widget.textNode,
             textSpanDecorator: _textSpanDecorator,
             editorState: widget.editorState,
           ),
-        ),
-      ],
+        ],
+      ),
     );
   }
 

+ 0 - 16
frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/flowy_rich_text.dart

@@ -11,22 +11,6 @@ import 'package:flowy_editor/render/rich_text/rich_text_style.dart';
 import 'package:flowy_editor/render/selection/selectable.dart';
 import 'package:flowy_editor/service/render_plugin_service.dart';
 
-class RichTextNodeWidgetBuilder extends NodeWidgetBuilder<TextNode> {
-  @override
-  Widget build(NodeWidgetContext<TextNode> context) {
-    return FlowyRichText(
-      key: context.node.key,
-      textNode: context.node,
-      editorState: context.editorState,
-    );
-  }
-
-  @override
-  NodeValidator<Node> get nodeValidator => ((node) {
-        return true;
-      });
-}
-
 typedef FlowyTextSpanDecorator = TextSpan Function(TextSpan textSpan);
 
 class FlowyRichText extends StatefulWidget {

+ 16 - 19
frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/heading_text.dart

@@ -56,25 +56,22 @@ class _HeadingTextNodeWidgetState extends State<HeadingTextNodeWidget>
 
   @override
   Widget build(BuildContext context) {
-    return Column(
-      children: [
-        Padding(
-          padding: EdgeInsets.only(
-            top: topPadding,
-            bottom: bottomPadding,
-          ),
-          child: Expanded(
-            child: FlowyRichText(
-              key: _richTextKey,
-              placeholderText: 'Heading',
-              placeholderTextSpanDecorator: _placeholderTextSpanDecorator,
-              textSpanDecorator: _textSpanDecorator,
-              textNode: widget.textNode,
-              editorState: widget.editorState,
-            ),
-          ),
-        )
-      ],
+    return SizedBox(
+      width: maxTextNodeWidth,
+      child: Padding(
+        padding: EdgeInsets.only(
+          top: topPadding,
+          bottom: bottomPadding,
+        ),
+        child: FlowyRichText(
+          key: _richTextKey,
+          placeholderText: 'Heading',
+          placeholderTextSpanDecorator: _placeholderTextSpanDecorator,
+          textSpanDecorator: _textSpanDecorator,
+          textNode: widget.textNode,
+          editorState: widget.editorState,
+        ),
+      ),
     );
   }
 

+ 11 - 10
frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/number_list_text.dart

@@ -57,21 +57,22 @@ class _NumberListTextNodeWidgetState extends State<NumberListTextNodeWidget>
 
   @override
   Widget build(BuildContext context) {
-    return Row(
-      children: [
-        FlowySvg(
-          size: Size.square(leftPadding),
-          number: widget.textNode.attributes.number,
-        ),
-        Expanded(
-          child: FlowyRichText(
+    return SizedBox(
+      width: maxTextNodeWidth,
+      child: Row(
+        children: [
+          FlowySvg(
+            size: Size.square(leftPadding),
+            number: widget.textNode.attributes.number,
+          ),
+          FlowyRichText(
             key: _richTextKey,
             placeholderText: 'List',
             textNode: widget.textNode,
             editorState: widget.editorState,
           ),
-        ),
-      ],
+        ],
+      ),
     );
   }
 }

+ 13 - 12
frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/quoted_text.dart

@@ -56,24 +56,25 @@ class _QuotedTextNodeWidgetState extends State<QuotedTextNodeWidget>
 
   @override
   Widget build(BuildContext context) {
-    return Row(
-      children: [
-        FlowySvg(
-          size: Size(
-            leftPadding,
-            _quoteHeight,
+    return SizedBox(
+      width: maxTextNodeWidth,
+      child: Row(
+        children: [
+          FlowySvg(
+            size: Size(
+              leftPadding,
+              _quoteHeight,
+            ),
+            name: 'quote',
           ),
-          name: 'quote',
-        ),
-        Expanded(
-          child: FlowyRichText(
+          FlowyRichText(
             key: _richTextKey,
             placeholderText: 'Quote',
             textNode: widget.textNode,
             editorState: widget.editorState,
           ),
-        ),
-      ],
+        ],
+      ),
     );
   }
 

+ 68 - 0
frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/rich_text.dart

@@ -0,0 +1,68 @@
+import 'package:flowy_editor/document/node.dart';
+import 'package:flowy_editor/editor_state.dart';
+import 'package:flowy_editor/infra/flowy_svg.dart';
+import 'package:flowy_editor/render/rich_text/default_selectable.dart';
+import 'package:flowy_editor/render/rich_text/flowy_rich_text.dart';
+import 'package:flowy_editor/render/rich_text/rich_text_style.dart';
+import 'package:flowy_editor/render/selection/selectable.dart';
+import 'package:flowy_editor/service/render_plugin_service.dart';
+import 'package:flutter/material.dart';
+
+class RichTextNodeWidgetBuilder extends NodeWidgetBuilder<TextNode> {
+  @override
+  Widget build(NodeWidgetContext<TextNode> context) {
+    return RichTextNodeWidget(
+      key: context.node.key,
+      textNode: context.node,
+      editorState: context.editorState,
+    );
+  }
+
+  @override
+  NodeValidator<Node> get nodeValidator => ((node) {
+        return true;
+      });
+}
+
+class RichTextNodeWidget extends StatefulWidget {
+  const RichTextNodeWidget({
+    Key? key,
+    required this.textNode,
+    required this.editorState,
+  }) : super(key: key);
+
+  final TextNode textNode;
+  final EditorState editorState;
+
+  @override
+  State<RichTextNodeWidget> createState() => _RichTextNodeWidgetState();
+}
+
+// customize
+
+class _RichTextNodeWidgetState extends State<RichTextNodeWidget>
+    with Selectable, DefaultSelectable {
+  final _richTextKey = GlobalKey(debugLabel: 'rich_text');
+  final leftPadding = 20.0;
+
+  @override
+  Selectable<StatefulWidget> get forward =>
+      _richTextKey.currentState as Selectable;
+
+  @override
+  Offset get baseOffset {
+    return Offset.zero;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return SizedBox(
+      width: maxTextNodeWidth,
+      child: FlowyRichText(
+        key: _richTextKey,
+        textNode: widget.textNode,
+        editorState: widget.editorState,
+      ),
+    );
+  }
+}

+ 41 - 20
frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/rich_text_style.dart

@@ -59,6 +59,8 @@ class StyleKey {
   ];
 }
 
+// TODO: customize
+double maxTextNodeWidth = 780.0;
 double baseFontSize = 16.0;
 // TODO: customize.
 Map<String, double> headingToFontSize = {
@@ -146,7 +148,7 @@ extension DeltaAttributesExtensions on Attributes {
     return null;
   }
 
-  Color? get hightlightColor {
+  Color? get highlightColor {
     if (containsKey(StyleKey.highlightColor) &&
         this[StyleKey.highlightColor] is String) {
       return Color(
@@ -183,19 +185,30 @@ class RichTextStyle {
     return TextSpan(
       text: text,
       style: TextStyle(
-        fontWeight: fontWeight,
-        fontStyle: fontStyle,
-        fontSize: fontSize,
-        color: textColor,
-        backgroundColor: backgroundColor,
-        decoration: textDecoration,
+        fontWeight: _fontWeight,
+        fontStyle: _fontStyle,
+        fontSize: _fontSize,
+        color: _textColor,
+        decoration: _textDecoration,
+        background: _background,
       ),
-      recognizer: recognizer,
+      recognizer: _recognizer,
     );
   }
 
+  Paint? get _background {
+    if (_backgroundColor != null) {
+      return Paint()
+        ..color = _backgroundColor!
+        ..strokeWidth = 24.0
+        ..style = PaintingStyle.fill
+        ..strokeJoin = StrokeJoin.round;
+    }
+    return null;
+  }
+
   // bold
-  FontWeight get fontWeight {
+  FontWeight get _fontWeight {
     if (attributes.bold) {
       return FontWeight.bold;
     }
@@ -203,38 +216,46 @@ class RichTextStyle {
   }
 
   // underline or strikethrough
-  TextDecoration get textDecoration {
+  TextDecoration get _textDecoration {
+    var decorations = [TextDecoration.none];
     if (attributes.underline || attributes.href != null) {
-      return TextDecoration.underline;
-    } else if (attributes.strikethrough) {
-      return TextDecoration.lineThrough;
+      decorations.add(TextDecoration.underline);
+      // TextDecoration.underline;
     }
-    return TextDecoration.none;
+    if (attributes.strikethrough) {
+      decorations.add(TextDecoration.lineThrough);
+    }
+    return TextDecoration.combine(decorations);
   }
 
   // font
-  FontStyle get fontStyle =>
+  FontStyle get _fontStyle =>
       attributes.italic ? FontStyle.italic : FontStyle.normal;
 
   // text color
-  Color get textColor {
+  Color get _textColor {
     if (attributes.href != null) {
       return Colors.lightBlue;
     }
     return attributes.color ?? Colors.black;
   }
 
-  Color get backgroundColor {
-    return attributes.hightlightColor ?? Colors.transparent;
+  Color? get _backgroundColor {
+    if (attributes.highlightColor != null) {
+      return attributes.highlightColor!;
+    } else if (attributes.code) {
+      return Colors.grey.withOpacity(0.4);
+    }
+    return null;
   }
 
   // font size
-  double get fontSize {
+  double get _fontSize {
     return baseFontSize;
   }
 
   // recognizer
-  GestureRecognizer? get recognizer {
+  GestureRecognizer? get _recognizer {
     final href = attributes.href;
     if (href != null) {
       return TapGestureRecognizer()

+ 0 - 5
frontend/app_flowy/packages/flowy_editor/lib/service/default_text_operations/format_rich_text_style.dart

@@ -97,11 +97,6 @@ bool formatRichTextPartialStyle(EditorState editorState, String styleKey) {
   Attributes attributes = {
     styleKey: value,
   };
-  if (styleKey == StyleKey.underline && value) {
-    attributes[StyleKey.strikethrough] = null;
-  } else if (styleKey == StyleKey.strikethrough && value) {
-    attributes[StyleKey.underline] = null;
-  }
 
   return formatRichTextStyle(editorState, attributes);
 }

+ 1 - 1
frontend/app_flowy/packages/flowy_editor/lib/service/editor_service.dart

@@ -4,10 +4,10 @@ import 'package:flowy_editor/editor_state.dart';
 import 'package:flowy_editor/render/editor/editor_entry.dart';
 import 'package:flowy_editor/render/rich_text/bulleted_list_text.dart';
 import 'package:flowy_editor/render/rich_text/checkbox_text.dart';
-import 'package:flowy_editor/render/rich_text/flowy_rich_text.dart';
 import 'package:flowy_editor/render/rich_text/heading_text.dart';
 import 'package:flowy_editor/render/rich_text/number_list_text.dart';
 import 'package:flowy_editor/render/rich_text/quoted_text.dart';
+import 'package:flowy_editor/render/rich_text/rich_text.dart';
 import 'package:flowy_editor/service/input_service.dart';
 import 'package:flowy_editor/service/internal_key_event_handlers/arrow_keys_handler.dart';
 import 'package:flowy_editor/service/internal_key_event_handlers/copy_paste_handler.dart';

+ 3 - 1
frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart

@@ -1,6 +1,7 @@
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 
+import 'package:flowy_editor/document/attributes.dart';
 import 'package:flowy_editor/document/node.dart';
 import 'package:flowy_editor/document/position.dart';
 import 'package:flowy_editor/document/selection.dart';
@@ -91,7 +92,8 @@ FlowyKeyEventHandler enterWithoutShiftInTextNodesHandler =
     ..insertNode(
       textNode.path.next,
       textNode.copyWith(
-        attributes: needCopyAttributes ? textNode.attributes : {},
+        attributes:
+            needCopyAttributes ? Attributes.from(textNode.attributes) : {},
         delta: textNode.delta.slice(selection.end.offset),
       ),
     )

+ 12 - 0
frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/update_text_style_by_command_x_handler.dart

@@ -23,6 +23,18 @@ FlowyKeyEventHandler updateTextStyleByCommandXHandler = (editorState, event) {
     case 'b':
       formatBold(editorState);
       return KeyEventResult.handled;
+    case 'I':
+    case 'i':
+      formatItalic(editorState);
+      return KeyEventResult.handled;
+    case 'U':
+    case 'u':
+      formatUnderline(editorState);
+      return KeyEventResult.handled;
+    case 'S':
+    case 's':
+      formatStrikethrough(editorState);
+      return KeyEventResult.handled;
     default:
       break;
   }