mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: refactor theme plugin, use themedata extension
This commit is contained in:
parent
cdee706f46
commit
dde50d32d6
@ -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<MyHomePage> {
|
||||
int _pageIndex = 0;
|
||||
EditorState? _editorState;
|
||||
bool darkMode = false;
|
||||
EditorStyle _editorStyle = EditorStyle.defaultStyle();
|
||||
Future<String>? _jsonString;
|
||||
|
||||
@override
|
||||
@ -132,7 +130,8 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
CheckboxPluginStyle.dark,
|
||||
NumberListPluginStyle.dark,
|
||||
QuotedTextPluginStyle.dark,
|
||||
BulletedListPluginStyle.dark
|
||||
BulletedListPluginStyle.dark,
|
||||
EditorStyle.dark,
|
||||
])
|
||||
: ThemeData.light().copyWith(
|
||||
extensions: [
|
||||
@ -140,34 +139,32 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
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<MyHomePage> {
|
||||
icon: const Icon(Icons.color_lens),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_editorStyle =
|
||||
darkMode ? EditorStyle.defaultStyle() : _customizedStyle();
|
||||
darkMode = !darkMode;
|
||||
});
|
||||
},
|
||||
@ -277,44 +272,4 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
};
|
||||
},
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
),
|
||||
),
|
||||
|
@ -59,13 +59,14 @@ class EditorState {
|
||||
/// Stores the selection menu items.
|
||||
List<SelectionMenuItem> selectionMenuItems = [];
|
||||
|
||||
/// Stores the editor style.
|
||||
EditorStyle editorStyle = EditorStyle.defaultStyle();
|
||||
|
||||
/// Operation stream.
|
||||
Stream<Transaction> get transactionStream => _observer.stream;
|
||||
final StreamController<Transaction> _observer = StreamController.broadcast();
|
||||
|
||||
late ThemeData themeData;
|
||||
EditorStyle get editorStyle =>
|
||||
themeData.extension<EditorStyle>() ?? EditorStyle.light;
|
||||
|
||||
final UndoManager undoManager = UndoManager();
|
||||
Selection? _cursorSelection;
|
||||
|
||||
|
@ -10,56 +10,6 @@ abstract class BuiltInTextWidget extends StatefulWidget {
|
||||
TextNode get textNode;
|
||||
}
|
||||
|
||||
mixin BuiltInStyleMixin<T extends BuiltInTextWidget> on State<T> {
|
||||
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<T extends BuiltInTextWidget> on State<T>
|
||||
implements DefaultSelectable {
|
||||
@override
|
||||
|
@ -61,7 +61,8 @@ class _BulletedListTextNodeWidgetState extends State<BulletedListTextNodeWidget>
|
||||
}
|
||||
|
||||
BulletedListPluginStyle get style =>
|
||||
Theme.of(context).extension<BulletedListPluginStyle>()!;
|
||||
Theme.of(context).extension<BulletedListPluginStyle>() ??
|
||||
BulletedListPluginStyle.light;
|
||||
|
||||
EdgeInsets get padding => style.padding(
|
||||
widget.editorState,
|
||||
@ -97,7 +98,7 @@ class _BulletedListTextNodeWidgetState extends State<BulletedListTextNodeWidget>
|
||||
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,
|
||||
),
|
||||
|
@ -54,7 +54,8 @@ class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
|
||||
}
|
||||
|
||||
CheckboxPluginStyle get style =>
|
||||
Theme.of(context).extension<CheckboxPluginStyle>()!;
|
||||
Theme.of(context).extension<CheckboxPluginStyle>() ??
|
||||
CheckboxPluginStyle.light;
|
||||
|
||||
EdgeInsets get padding => style.padding(
|
||||
widget.editorState,
|
||||
@ -94,7 +95,7 @@ class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
|
||||
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),
|
||||
|
@ -202,12 +202,13 @@ class _FlowyRichTextState extends State<FlowyRichText> 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<FlowyRichText> with SelectableMixin {
|
||||
TextSpan get _textSpan {
|
||||
var offset = 0;
|
||||
List<TextSpan> textSpans = [];
|
||||
final style = widget.editorState.editorStyle.textStyle;
|
||||
final style = widget.editorState.editorStyle;
|
||||
final textInserts = widget.textNode.delta.whereType<TextInsert>();
|
||||
for (final textInsert in textInserts) {
|
||||
var textStyle = style.defaultTextStyle;
|
||||
var textStyle = style.textStyle!;
|
||||
GestureRecognizer? recognizer;
|
||||
final attributes = textInsert.attributes;
|
||||
if (attributes != null) {
|
||||
|
@ -60,7 +60,8 @@ class _HeadingTextNodeWidgetState extends State<HeadingTextNodeWidget>
|
||||
}
|
||||
|
||||
HeadingPluginStyle get style =>
|
||||
Theme.of(context).extension<HeadingPluginStyle>()!;
|
||||
Theme.of(context).extension<HeadingPluginStyle>() ??
|
||||
HeadingPluginStyle.light;
|
||||
|
||||
EdgeInsets get padding => style.padding(
|
||||
widget.editorState,
|
||||
@ -82,7 +83,7 @@ class _HeadingTextNodeWidgetState extends State<HeadingTextNodeWidget>
|
||||
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,
|
||||
),
|
||||
|
@ -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<NumberListTextNodeWidget>
|
||||
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<NumberListPluginStyle>()!;
|
||||
Theme.of(context).extension<NumberListPluginStyle>() ??
|
||||
NumberListPluginStyle.light;
|
||||
|
||||
EdgeInsets get padding => style.padding(
|
||||
widget.editorState,
|
||||
@ -106,7 +95,7 @@ class _NumberListTextNodeWidgetState extends State<NumberListTextNodeWidget>
|
||||
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) =>
|
||||
|
@ -60,7 +60,8 @@ class _QuotedTextNodeWidgetState extends State<QuotedTextNodeWidget>
|
||||
}
|
||||
|
||||
QuotedTextPluginStyle get style =>
|
||||
Theme.of(context).extension<QuotedTextPluginStyle>()!;
|
||||
Theme.of(context).extension<QuotedTextPluginStyle>() ??
|
||||
QuotedTextPluginStyle.light;
|
||||
|
||||
EdgeInsets get padding => style.padding(
|
||||
widget.editorState,
|
||||
@ -98,7 +99,7 @@ class _QuotedTextNodeWidgetState extends State<QuotedTextNodeWidget>
|
||||
textSpan.updateTextStyle(textStyle),
|
||||
placeholderTextSpanDecorator: (textSpan) =>
|
||||
textSpan.updateTextStyle(textStyle),
|
||||
lineHeight: widget.editorState.editorStyle.textStyle.lineHeight,
|
||||
lineHeight: widget.editorState.editorStyle.lineHeight,
|
||||
editorState: widget.editorState,
|
||||
),
|
||||
),
|
||||
|
@ -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<RichTextNodeWidget>
|
||||
with
|
||||
SelectableMixin,
|
||||
DefaultSelectable,
|
||||
BuiltInStyleMixin,
|
||||
BuiltInTextWidgetMixin {
|
||||
with SelectableMixin, DefaultSelectable, BuiltInTextWidgetMixin {
|
||||
@override
|
||||
GlobalKey? get iconKey => null;
|
||||
|
||||
@ -59,20 +56,26 @@ class _RichTextNodeWidgetState extends State<RichTextNodeWidget>
|
||||
|
||||
@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,
|
||||
),
|
||||
);
|
||||
|
@ -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<ThemeExtension<dynamic>> get lightPlguinStyleExtension => [
|
||||
HeadingPluginStyle.light,
|
||||
CheckboxPluginStyle.light,
|
||||
NumberListPluginStyle.light,
|
||||
QuotedTextPluginStyle.light,
|
||||
];
|
||||
|
||||
Iterable<ThemeExtension<dynamic>> get darkPlguinStyleExtension => [
|
||||
HeadingPluginStyle.dark,
|
||||
CheckboxPluginStyle.dark,
|
||||
NumberListPluginStyle.dark,
|
||||
QuotedTextPluginStyle.dark,
|
||||
BulletedListPluginStyle.dark,
|
||||
];
|
||||
|
||||
typedef TextStyleCustomizer = TextStyle Function(
|
||||
EditorState editorState, TextNode textNode);
|
||||
typedef PaddingCustomizer = EdgeInsets Function(
|
@ -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<ThemeExtension<dynamic>> get lightEditorStyleExtension => [
|
||||
EditorStyle.light,
|
||||
];
|
||||
|
||||
class EditorStyleV2 extends ThemeExtension<EditorStyleV2> {
|
||||
Iterable<ThemeExtension<dynamic>> get dartEditorStyleExtension => [
|
||||
EditorStyle.dark,
|
||||
];
|
||||
|
||||
class EditorStyle extends ThemeExtension<EditorStyle> {
|
||||
// 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<EditorStyleV2> {
|
||||
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<EditorStyleV2> {
|
||||
});
|
||||
|
||||
@override
|
||||
EditorStyleV2 copyWith({
|
||||
EditorStyle copyWith({
|
||||
EdgeInsets? padding,
|
||||
Color? cursorColor,
|
||||
Color? selectionColor,
|
||||
@ -56,10 +62,11 @@ class EditorStyleV2 extends ThemeExtension<EditorStyleV2> {
|
||||
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<EditorStyleV2> {
|
||||
}
|
||||
|
||||
@override
|
||||
ThemeExtension<EditorStyleV2> lerp(
|
||||
ThemeExtension<EditorStyleV2>? other, double t) {
|
||||
if (other == null || other is! EditorStyleV2) {
|
||||
ThemeExtension<EditorStyle> lerp(
|
||||
ThemeExtension<EditorStyle>? 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<EditorStyleV2> {
|
||||
lineHeight: lineHeight,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
typedef PluginStyler = Object Function(EditorState editorState, Node node);
|
||||
typedef PluginStyle = Map<String, PluginStyler>;
|
||||
|
||||
/// Editor style configuration
|
||||
class EditorStyle {
|
||||
EditorStyle({
|
||||
required this.padding,
|
||||
required this.textStyle,
|
||||
required this.cursorColor,
|
||||
required this.selectionColor,
|
||||
Map<String, PluginStyle> 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<String, PluginStyle> _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<String, PluginStyle>? 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<String, PluginStyle> 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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -259,7 +259,7 @@ List<ToolbarItem> defaultToolbarItems = [
|
||||
),
|
||||
handler: (editorState, context) => formatHighlight(
|
||||
editorState,
|
||||
editorState.editorStyle.textStyle.highlightColorHex,
|
||||
editorState.editorStyle.highlightColorHex!,
|
||||
),
|
||||
),
|
||||
];
|
||||
|
@ -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<SelectionMenuItem> selectionMenuItems;
|
||||
|
||||
final EditorStyle editorStyle;
|
||||
late final ThemeData themeData;
|
||||
|
||||
final bool editable;
|
||||
|
||||
@ -65,13 +62,15 @@ class _AppFlowyEditorState extends State<AppFlowyEditor> {
|
||||
Widget? services;
|
||||
|
||||
EditorState get editorState => widget.editorState;
|
||||
EditorStyle get editorStyle =>
|
||||
editorState.themeData.extension<EditorStyle>() ?? 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<AppFlowyEditor> {
|
||||
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<AppFlowyEditor> {
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -57,7 +57,7 @@ ShortcutEventHandler formatHighlightEventHandler = (editorState, event) {
|
||||
}
|
||||
formatHighlight(
|
||||
editorState,
|
||||
editorState.editorStyle.textStyle.highlightColorHex,
|
||||
editorState.editorStyle.highlightColorHex!,
|
||||
);
|
||||
return KeyEventResult.handled;
|
||||
};
|
||||
|
@ -39,7 +39,6 @@ class EditorWidgetTester {
|
||||
home: Scaffold(
|
||||
body: AppFlowyEditor(
|
||||
editorState: _editorState,
|
||||
editorStyle: EditorStyle.defaultStyle(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user