Ver código fonte

feat: refactor theme plugin, use themedata extension

Lucas.Xu 2 anos atrás
pai
commit
cdee706f46

+ 43 - 22
frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart

@@ -38,6 +38,7 @@ class MyApp extends StatelessWidget {
       debugShowCheckedModeBanner: false,
       debugShowCheckedModeBanner: false,
       theme: ThemeData(
       theme: ThemeData(
         primarySwatch: Colors.blue,
         primarySwatch: Colors.blue,
+        // extensions: [HeadingPluginStyle.light],
       ),
       ),
       home: const MyHomePage(title: 'AppFlowyEditor Example'),
       home: const MyHomePage(title: 'AppFlowyEditor Example'),
     );
     );
@@ -125,28 +126,48 @@ class _MyHomePageState extends State<MyHomePage> {
           _editorState!.transactionStream.listen((event) {
           _editorState!.transactionStream.listen((event) {
             debugPrint('Transaction: ${event.toJson()}');
             debugPrint('Transaction: ${event.toJson()}');
           });
           });
-          return Container(
-            color: darkMode ? Colors.black : Colors.white,
-            width: MediaQuery.of(context).size.width,
-            child: AppFlowyEditor(
-              editorState: _editorState!,
-              editorStyle: _editorStyle,
-              editable: true,
-              customBuilders: {
-                'text/code_block': CodeBlockNodeWidgetBuilder(),
-                'tex': TeXBlockNodeWidgetBuidler(),
-                'horizontal_rule': HorizontalRuleWidgetBuilder(),
-              },
-              shortcutEvents: [
-                enterInCodeBlock,
-                ignoreKeysInCodeBlock,
-                insertHorizontalRule,
-              ],
-              selectionMenuItems: [
-                codeBlockMenuItem,
-                teXBlockMenuItem,
-                horizontalRuleMenuItem,
-              ],
+          final themeData = darkMode
+              ? ThemeData.dark().copyWith(extensions: [
+                  HeadingPluginStyle.dark,
+                  CheckboxPluginStyle.dark,
+                  NumberListPluginStyle.dark,
+                  QuotedTextPluginStyle.dark,
+                  BulletedListPluginStyle.dark
+                ])
+              : ThemeData.light().copyWith(
+                  extensions: [
+                    HeadingPluginStyle.light,
+                    CheckboxPluginStyle.light,
+                    NumberListPluginStyle.light,
+                    QuotedTextPluginStyle.light,
+                    BulletedListPluginStyle.light
+                  ],
+                );
+          return Theme(
+            data: themeData,
+            child: Container(
+              color: darkMode ? Colors.black : Colors.white,
+              width: MediaQuery.of(context).size.width,
+              child: AppFlowyEditor(
+                editorState: _editorState!,
+                editorStyle: _editorStyle,
+                editable: true,
+                customBuilders: {
+                  'text/code_block': CodeBlockNodeWidgetBuilder(),
+                  'tex': TeXBlockNodeWidgetBuidler(),
+                  'horizontal_rule': HorizontalRuleWidgetBuilder(),
+                },
+                shortcutEvents: [
+                  enterInCodeBlock,
+                  ignoreKeysInCodeBlock,
+                  insertHorizontalRule,
+                ],
+                selectionMenuItems: [
+                  codeBlockMenuItem,
+                  teXBlockMenuItem,
+                  horizontalRuleMenuItem,
+                ],
+              ),
             ),
             ),
           );
           );
         } else {
         } else {

+ 1 - 0
frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart

@@ -31,3 +31,4 @@ export 'src/render/rich_text/default_selectable.dart';
 export 'src/render/rich_text/flowy_rich_text.dart';
 export 'src/render/rich_text/flowy_rich_text.dart';
 export 'src/render/selection_menu/selection_menu_widget.dart';
 export 'src/render/selection_menu/selection_menu_widget.dart';
 export 'src/l10n/l10n.dart';
 export 'src/l10n/l10n.dart';
+export 'src/render/style/built_in_plugin_styles.dart';

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

@@ -1,10 +1,10 @@
 import 'package:appflowy_editor/src/core/document/node.dart';
 import 'package:appflowy_editor/src/core/document/node.dart';
 import 'package:appflowy_editor/src/editor_state.dart';
 import 'package:appflowy_editor/src/editor_state.dart';
-import 'package:appflowy_editor/src/infra/flowy_svg.dart';
 import 'package:appflowy_editor/src/render/rich_text/built_in_text_widget.dart';
 import 'package:appflowy_editor/src/render/rich_text/built_in_text_widget.dart';
 import 'package:appflowy_editor/src/render/rich_text/default_selectable.dart';
 import 'package:appflowy_editor/src/render/rich_text/default_selectable.dart';
 import 'package:appflowy_editor/src/render/rich_text/flowy_rich_text.dart';
 import 'package:appflowy_editor/src/render/rich_text/flowy_rich_text.dart';
 import 'package:appflowy_editor/src/render/selection/selectable.dart';
 import 'package:appflowy_editor/src/render/selection/selectable.dart';
+import 'package:appflowy_editor/src/render/style/built_in_plugin_styles.dart';
 import 'package:appflowy_editor/src/service/render_plugin_service.dart';
 import 'package:appflowy_editor/src/service/render_plugin_service.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:appflowy_editor/src/extensions/text_style_extension.dart';
 import 'package:appflowy_editor/src/extensions/text_style_extension.dart';
@@ -45,11 +45,7 @@ class BulletedListTextNodeWidget extends BuiltInTextWidget {
 // customize
 // customize
 
 
 class _BulletedListTextNodeWidgetState extends State<BulletedListTextNodeWidget>
 class _BulletedListTextNodeWidgetState extends State<BulletedListTextNodeWidget>
-    with
-        SelectableMixin,
-        DefaultSelectable,
-        BuiltInStyleMixin,
-        BuiltInTextWidgetMixin {
+    with SelectableMixin, DefaultSelectable, BuiltInTextWidgetMixin {
   @override
   @override
   final iconKey = GlobalKey();
   final iconKey = GlobalKey();
 
 
@@ -64,17 +60,23 @@ class _BulletedListTextNodeWidgetState extends State<BulletedListTextNodeWidget>
     return super.baseOffset.translate(0, padding.top);
     return super.baseOffset.translate(0, padding.top);
   }
   }
 
 
-  Color get bulletColor {
-    final bulletColor = widget.editorState.editorStyle.style(
-      widget.editorState,
-      widget.textNode,
-      'bulletColor',
-    );
-    if (bulletColor is Color) {
-      return bulletColor;
-    }
-    return Colors.black;
-  }
+  BulletedListPluginStyle get style =>
+      Theme.of(context).extension<BulletedListPluginStyle>()!;
+
+  EdgeInsets get padding => style.padding(
+        widget.editorState,
+        widget.textNode,
+      );
+
+  TextStyle get textStyle => style.textStyle(
+        widget.editorState,
+        widget.textNode,
+      );
+
+  Widget get icon => style.icon(
+        widget.editorState,
+        widget.textNode,
+      );
 
 
   @override
   @override
   Widget buildWithSingle(BuildContext context) {
   Widget buildWithSingle(BuildContext context) {
@@ -83,13 +85,9 @@ class _BulletedListTextNodeWidgetState extends State<BulletedListTextNodeWidget>
       child: Row(
       child: Row(
         crossAxisAlignment: CrossAxisAlignment.start,
         crossAxisAlignment: CrossAxisAlignment.start,
         children: [
         children: [
-          FlowySvg(
+          Container(
             key: iconKey,
             key: iconKey,
-            width: iconSize?.width,
-            height: iconSize?.height,
-            padding: iconPadding,
-            color: bulletColor,
-            name: 'point',
+            child: icon,
           ),
           ),
           Flexible(
           Flexible(
             child: FlowyRichText(
             child: FlowyRichText(

+ 20 - 12
frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart

@@ -1,6 +1,5 @@
 import 'package:appflowy_editor/appflowy_editor.dart';
 import 'package:appflowy_editor/appflowy_editor.dart';
 import 'package:appflowy_editor/src/commands/text/text_commands.dart';
 import 'package:appflowy_editor/src/commands/text/text_commands.dart';
-import 'package:appflowy_editor/src/infra/flowy_svg.dart';
 import 'package:appflowy_editor/src/render/rich_text/built_in_text_widget.dart';
 import 'package:appflowy_editor/src/render/rich_text/built_in_text_widget.dart';
 
 
 import 'package:appflowy_editor/src/extensions/text_style_extension.dart';
 import 'package:appflowy_editor/src/extensions/text_style_extension.dart';
@@ -39,11 +38,7 @@ class CheckboxNodeWidget extends BuiltInTextWidget {
 }
 }
 
 
 class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
 class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
-    with
-        SelectableMixin,
-        DefaultSelectable,
-        BuiltInStyleMixin,
-        BuiltInTextWidgetMixin {
+    with SelectableMixin, DefaultSelectable, BuiltInTextWidgetMixin {
   @override
   @override
   final iconKey = GlobalKey();
   final iconKey = GlobalKey();
 
 
@@ -58,6 +53,24 @@ class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
     return super.baseOffset.translate(0, padding.top);
     return super.baseOffset.translate(0, padding.top);
   }
   }
 
 
+  CheckboxPluginStyle get style =>
+      Theme.of(context).extension<CheckboxPluginStyle>()!;
+
+  EdgeInsets get padding => style.padding(
+        widget.editorState,
+        widget.textNode,
+      );
+
+  TextStyle get textStyle => style.textStyle(
+        widget.editorState,
+        widget.textNode,
+      );
+
+  Widget get icon => style.icon(
+        widget.editorState,
+        widget.textNode,
+      );
+
   @override
   @override
   Widget buildWithSingle(BuildContext context) {
   Widget buildWithSingle(BuildContext context) {
     final check = widget.textNode.attributes.check;
     final check = widget.textNode.attributes.check;
@@ -68,12 +81,7 @@ class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
         children: [
         children: [
           GestureDetector(
           GestureDetector(
             key: iconKey,
             key: iconKey,
-            child: FlowySvg(
-              width: iconSize?.width,
-              height: iconSize?.height,
-              padding: iconPadding,
-              name: check ? 'check' : 'uncheck',
-            ),
+            child: icon,
             onTap: () async {
             onTap: () async {
               await widget.editorState.formatTextToCheckbox(
               await widget.editorState.formatTextToCheckbox(
                 widget.editorState,
                 widget.editorState,

+ 15 - 1
frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/heading_text.dart

@@ -4,6 +4,7 @@ import 'package:appflowy_editor/src/render/rich_text/built_in_text_widget.dart';
 import 'package:appflowy_editor/src/render/rich_text/default_selectable.dart';
 import 'package:appflowy_editor/src/render/rich_text/default_selectable.dart';
 import 'package:appflowy_editor/src/render/rich_text/flowy_rich_text.dart';
 import 'package:appflowy_editor/src/render/rich_text/flowy_rich_text.dart';
 import 'package:appflowy_editor/src/render/selection/selectable.dart';
 import 'package:appflowy_editor/src/render/selection/selectable.dart';
+import 'package:appflowy_editor/src/render/style/built_in_plugin_styles.dart';
 import 'package:appflowy_editor/src/service/render_plugin_service.dart';
 import 'package:appflowy_editor/src/service/render_plugin_service.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:appflowy_editor/src/extensions/attributes_extension.dart';
 import 'package:appflowy_editor/src/extensions/attributes_extension.dart';
@@ -43,7 +44,7 @@ class HeadingTextNodeWidget extends BuiltInTextWidget {
 
 
 // customize
 // customize
 class _HeadingTextNodeWidgetState extends State<HeadingTextNodeWidget>
 class _HeadingTextNodeWidgetState extends State<HeadingTextNodeWidget>
-    with SelectableMixin, DefaultSelectable, BuiltInStyleMixin {
+    with SelectableMixin, DefaultSelectable {
   @override
   @override
   GlobalKey? get iconKey => null;
   GlobalKey? get iconKey => null;
 
 
@@ -58,6 +59,19 @@ class _HeadingTextNodeWidgetState extends State<HeadingTextNodeWidget>
     return padding.topLeft;
     return padding.topLeft;
   }
   }
 
 
+  HeadingPluginStyle get style =>
+      Theme.of(context).extension<HeadingPluginStyle>()!;
+
+  EdgeInsets get padding => style.padding(
+        widget.editorState,
+        widget.textNode,
+      );
+
+  TextStyle get textStyle => style.textStyle(
+        widget.editorState,
+        widget.textNode,
+      );
+
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     return Padding(
     return Padding(

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

@@ -4,6 +4,7 @@ import 'package:appflowy_editor/src/render/rich_text/built_in_text_widget.dart';
 import 'package:appflowy_editor/src/render/rich_text/default_selectable.dart';
 import 'package:appflowy_editor/src/render/rich_text/default_selectable.dart';
 import 'package:appflowy_editor/src/render/rich_text/flowy_rich_text.dart';
 import 'package:appflowy_editor/src/render/rich_text/flowy_rich_text.dart';
 import 'package:appflowy_editor/src/render/selection/selectable.dart';
 import 'package:appflowy_editor/src/render/selection/selectable.dart';
+import 'package:appflowy_editor/src/render/style/plugin_style.dart';
 import 'package:appflowy_editor/src/service/render_plugin_service.dart';
 import 'package:appflowy_editor/src/service/render_plugin_service.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:appflowy_editor/src/extensions/attributes_extension.dart';
 import 'package:appflowy_editor/src/extensions/attributes_extension.dart';
@@ -43,7 +44,7 @@ class NumberListTextNodeWidget extends BuiltInTextWidget {
 }
 }
 
 
 class _NumberListTextNodeWidgetState extends State<NumberListTextNodeWidget>
 class _NumberListTextNodeWidgetState extends State<NumberListTextNodeWidget>
-    with SelectableMixin, DefaultSelectable, BuiltInStyleMixin {
+    with SelectableMixin, DefaultSelectable {
   @override
   @override
   final iconKey = GlobalKey();
   final iconKey = GlobalKey();
 
 
@@ -70,6 +71,24 @@ class _NumberListTextNodeWidgetState extends State<NumberListTextNodeWidget>
     return Colors.black;
     return Colors.black;
   }
   }
 
 
+  NumberListPluginStyle get style =>
+      Theme.of(context).extension<NumberListPluginStyle>()!;
+
+  EdgeInsets get padding => style.padding(
+        widget.editorState,
+        widget.textNode,
+      );
+
+  TextStyle get textStyle => style.textStyle(
+        widget.editorState,
+        widget.textNode,
+      );
+
+  Widget get icon => style.icon(
+        widget.editorState,
+        widget.textNode,
+      );
+
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     return Padding(
     return Padding(
@@ -79,15 +98,7 @@ class _NumberListTextNodeWidgetState extends State<NumberListTextNodeWidget>
         children: [
         children: [
           Container(
           Container(
             key: iconKey,
             key: iconKey,
-            padding: iconPadding,
-            child: Text(
-              '${widget.textNode.attributes.number.toString()}.',
-              style: TextStyle(
-                fontSize: widget.editorState.editorStyle.textStyle
-                    .defaultTextStyle.fontSize,
-                color: numberColor,
-              ),
-            ),
+            child: icon,
           ),
           ),
           Flexible(
           Flexible(
             child: FlowyRichText(
             child: FlowyRichText(

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

@@ -1,10 +1,10 @@
 import 'package:appflowy_editor/src/core/document/node.dart';
 import 'package:appflowy_editor/src/core/document/node.dart';
 import 'package:appflowy_editor/src/editor_state.dart';
 import 'package:appflowy_editor/src/editor_state.dart';
-import 'package:appflowy_editor/src/infra/flowy_svg.dart';
 import 'package:appflowy_editor/src/render/rich_text/built_in_text_widget.dart';
 import 'package:appflowy_editor/src/render/rich_text/built_in_text_widget.dart';
 import 'package:appflowy_editor/src/render/rich_text/default_selectable.dart';
 import 'package:appflowy_editor/src/render/rich_text/default_selectable.dart';
 import 'package:appflowy_editor/src/render/rich_text/flowy_rich_text.dart';
 import 'package:appflowy_editor/src/render/rich_text/flowy_rich_text.dart';
 import 'package:appflowy_editor/src/render/selection/selectable.dart';
 import 'package:appflowy_editor/src/render/selection/selectable.dart';
+import 'package:appflowy_editor/src/render/style/built_in_plugin_styles.dart';
 import 'package:appflowy_editor/src/service/render_plugin_service.dart';
 import 'package:appflowy_editor/src/service/render_plugin_service.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:appflowy_editor/src/extensions/text_style_extension.dart';
 import 'package:appflowy_editor/src/extensions/text_style_extension.dart';
@@ -44,7 +44,7 @@ class QuotedTextNodeWidget extends BuiltInTextWidget {
 // customize
 // customize
 
 
 class _QuotedTextNodeWidgetState extends State<QuotedTextNodeWidget>
 class _QuotedTextNodeWidgetState extends State<QuotedTextNodeWidget>
-    with SelectableMixin, DefaultSelectable, BuiltInStyleMixin {
+    with SelectableMixin, DefaultSelectable {
   @override
   @override
   final iconKey = GlobalKey();
   final iconKey = GlobalKey();
 
 
@@ -59,6 +59,24 @@ class _QuotedTextNodeWidgetState extends State<QuotedTextNodeWidget>
     return super.baseOffset.translate(0, padding.top);
     return super.baseOffset.translate(0, padding.top);
   }
   }
 
 
+  QuotedTextPluginStyle get style =>
+      Theme.of(context).extension<QuotedTextPluginStyle>()!;
+
+  EdgeInsets get padding => style.padding(
+        widget.editorState,
+        widget.textNode,
+      );
+
+  TextStyle get textStyle => style.textStyle(
+        widget.editorState,
+        widget.textNode,
+      );
+
+  Widget get icon => style.icon(
+        widget.editorState,
+        widget.textNode,
+      );
+
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     return Padding(
     return Padding(
@@ -67,11 +85,9 @@ class _QuotedTextNodeWidgetState extends State<QuotedTextNodeWidget>
         child: Row(
         child: Row(
           crossAxisAlignment: CrossAxisAlignment.stretch,
           crossAxisAlignment: CrossAxisAlignment.stretch,
           children: [
           children: [
-            FlowySvg(
+            Container(
               key: iconKey,
               key: iconKey,
-              width: iconSize?.width,
-              padding: iconPadding,
-              name: 'quote',
+              child: icon,
             ),
             ),
             Flexible(
             Flexible(
               child: FlowyRichText(
               child: FlowyRichText(

+ 95 - 0
frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/editor_style.dart

@@ -4,6 +4,100 @@ import 'package:appflowy_editor/src/core/document/node.dart';
 import 'package:appflowy_editor/src/editor_state.dart';
 import 'package:appflowy_editor/src/editor_state.dart';
 import 'package:appflowy_editor/src/extensions/attributes_extension.dart';
 import 'package:appflowy_editor/src/extensions/attributes_extension.dart';
 
 
+class EditorStyleV2 extends ThemeExtension<EditorStyleV2> {
+  // Editor styles
+  final EdgeInsets? padding;
+  final Color? cursorColor;
+  final Color? selectionColor;
+
+  // Text styles
+  final TextStyle? textStyle;
+  final TextStyle? placeholderTextStyle;
+  final double lineHeight;
+
+  // Rich text styles
+  final TextStyle? bold;
+  final TextStyle? italic;
+  final TextStyle? underline;
+  final TextStyle? strikethrough;
+  final TextStyle? href;
+  final TextStyle? code;
+  final String? highlightColorHex;
+
+  EditorStyleV2({
+    required this.padding,
+    required this.cursorColor,
+    required this.selectionColor,
+    required this.textStyle,
+    required this.placeholderTextStyle,
+    required this.bold,
+    required this.italic,
+    required this.underline,
+    required this.strikethrough,
+    required this.href,
+    required this.code,
+    required this.highlightColorHex,
+    required this.lineHeight,
+  });
+
+  @override
+  EditorStyleV2 copyWith({
+    EdgeInsets? padding,
+    Color? cursorColor,
+    Color? selectionColor,
+    TextStyle? textStyle,
+    TextStyle? placeholderTextStyle,
+    TextStyle? bold,
+    TextStyle? italic,
+    TextStyle? underline,
+    TextStyle? strikethrough,
+    TextStyle? href,
+    TextStyle? code,
+    String? highlightColorHex,
+    double? lineHeight,
+  }) {
+    return EditorStyleV2(
+      padding: padding ?? this.padding,
+      cursorColor: cursorColor ?? this.cursorColor,
+      selectionColor: selectionColor ?? this.selectionColor,
+      textStyle: textStyle ?? this.textStyle,
+      placeholderTextStyle: placeholderTextStyle ?? this.placeholderTextStyle,
+      bold: bold ?? this.bold,
+      italic: italic ?? this.italic,
+      underline: underline ?? this.underline,
+      strikethrough: strikethrough ?? this.strikethrough,
+      href: href ?? this.href,
+      code: code ?? this.code,
+      highlightColorHex: highlightColorHex ?? this.highlightColorHex,
+      lineHeight: lineHeight ?? this.lineHeight,
+    );
+  }
+
+  @override
+  ThemeExtension<EditorStyleV2> lerp(
+      ThemeExtension<EditorStyleV2>? other, double t) {
+    if (other == null || other is! EditorStyleV2) {
+      return this;
+    }
+    return EditorStyleV2(
+      padding: EdgeInsets.lerp(padding, other.padding, t),
+      cursorColor: Color.lerp(cursorColor, other.cursorColor, t),
+      selectionColor: Color.lerp(selectionColor, other.selectionColor, t),
+      textStyle: TextStyle.lerp(textStyle, other.textStyle, t),
+      placeholderTextStyle:
+          TextStyle.lerp(placeholderTextStyle, other.placeholderTextStyle, t),
+      bold: TextStyle.lerp(bold, other.bold, t),
+      italic: TextStyle.lerp(italic, other.italic, t),
+      underline: TextStyle.lerp(underline, other.underline, t),
+      strikethrough: TextStyle.lerp(strikethrough, other.strikethrough, t),
+      href: TextStyle.lerp(href, other.href, t),
+      code: TextStyle.lerp(code, other.code, t),
+      highlightColorHex: highlightColorHex,
+      lineHeight: lineHeight,
+    );
+  }
+}
+
 typedef PluginStyler = Object Function(EditorState editorState, Node node);
 typedef PluginStyler = Object Function(EditorState editorState, Node node);
 typedef PluginStyle = Map<String, PluginStyler>;
 typedef PluginStyle = Map<String, PluginStyler>;
 
 
@@ -41,6 +135,7 @@ class EditorStyle {
     return null;
     return null;
   }
   }
 
 
+  @override
   EditorStyle copyWith({
   EditorStyle copyWith({
     EdgeInsets? padding,
     EdgeInsets? padding,
     BuiltInTextStyle? textStyle,
     BuiltInTextStyle? textStyle,

+ 312 - 0
frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/plugin_style.dart

@@ -0,0 +1,312 @@
+import 'package:appflowy_editor/appflowy_editor.dart';
+import 'package:appflowy_editor/src/infra/flowy_svg.dart';
+import 'package:flutter/material.dart';
+
+typedef TextStyleCustomizer = TextStyle Function(
+    EditorState editorState, TextNode textNode);
+typedef PaddingCustomizer = EdgeInsets Function(
+    EditorState editorState, TextNode textNode);
+typedef IconCustomizer = Widget Function(
+    EditorState editorState, TextNode textNode);
+
+class HeadingPluginStyle extends ThemeExtension<HeadingPluginStyle> {
+  const HeadingPluginStyle({
+    required this.textStyle,
+    required this.padding,
+  });
+
+  final TextStyleCustomizer textStyle;
+  final PaddingCustomizer padding;
+
+  @override
+  HeadingPluginStyle copyWith({
+    TextStyleCustomizer? textStyle,
+    PaddingCustomizer? padding,
+  }) {
+    return HeadingPluginStyle(
+      textStyle: textStyle ?? this.textStyle,
+      padding: padding ?? this.padding,
+    );
+  }
+
+  @override
+  ThemeExtension<HeadingPluginStyle> lerp(
+      ThemeExtension<HeadingPluginStyle>? other, double t) {
+    if (other is! HeadingPluginStyle) {
+      return this;
+    }
+    return HeadingPluginStyle(
+      textStyle: other.textStyle,
+      padding: other.padding,
+    );
+  }
+
+  static final light = HeadingPluginStyle(
+    padding: (_, __) => const EdgeInsets.symmetric(vertical: 8.0),
+    textStyle: (editorState, textNode) {
+      final headingToFontSize = {
+        'h1': 32.0,
+        'h2': 28.0,
+        'h3': 24.0,
+        'h4': 18.0,
+        'h5': 18.0,
+        'h6': 18.0,
+      };
+      final fontSize = headingToFontSize[textNode.attributes.heading] ?? 18.0;
+      return TextStyle(
+        fontSize: fontSize,
+        fontWeight: FontWeight.bold,
+      );
+    },
+  );
+
+  static final dark = light;
+}
+
+class CheckboxPluginStyle extends ThemeExtension<CheckboxPluginStyle> {
+  const CheckboxPluginStyle({
+    required this.textStyle,
+    required this.padding,
+    required this.icon,
+  });
+
+  final TextStyleCustomizer textStyle;
+  final PaddingCustomizer padding;
+  final IconCustomizer icon;
+
+  @override
+  CheckboxPluginStyle copyWith({
+    TextStyleCustomizer? textStyle,
+    PaddingCustomizer? padding,
+    IconCustomizer? icon,
+  }) {
+    return CheckboxPluginStyle(
+      textStyle: textStyle ?? this.textStyle,
+      padding: padding ?? this.padding,
+      icon: icon ?? this.icon,
+    );
+  }
+
+  @override
+  ThemeExtension<CheckboxPluginStyle> lerp(
+      ThemeExtension<CheckboxPluginStyle>? other, double t) {
+    if (other is! CheckboxPluginStyle) {
+      return this;
+    }
+    return CheckboxPluginStyle(
+      textStyle: other.textStyle,
+      padding: other.padding,
+      icon: other.icon,
+    );
+  }
+
+  static final light = CheckboxPluginStyle(
+    padding: (_, __) => const EdgeInsets.symmetric(vertical: 8.0),
+    textStyle: (editorState, textNode) => const TextStyle(),
+    icon: (editorState, textNode) {
+      final isCheck = textNode.attributes.check;
+      const iconSize = Size.square(20.0);
+      const iconPadding = EdgeInsets.only(right: 5.0);
+      return FlowySvg(
+        width: iconSize.width,
+        height: iconSize.height,
+        padding: iconPadding,
+        name: isCheck ? 'check' : 'uncheck',
+      );
+    },
+  );
+
+  static final dark = light;
+}
+
+class BulletedListPluginStyle extends ThemeExtension<BulletedListPluginStyle> {
+  const BulletedListPluginStyle({
+    required this.textStyle,
+    required this.padding,
+    required this.icon,
+  });
+
+  final TextStyleCustomizer textStyle;
+  final PaddingCustomizer padding;
+  final IconCustomizer icon;
+
+  @override
+  BulletedListPluginStyle copyWith({
+    TextStyleCustomizer? textStyle,
+    PaddingCustomizer? padding,
+    IconCustomizer? icon,
+  }) {
+    return BulletedListPluginStyle(
+      textStyle: textStyle ?? this.textStyle,
+      padding: padding ?? this.padding,
+      icon: icon ?? this.icon,
+    );
+  }
+
+  @override
+  ThemeExtension<BulletedListPluginStyle> lerp(
+      ThemeExtension<BulletedListPluginStyle>? other, double t) {
+    if (other is! BulletedListPluginStyle) {
+      return this;
+    }
+    return BulletedListPluginStyle(
+      textStyle: other.textStyle,
+      padding: other.padding,
+      icon: other.icon,
+    );
+  }
+
+  static final light = BulletedListPluginStyle(
+    padding: (_, __) => const EdgeInsets.symmetric(vertical: 8.0),
+    textStyle: (_, __) => const TextStyle(),
+    icon: (_, __) {
+      const iconSize = Size.square(20.0);
+      const iconPadding = EdgeInsets.only(right: 5.0);
+      return FlowySvg(
+        width: iconSize.width,
+        height: iconSize.height,
+        padding: iconPadding,
+        color: Colors.black,
+        name: 'point',
+      );
+    },
+  );
+
+  static final dark = light.copyWith(icon: (_, __) {
+    const iconSize = Size.square(20.0);
+    const iconPadding = EdgeInsets.only(right: 5.0);
+    return FlowySvg(
+      width: iconSize.width,
+      height: iconSize.height,
+      padding: iconPadding,
+      color: Colors.white,
+      name: 'point',
+    );
+  });
+}
+
+class NumberListPluginStyle extends ThemeExtension<NumberListPluginStyle> {
+  const NumberListPluginStyle({
+    required this.textStyle,
+    required this.padding,
+    required this.icon,
+  });
+
+  final TextStyleCustomizer textStyle;
+  final PaddingCustomizer padding;
+  final IconCustomizer icon;
+
+  @override
+  NumberListPluginStyle copyWith({
+    TextStyleCustomizer? textStyle,
+    PaddingCustomizer? padding,
+    IconCustomizer? icon,
+  }) {
+    return NumberListPluginStyle(
+      textStyle: textStyle ?? this.textStyle,
+      padding: padding ?? this.padding,
+      icon: icon ?? this.icon,
+    );
+  }
+
+  @override
+  ThemeExtension<NumberListPluginStyle> lerp(
+    ThemeExtension<NumberListPluginStyle>? other,
+    double t,
+  ) {
+    if (other is! NumberListPluginStyle) {
+      return this;
+    }
+    return NumberListPluginStyle(
+      textStyle: other.textStyle,
+      padding: other.padding,
+      icon: other.icon,
+    );
+  }
+
+  static final light = NumberListPluginStyle(
+    padding: (_, __) => const EdgeInsets.symmetric(vertical: 8.0),
+    textStyle: (_, __) => const TextStyle(),
+    icon: (_, textNode) {
+      const iconPadding = EdgeInsets.only(left: 5.0, right: 5.0);
+      return Container(
+        padding: iconPadding,
+        child: Text(
+          '${textNode.attributes.number.toString()}.',
+          style: const TextStyle(
+            fontSize: 16,
+            color: Colors.black,
+          ),
+        ),
+      );
+    },
+  );
+
+  static final dark = light.copyWith(icon: (editorState, textNode) {
+    const iconPadding = EdgeInsets.only(left: 5.0, right: 5.0);
+    return Container(
+      padding: iconPadding,
+      child: Text(
+        '${textNode.attributes.number.toString()}.',
+        style: const TextStyle(
+          fontSize: 16,
+          color: Colors.white,
+        ),
+      ),
+    );
+  });
+}
+
+class QuotedTextPluginStyle extends ThemeExtension<QuotedTextPluginStyle> {
+  const QuotedTextPluginStyle({
+    required this.textStyle,
+    required this.padding,
+    required this.icon,
+  });
+
+  final TextStyleCustomizer textStyle;
+  final PaddingCustomizer padding;
+  final IconCustomizer icon;
+
+  @override
+  QuotedTextPluginStyle copyWith({
+    TextStyleCustomizer? textStyle,
+    PaddingCustomizer? padding,
+    IconCustomizer? icon,
+  }) {
+    return QuotedTextPluginStyle(
+      textStyle: textStyle ?? this.textStyle,
+      padding: padding ?? this.padding,
+      icon: icon ?? this.icon,
+    );
+  }
+
+  @override
+  ThemeExtension<QuotedTextPluginStyle> lerp(
+      ThemeExtension<QuotedTextPluginStyle>? other, double t) {
+    if (other is! QuotedTextPluginStyle) {
+      return this;
+    }
+    return QuotedTextPluginStyle(
+      textStyle: other.textStyle,
+      padding: other.padding,
+      icon: other.icon,
+    );
+  }
+
+  static final light = QuotedTextPluginStyle(
+    padding: (_, __) => const EdgeInsets.symmetric(vertical: 8.0),
+    textStyle: (_, __) => const TextStyle(),
+    icon: (_, __) {
+      const iconSize = Size.square(20.0);
+      const iconPadding = EdgeInsets.only(right: 5.0);
+      return FlowySvg(
+        width: iconSize.width,
+        padding: iconPadding,
+        name: 'quote',
+      );
+    },
+  );
+
+  static final dark = light;
+}