Browse Source

feat: decorate TextSpan with global style

Lucas.Xu 2 years ago
parent
commit
612e3dd50f

+ 6 - 20
frontend/app_flowy/packages/flowy_editor/example/assets/example.json

@@ -13,10 +13,7 @@
         "type": "text",
         "delta": [
           {
-            "insert": "🌶 Read Me",
-            "attributes": {
-              "heading": "h1"
-            }
+            "insert": "🌶 Read Me"
           }
         ],
         "attributes": {
@@ -27,10 +24,7 @@
         "type": "text",
         "delta": [
           {
-            "insert": "👋 Welcome to Appflowy",
-            "attributes": {
-              "heading": "h2"
-            }
+            "insert": "👋 Welcome to Appflowy"
           }
         ],
         "attributes": {
@@ -41,10 +35,7 @@
         "type": "text",
         "delta": [
           {
-            "insert": "Here are the basics:",
-            "attributes": {
-              "heading": "h3"
-            }
+            "insert": "Here are the basics:"
           }
         ],
         "attributes": {
@@ -102,10 +93,7 @@
         "type": "text",
         "delta": [
           {
-            "insert": "Here are the examples:",
-            "attributes": {
-              "heading": "h3"
-            }
+            "insert": "Here are the examples:"
           }
         ],
         "attributes": {
@@ -149,8 +137,7 @@
         "type": "text",
         "delta": [
           {
-            "insert": "Hello world",
-            "attributes": { "quote": true }
+            "insert": "Hello world"
           }
         ],
         "attributes": {
@@ -161,8 +148,7 @@
         "type": "text",
         "delta": [
           {
-            "insert": "Hello world",
-            "attributes": { "quote": true }
+            "insert": "Hello world"
           }
         ],
         "attributes": {

+ 17 - 1
frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/flowy_rich_text.dart

@@ -169,7 +169,7 @@ class _FlowyRichTextState extends State<FlowyRichText> with Selectable {
     return SizedBox(
       width:
           MediaQuery.of(context).size.width - 20, // FIXME: use the const value
-      child: RichText(key: _textKey, text: _textSpan),
+      child: RichText(key: _textKey, text: _decorateTextSpanWithGlobalStyle),
     );
   }
 
@@ -255,6 +255,22 @@ class _FlowyRichTextState extends State<FlowyRichText> with Selectable {
     return Rect.zero;
   }
 
+  TextSpan get _decorateTextSpanWithGlobalStyle => TextSpan(
+        children: _textSpan.children
+            ?.whereType<TextSpan>()
+            .map(
+              (span) => TextSpan(
+                text: span.text,
+                style: span.style?.copyWith(
+                  fontSize: _textNode.attributes.fontSize,
+                  color: _textNode.attributes.quoteColor,
+                ),
+                recognizer: span.recognizer,
+              ),
+            )
+            .toList(),
+      );
+
   TextSpan get _textSpan => TextSpan(
       children: _textNode.delta.operations
           .whereType<TextInsert>()

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

@@ -2,6 +2,19 @@ import 'package:flowy_editor/document/attributes.dart';
 import 'package:flutter/gestures.dart';
 import 'package:flutter/material.dart';
 
+///
+/// Supported partial rendering types:
+///   bold, italic,
+///   underline, strikethrough,
+///   color, font,
+///   href
+///
+/// Supported global rendering types:
+///   heading: h1, h2, h3, h4, h5, h6, ...
+///   block quote,
+///   list: ordered list, bulleted list,
+///   code block
+///
 class StyleKey {
   static String bold = 'bold';
   static String italic = 'italic';
@@ -11,6 +24,7 @@ class StyleKey {
   static String highlightColor = 'highlightColor';
   static String font = 'font';
   static String href = 'href';
+
   static String heading = 'heading';
   static String quote = 'quote';
   static String list = 'list';
@@ -19,7 +33,76 @@ class StyleKey {
   static String code = 'code';
 }
 
-extension AttributesExtensions on Attributes {
+double baseFontSize = 16.0;
+// TODO: customize.
+Map<String, double> headingToFontSize = {
+  'h1': baseFontSize + 15,
+  'h2': baseFontSize + 12,
+  'h3': baseFontSize + 9,
+  'h4': baseFontSize + 6,
+  'h5': baseFontSize + 3,
+  'h6': baseFontSize,
+};
+
+extension NodeAttributesExtensions on Attributes {
+  String? get heading {
+    if (containsKey(StyleKey.heading) && this[StyleKey.heading] is String) {
+      return this[StyleKey.heading];
+    }
+    return null;
+  }
+
+  double get fontSize {
+    if (heading != null) {
+      return headingToFontSize[heading]!;
+    }
+    return baseFontSize;
+  }
+
+  bool get quote {
+    if (containsKey(StyleKey.quote) && this[StyleKey.quote] == true) {
+      return this[StyleKey.quote];
+    }
+    return false;
+  }
+
+  Color? get quoteColor {
+    if (quote) {
+      return Colors.grey;
+    }
+    return null;
+  }
+
+  String? get list {
+    if (containsKey(StyleKey.list) && this[StyleKey.list] is String) {
+      return this[StyleKey.list];
+    }
+    return null;
+  }
+
+  int? get number {
+    if (containsKey(StyleKey.number) && this[StyleKey.number] is int) {
+      return this[StyleKey.number];
+    }
+    return null;
+  }
+
+  bool get todo {
+    if (containsKey(StyleKey.todo) && this[StyleKey.todo] is bool) {
+      return this[StyleKey.todo];
+    }
+    return false;
+  }
+
+  bool get code {
+    if (containsKey(StyleKey.code) && this[StyleKey.code] == true) {
+      return this[StyleKey.code];
+    }
+    return false;
+  }
+}
+
+extension DeltaAttributesExtensions on Attributes {
   bool get bold {
     return (containsKey(StyleKey.bold) && this[StyleKey.bold] == true);
   }
@@ -68,63 +151,8 @@ extension AttributesExtensions on Attributes {
     }
     return null;
   }
-
-  String? get heading {
-    if (containsKey(StyleKey.heading) && this[StyleKey.heading] is String) {
-      return this[StyleKey.heading];
-    }
-    return null;
-  }
-
-  bool get quote {
-    if (containsKey(StyleKey.quote) && this[StyleKey.quote] == true) {
-      return this[StyleKey.quote];
-    }
-    return false;
-  }
-
-  String? get list {
-    if (containsKey(StyleKey.list) && this[StyleKey.list] is String) {
-      return this[StyleKey.list];
-    }
-    return null;
-  }
-
-  int? get number {
-    if (containsKey(StyleKey.number) && this[StyleKey.number] is int) {
-      return this[StyleKey.number];
-    }
-    return null;
-  }
-
-  bool get todo {
-    if (containsKey(StyleKey.todo) && this[StyleKey.todo] is bool) {
-      return this[StyleKey.todo];
-    }
-    return false;
-  }
-
-  bool get code {
-    if (containsKey(StyleKey.code) && this[StyleKey.code] == true) {
-      return this[StyleKey.code];
-    }
-    return false;
-  }
 }
 
-///
-/// Supported partial rendering types:
-///   bold, italic,
-///   underline, strikethrough,
-///   color, font,
-///   href
-///
-/// Supported global rendering types:
-///   heading: h1, h2, h3, h4, h5, h6,
-///   block quote,
-///   list: ordered list, bulleted list,
-///   code block
-///
 class RichTextStyle {
   // TODO: customize
   RichTextStyle({
@@ -154,8 +182,6 @@ class RichTextStyle {
   FontWeight get fontWeight {
     if (attributes.bold) {
       return FontWeight.bold;
-    } else if (attributes.heading != null) {
-      return FontWeight.bold;
     }
     return FontWeight.normal;
   }
@@ -178,8 +204,6 @@ class RichTextStyle {
   Color get textColor {
     if (attributes.href != null) {
       return Colors.lightBlue;
-    } else if (attributes.quote) {
-      return Colors.grey;
     }
     return attributes.color ?? Colors.black;
   }
@@ -190,14 +214,7 @@ class RichTextStyle {
 
   // font size
   double get fontSize {
-    final heading = attributes.heading;
-    if (heading != null) {
-      final headings = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
-      final fontSizes = [30.0, 25.0, 20.0, 20.0, 20.0, 20.0];
-      return fontSizes[headings.indexOf(heading)];
-    } else {
-      return 16.0;
-    }
+    return baseFontSize;
   }
 
   // recognizer