From 3fb997af840656bd66d9756ae7c5a21d51f2b242 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Tue, 25 Oct 2022 14:40:18 +0800 Subject: [PATCH 01/15] feat: support dark mode for number-list and bulleted-list --- .../lib/plugins/doc/editor_styles.dart | 20 ++++++++++++++++++- .../render/rich_text/bulleted_list_text.dart | 13 ++++++++++++ .../render/rich_text/number_list_text.dart | 19 ++++++++++++++++-- .../lib/src/render/style/editor_style.dart | 16 +++++++++++---- 4 files changed, 61 insertions(+), 7 deletions(-) diff --git a/frontend/app_flowy/lib/plugins/doc/editor_styles.dart b/frontend/app_flowy/lib/plugins/doc/editor_styles.dart index dc472aeaf1..09437ff36d 100644 --- a/frontend/app_flowy/lib/plugins/doc/editor_styles.dart +++ b/frontend/app_flowy/lib/plugins/doc/editor_styles.dart @@ -55,7 +55,25 @@ EditorStyle customEditorStyle(BuildContext context) { headingToPadding[node.attributes.heading] ?? basePadding; return EdgeInsets.only(bottom: padding); }, - ) + ), + 'text/number-list': builtInPluginStyle + ..addAll( + { + 'numberColor': (EditorState editorState, Node node) { + final theme = context.watch(); + return theme.isDark ? Colors.white : Colors.black; + }, + }, + ), + 'text/bulleted-list': builtInPluginStyle + ..addAll( + { + 'bulletColor': (EditorState editorState, Node node) { + final theme = context.watch(); + return theme.isDark ? Colors.white : Colors.black; + }, + }, + ), }, ); } 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 36e3568684..5fbac22824 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 @@ -64,6 +64,18 @@ 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; + } + @override Widget buildWithSingle(BuildContext context) { return Padding( @@ -76,6 +88,7 @@ class _BulletedListTextNodeWidgetState extends State width: iconSize?.width, height: iconSize?.height, padding: iconPadding, + color: bulletColor, name: 'point', ), Flexible( 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 6ce4bd0fee..6d8004028b 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 @@ -58,6 +58,18 @@ class _NumberListTextNodeWidgetState extends State return super.baseOffset.translate(0, padding.top); } + Color get numberColor { + final numberColor = widget.editorState.editorStyle.style( + widget.editorState, + widget.textNode, + 'numberColor', + ); + if (numberColor is Color) { + return numberColor; + } + return Colors.black; + } + @override Widget build(BuildContext context) { return Padding( @@ -70,8 +82,11 @@ class _NumberListTextNodeWidgetState extends State padding: iconPadding, child: Text( '${widget.textNode.attributes.number.toString()}.', - // FIXME: customize - style: const TextStyle(fontSize: 16.0, color: Colors.black), + style: TextStyle( + fontSize: widget.editorState.editorStyle.textStyle + .defaultTextStyle.fontSize, + color: numberColor, + ), ), ), Flexible( 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 a8cc9eb638..331ec1d1af 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 @@ -117,12 +117,20 @@ Map builtInTextStylers = { ), 'text/bulleted-list': builtInPluginStyle, 'text/number-list': builtInPluginStyle - ..update( - 'iconPadding', - (_) => (EditorState editorState, Node node) { + ..addAll({ + 'numberColor': (EditorState editorState, Node node) { + return Colors.black; + }, + 'iconPadding': (EditorState editorState, Node node) { return const EdgeInsets.only(left: 5.0, right: 5.0); }, - ), + }), + 'text/bulleted-list': builtInPluginStyle + ..addAll({ + 'bulletColor': (EditorState editorState, Node node) { + return Colors.black; + }, + }), 'text/quote': builtInPluginStyle, 'image': builtInPluginStyle, }; From 68da3955c1bda2f245688896c584fff41ae69dad Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Tue, 25 Oct 2022 14:41:50 +0800 Subject: [PATCH 02/15] fix: disable toolbar hover color --- .../lib/src/render/toolbar/toolbar_item_widget.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item_widget.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item_widget.dart index 71cfd030d5..4b6170620b 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item_widget.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item_widget.dart @@ -25,6 +25,7 @@ class ToolbarItemWidget extends StatelessWidget { child: MouseRegion( cursor: SystemMouseCursors.click, child: IconButton( + hoverColor: Colors.transparent, highlightColor: Colors.transparent, padding: EdgeInsets.zero, icon: item.iconBuilder(isHighlight), From cdee706f46249507876a1e7eff67b3edad61fdc6 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Tue, 25 Oct 2022 20:01:17 +0800 Subject: [PATCH 03/15] feat: refactor theme plugin, use themedata extension --- .../appflowy_editor/example/lib/main.dart | 65 ++-- .../appflowy_editor/lib/appflowy_editor.dart | 1 + .../render/rich_text/bulleted_list_text.dart | 44 ++- .../src/render/rich_text/checkbox_text.dart | 32 +- .../src/render/rich_text/heading_text.dart | 16 +- .../render/rich_text/number_list_text.dart | 31 +- .../lib/src/render/rich_text/quoted_text.dart | 28 +- .../lib/src/render/style/editor_style.dart | 95 ++++++ .../lib/src/render/style/plugin_style.dart | 312 ++++++++++++++++++ 9 files changed, 550 insertions(+), 74 deletions(-) create mode 100644 frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/plugin_style.dart 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; +} From dde50d32d6fa854b2c1710ea56d3aeaca67e7e55 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Tue, 25 Oct 2022 21:31:19 +0800 Subject: [PATCH 04/15] feat: refactor theme plugin, use themedata extension --- .../appflowy_editor/example/lib/main.dart | 97 ++---- .../lib/plugin/code_block_node_widget.dart | 2 +- .../appflowy_editor/lib/src/editor_state.dart | 7 +- .../rich_text/built_in_text_widget.dart | 50 --- .../render/rich_text/bulleted_list_text.dart | 5 +- .../src/render/rich_text/checkbox_text.dart | 5 +- .../src/render/rich_text/flowy_rich_text.dart | 9 +- .../src/render/rich_text/heading_text.dart | 5 +- .../render/rich_text/number_list_text.dart | 19 +- .../lib/src/render/rich_text/quoted_text.dart | 5 +- .../lib/src/render/rich_text/rich_text.dart | 19 +- ...style.dart => built_in_plugin_styles.dart} | 15 + .../lib/src/render/style/editor_style.dart | 312 +++--------------- .../lib/src/render/toolbar/toolbar_item.dart | 2 +- .../lib/src/service/editor_service.dart | 90 ++--- .../format_style_handler.dart | 2 +- .../test/infra/test_editor.dart | 1 - .../render/image/image_node_builder_test.dart | 5 +- 18 files changed, 176 insertions(+), 474 deletions(-) rename frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/{plugin_style.dart => built_in_plugin_styles.dart} (94%) 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 5540e5b443..bef4dce946 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart @@ -10,7 +10,6 @@ import 'package:flutter/services.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; -import 'package:google_fonts/google_fonts.dart'; import 'package:path_provider/path_provider.dart'; import 'package:universal_html/html.dart' as html; @@ -57,7 +56,6 @@ class _MyHomePageState extends State { int _pageIndex = 0; EditorState? _editorState; bool darkMode = false; - EditorStyle _editorStyle = EditorStyle.defaultStyle(); Future? _jsonString; @override @@ -132,7 +130,8 @@ class _MyHomePageState extends State { CheckboxPluginStyle.dark, NumberListPluginStyle.dark, QuotedTextPluginStyle.dark, - BulletedListPluginStyle.dark + BulletedListPluginStyle.dark, + EditorStyle.dark, ]) : ThemeData.light().copyWith( extensions: [ @@ -140,34 +139,32 @@ class _MyHomePageState extends State { CheckboxPluginStyle.light, NumberListPluginStyle.light, QuotedTextPluginStyle.light, - BulletedListPluginStyle.light + BulletedListPluginStyle.light, + EditorStyle.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, - ], - ), + return Container( + color: darkMode ? Colors.black : Colors.white, + width: MediaQuery.of(context).size.width, + child: AppFlowyEditor( + editorState: _editorState!, + themeData: themeData, + editable: true, + customBuilders: { + 'text/code_block': CodeBlockNodeWidgetBuilder(), + 'tex': TeXBlockNodeWidgetBuidler(), + 'horizontal_rule': HorizontalRuleWidgetBuilder(), + }, + shortcutEvents: [ + enterInCodeBlock, + ignoreKeysInCodeBlock, + insertHorizontalRule, + ], + selectionMenuItems: [ + codeBlockMenuItem, + teXBlockMenuItem, + horizontalRuleMenuItem, + ], ), ); } else { @@ -207,8 +204,6 @@ class _MyHomePageState extends State { icon: const Icon(Icons.color_lens), onPressed: () { setState(() { - _editorStyle = - darkMode ? EditorStyle.defaultStyle() : _customizedStyle(); darkMode = !darkMode; }); }, @@ -277,44 +272,4 @@ class _MyHomePageState extends State { }); } } - - EditorStyle _customizedStyle() { - final editorStyle = EditorStyle.defaultStyle(); - return editorStyle.copyWith( - cursorColor: Colors.white, - selectionColor: Colors.blue.withOpacity(0.3), - textStyle: editorStyle.textStyle.copyWith( - defaultTextStyle: GoogleFonts.poppins().copyWith( - color: Colors.white, - fontSize: 14.0, - ), - defaultPlaceholderTextStyle: GoogleFonts.poppins().copyWith( - color: Colors.white.withOpacity(0.5), - fontSize: 14.0, - ), - bold: const TextStyle(fontWeight: FontWeight.w900), - code: TextStyle( - fontStyle: FontStyle.italic, - color: Colors.red[300], - backgroundColor: Colors.grey.withOpacity(0.3), - ), - highlightColorHex: '0x6FFFEB3B', - ), - pluginStyles: { - 'text/quote': builtInPluginStyle - ..update( - 'textStyle', - (_) { - return (EditorState editorState, Node node) { - return TextStyle( - color: Colors.blue[200], - fontStyle: FontStyle.italic, - fontSize: 12.0, - ); - }; - }, - ), - }, - ); - } } diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/code_block_node_widget.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/code_block_node_widget.dart index a82d20157e..6716cca163 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/code_block_node_widget.dart +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/code_block_node_widget.dart @@ -167,7 +167,7 @@ class __CodeBlockNodeWidgeState extends State<_CodeBlockNodeWidge> textNode: widget.textNode, editorState: widget.editorState, textSpanDecorator: (textSpan) => TextSpan( - style: widget.editorState.editorStyle.textStyle.defaultTextStyle, + style: widget.editorState.editorStyle.textStyle, children: codeTextSpan, ), ), diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/editor_state.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/editor_state.dart index 872dad8e7a..95bab3c231 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/editor_state.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/editor_state.dart @@ -59,13 +59,14 @@ class EditorState { /// Stores the selection menu items. List selectionMenuItems = []; - /// Stores the editor style. - EditorStyle editorStyle = EditorStyle.defaultStyle(); - /// Operation stream. Stream get transactionStream => _observer.stream; final StreamController _observer = StreamController.broadcast(); + late ThemeData themeData; + EditorStyle get editorStyle => + themeData.extension() ?? EditorStyle.light; + final UndoManager undoManager = UndoManager(); Selection? _cursorSelection; diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/built_in_text_widget.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/built_in_text_widget.dart index a0a65d9583..7fb2cee2cb 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/built_in_text_widget.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/built_in_text_widget.dart @@ -10,56 +10,6 @@ abstract class BuiltInTextWidget extends StatefulWidget { TextNode get textNode; } -mixin BuiltInStyleMixin on State { - EdgeInsets get padding { - final padding = widget.editorState.editorStyle.style( - widget.editorState, - widget.textNode, - 'padding', - ); - if (padding is EdgeInsets) { - return padding; - } - return const EdgeInsets.all(0); - } - - TextStyle get textStyle { - final textStyle = widget.editorState.editorStyle.style( - widget.editorState, - widget.textNode, - 'textStyle', - ); - if (textStyle is TextStyle) { - return textStyle; - } - return const TextStyle(); - } - - Size? get iconSize { - final iconSize = widget.editorState.editorStyle.style( - widget.editorState, - widget.textNode, - 'iconSize', - ); - if (iconSize is Size) { - return iconSize; - } - return const Size.square(18.0); - } - - EdgeInsets? get iconPadding { - final iconPadding = widget.editorState.editorStyle.style( - widget.editorState, - widget.textNode, - 'iconPadding', - ); - if (iconPadding is EdgeInsets) { - return iconPadding; - } - return const EdgeInsets.all(0); - } -} - mixin BuiltInTextWidgetMixin on State implements DefaultSelectable { @override 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 3e0b39e803..52fa095f39 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 @@ -61,7 +61,8 @@ class _BulletedListTextNodeWidgetState extends State } BulletedListPluginStyle get style => - Theme.of(context).extension()!; + Theme.of(context).extension() ?? + BulletedListPluginStyle.light; EdgeInsets get padding => style.padding( widget.editorState, @@ -97,7 +98,7 @@ class _BulletedListTextNodeWidgetState extends State textSpan.updateTextStyle(textStyle), placeholderTextSpanDecorator: (textSpan) => textSpan.updateTextStyle(textStyle), - lineHeight: widget.editorState.editorStyle.textStyle.lineHeight, + lineHeight: widget.editorState.editorStyle.lineHeight, textNode: widget.textNode, editorState: widget.editorState, ), 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 d4b295a060..39115b69e8 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 @@ -54,7 +54,8 @@ class _CheckboxNodeWidgetState extends State } CheckboxPluginStyle get style => - Theme.of(context).extension()!; + Theme.of(context).extension() ?? + CheckboxPluginStyle.light; EdgeInsets get padding => style.padding( widget.editorState, @@ -94,7 +95,7 @@ class _CheckboxNodeWidgetState extends State child: FlowyRichText( key: _richTextKey, placeholderText: 'To-do', - lineHeight: widget.editorState.editorStyle.textStyle.lineHeight, + lineHeight: widget.editorState.editorStyle.lineHeight, textNode: widget.textNode, textSpanDecorator: (textSpan) => textSpan.updateTextStyle(textStyle), diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/flowy_rich_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/flowy_rich_text.dart index 40e0cffb26..8d96d143cf 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/flowy_rich_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/flowy_rich_text.dart @@ -202,12 +202,13 @@ class _FlowyRichTextState extends State with SelectableMixin { } TextSpan get _placeholderTextSpan { - final style = widget.editorState.editorStyle.textStyle; + final placeholderTextStyle = + widget.editorState.editorStyle.placeholderTextStyle; return TextSpan( children: [ TextSpan( text: widget.placeholderText, - style: style.defaultPlaceholderTextStyle, + style: placeholderTextStyle, ), ], ); @@ -216,10 +217,10 @@ class _FlowyRichTextState extends State with SelectableMixin { TextSpan get _textSpan { var offset = 0; List textSpans = []; - final style = widget.editorState.editorStyle.textStyle; + final style = widget.editorState.editorStyle; final textInserts = widget.textNode.delta.whereType(); for (final textInsert in textInserts) { - var textStyle = style.defaultTextStyle; + var textStyle = style.textStyle!; GestureRecognizer? recognizer; final attributes = textInsert.attributes; if (attributes != null) { 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 98f580cfc3..0ab06bcb6f 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 @@ -60,7 +60,8 @@ class _HeadingTextNodeWidgetState extends State } HeadingPluginStyle get style => - Theme.of(context).extension()!; + Theme.of(context).extension() ?? + HeadingPluginStyle.light; EdgeInsets get padding => style.padding( widget.editorState, @@ -82,7 +83,7 @@ class _HeadingTextNodeWidgetState extends State placeholderTextSpanDecorator: (textSpan) => textSpan.updateTextStyle(textStyle), textSpanDecorator: (textSpan) => textSpan.updateTextStyle(textStyle), - lineHeight: widget.editorState.editorStyle.textStyle.lineHeight, + lineHeight: widget.editorState.editorStyle.lineHeight, textNode: widget.textNode, editorState: widget.editorState, ), 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 611e87c214..bb9d1bd535 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,7 +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/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'; @@ -59,20 +59,9 @@ class _NumberListTextNodeWidgetState extends State return super.baseOffset.translate(0, padding.top); } - Color get numberColor { - final numberColor = widget.editorState.editorStyle.style( - widget.editorState, - widget.textNode, - 'numberColor', - ); - if (numberColor is Color) { - return numberColor; - } - return Colors.black; - } - NumberListPluginStyle get style => - Theme.of(context).extension()!; + Theme.of(context).extension() ?? + NumberListPluginStyle.light; EdgeInsets get padding => style.padding( widget.editorState, @@ -106,7 +95,7 @@ class _NumberListTextNodeWidgetState extends State placeholderText: 'List', textNode: widget.textNode, editorState: widget.editorState, - lineHeight: widget.editorState.editorStyle.textStyle.lineHeight, + lineHeight: widget.editorState.editorStyle.lineHeight, placeholderTextSpanDecorator: (textSpan) => textSpan.updateTextStyle(textStyle), textSpanDecorator: (textSpan) => 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 e212fec4b9..a8176219cf 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 @@ -60,7 +60,8 @@ class _QuotedTextNodeWidgetState extends State } QuotedTextPluginStyle get style => - Theme.of(context).extension()!; + Theme.of(context).extension() ?? + QuotedTextPluginStyle.light; EdgeInsets get padding => style.padding( widget.editorState, @@ -98,7 +99,7 @@ class _QuotedTextNodeWidgetState extends State textSpan.updateTextStyle(textStyle), placeholderTextSpanDecorator: (textSpan) => textSpan.updateTextStyle(textStyle), - lineHeight: widget.editorState.editorStyle.textStyle.lineHeight, + lineHeight: widget.editorState.editorStyle.lineHeight, editorState: widget.editorState, ), ), diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/rich_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/rich_text.dart index a28270bf7c..f48714045b 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/rich_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/rich_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/editor_style.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'; @@ -43,11 +44,7 @@ class RichTextNodeWidget extends BuiltInTextWidget { // customize class _RichTextNodeWidgetState extends State - with - SelectableMixin, - DefaultSelectable, - BuiltInStyleMixin, - BuiltInTextWidgetMixin { + with SelectableMixin, DefaultSelectable, BuiltInTextWidgetMixin { @override GlobalKey? get iconKey => null; @@ -59,20 +56,26 @@ class _RichTextNodeWidgetState extends State @override Offset get baseOffset { - return padding.topLeft; + return textPadding.topLeft; } + EditorStyle get style => widget.editorState.editorStyle; + + EdgeInsets get textPadding => style.textPadding!; + + TextStyle get textStyle => style.textStyle!; + @override Widget buildWithSingle(BuildContext context) { return Padding( - padding: padding, + padding: textPadding, child: FlowyRichText( key: _richTextKey, textNode: widget.textNode, textSpanDecorator: (textSpan) => textSpan.updateTextStyle(textStyle), placeholderTextSpanDecorator: (textSpan) => textSpan.updateTextStyle(textStyle), - lineHeight: widget.editorState.editorStyle.textStyle.lineHeight, + lineHeight: widget.editorState.editorStyle.lineHeight, editorState: widget.editorState, ), ); 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/built_in_plugin_styles.dart similarity index 94% rename from frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/plugin_style.dart rename to frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/built_in_plugin_styles.dart index f1d0f7cd88..832244a64f 100644 --- 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/built_in_plugin_styles.dart @@ -2,6 +2,21 @@ import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/src/infra/flowy_svg.dart'; import 'package:flutter/material.dart'; +Iterable> get lightPlguinStyleExtension => [ + HeadingPluginStyle.light, + CheckboxPluginStyle.light, + NumberListPluginStyle.light, + QuotedTextPluginStyle.light, + ]; + +Iterable> get darkPlguinStyleExtension => [ + HeadingPluginStyle.dark, + CheckboxPluginStyle.dark, + NumberListPluginStyle.dark, + QuotedTextPluginStyle.dark, + BulletedListPluginStyle.dark, + ]; + typedef TextStyleCustomizer = TextStyle Function( EditorState editorState, TextNode textNode); typedef PaddingCustomizer = EdgeInsets Function( 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 6cfb6d8f95..28fa5f95e5 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 @@ -1,16 +1,21 @@ import 'package:flutter/material.dart'; -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'; +Iterable> get lightEditorStyleExtension => [ + EditorStyle.light, + ]; -class EditorStyleV2 extends ThemeExtension { +Iterable> get dartEditorStyleExtension => [ + EditorStyle.dark, + ]; + +class EditorStyle extends ThemeExtension { // Editor styles final EdgeInsets? padding; final Color? cursorColor; final Color? selectionColor; // Text styles + final EdgeInsets? textPadding; final TextStyle? textStyle; final TextStyle? placeholderTextStyle; final double lineHeight; @@ -24,10 +29,11 @@ class EditorStyleV2 extends ThemeExtension { final TextStyle? code; final String? highlightColorHex; - EditorStyleV2({ + EditorStyle({ required this.padding, required this.cursorColor, required this.selectionColor, + required this.textPadding, required this.textStyle, required this.placeholderTextStyle, required this.bold, @@ -41,7 +47,7 @@ class EditorStyleV2 extends ThemeExtension { }); @override - EditorStyleV2 copyWith({ + EditorStyle copyWith({ EdgeInsets? padding, Color? cursorColor, Color? selectionColor, @@ -56,10 +62,11 @@ class EditorStyleV2 extends ThemeExtension { String? highlightColorHex, double? lineHeight, }) { - return EditorStyleV2( + return EditorStyle( padding: padding ?? this.padding, cursorColor: cursorColor ?? this.cursorColor, selectionColor: selectionColor ?? this.selectionColor, + textPadding: textPadding ?? textPadding, textStyle: textStyle ?? this.textStyle, placeholderTextStyle: placeholderTextStyle ?? this.placeholderTextStyle, bold: bold ?? this.bold, @@ -74,14 +81,15 @@ class EditorStyleV2 extends ThemeExtension { } @override - ThemeExtension lerp( - ThemeExtension? other, double t) { - if (other == null || other is! EditorStyleV2) { + ThemeExtension lerp( + ThemeExtension? other, double t) { + if (other == null || other is! EditorStyle) { return this; } - return EditorStyleV2( + return EditorStyle( padding: EdgeInsets.lerp(padding, other.padding, t), cursorColor: Color.lerp(cursorColor, other.cursorColor, t), + textPadding: EdgeInsets.lerp(textPadding, other.textPadding, t), selectionColor: Color.lerp(selectionColor, other.selectionColor, t), textStyle: TextStyle.lerp(textStyle, other.textStyle, t), placeholderTextStyle: @@ -96,262 +104,36 @@ class EditorStyleV2 extends ThemeExtension { lineHeight: lineHeight, ); } -} -typedef PluginStyler = Object Function(EditorState editorState, Node node); -typedef PluginStyle = Map; - -/// Editor style configuration -class EditorStyle { - EditorStyle({ - required this.padding, - required this.textStyle, - required this.cursorColor, - required this.selectionColor, - Map pluginStyles = const {}, - }) { - _pluginStyles.addAll(pluginStyles); - } - - EditorStyle.defaultStyle() - : padding = const EdgeInsets.fromLTRB(200.0, 0.0, 200.0, 0.0), - textStyle = BuiltInTextStyle.builtIn(), - cursorColor = const Color(0xFF00BCF0), - selectionColor = const Color.fromARGB(53, 111, 201, 231); - - /// The margin of the document context from the editor. - final EdgeInsets padding; - final BuiltInTextStyle textStyle; - final Color cursorColor; - final Color selectionColor; - - final Map _pluginStyles = Map.from(builtInTextStylers); - - Object? style(EditorState editorState, Node node, String key) { - final styler = _pluginStyles[node.id]?[key]; - if (styler != null) { - return styler(editorState, node); - } - return null; - } - - @override - EditorStyle copyWith({ - EdgeInsets? padding, - BuiltInTextStyle? textStyle, - Color? cursorColor, - Color? selectionColor, - Map? pluginStyles, - }) { - return EditorStyle( - padding: padding ?? this.padding, - textStyle: textStyle ?? this.textStyle, - cursorColor: cursorColor ?? this.cursorColor, - selectionColor: selectionColor ?? this.selectionColor, - pluginStyles: pluginStyles ?? {}, - ); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is EditorStyle && - other.padding == padding && - other.textStyle == textStyle && - other.cursorColor == cursorColor && - other.selectionColor == selectionColor; - } - - @override - int get hashCode { - return padding.hashCode ^ - textStyle.hashCode ^ - cursorColor.hashCode ^ - selectionColor.hashCode; - } -} - -PluginStyle get builtInPluginStyle => Map.from({ - 'padding': (_, __) => const EdgeInsets.symmetric(vertical: 8.0), - 'textStyle': (_, __) => const TextStyle(), - 'iconSize': (_, __) => const Size.square(20.0), - 'iconPadding': (_, __) => const EdgeInsets.only(right: 5.0), - }); - -Map builtInTextStylers = { - 'text': builtInPluginStyle, - 'text/checkbox': builtInPluginStyle - ..update( - 'textStyle', - (_) => (EditorState editorState, Node node) { - if (node is TextNode && node.attributes.check == true) { - return const TextStyle( - color: Colors.grey, - decoration: TextDecoration.lineThrough, - ); - } - return const TextStyle(); - }, + static final light = EditorStyle( + padding: const EdgeInsets.fromLTRB(200.0, 0.0, 200.0, 0.0), + cursorColor: const Color(0xFF00BCF0), + selectionColor: const Color.fromARGB(53, 111, 201, 231), + textPadding: const EdgeInsets.symmetric(vertical: 8.0), + textStyle: const TextStyle(fontSize: 16.0, color: Colors.black), + placeholderTextStyle: const TextStyle(fontSize: 16.0, color: Colors.grey), + bold: const TextStyle(fontWeight: FontWeight.bold), + italic: const TextStyle(fontStyle: FontStyle.italic), + underline: const TextStyle(decoration: TextDecoration.underline), + strikethrough: const TextStyle(decoration: TextDecoration.lineThrough), + href: const TextStyle( + color: Colors.blue, + decoration: TextDecoration.underline, ), - 'text/heading': builtInPluginStyle - ..update( - 'textStyle', - (_) => (EditorState editorState, Node node) { - final headingToFontSize = { - 'h1': 32.0, - 'h2': 28.0, - 'h3': 24.0, - 'h4': 18.0, - 'h5': 18.0, - 'h6': 18.0, - }; - final fontSize = headingToFontSize[node.attributes.heading] ?? 18.0; - return TextStyle(fontSize: fontSize, fontWeight: FontWeight.bold); - }, + code: const TextStyle( + fontFamily: 'monospace', + color: Color(0xFF00BCF0), + backgroundColor: Color(0xFFE0F8FF), ), - 'text/bulleted-list': builtInPluginStyle, - 'text/number-list': builtInPluginStyle - ..addAll({ - 'numberColor': (EditorState editorState, Node node) { - return Colors.black; - }, - 'iconPadding': (EditorState editorState, Node node) { - return const EdgeInsets.only(left: 5.0, right: 5.0); - }, - }), - 'text/bulleted-list': builtInPluginStyle - ..addAll({ - 'bulletColor': (EditorState editorState, Node node) { - return Colors.black; - }, - }), - 'text/quote': builtInPluginStyle, - 'image': builtInPluginStyle, -}; + highlightColorHex: '0x6000BCF0', + lineHeight: 1.5, + ); -class BuiltInTextStyle { - const BuiltInTextStyle({ - required this.defaultTextStyle, - required this.defaultPlaceholderTextStyle, - required this.bold, - required this.italic, - required this.underline, - required this.strikethrough, - required this.href, - required this.code, - this.highlightColorHex = '0x6000BCF0', - this.lineHeight = 1.5, - }); - - final TextStyle defaultTextStyle; - final TextStyle defaultPlaceholderTextStyle; - final TextStyle bold; - final TextStyle italic; - final TextStyle underline; - final TextStyle strikethrough; - final TextStyle href; - final TextStyle code; - final String highlightColorHex; - final double lineHeight; - - BuiltInTextStyle.builtIn() - : defaultTextStyle = const TextStyle(fontSize: 16.0, color: Colors.black), - defaultPlaceholderTextStyle = - const TextStyle(fontSize: 16.0, color: Colors.grey), - bold = const TextStyle(fontWeight: FontWeight.bold), - italic = const TextStyle(fontStyle: FontStyle.italic), - underline = const TextStyle(decoration: TextDecoration.underline), - strikethrough = const TextStyle(decoration: TextDecoration.lineThrough), - href = const TextStyle( - color: Colors.blue, - decoration: TextDecoration.underline, - ), - code = const TextStyle( - fontFamily: 'monospace', - color: Color(0xFF00BCF0), - backgroundColor: Color(0xFFE0F8FF), - ), - highlightColorHex = '0x6000BCF0', - lineHeight = 1.5; - - BuiltInTextStyle.builtInDarkMode() - : defaultTextStyle = const TextStyle(fontSize: 16.0, color: Colors.white), - defaultPlaceholderTextStyle = TextStyle( - fontSize: 16.0, - color: Colors.white.withOpacity(0.3), - ), - bold = const TextStyle(fontWeight: FontWeight.bold), - italic = const TextStyle(fontStyle: FontStyle.italic), - underline = const TextStyle(decoration: TextDecoration.underline), - strikethrough = const TextStyle(decoration: TextDecoration.lineThrough), - href = const TextStyle( - color: Colors.blue, - decoration: TextDecoration.underline, - ), - code = const TextStyle( - fontFamily: 'monospace', - color: Color(0xFF00BCF0), - backgroundColor: Color(0xFFE0F8FF), - ), - highlightColorHex = '0x6000BCF0', - lineHeight = 1.5; - - BuiltInTextStyle copyWith({ - TextStyle? defaultTextStyle, - TextStyle? defaultPlaceholderTextStyle, - TextStyle? bold, - TextStyle? italic, - TextStyle? underline, - TextStyle? strikethrough, - TextStyle? href, - TextStyle? code, - String? highlightColorHex, - double? lineHeight, - }) { - return BuiltInTextStyle( - defaultTextStyle: defaultTextStyle ?? this.defaultTextStyle, - defaultPlaceholderTextStyle: - defaultPlaceholderTextStyle ?? this.defaultPlaceholderTextStyle, - 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 - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is BuiltInTextStyle && - other.defaultTextStyle == defaultTextStyle && - other.defaultPlaceholderTextStyle == defaultPlaceholderTextStyle && - other.bold == bold && - other.italic == italic && - other.underline == underline && - other.strikethrough == strikethrough && - other.href == href && - other.code == code && - other.highlightColorHex == highlightColorHex && - other.lineHeight == lineHeight; - } - - @override - int get hashCode { - return defaultTextStyle.hashCode ^ - defaultPlaceholderTextStyle.hashCode ^ - bold.hashCode ^ - italic.hashCode ^ - underline.hashCode ^ - strikethrough.hashCode ^ - href.hashCode ^ - code.hashCode ^ - highlightColorHex.hashCode ^ - lineHeight.hashCode; - } + static final dark = light.copyWith( + textStyle: const TextStyle(fontSize: 16.0, color: Colors.white), + placeholderTextStyle: TextStyle( + fontSize: 16.0, + color: Colors.white.withOpacity(0.3), + ), + ); } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item.dart index 56008dced2..bae2367fb9 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item.dart @@ -259,7 +259,7 @@ List defaultToolbarItems = [ ), handler: (editorState, context) => formatHighlight( editorState, - editorState.editorStyle.textStyle.highlightColorHex, + editorState.editorStyle.highlightColorHex!, ), ), ]; diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/editor_service.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/editor_service.dart index dd00a3e0c9..af9c9e7380 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/editor_service.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/editor_service.dart @@ -1,12 +1,9 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/src/flutter/overlay.dart'; 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/shortcut_event/built_in_shortcut_events.dart'; -import 'package:appflowy_editor/src/service/shortcut_event/shortcut_event.dart'; import 'package:flutter/material.dart' hide Overlay, OverlayEntry; -import 'package:appflowy_editor/src/editor_state.dart'; import 'package:appflowy_editor/src/render/editor/editor_entry.dart'; import 'package:appflowy_editor/src/render/rich_text/bulleted_list_text.dart'; import 'package:appflowy_editor/src/render/rich_text/checkbox_text.dart'; @@ -14,12 +11,6 @@ import 'package:appflowy_editor/src/render/rich_text/heading_text.dart'; import 'package:appflowy_editor/src/render/rich_text/number_list_text.dart'; import 'package:appflowy_editor/src/render/rich_text/quoted_text.dart'; import 'package:appflowy_editor/src/render/rich_text/rich_text.dart'; -import 'package:appflowy_editor/src/service/input_service.dart'; -import 'package:appflowy_editor/src/service/keyboard_service.dart'; -import 'package:appflowy_editor/src/service/render_plugin_service.dart'; -import 'package:appflowy_editor/src/service/scroll_service.dart'; -import 'package:appflowy_editor/src/service/selection_service.dart'; -import 'package:appflowy_editor/src/service/toolbar_service.dart'; NodeWidgetBuilders defaultBuilders = { 'editor': EditorEntryWidgetBuilder(), @@ -33,15 +24,21 @@ NodeWidgetBuilders defaultBuilders = { }; class AppFlowyEditor extends StatefulWidget { - const AppFlowyEditor({ + AppFlowyEditor({ Key? key, required this.editorState, this.customBuilders = const {}, this.shortcutEvents = const [], this.selectionMenuItems = const [], this.editable = true, - required this.editorStyle, - }) : super(key: key); + ThemeData? themeData, + }) : super(key: key) { + this.themeData = themeData ?? + ThemeData.light().copyWith(extensions: [ + ...lightEditorStyleExtension, + ...lightPlguinStyleExtension, + ]); + } final EditorState editorState; @@ -53,7 +50,7 @@ class AppFlowyEditor extends StatefulWidget { final List selectionMenuItems; - final EditorStyle editorStyle; + late final ThemeData themeData; final bool editable; @@ -65,13 +62,15 @@ class _AppFlowyEditorState extends State { Widget? services; EditorState get editorState => widget.editorState; + EditorStyle get editorStyle => + editorState.themeData.extension() ?? EditorStyle.light; @override void initState() { super.initState(); editorState.selectionMenuItems = widget.selectionMenuItems; - editorState.editorStyle = widget.editorStyle; + editorState.themeData = widget.themeData; editorState.service.renderPluginService = _createRenderPlugin(); editorState.editable = widget.editable; } @@ -85,7 +84,7 @@ class _AppFlowyEditorState extends State { editorState.service.renderPluginService = _createRenderPlugin(); } - editorState.editorStyle = widget.editorStyle; + editorState.themeData = widget.themeData; editorState.editable = widget.editable; services = null; } @@ -102,38 +101,41 @@ class _AppFlowyEditorState extends State { ); } - AppFlowyScroll _buildServices(BuildContext context) { - return AppFlowyScroll( - key: editorState.service.scrollServiceKey, - child: Padding( - padding: widget.editorStyle.padding, - child: AppFlowySelection( - key: editorState.service.selectionServiceKey, - cursorColor: widget.editorStyle.cursorColor, - selectionColor: widget.editorStyle.selectionColor, - editorState: editorState, - editable: widget.editable, - child: AppFlowyInput( - key: editorState.service.inputServiceKey, + Widget _buildServices(BuildContext context) { + return Theme( + data: widget.themeData, + child: AppFlowyScroll( + key: editorState.service.scrollServiceKey, + child: Padding( + padding: editorStyle.padding!, + child: AppFlowySelection( + key: editorState.service.selectionServiceKey, + cursorColor: editorStyle.cursorColor!, + selectionColor: editorStyle.selectionColor!, editorState: editorState, editable: widget.editable, - child: AppFlowyKeyboard( - key: editorState.service.keyboardServiceKey, - editable: widget.editable, - shortcutEvents: [ - ...widget.shortcutEvents, - ...builtInShortcutEvents, - ], + child: AppFlowyInput( + key: editorState.service.inputServiceKey, editorState: editorState, - child: FlowyToolbar( - key: editorState.service.toolbarServiceKey, + editable: widget.editable, + child: AppFlowyKeyboard( + key: editorState.service.keyboardServiceKey, + editable: widget.editable, + shortcutEvents: [ + ...widget.shortcutEvents, + ...builtInShortcutEvents, + ], 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, + ), ), ), ), diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/format_style_handler.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/format_style_handler.dart index 47e2dc10ab..1535efdb94 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/format_style_handler.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/format_style_handler.dart @@ -57,7 +57,7 @@ ShortcutEventHandler formatHighlightEventHandler = (editorState, event) { } formatHighlight( editorState, - editorState.editorStyle.textStyle.highlightColorHex, + editorState.editorStyle.highlightColorHex!, ); return KeyEventResult.handled; }; diff --git a/frontend/app_flowy/packages/appflowy_editor/test/infra/test_editor.dart b/frontend/app_flowy/packages/appflowy_editor/test/infra/test_editor.dart index 6396e47ebe..d371895487 100644 --- a/frontend/app_flowy/packages/appflowy_editor/test/infra/test_editor.dart +++ b/frontend/app_flowy/packages/appflowy_editor/test/infra/test_editor.dart @@ -39,7 +39,6 @@ class EditorWidgetTester { home: Scaffold( body: AppFlowyEditor( editorState: _editorState, - editorStyle: EditorStyle.defaultStyle(), ), ), ); diff --git a/frontend/app_flowy/packages/appflowy_editor/test/render/image/image_node_builder_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/render/image/image_node_builder_test.dart index a9732d8a20..201f07861a 100644 --- a/frontend/app_flowy/packages/appflowy_editor/test/render/image/image_node_builder_test.dart +++ b/frontend/app_flowy/packages/appflowy_editor/test/render/image/image_node_builder_test.dart @@ -49,10 +49,11 @@ void main() async { final editorRect = tester.getRect(editorFinder); final leftImageRect = tester.getRect(imageFinder.at(0)); - expect(leftImageRect.left, editor.editorState.editorStyle.padding.left); + expect( + leftImageRect.left, editor.editorState.editorStyle.padding!.left); final rightImageRect = tester.getRect(imageFinder.at(2)); expect(rightImageRect.right, - editorRect.right - editor.editorState.editorStyle.padding.right); + editorRect.right - editor.editorState.editorStyle.padding!.right); final centerImageRect = tester.getRect(imageFinder.at(1)); expect(centerImageRect.left, (leftImageRect.left + rightImageRect.left) / 2.0); From c5cea81be2cddf2b13ee274b2b3b6d039210d625 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Tue, 25 Oct 2022 21:58:44 +0800 Subject: [PATCH 05/15] feat: integrate appflowy editor dark mode --- .../lib/plugins/doc/document_page.dart | 7 +- .../lib/plugins/doc/editor_styles.dart | 136 +++++++++--------- .../appflowy_editor/lib/appflowy_editor.dart | 3 +- .../render/rich_text/bulleted_list_text.dart | 2 +- .../src/render/rich_text/heading_text.dart | 2 +- .../render/rich_text/number_list_text.dart | 2 +- .../lib/src/render/rich_text/quoted_text.dart | 2 +- .../lib/src/render/style/editor_style.dart | 2 +- ..._plugin_styles.dart => plugin_styles.dart} | 0 9 files changed, 85 insertions(+), 71 deletions(-) rename frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/{built_in_plugin_styles.dart => plugin_styles.dart} (100%) diff --git a/frontend/app_flowy/lib/plugins/doc/document_page.dart b/frontend/app_flowy/lib/plugins/doc/document_page.dart index fec32ca76f..7e68111f0a 100644 --- a/frontend/app_flowy/lib/plugins/doc/document_page.dart +++ b/frontend/app_flowy/lib/plugins/doc/document_page.dart @@ -93,15 +93,20 @@ class _DocumentPageState extends State { } Widget _renderAppFlowyEditor(EditorState editorState) { + final theme = Theme.of(context); final editor = AppFlowyEditor( editorState: editorState, - editorStyle: customEditorStyle(context), customBuilders: { 'horizontal_rule': HorizontalRuleWidgetBuilder(), }, shortcutEvents: [ insertHorizontalRule, ], + themeData: theme.copyWith(extensions: [ + ...theme.extensions.values, + customEditorTheme(context), + ...customPluginTheme(context), + ]), ); return Expanded( child: SizedBox.expand( diff --git a/frontend/app_flowy/lib/plugins/doc/editor_styles.dart b/frontend/app_flowy/lib/plugins/doc/editor_styles.dart index 09437ff36d..5d638ea73f 100644 --- a/frontend/app_flowy/lib/plugins/doc/editor_styles.dart +++ b/frontend/app_flowy/lib/plugins/doc/editor_styles.dart @@ -3,77 +3,85 @@ import 'package:flowy_infra/theme.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -EditorStyle customEditorStyle(BuildContext context) { +EditorStyle customEditorTheme(BuildContext context) { final theme = context.watch(); const baseFontSize = 14.0; const basePadding = 12.0; - var textStyle = theme.isDark - ? BuiltInTextStyle.builtInDarkMode() - : BuiltInTextStyle.builtIn(); - textStyle = textStyle.copyWith( - defaultTextStyle: textStyle.defaultTextStyle.copyWith( + + var editorStyle = theme.isDark ? EditorStyle.dark : EditorStyle.light; + editorStyle = editorStyle.copyWith( + textStyle: editorStyle.textStyle?.copyWith( fontFamily: 'poppins', fontSize: baseFontSize, ), - bold: textStyle.bold.copyWith( + bold: editorStyle.bold?.copyWith( fontWeight: FontWeight.w500, ), ); - return EditorStyle.defaultStyle().copyWith( - padding: const EdgeInsets.symmetric(horizontal: 80), - textStyle: textStyle, - pluginStyles: { - 'text/heading': builtInPluginStyle - ..update( - 'textStyle', - (_) => (EditorState editorState, Node node) { - final headingToFontSize = { - 'h1': baseFontSize + 12, - 'h2': baseFontSize + 8, - 'h3': baseFontSize + 4, - 'h4': baseFontSize, - 'h5': baseFontSize, - 'h6': baseFontSize, - }; - final fontSize = - headingToFontSize[node.attributes.heading] ?? baseFontSize; - return TextStyle(fontSize: fontSize, fontWeight: FontWeight.w600); - }, - ) - ..update( - 'padding', - (_) => (EditorState editorState, Node node) { - final headingToPadding = { - 'h1': basePadding + 6, - 'h2': basePadding + 4, - 'h3': basePadding + 2, - 'h4': basePadding, - 'h5': basePadding, - 'h6': basePadding, - }; - final padding = - headingToPadding[node.attributes.heading] ?? basePadding; - return EdgeInsets.only(bottom: padding); - }, - ), - 'text/number-list': builtInPluginStyle - ..addAll( - { - 'numberColor': (EditorState editorState, Node node) { - final theme = context.watch(); - return theme.isDark ? Colors.white : Colors.black; - }, - }, - ), - 'text/bulleted-list': builtInPluginStyle - ..addAll( - { - 'bulletColor': (EditorState editorState, Node node) { - final theme = context.watch(); - return theme.isDark ? Colors.white : Colors.black; - }, - }, - ), - }, - ); + return editorStyle; } + +Iterable> customPluginTheme(BuildContext context) { + final theme = context.watch(); + + return theme.isDark ? darkPlguinStyleExtension : lightPlguinStyleExtension; +} + +// return EditorStyle.defaultStyle().copyWith( +// padding = const EdgeInsets.symmetric(horizontal: 80), +// textStyle = textStyle, +// pluginStyles = { +// 'text/heading': builtInPluginStyle +// ..update( +// 'textStyle', +// (_) => (EditorState editorState, Node node) { +// final headingToFontSize = { +// 'h1': baseFontSize + 12, +// 'h2': baseFontSize + 8, +// 'h3': baseFontSize + 4, +// 'h4': baseFontSize, +// 'h5': baseFontSize, +// 'h6': baseFontSize, +// }; +// final fontSize = +// headingToFontSize[node.attributes.heading] ?? baseFontSize; +// return TextStyle(fontSize: fontSize, fontWeight: FontWeight.w600); +// }, +// ) +// ..update( +// 'padding', +// (_) => (EditorState editorState, Node node) { +// final headingToPadding = { +// 'h1': basePadding + 6, +// 'h2': basePadding + 4, +// 'h3': basePadding + 2, +// 'h4': basePadding, +// 'h5': basePadding, +// 'h6': basePadding, +// }; +// final padding = +// headingToPadding[node.attributes.heading] ?? basePadding; +// return EdgeInsets.only(bottom: padding); +// }, +// ), +// 'text/number-list': builtInPluginStyle +// ..addAll( +// { +// 'numberColor': (EditorState editorState, Node node) { +// final theme = context.watch(); +// return theme.isDark ? Colors.white : Colors.black; +// }, +// }, +// ), +// 'text/bulleted-list': builtInPluginStyle +// ..addAll( +// { +// 'bulletColor': (EditorState editorState, Node node) { +// final theme = context.watch(); +// return theme.isDark ? Colors.white : Colors.black; +// }, +// }, +// ), +// }, +// ); +// } 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 50d6e80aae..29cb9f87f6 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart @@ -31,4 +31,5 @@ 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'; +export 'src/render/style/plugin_styles.dart'; +export 'src/render/style/editor_style.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 52fa095f39..77092f75b3 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 @@ -4,7 +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/render/style/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'; 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 0ab06bcb6f..60803a7fe9 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,7 +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/render/style/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'; 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 bb9d1bd535..cb2a1a68cd 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,7 +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/render/style/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'; 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 a8176219cf..3182ba9eae 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 @@ -4,7 +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/render/style/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'; 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 28fa5f95e5..47d64ac373 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,7 +4,7 @@ Iterable> get lightEditorStyleExtension => [ EditorStyle.light, ]; -Iterable> get dartEditorStyleExtension => [ +Iterable> get darkEditorStyleExtension => [ EditorStyle.dark, ]; diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/built_in_plugin_styles.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/plugin_styles.dart similarity index 100% rename from frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/built_in_plugin_styles.dart rename to frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/plugin_styles.dart From 6a853036a56c84bdb7fd931b07a564831b451446 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Tue, 25 Oct 2022 22:07:30 +0800 Subject: [PATCH 06/15] feat: customize appflowy editor heading style --- .../lib/plugins/doc/editor_styles.dart | 102 +++++++----------- 1 file changed, 38 insertions(+), 64 deletions(-) diff --git a/frontend/app_flowy/lib/plugins/doc/editor_styles.dart b/frontend/app_flowy/lib/plugins/doc/editor_styles.dart index 5d638ea73f..bbc9275444 100644 --- a/frontend/app_flowy/lib/plugins/doc/editor_styles.dart +++ b/frontend/app_flowy/lib/plugins/doc/editor_styles.dart @@ -3,16 +3,16 @@ import 'package:flowy_infra/theme.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +const _baseFontSize = 14.0; + EditorStyle customEditorTheme(BuildContext context) { final theme = context.watch(); - const baseFontSize = 14.0; - const basePadding = 12.0; var editorStyle = theme.isDark ? EditorStyle.dark : EditorStyle.light; editorStyle = editorStyle.copyWith( textStyle: editorStyle.textStyle?.copyWith( fontFamily: 'poppins', - fontSize: baseFontSize, + fontSize: _baseFontSize, ), bold: editorStyle.bold?.copyWith( fontWeight: FontWeight.w500, @@ -23,65 +23,39 @@ EditorStyle customEditorTheme(BuildContext context) { Iterable> customPluginTheme(BuildContext context) { final theme = context.watch(); - - return theme.isDark ? darkPlguinStyleExtension : lightPlguinStyleExtension; + const basePadding = 12.0; + var headingPluginStyle = + theme.isDark ? HeadingPluginStyle.dark : HeadingPluginStyle.light; + headingPluginStyle = headingPluginStyle.copyWith( + textStyle: (EditorState editorState, Node node) { + final headingToFontSize = { + 'h1': _baseFontSize + 12, + 'h2': _baseFontSize + 8, + 'h3': _baseFontSize + 4, + 'h4': _baseFontSize, + 'h5': _baseFontSize, + 'h6': _baseFontSize, + }; + final fontSize = + headingToFontSize[node.attributes.heading] ?? _baseFontSize; + return TextStyle(fontSize: fontSize, fontWeight: FontWeight.w600); + }, + padding: (EditorState editorState, Node node) { + final headingToPadding = { + 'h1': basePadding + 6, + 'h2': basePadding + 4, + 'h3': basePadding + 2, + 'h4': basePadding, + 'h5': basePadding, + 'h6': basePadding, + }; + final padding = headingToPadding[node.attributes.heading] ?? basePadding; + return EdgeInsets.only(bottom: padding); + }, + ); + final pluginTheme = + theme.isDark ? darkPlguinStyleExtension : lightPlguinStyleExtension; + return pluginTheme.toList() + ..removeWhere((element) => element is HeadingPluginStyle) + ..add(headingPluginStyle); } - -// return EditorStyle.defaultStyle().copyWith( -// padding = const EdgeInsets.symmetric(horizontal: 80), -// textStyle = textStyle, -// pluginStyles = { -// 'text/heading': builtInPluginStyle -// ..update( -// 'textStyle', -// (_) => (EditorState editorState, Node node) { -// final headingToFontSize = { -// 'h1': baseFontSize + 12, -// 'h2': baseFontSize + 8, -// 'h3': baseFontSize + 4, -// 'h4': baseFontSize, -// 'h5': baseFontSize, -// 'h6': baseFontSize, -// }; -// final fontSize = -// headingToFontSize[node.attributes.heading] ?? baseFontSize; -// return TextStyle(fontSize: fontSize, fontWeight: FontWeight.w600); -// }, -// ) -// ..update( -// 'padding', -// (_) => (EditorState editorState, Node node) { -// final headingToPadding = { -// 'h1': basePadding + 6, -// 'h2': basePadding + 4, -// 'h3': basePadding + 2, -// 'h4': basePadding, -// 'h5': basePadding, -// 'h6': basePadding, -// }; -// final padding = -// headingToPadding[node.attributes.heading] ?? basePadding; -// return EdgeInsets.only(bottom: padding); -// }, -// ), -// 'text/number-list': builtInPluginStyle -// ..addAll( -// { -// 'numberColor': (EditorState editorState, Node node) { -// final theme = context.watch(); -// return theme.isDark ? Colors.white : Colors.black; -// }, -// }, -// ), -// 'text/bulleted-list': builtInPluginStyle -// ..addAll( -// { -// 'bulletColor': (EditorState editorState, Node node) { -// final theme = context.watch(); -// return theme.isDark ? Colors.white : Colors.black; -// }, -// }, -// ), -// }, -// ); -// } From 8656cfeb1ec833274bdd6afd1e7475ce3d30767e Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Tue, 25 Oct 2022 22:15:31 +0800 Subject: [PATCH 07/15] chore: add extension for themedata --- .../lib/src/extensions/theme_extension.dart | 10 ++++++++++ .../lib/src/render/rich_text/bulleted_list_text.dart | 3 ++- .../lib/src/render/rich_text/checkbox_text.dart | 3 ++- .../lib/src/render/rich_text/heading_text.dart | 3 ++- .../lib/src/render/rich_text/number_list_text.dart | 3 ++- .../lib/src/render/rich_text/quoted_text.dart | 3 ++- 6 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 frontend/app_flowy/packages/appflowy_editor/lib/src/extensions/theme_extension.dart diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/extensions/theme_extension.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/extensions/theme_extension.dart new file mode 100644 index 0000000000..9b8f01aafa --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/extensions/theme_extension.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +extension ThemeExtension on ThemeData { + T? extensionOrNull() { + if (extensions.containsKey(T)) { + return extensions[T] as T; + } + return null; + } +} 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 77092f75b3..3f6927df4c 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 @@ -8,6 +8,7 @@ import 'package:appflowy_editor/src/render/style/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'; +import 'package:appflowy_editor/src/extensions/theme_extension.dart'; class BulletedListTextNodeWidgetBuilder extends NodeWidgetBuilder { @override @@ -61,7 +62,7 @@ class _BulletedListTextNodeWidgetState extends State } BulletedListPluginStyle get style => - Theme.of(context).extension() ?? + Theme.of(context).extensionOrNull() ?? BulletedListPluginStyle.light; EdgeInsets get padding => style.padding( 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 39115b69e8..a2e0aa5d32 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 @@ -4,6 +4,7 @@ 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:flutter/material.dart'; +import 'package:appflowy_editor/src/extensions/theme_extension.dart'; class CheckboxNodeWidgetBuilder extends NodeWidgetBuilder { @override @@ -54,7 +55,7 @@ class _CheckboxNodeWidgetState extends State } CheckboxPluginStyle get style => - Theme.of(context).extension() ?? + Theme.of(context).extensionOrNull() ?? CheckboxPluginStyle.light; EdgeInsets get padding => style.padding( 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 60803a7fe9..f8f5bd0f92 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 @@ -9,6 +9,7 @@ import 'package:appflowy_editor/src/service/render_plugin_service.dart'; import 'package:flutter/material.dart'; import 'package:appflowy_editor/src/extensions/attributes_extension.dart'; import 'package:appflowy_editor/src/extensions/text_style_extension.dart'; +import 'package:appflowy_editor/src/extensions/theme_extension.dart'; class HeadingTextNodeWidgetBuilder extends NodeWidgetBuilder { @override @@ -60,7 +61,7 @@ class _HeadingTextNodeWidgetState extends State } HeadingPluginStyle get style => - Theme.of(context).extension() ?? + Theme.of(context).extensionOrNull() ?? HeadingPluginStyle.light; EdgeInsets get padding => style.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 cb2a1a68cd..60698d6aad 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 @@ -9,6 +9,7 @@ import 'package:appflowy_editor/src/service/render_plugin_service.dart'; import 'package:flutter/material.dart'; import 'package:appflowy_editor/src/extensions/attributes_extension.dart'; import 'package:appflowy_editor/src/extensions/text_style_extension.dart'; +import 'package:appflowy_editor/src/extensions/theme_extension.dart'; class NumberListTextNodeWidgetBuilder extends NodeWidgetBuilder { @override @@ -60,7 +61,7 @@ class _NumberListTextNodeWidgetState extends State } NumberListPluginStyle get style => - Theme.of(context).extension() ?? + Theme.of(context).extensionOrNull() ?? NumberListPluginStyle.light; EdgeInsets get padding => style.padding( 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 3182ba9eae..370d328d1e 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 @@ -8,6 +8,7 @@ import 'package:appflowy_editor/src/render/style/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'; +import 'package:appflowy_editor/src/extensions/theme_extension.dart'; class QuotedTextNodeWidgetBuilder extends NodeWidgetBuilder { @override @@ -60,7 +61,7 @@ class _QuotedTextNodeWidgetState extends State } QuotedTextPluginStyle get style => - Theme.of(context).extension() ?? + Theme.of(context).extensionOrNull() ?? QuotedTextPluginStyle.light; EdgeInsets get padding => style.padding( From 23a65bfa2a9c714be763ef1af4564c757d586a02 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Wed, 26 Oct 2022 10:16:14 +0800 Subject: [PATCH 08/15] feat: customize appflowy editor selection menu style --- .../appflowy_editor/example/lib/main.dart | 2 + .../lib/plugin/code_block_node_widget.dart | 2 +- .../plugin/horizontal_rule_node_widget.dart | 2 +- .../lib/plugin/tex_block_node_widget.dart | 2 +- .../selection_menu_item_widget.dart | 44 ++++++++++---- .../selection_menu_service.dart | 34 +++++++---- .../selection_menu/selection_menu_widget.dart | 4 +- .../lib/src/render/style/editor_style.dart | 60 +++++++++++++++++++ .../selection_menu_widget_test.dart | 2 +- .../slash_handler_test.dart | 2 +- 10 files changed, 123 insertions(+), 31 deletions(-) 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 bef4dce946..128f227676 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart @@ -39,6 +39,8 @@ class MyApp extends StatelessWidget { primarySwatch: Colors.blue, // extensions: [HeadingPluginStyle.light], ), + darkTheme: ThemeData.dark(), + themeMode: ThemeMode.dark, home: const MyHomePage(title: 'AppFlowyEditor Example'), ); } diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/code_block_node_widget.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/code_block_node_widget.dart index 6716cca163..5ecf4d4ed8 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/code_block_node_widget.dart +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/code_block_node_widget.dart @@ -46,7 +46,7 @@ ShortcutEventHandler _ignorekHandler = (editorState, event) { SelectionMenuItem codeBlockMenuItem = SelectionMenuItem( name: () => 'Code Block', - icon: const Icon( + icon: (_, __) => const Icon( Icons.abc, color: Colors.black, size: 18.0, diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/horizontal_rule_node_widget.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/horizontal_rule_node_widget.dart index c38cc0846c..0ca302de18 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/horizontal_rule_node_widget.dart +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/horizontal_rule_node_widget.dart @@ -38,7 +38,7 @@ ShortcutEventHandler _insertHorzaontalRule = (editorState, event) { SelectionMenuItem horizontalRuleMenuItem = SelectionMenuItem( name: () => 'Horizontal rule', - icon: const Icon( + icon: (_, __) => const Icon( Icons.horizontal_rule, color: Colors.black, size: 18.0, diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/tex_block_node_widget.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/tex_block_node_widget.dart index a6b958b0ed..c9b0e8d478 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/tex_block_node_widget.dart +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/tex_block_node_widget.dart @@ -6,7 +6,7 @@ import 'package:flutter_math_fork/flutter_math.dart'; SelectionMenuItem teXBlockMenuItem = SelectionMenuItem( name: () => 'Tex', - icon: const Icon( + icon: (_, __) => const Icon( Icons.text_fields_rounded, color: Colors.black, size: 18.0, diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_item_widget.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_item_widget.dart index a4322c59fe..cc92b60da0 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_item_widget.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_item_widget.dart @@ -3,7 +3,7 @@ import 'package:appflowy_editor/src/render/selection_menu/selection_menu_service import 'package:appflowy_editor/src/render/selection_menu/selection_menu_widget.dart'; import 'package:flutter/material.dart'; -class SelectionMenuItemWidget extends StatelessWidget { +class SelectionMenuItemWidget extends StatefulWidget { const SelectionMenuItemWidget({ Key? key, required this.editorState, @@ -11,7 +11,6 @@ class SelectionMenuItemWidget extends StatelessWidget { required this.item, required this.isSelected, this.width = 140.0, - this.selectedColor = const Color(0xFFE0F8FF), }) : super(key: key); final EditorState editorState; @@ -19,33 +18,52 @@ class SelectionMenuItemWidget extends StatelessWidget { final SelectionMenuItem item; final double width; final bool isSelected; - final Color selectedColor; + + @override + State createState() => + _SelectionMenuItemWidgetState(); +} + +class _SelectionMenuItemWidgetState extends State { + var _onHover = false; @override Widget build(BuildContext context) { + final editorStyle = widget.editorState.editorStyle; return Container( padding: const EdgeInsets.fromLTRB(8.0, 5.0, 8.0, 5.0), child: SizedBox( - width: width, + width: widget.width, child: TextButton.icon( - icon: item.icon, + icon: widget.item + .icon(widget.editorState, widget.isSelected || _onHover), style: ButtonStyle( alignment: Alignment.centerLeft, - overlayColor: MaterialStateProperty.all(selectedColor), - backgroundColor: isSelected - ? MaterialStateProperty.all(selectedColor) + overlayColor: MaterialStateProperty.all( + editorStyle.selectionMenuItemSelectedColor), + backgroundColor: widget.isSelected + ? MaterialStateProperty.all( + editorStyle.selectionMenuItemSelectedColor) : MaterialStateProperty.all(Colors.transparent), ), label: Text( - item.name(), + widget.item.name(), textAlign: TextAlign.left, - style: const TextStyle( - color: Colors.black, - fontSize: 14.0, + style: TextStyle( + color: widget.isSelected || _onHover + ? editorStyle.selectionMenuItemSelectedTextColor + : editorStyle.selectionMenuItemTextColor, + fontSize: 12.0, ), ), onPressed: () { - item.handler(editorState, menuService, context); + widget.item + .handler(widget.editorState, widget.menuService, context); + }, + onHover: (value) { + setState(() { + _onHover = value; + }); }, ), ), diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_service.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_service.dart index c36b9adb8e..1f47ad823b 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_service.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_service.dart @@ -131,7 +131,8 @@ List get defaultSelectionMenuItems => final List _defaultSelectionMenuItems = [ SelectionMenuItem( name: () => AppFlowyEditorLocalizations.current.text, - icon: _selectionMenuIcon('text'), + icon: (editorState, onSelected) => + _selectionMenuIcon('text', editorState, onSelected), keywords: ['text'], handler: (editorState, _, __) { insertTextNodeAfterSelection(editorState, {}); @@ -139,7 +140,8 @@ final List _defaultSelectionMenuItems = [ ), SelectionMenuItem( name: () => AppFlowyEditorLocalizations.current.heading1, - icon: _selectionMenuIcon('h1'), + icon: (editorState, onSelected) => + _selectionMenuIcon('h1', editorState, onSelected), keywords: ['heading 1, h1'], handler: (editorState, _, __) { insertHeadingAfterSelection(editorState, BuiltInAttributeKey.h1); @@ -147,7 +149,8 @@ final List _defaultSelectionMenuItems = [ ), SelectionMenuItem( name: () => AppFlowyEditorLocalizations.current.heading2, - icon: _selectionMenuIcon('h2'), + icon: (editorState, onSelected) => + _selectionMenuIcon('h2', editorState, onSelected), keywords: ['heading 2, h2'], handler: (editorState, _, __) { insertHeadingAfterSelection(editorState, BuiltInAttributeKey.h2); @@ -155,7 +158,8 @@ final List _defaultSelectionMenuItems = [ ), SelectionMenuItem( name: () => AppFlowyEditorLocalizations.current.heading3, - icon: _selectionMenuIcon('h3'), + icon: (editorState, onSelected) => + _selectionMenuIcon('h3', editorState, onSelected), keywords: ['heading 3, h3'], handler: (editorState, _, __) { insertHeadingAfterSelection(editorState, BuiltInAttributeKey.h3); @@ -163,13 +167,15 @@ final List _defaultSelectionMenuItems = [ ), SelectionMenuItem( name: () => AppFlowyEditorLocalizations.current.image, - icon: _selectionMenuIcon('image'), + icon: (editorState, onSelected) => + _selectionMenuIcon('image', editorState, onSelected), keywords: ['image'], handler: showImageUploadMenu, ), SelectionMenuItem( name: () => AppFlowyEditorLocalizations.current.bulletedList, - icon: _selectionMenuIcon('bulleted_list'), + icon: (editorState, onSelected) => + _selectionMenuIcon('bulleted_list', editorState, onSelected), keywords: ['bulleted list', 'list', 'unordered list'], handler: (editorState, _, __) { insertBulletedListAfterSelection(editorState); @@ -177,7 +183,8 @@ final List _defaultSelectionMenuItems = [ ), SelectionMenuItem( name: () => AppFlowyEditorLocalizations.current.numberedList, - icon: _selectionMenuIcon('number'), + icon: (editorState, onSelected) => + _selectionMenuIcon('number', editorState, onSelected), keywords: ['numbered list', 'list', 'ordered list'], handler: (editorState, _, __) { insertNumberedListAfterSelection(editorState); @@ -185,7 +192,8 @@ final List _defaultSelectionMenuItems = [ ), SelectionMenuItem( name: () => AppFlowyEditorLocalizations.current.checkbox, - icon: _selectionMenuIcon('checkbox'), + icon: (editorState, onSelected) => + _selectionMenuIcon('checkbox', editorState, onSelected), keywords: ['todo list', 'list', 'checkbox list'], handler: (editorState, _, __) { insertCheckboxAfterSelection(editorState); @@ -193,7 +201,8 @@ final List _defaultSelectionMenuItems = [ ), SelectionMenuItem( name: () => AppFlowyEditorLocalizations.current.quote, - icon: _selectionMenuIcon('quote'), + icon: (editorState, onSelected) => + _selectionMenuIcon('quote', editorState, onSelected), keywords: ['quote', 'refer'], handler: (editorState, _, __) { insertQuoteAfterSelection(editorState); @@ -201,10 +210,13 @@ final List _defaultSelectionMenuItems = [ ), ]; -Widget _selectionMenuIcon(String name) { +Widget _selectionMenuIcon( + String name, EditorState editorState, bool onSelected) { return FlowySvg( name: 'selection_menu/$name', - color: Colors.black, + color: onSelected + ? editorState.editorStyle.selectionMenuItemSelectedIconColor + : editorState.editorStyle.selectionMenuItemIconColor, width: 18.0, height: 18.0, ); diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_widget.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_widget.dart index 2f64e8a4a6..2007c172f5 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_widget.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_widget.dart @@ -29,7 +29,7 @@ class SelectionMenuItem { } final String Function() name; - final Widget icon; + final Widget Function(EditorState editorState, bool onSelected) icon; /// Customizes keywords for item. /// @@ -142,7 +142,7 @@ class _SelectionMenuWidgetState extends State { onKey: _onKey, child: Container( decoration: BoxDecoration( - color: Colors.white, + color: widget.editorState.editorStyle.selectionMenuBackgroundColor, boxShadow: [ BoxShadow( blurRadius: 5, 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 47d64ac373..892c274474 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 @@ -14,6 +14,14 @@ class EditorStyle extends ThemeExtension { final Color? cursorColor; final Color? selectionColor; + // Selection menu styles + final Color? selectionMenuBackgroundColor; + final Color? selectionMenuItemTextColor; + final Color? selectionMenuItemIconColor; + final Color? selectionMenuItemSelectedTextColor; + final Color? selectionMenuItemSelectedIconColor; + final Color? selectionMenuItemSelectedColor; + // Text styles final EdgeInsets? textPadding; final TextStyle? textStyle; @@ -33,6 +41,12 @@ class EditorStyle extends ThemeExtension { required this.padding, required this.cursorColor, required this.selectionColor, + required this.selectionMenuBackgroundColor, + required this.selectionMenuItemTextColor, + required this.selectionMenuItemIconColor, + required this.selectionMenuItemSelectedTextColor, + required this.selectionMenuItemSelectedIconColor, + required this.selectionMenuItemSelectedColor, required this.textPadding, required this.textStyle, required this.placeholderTextStyle, @@ -51,6 +65,12 @@ class EditorStyle extends ThemeExtension { EdgeInsets? padding, Color? cursorColor, Color? selectionColor, + Color? selectionMenuBackgroundColor, + Color? selectionMenuItemTextColor, + Color? selectionMenuItemIconColor, + Color? selectionMenuItemSelectedTextColor, + Color? selectionMenuItemSelectedIconColor, + Color? selectionMenuItemSelectedColor, TextStyle? textStyle, TextStyle? placeholderTextStyle, TextStyle? bold, @@ -66,6 +86,18 @@ class EditorStyle extends ThemeExtension { padding: padding ?? this.padding, cursorColor: cursorColor ?? this.cursorColor, selectionColor: selectionColor ?? this.selectionColor, + selectionMenuBackgroundColor: + selectionMenuBackgroundColor ?? this.selectionMenuBackgroundColor, + selectionMenuItemTextColor: + selectionMenuItemTextColor ?? this.selectionMenuItemTextColor, + selectionMenuItemIconColor: + selectionMenuItemIconColor ?? this.selectionMenuItemIconColor, + selectionMenuItemSelectedTextColor: selectionMenuItemSelectedTextColor ?? + selectionMenuItemSelectedTextColor, + selectionMenuItemSelectedIconColor: selectionMenuItemSelectedIconColor ?? + selectionMenuItemSelectedIconColor, + selectionMenuItemSelectedColor: + selectionMenuItemSelectedColor ?? this.selectionMenuItemSelectedColor, textPadding: textPadding ?? textPadding, textStyle: textStyle ?? this.textStyle, placeholderTextStyle: placeholderTextStyle ?? this.placeholderTextStyle, @@ -91,6 +123,22 @@ class EditorStyle extends ThemeExtension { cursorColor: Color.lerp(cursorColor, other.cursorColor, t), textPadding: EdgeInsets.lerp(textPadding, other.textPadding, t), selectionColor: Color.lerp(selectionColor, other.selectionColor, t), + selectionMenuBackgroundColor: Color.lerp( + selectionMenuBackgroundColor, other.selectionMenuBackgroundColor, t), + selectionMenuItemTextColor: Color.lerp( + selectionMenuItemTextColor, other.selectionMenuItemTextColor, t), + selectionMenuItemIconColor: Color.lerp( + selectionMenuItemIconColor, other.selectionMenuItemIconColor, t), + selectionMenuItemSelectedTextColor: Color.lerp( + selectionMenuItemSelectedTextColor, + other.selectionMenuItemSelectedTextColor, + t), + selectionMenuItemSelectedIconColor: Color.lerp( + selectionMenuItemSelectedIconColor, + other.selectionMenuItemSelectedIconColor, + t), + selectionMenuItemSelectedColor: Color.lerp(selectionMenuItemSelectedColor, + other.selectionMenuItemSelectedColor, t), textStyle: TextStyle.lerp(textStyle, other.textStyle, t), placeholderTextStyle: TextStyle.lerp(placeholderTextStyle, other.placeholderTextStyle, t), @@ -109,6 +157,12 @@ class EditorStyle extends ThemeExtension { padding: const EdgeInsets.fromLTRB(200.0, 0.0, 200.0, 0.0), cursorColor: const Color(0xFF00BCF0), selectionColor: const Color.fromARGB(53, 111, 201, 231), + selectionMenuBackgroundColor: const Color(0xFFFFFFFF), + selectionMenuItemTextColor: const Color(0xFF333333), + selectionMenuItemIconColor: const Color(0xFF333333), + selectionMenuItemSelectedTextColor: const Color(0xFF333333), + selectionMenuItemSelectedIconColor: const Color(0xFF333333), + selectionMenuItemSelectedColor: const Color(0xFFE0F8FF), textPadding: const EdgeInsets.symmetric(vertical: 8.0), textStyle: const TextStyle(fontSize: 16.0, color: Colors.black), placeholderTextStyle: const TextStyle(fontSize: 16.0, color: Colors.grey), @@ -135,5 +189,11 @@ class EditorStyle extends ThemeExtension { fontSize: 16.0, color: Colors.white.withOpacity(0.3), ), + selectionMenuBackgroundColor: const Color(0xFF282E3A), + selectionMenuItemTextColor: const Color(0xFFBBC3CD), + selectionMenuItemIconColor: const Color(0xFFBBC3CD), + selectionMenuItemSelectedTextColor: const Color(0xFF131720), + selectionMenuItemSelectedIconColor: const Color(0xFF131720), + selectionMenuItemSelectedColor: const Color(0xFF00BCF0), ); } diff --git a/frontend/app_flowy/packages/appflowy_editor/test/render/selection_menu/selection_menu_widget_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/render/selection_menu/selection_menu_widget_test.dart index f60d1610ad..d058fc2199 100644 --- a/frontend/app_flowy/packages/appflowy_editor/test/render/selection_menu/selection_menu_widget_test.dart +++ b/frontend/app_flowy/packages/appflowy_editor/test/render/selection_menu/selection_menu_widget_test.dart @@ -137,7 +137,7 @@ Future _prepare(WidgetTester tester) async { ); for (final item in defaultSelectionMenuItems) { - expect(find.byWidget(item.icon), findsOneWidget); + expect(find.text(item.name()), findsOneWidget); } return Future.value(editor); diff --git a/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/slash_handler_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/slash_handler_test.dart index b3166a46fb..0b53b43af9 100644 --- a/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/slash_handler_test.dart +++ b/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/slash_handler_test.dart @@ -30,7 +30,7 @@ void main() async { ); for (final item in defaultSelectionMenuItems) { - expect(find.byWidget(item.icon), findsOneWidget); + expect(find.text(item.name()), findsOneWidget); } await editor.updateSelection(Selection.single(path: [1], startOffset: 0)); From fac76ac5b812be6ede96017b4ae97a40a0d32c45 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Wed, 26 Oct 2022 10:52:30 +0800 Subject: [PATCH 09/15] feat: custom selection menu style --- .../presentation/plugins/horizontal_rule_node_widget.dart | 6 ++++-- .../render/selection_menu/selection_menu_item_widget.dart | 2 +- .../appflowy_editor/lib/src/render/style/editor_style.dart | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/frontend/app_flowy/lib/plugins/doc/presentation/plugins/horizontal_rule_node_widget.dart b/frontend/app_flowy/lib/plugins/doc/presentation/plugins/horizontal_rule_node_widget.dart index c38cc0846c..c3d4cbeb35 100644 --- a/frontend/app_flowy/lib/plugins/doc/presentation/plugins/horizontal_rule_node_widget.dart +++ b/frontend/app_flowy/lib/plugins/doc/presentation/plugins/horizontal_rule_node_widget.dart @@ -38,9 +38,11 @@ ShortcutEventHandler _insertHorzaontalRule = (editorState, event) { SelectionMenuItem horizontalRuleMenuItem = SelectionMenuItem( name: () => 'Horizontal rule', - icon: const Icon( + icon: (editorState, onSelected) => Icon( Icons.horizontal_rule, - color: Colors.black, + color: onSelected + ? editorState.editorStyle.selectionMenuItemSelectedIconColor + : editorState.editorStyle.selectionMenuItemIconColor, size: 18.0, ), keywords: ['horizontal rule'], diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_item_widget.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_item_widget.dart index cc92b60da0..912d9447ff 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_item_widget.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_item_widget.dart @@ -50,7 +50,7 @@ class _SelectionMenuItemWidgetState extends State { widget.item.name(), textAlign: TextAlign.left, style: TextStyle( - color: widget.isSelected || _onHover + color: (widget.isSelected || _onHover) ? editorStyle.selectionMenuItemSelectedTextColor : editorStyle.selectionMenuItemTextColor, fontSize: 12.0, 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 892c274474..30e4465bf8 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 @@ -93,9 +93,9 @@ class EditorStyle extends ThemeExtension { selectionMenuItemIconColor: selectionMenuItemIconColor ?? this.selectionMenuItemIconColor, selectionMenuItemSelectedTextColor: selectionMenuItemSelectedTextColor ?? - selectionMenuItemSelectedTextColor, + this.selectionMenuItemSelectedTextColor, selectionMenuItemSelectedIconColor: selectionMenuItemSelectedIconColor ?? - selectionMenuItemSelectedIconColor, + this.selectionMenuItemSelectedIconColor, selectionMenuItemSelectedColor: selectionMenuItemSelectedColor ?? this.selectionMenuItemSelectedColor, textPadding: textPadding ?? textPadding, From a702c06dc8ea947ae1e519f13438a34f3cd38e06 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Wed, 26 Oct 2022 11:01:25 +0800 Subject: [PATCH 10/15] feat: custom context menu style --- .../service/context_menu/context_menu.dart | 55 ++++++++++++------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/context_menu/context_menu.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/context_menu/context_menu.dart index 80fceb03d1..92b43abdbb 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/context_menu/context_menu.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/context_menu/context_menu.dart @@ -30,26 +30,43 @@ class ContextMenu extends StatelessWidget { final children = []; for (var i = 0; i < items.length; i++) { for (var j = 0; j < items[i].length; j++) { + var onHover = false; children.add( - Material( - child: InkWell( - hoverColor: const Color(0xFFE0F8FF), - customBorder: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), - ), - onTap: () { - items[i][j].onPressed(editorState); - onPressed(); - }, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - items[i][j].name, - textAlign: TextAlign.start, - style: const TextStyle(fontSize: 14), + StatefulBuilder( + builder: (BuildContext context, setState) { + return Material( + color: editorState.editorStyle.selectionMenuBackgroundColor, + child: InkWell( + hoverColor: + editorState.editorStyle.selectionMenuItemSelectedColor, + customBorder: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), + onTap: () { + items[i][j].onPressed(editorState); + onPressed(); + }, + onHover: (value) => setState(() { + onHover = value; + }), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + items[i][j].name, + textAlign: TextAlign.start, + style: TextStyle( + fontSize: 14, + color: onHover + ? editorState + .editorStyle.selectionMenuItemSelectedTextColor + : editorState + .editorStyle.selectionMenuItemTextColor, + ), + ), + ), ), - ), - ), + ); + }, ), ); } @@ -67,7 +84,7 @@ class ContextMenu extends StatelessWidget { minWidth: 140, ), decoration: BoxDecoration( - color: Colors.white, + color: editorState.editorStyle.selectionMenuBackgroundColor, boxShadow: [ BoxShadow( blurRadius: 5, From a68903918c0ca8165d24225ad14e6f349d824c51 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Wed, 26 Oct 2022 11:43:10 +0800 Subject: [PATCH 11/15] feat: custom link menu style --- .../lib/src/render/link_menu/link_menu.dart | 16 ++++++++++++---- .../lib/src/render/toolbar/toolbar_item.dart | 1 + 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/link_menu/link_menu.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/link_menu/link_menu.dart index 3a1785391b..58e8111793 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/link_menu/link_menu.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/link_menu/link_menu.dart @@ -1,10 +1,13 @@ +import 'package:appflowy_editor/src/editor_state.dart'; import 'package:appflowy_editor/src/infra/flowy_svg.dart'; +import 'package:appflowy_editor/src/render/style/editor_style.dart'; import 'package:flutter/material.dart'; class LinkMenu extends StatefulWidget { const LinkMenu({ Key? key, this.linkText, + this.editorState, required this.onSubmitted, required this.onOpenLink, required this.onCopyLink, @@ -13,6 +16,7 @@ class LinkMenu extends StatefulWidget { }) : super(key: key); final String? linkText; + final EditorState? editorState; final void Function(String text) onSubmitted; final VoidCallback onOpenLink; final VoidCallback onCopyLink; @@ -27,6 +31,8 @@ class _LinkMenuState extends State { final _textEditingController = TextEditingController(); final _focusNode = FocusNode(); + EditorStyle? get style => widget.editorState?.editorStyle; + @override void initState() { super.initState(); @@ -48,7 +54,7 @@ class _LinkMenuState extends State { width: 350, child: Container( decoration: BoxDecoration( - color: Colors.white, + color: style?.selectionMenuBackgroundColor ?? Colors.white, boxShadow: [ BoxShadow( blurRadius: 5, @@ -71,17 +77,19 @@ class _LinkMenuState extends State { if (widget.linkText != null) ...[ _buildIconButton( iconName: 'link', + color: style?.selectionMenuItemIconColor, text: 'Open link', onPressed: widget.onOpenLink, ), _buildIconButton( iconName: 'copy', - color: Colors.black, + color: style?.selectionMenuItemIconColor, text: 'Copy link', onPressed: widget.onCopyLink, ), _buildIconButton( iconName: 'delete', + color: style?.selectionMenuItemIconColor, text: 'Remove link', onPressed: widget.onRemoveLink, ), @@ -154,8 +162,8 @@ class _LinkMenuState extends State { label: Text( text, textAlign: TextAlign.left, - style: const TextStyle( - color: Colors.black, + style: TextStyle( + color: style?.selectionMenuItemTextColor ?? Colors.black, fontSize: 14.0, ), ), diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item.dart index bae2367fb9..5ab7f6cc50 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item.dart @@ -348,6 +348,7 @@ void showLinkMenu( child: Material( child: LinkMenu( linkText: linkText, + editorState: editorState, onOpenLink: () async { await safeLaunchUrl(linkText); }, From 799ed2fdb9a810ed88103d1d3af03bdd18ecebc4 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Wed, 26 Oct 2022 11:47:38 +0800 Subject: [PATCH 12/15] feat: custom upload image menu style --- .../lib/src/render/image/image_upload_widget.dart | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/image/image_upload_widget.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/image/image_upload_widget.dart index bb02c74600..078609db91 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/image/image_upload_widget.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/image/image_upload_widget.dart @@ -2,6 +2,7 @@ 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/selection_menu/selection_menu_service.dart'; +import 'package:appflowy_editor/src/render/style/editor_style.dart'; import 'package:flutter/material.dart'; OverlayEntry? _imageUploadMenu; @@ -20,6 +21,7 @@ void showImageUploadMenu( left: menuService.topLeft.dx, child: Material( child: ImageUploadMenu( + editorState: editorState, onSubmitted: (text) { // _dismissImageUploadMenu(); editorState.insertImageNode(text); @@ -53,10 +55,12 @@ class ImageUploadMenu extends StatefulWidget { Key? key, required this.onSubmitted, required this.onUpload, + this.editorState, }) : super(key: key); final void Function(String text) onSubmitted; final void Function(String text) onUpload; + final EditorState? editorState; @override State createState() => _ImageUploadMenuState(); @@ -66,6 +70,8 @@ class _ImageUploadMenuState extends State { final _textEditingController = TextEditingController(); final _focusNode = FocusNode(); + EditorStyle? get style => widget.editorState?.editorStyle; + @override void initState() { super.initState(); @@ -84,7 +90,7 @@ class _ImageUploadMenuState extends State { width: 300, padding: const EdgeInsets.all(24.0), decoration: BoxDecoration( - color: Colors.white, + color: style?.selectionMenuBackgroundColor ?? Colors.white, boxShadow: [ BoxShadow( blurRadius: 5, @@ -108,12 +114,12 @@ class _ImageUploadMenuState extends State { } Widget _buildHeader(BuildContext context) { - return const Text( + return Text( 'URL Image', textAlign: TextAlign.left, style: TextStyle( fontSize: 14.0, - color: Colors.black, + color: style?.selectionMenuItemTextColor ?? Colors.black, fontWeight: FontWeight.w500, ), ); From 381913307c6b446e8d2c10d78fa67b5371e14b2a Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Wed, 26 Oct 2022 11:54:21 +0800 Subject: [PATCH 13/15] fix: copy selection exception --- .../copy_paste_handler.dart | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart index 25f52c6914..9ab74cdb14 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart @@ -41,7 +41,10 @@ void _handleCopy(EditorState editorState) async { Log.keyboard.debug('copy html: $htmlString'); RichClipboard.setData(RichClipboardData( html: htmlString, - text: textNode.toPlainText(), + text: textNode.toPlainText().substring( + selection.startIndex, + selection.endIndex, + ), )); } else { Log.keyboard.debug('unimplemented: copy non-text'); @@ -63,9 +66,19 @@ void _handleCopy(EditorState editorState) async { startOffset: selection.start.offset, endOffset: selection.end.offset, ).toHTMLString(); - final text = nodes - .map((node) => node is TextNode ? node.toPlainText() : '\n') - .join('\n'); + var text = ''; + for (final node in nodes) { + if (node is TextNode) { + if (node.path == selection.start.path) { + text += node.toPlainText().substring(selection.start.offset); + } else if (node.path == selection.end.path) { + text += node.toPlainText().substring(0, selection.end.offset); + } else { + text += node.toPlainText(); + } + } + text += '\n'; + } RichClipboard.setData(RichClipboardData(html: html, text: text)); } From e7adc3bcbfdfa0ff0a6b0f8c717f8f0288e7dc51 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Wed, 26 Oct 2022 14:36:00 +0800 Subject: [PATCH 14/15] fix: selection menu show in wrong position --- .../selection_menu_service.dart | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_service.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_service.dart index 1f47ad823b..76e12a5f62 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_service.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_service.dart @@ -61,19 +61,27 @@ class SelectionMenu implements SelectionMenuService { // Just subtract the padding here as a result. const menuHeight = 200.0; const menuOffset = Offset(10, 10); - final baseOffset = + final editorOffset = editorState.renderBox?.localToGlobal(Offset.zero) ?? Offset.zero; - var offset = selectionRects.first.bottomRight + menuOffset; - if (offset.dy >= - baseOffset.dy + editorState.renderBox!.size.height - menuHeight) { - offset = selectionRects.first.topRight - menuOffset; - offset = offset.translate(0, -menuHeight); + final editorHeight = editorState.renderBox!.size.height; + + // show below defualt + var showBelow = true; + final bottomRight = selectionRects.first.bottomRight; + final topRight = selectionRects.first.topRight; + var offset = bottomRight + menuOffset; + // overflow + if (offset.dy + menuHeight >= editorOffset.dy + editorHeight) { + // show above + offset = topRight - menuOffset; + showBelow = false; } _topLeft = offset; _selectionMenuEntry = OverlayEntry(builder: (context) { return Positioned( - top: offset.dy, + top: showBelow ? offset.dy : null, + bottom: showBelow ? null : editorHeight - offset.dy, left: offset.dx, child: SelectionMenuWidget( items: [ From 3f97820094e46bdea3b7184ddf87083bdb59c09f Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Wed, 26 Oct 2022 14:51:39 +0800 Subject: [PATCH 15/15] feat: customize the placeholder text style --- frontend/app_flowy/lib/plugins/doc/editor_styles.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/app_flowy/lib/plugins/doc/editor_styles.dart b/frontend/app_flowy/lib/plugins/doc/editor_styles.dart index bbc9275444..0c503e19cb 100644 --- a/frontend/app_flowy/lib/plugins/doc/editor_styles.dart +++ b/frontend/app_flowy/lib/plugins/doc/editor_styles.dart @@ -14,6 +14,10 @@ EditorStyle customEditorTheme(BuildContext context) { fontFamily: 'poppins', fontSize: _baseFontSize, ), + placeholderTextStyle: editorStyle.placeholderTextStyle?.copyWith( + fontFamily: 'poppins', + fontSize: _baseFontSize, + ), bold: editorStyle.bold?.copyWith( fontWeight: FontWeight.w500, ),