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 488839a980..5540e5b443 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart @@ -38,6 +38,7 @@ class MyApp extends StatelessWidget { debugShowCheckedModeBanner: false, theme: ThemeData( primarySwatch: Colors.blue, + // extensions: [HeadingPluginStyle.light], ), home: const MyHomePage(title: 'AppFlowyEditor Example'), ); @@ -125,28 +126,48 @@ class _MyHomePageState extends State { _editorState!.transactionStream.listen((event) { 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 { 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 04b2714879..50d6e80aae 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart +++ b/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/selection_menu/selection_menu_widget.dart'; export 'src/l10n/l10n.dart'; +export 'src/render/style/built_in_plugin_styles.dart'; 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 5fbac22824..3e0b39e803 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 @@ -1,10 +1,10 @@ import 'package:appflowy_editor/src/core/document/node.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/default_selectable.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/style/built_in_plugin_styles.dart'; import 'package:appflowy_editor/src/service/render_plugin_service.dart'; import 'package:flutter/material.dart'; import 'package:appflowy_editor/src/extensions/text_style_extension.dart'; @@ -45,11 +45,7 @@ class BulletedListTextNodeWidget extends BuiltInTextWidget { // customize class _BulletedListTextNodeWidgetState extends State - with - SelectableMixin, - DefaultSelectable, - BuiltInStyleMixin, - BuiltInTextWidgetMixin { + with SelectableMixin, DefaultSelectable, BuiltInTextWidgetMixin { @override final iconKey = GlobalKey(); @@ -64,17 +60,23 @@ class _BulletedListTextNodeWidgetState extends State 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()!; + + 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 Widget buildWithSingle(BuildContext context) { @@ -83,13 +85,9 @@ class _BulletedListTextNodeWidgetState extends State child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - FlowySvg( + Container( key: iconKey, - width: iconSize?.width, - height: iconSize?.height, - padding: iconPadding, - color: bulletColor, - name: 'point', + child: icon, ), Flexible( child: FlowyRichText( 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 de12388937..d4b295a060 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 @@ -1,6 +1,5 @@ import 'package:appflowy_editor/appflowy_editor.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/extensions/text_style_extension.dart'; @@ -39,11 +38,7 @@ class CheckboxNodeWidget extends BuiltInTextWidget { } class _CheckboxNodeWidgetState extends State - with - SelectableMixin, - DefaultSelectable, - BuiltInStyleMixin, - BuiltInTextWidgetMixin { + with SelectableMixin, DefaultSelectable, BuiltInTextWidgetMixin { @override final iconKey = GlobalKey(); @@ -58,6 +53,24 @@ class _CheckboxNodeWidgetState extends State return super.baseOffset.translate(0, padding.top); } + CheckboxPluginStyle get style => + Theme.of(context).extension()!; + + 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 Widget buildWithSingle(BuildContext context) { final check = widget.textNode.attributes.check; @@ -68,12 +81,7 @@ class _CheckboxNodeWidgetState extends State children: [ GestureDetector( key: iconKey, - child: FlowySvg( - width: iconSize?.width, - height: iconSize?.height, - padding: iconPadding, - name: check ? 'check' : 'uncheck', - ), + child: icon, onTap: () async { await widget.editorState.formatTextToCheckbox( 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 5b6c75cc6a..98f580cfc3 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 @@ -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/flowy_rich_text.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:flutter/material.dart'; import 'package:appflowy_editor/src/extensions/attributes_extension.dart'; @@ -43,7 +44,7 @@ class HeadingTextNodeWidget extends BuiltInTextWidget { // customize class _HeadingTextNodeWidgetState extends State - with SelectableMixin, DefaultSelectable, BuiltInStyleMixin { + with SelectableMixin, DefaultSelectable { @override GlobalKey? get iconKey => null; @@ -58,6 +59,19 @@ class _HeadingTextNodeWidgetState extends State return padding.topLeft; } + HeadingPluginStyle get style => + Theme.of(context).extension()!; + + EdgeInsets get padding => style.padding( + widget.editorState, + widget.textNode, + ); + + TextStyle get textStyle => style.textStyle( + widget.editorState, + widget.textNode, + ); + @override Widget build(BuildContext context) { return Padding( 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 6d8004028b..611e87c214 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 @@ -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/flowy_rich_text.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:flutter/material.dart'; import 'package:appflowy_editor/src/extensions/attributes_extension.dart'; @@ -43,7 +44,7 @@ class NumberListTextNodeWidget extends BuiltInTextWidget { } class _NumberListTextNodeWidgetState extends State - with SelectableMixin, DefaultSelectable, BuiltInStyleMixin { + with SelectableMixin, DefaultSelectable { @override final iconKey = GlobalKey(); @@ -70,6 +71,24 @@ class _NumberListTextNodeWidgetState extends State return Colors.black; } + NumberListPluginStyle get style => + Theme.of(context).extension()!; + + 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 Widget build(BuildContext context) { return Padding( @@ -79,15 +98,7 @@ class _NumberListTextNodeWidgetState extends State children: [ Container( 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( child: FlowyRichText( 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 b68fc38923..e212fec4b9 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 @@ -1,10 +1,10 @@ import 'package:appflowy_editor/src/core/document/node.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/default_selectable.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/style/built_in_plugin_styles.dart'; import 'package:appflowy_editor/src/service/render_plugin_service.dart'; import 'package:flutter/material.dart'; import 'package:appflowy_editor/src/extensions/text_style_extension.dart'; @@ -44,7 +44,7 @@ class QuotedTextNodeWidget extends BuiltInTextWidget { // customize class _QuotedTextNodeWidgetState extends State - with SelectableMixin, DefaultSelectable, BuiltInStyleMixin { + with SelectableMixin, DefaultSelectable { @override final iconKey = GlobalKey(); @@ -59,6 +59,24 @@ class _QuotedTextNodeWidgetState extends State return super.baseOffset.translate(0, padding.top); } + QuotedTextPluginStyle get style => + Theme.of(context).extension()!; + + 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 Widget build(BuildContext context) { return Padding( @@ -67,11 +85,9 @@ class _QuotedTextNodeWidgetState extends State child: Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - FlowySvg( + Container( key: iconKey, - width: iconSize?.width, - padding: iconPadding, - name: 'quote', + child: icon, ), Flexible( child: FlowyRichText( 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 index 331ec1d1af..6cfb6d8f95 100644 --- 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 @@ -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/extensions/attributes_extension.dart'; +class EditorStyleV2 extends ThemeExtension { + // 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 lerp( + ThemeExtension? 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 PluginStyle = Map; @@ -41,6 +135,7 @@ class EditorStyle { return null; } + @override EditorStyle copyWith({ EdgeInsets? padding, BuiltInTextStyle? textStyle, diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/plugin_style.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/plugin_style.dart new file mode 100644 index 0000000000..f1d0f7cd88 --- /dev/null +++ b/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 { + 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 lerp( + ThemeExtension? 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 { + 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 lerp( + ThemeExtension? 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 { + 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 lerp( + ThemeExtension? 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 { + 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 lerp( + ThemeExtension? 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 { + 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 lerp( + ThemeExtension? 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; +}