Merge pull request #798 from LucasXu0/feat/text_style

update text style and document style.
This commit is contained in:
Lucas.Xu 2022-08-09 15:22:14 +08:00 committed by GitHub
commit a032a0e8db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 204 additions and 116 deletions

View File

@ -37,7 +37,11 @@
"type": "text", "type": "text",
"delta": [ "delta": [
{ {
"insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowys five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." "insert": "At "
},
{ "insert": "AppFlowy", "attributes": { "code": true, "bold": true, "color": "0xFFED459C"} },
{
"insert": ", we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowys five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow."
} }
] ]
}, },

View File

@ -33,7 +33,7 @@ class EditorNodeWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center,
children: node.children children: node.children
.map( .map(
(child) => (child) =>

View File

@ -3,6 +3,7 @@ import 'package:flowy_editor/editor_state.dart';
import 'package:flowy_editor/infra/flowy_svg.dart'; import 'package:flowy_editor/infra/flowy_svg.dart';
import 'package:flowy_editor/render/rich_text/default_selectable.dart'; import 'package:flowy_editor/render/rich_text/default_selectable.dart';
import 'package:flowy_editor/render/rich_text/flowy_rich_text.dart'; import 'package:flowy_editor/render/rich_text/flowy_rich_text.dart';
import 'package:flowy_editor/render/rich_text/rich_text_style.dart';
import 'package:flowy_editor/render/selection/selectable.dart'; import 'package:flowy_editor/render/selection/selectable.dart';
import 'package:flowy_editor/service/render_plugin_service.dart'; import 'package:flowy_editor/service/render_plugin_service.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -56,21 +57,22 @@ class _BulletedListTextNodeWidgetState extends State<BulletedListTextNodeWidget>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( return SizedBox(
children: [ width: maxTextNodeWidth,
FlowySvg( child: Row(
size: Size.square(leftPadding), children: [
name: 'point', FlowySvg(
), size: Size.square(leftPadding),
Expanded( name: 'point',
child: FlowyRichText( ),
FlowyRichText(
key: _richTextKey, key: _richTextKey,
placeholderText: 'List', placeholderText: 'List',
textNode: widget.textNode, textNode: widget.textNode,
editorState: widget.editorState, editorState: widget.editorState,
), ),
), ],
], ),
); );
} }
} }

View File

@ -65,33 +65,34 @@ class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
Widget _buildWithSingle(BuildContext context) { Widget _buildWithSingle(BuildContext context) {
final check = widget.textNode.attributes.check; final check = widget.textNode.attributes.check;
return Row( return SizedBox(
crossAxisAlignment: CrossAxisAlignment.start, width: maxTextNodeWidth,
children: [ child: Row(
GestureDetector( crossAxisAlignment: CrossAxisAlignment.start,
child: FlowySvg( children: [
size: Size.square(leftPadding), GestureDetector(
name: check ? 'check' : 'uncheck', child: FlowySvg(
size: Size.square(leftPadding),
name: check ? 'check' : 'uncheck',
),
onTap: () {
debugPrint('[Checkbox] onTap...');
TransactionBuilder(widget.editorState)
..updateNode(widget.textNode, {
StyleKey.checkbox: !check,
})
..commit();
},
), ),
onTap: () { FlowyRichText(
debugPrint('[Checkbox] onTap...');
TransactionBuilder(widget.editorState)
..updateNode(widget.textNode, {
StyleKey.checkbox: !check,
})
..commit();
},
),
Expanded(
child: FlowyRichText(
key: _richTextKey, key: _richTextKey,
placeholderText: 'To-do', placeholderText: 'To-do',
textNode: widget.textNode, textNode: widget.textNode,
textSpanDecorator: _textSpanDecorator, textSpanDecorator: _textSpanDecorator,
editorState: widget.editorState, editorState: widget.editorState,
), ),
), ],
], ),
); );
} }

View File

@ -11,22 +11,6 @@ import 'package:flowy_editor/render/rich_text/rich_text_style.dart';
import 'package:flowy_editor/render/selection/selectable.dart'; import 'package:flowy_editor/render/selection/selectable.dart';
import 'package:flowy_editor/service/render_plugin_service.dart'; import 'package:flowy_editor/service/render_plugin_service.dart';
class RichTextNodeWidgetBuilder extends NodeWidgetBuilder<TextNode> {
@override
Widget build(NodeWidgetContext<TextNode> context) {
return FlowyRichText(
key: context.node.key,
textNode: context.node,
editorState: context.editorState,
);
}
@override
NodeValidator<Node> get nodeValidator => ((node) {
return true;
});
}
typedef FlowyTextSpanDecorator = TextSpan Function(TextSpan textSpan); typedef FlowyTextSpanDecorator = TextSpan Function(TextSpan textSpan);
class FlowyRichText extends StatefulWidget { class FlowyRichText extends StatefulWidget {

View File

@ -56,25 +56,22 @@ class _HeadingTextNodeWidgetState extends State<HeadingTextNodeWidget>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return SizedBox(
children: [ width: maxTextNodeWidth,
Padding( child: Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
top: topPadding, top: topPadding,
bottom: bottomPadding, bottom: bottomPadding,
), ),
child: Expanded( child: FlowyRichText(
child: FlowyRichText( key: _richTextKey,
key: _richTextKey, placeholderText: 'Heading',
placeholderText: 'Heading', placeholderTextSpanDecorator: _placeholderTextSpanDecorator,
placeholderTextSpanDecorator: _placeholderTextSpanDecorator, textSpanDecorator: _textSpanDecorator,
textSpanDecorator: _textSpanDecorator, textNode: widget.textNode,
textNode: widget.textNode, editorState: widget.editorState,
editorState: widget.editorState, ),
), ),
),
)
],
); );
} }

View File

@ -57,21 +57,22 @@ class _NumberListTextNodeWidgetState extends State<NumberListTextNodeWidget>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( return SizedBox(
children: [ width: maxTextNodeWidth,
FlowySvg( child: Row(
size: Size.square(leftPadding), children: [
number: widget.textNode.attributes.number, FlowySvg(
), size: Size.square(leftPadding),
Expanded( number: widget.textNode.attributes.number,
child: FlowyRichText( ),
FlowyRichText(
key: _richTextKey, key: _richTextKey,
placeholderText: 'List', placeholderText: 'List',
textNode: widget.textNode, textNode: widget.textNode,
editorState: widget.editorState, editorState: widget.editorState,
), ),
), ],
], ),
); );
} }
} }

View File

@ -56,24 +56,25 @@ class _QuotedTextNodeWidgetState extends State<QuotedTextNodeWidget>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( return SizedBox(
children: [ width: maxTextNodeWidth,
FlowySvg( child: Row(
size: Size( children: [
leftPadding, FlowySvg(
_quoteHeight, size: Size(
leftPadding,
_quoteHeight,
),
name: 'quote',
), ),
name: 'quote', FlowyRichText(
),
Expanded(
child: FlowyRichText(
key: _richTextKey, key: _richTextKey,
placeholderText: 'Quote', placeholderText: 'Quote',
textNode: widget.textNode, textNode: widget.textNode,
editorState: widget.editorState, editorState: widget.editorState,
), ),
), ],
], ),
); );
} }

View File

@ -0,0 +1,68 @@
import 'package:flowy_editor/document/node.dart';
import 'package:flowy_editor/editor_state.dart';
import 'package:flowy_editor/infra/flowy_svg.dart';
import 'package:flowy_editor/render/rich_text/default_selectable.dart';
import 'package:flowy_editor/render/rich_text/flowy_rich_text.dart';
import 'package:flowy_editor/render/rich_text/rich_text_style.dart';
import 'package:flowy_editor/render/selection/selectable.dart';
import 'package:flowy_editor/service/render_plugin_service.dart';
import 'package:flutter/material.dart';
class RichTextNodeWidgetBuilder extends NodeWidgetBuilder<TextNode> {
@override
Widget build(NodeWidgetContext<TextNode> context) {
return RichTextNodeWidget(
key: context.node.key,
textNode: context.node,
editorState: context.editorState,
);
}
@override
NodeValidator<Node> get nodeValidator => ((node) {
return true;
});
}
class RichTextNodeWidget extends StatefulWidget {
const RichTextNodeWidget({
Key? key,
required this.textNode,
required this.editorState,
}) : super(key: key);
final TextNode textNode;
final EditorState editorState;
@override
State<RichTextNodeWidget> createState() => _RichTextNodeWidgetState();
}
// customize
class _RichTextNodeWidgetState extends State<RichTextNodeWidget>
with Selectable, DefaultSelectable {
final _richTextKey = GlobalKey(debugLabel: 'rich_text');
final leftPadding = 20.0;
@override
Selectable<StatefulWidget> get forward =>
_richTextKey.currentState as Selectable;
@override
Offset get baseOffset {
return Offset.zero;
}
@override
Widget build(BuildContext context) {
return SizedBox(
width: maxTextNodeWidth,
child: FlowyRichText(
key: _richTextKey,
textNode: widget.textNode,
editorState: widget.editorState,
),
);
}
}

View File

@ -59,6 +59,8 @@ class StyleKey {
]; ];
} }
// TODO: customize
double maxTextNodeWidth = 780.0;
double baseFontSize = 16.0; double baseFontSize = 16.0;
// TODO: customize. // TODO: customize.
Map<String, double> headingToFontSize = { Map<String, double> headingToFontSize = {
@ -146,7 +148,7 @@ extension DeltaAttributesExtensions on Attributes {
return null; return null;
} }
Color? get hightlightColor { Color? get highlightColor {
if (containsKey(StyleKey.highlightColor) && if (containsKey(StyleKey.highlightColor) &&
this[StyleKey.highlightColor] is String) { this[StyleKey.highlightColor] is String) {
return Color( return Color(
@ -183,19 +185,30 @@ class RichTextStyle {
return TextSpan( return TextSpan(
text: text, text: text,
style: TextStyle( style: TextStyle(
fontWeight: fontWeight, fontWeight: _fontWeight,
fontStyle: fontStyle, fontStyle: _fontStyle,
fontSize: fontSize, fontSize: _fontSize,
color: textColor, color: _textColor,
backgroundColor: backgroundColor, decoration: _textDecoration,
decoration: textDecoration, background: _background,
), ),
recognizer: recognizer, recognizer: _recognizer,
); );
} }
Paint? get _background {
if (_backgroundColor != null) {
return Paint()
..color = _backgroundColor!
..strokeWidth = 24.0
..style = PaintingStyle.fill
..strokeJoin = StrokeJoin.round;
}
return null;
}
// bold // bold
FontWeight get fontWeight { FontWeight get _fontWeight {
if (attributes.bold) { if (attributes.bold) {
return FontWeight.bold; return FontWeight.bold;
} }
@ -203,38 +216,46 @@ class RichTextStyle {
} }
// underline or strikethrough // underline or strikethrough
TextDecoration get textDecoration { TextDecoration get _textDecoration {
var decorations = [TextDecoration.none];
if (attributes.underline || attributes.href != null) { if (attributes.underline || attributes.href != null) {
return TextDecoration.underline; decorations.add(TextDecoration.underline);
} else if (attributes.strikethrough) { // TextDecoration.underline;
return TextDecoration.lineThrough;
} }
return TextDecoration.none; if (attributes.strikethrough) {
decorations.add(TextDecoration.lineThrough);
}
return TextDecoration.combine(decorations);
} }
// font // font
FontStyle get fontStyle => FontStyle get _fontStyle =>
attributes.italic ? FontStyle.italic : FontStyle.normal; attributes.italic ? FontStyle.italic : FontStyle.normal;
// text color // text color
Color get textColor { Color get _textColor {
if (attributes.href != null) { if (attributes.href != null) {
return Colors.lightBlue; return Colors.lightBlue;
} }
return attributes.color ?? Colors.black; return attributes.color ?? Colors.black;
} }
Color get backgroundColor { Color? get _backgroundColor {
return attributes.hightlightColor ?? Colors.transparent; if (attributes.highlightColor != null) {
return attributes.highlightColor!;
} else if (attributes.code) {
return Colors.grey.withOpacity(0.4);
}
return null;
} }
// font size // font size
double get fontSize { double get _fontSize {
return baseFontSize; return baseFontSize;
} }
// recognizer // recognizer
GestureRecognizer? get recognizer { GestureRecognizer? get _recognizer {
final href = attributes.href; final href = attributes.href;
if (href != null) { if (href != null) {
return TapGestureRecognizer() return TapGestureRecognizer()

View File

@ -97,11 +97,6 @@ bool formatRichTextPartialStyle(EditorState editorState, String styleKey) {
Attributes attributes = { Attributes attributes = {
styleKey: value, styleKey: value,
}; };
if (styleKey == StyleKey.underline && value) {
attributes[StyleKey.strikethrough] = null;
} else if (styleKey == StyleKey.strikethrough && value) {
attributes[StyleKey.underline] = null;
}
return formatRichTextStyle(editorState, attributes); return formatRichTextStyle(editorState, attributes);
} }

View File

@ -4,10 +4,10 @@ import 'package:flowy_editor/editor_state.dart';
import 'package:flowy_editor/render/editor/editor_entry.dart'; import 'package:flowy_editor/render/editor/editor_entry.dart';
import 'package:flowy_editor/render/rich_text/bulleted_list_text.dart'; import 'package:flowy_editor/render/rich_text/bulleted_list_text.dart';
import 'package:flowy_editor/render/rich_text/checkbox_text.dart'; import 'package:flowy_editor/render/rich_text/checkbox_text.dart';
import 'package:flowy_editor/render/rich_text/flowy_rich_text.dart';
import 'package:flowy_editor/render/rich_text/heading_text.dart'; import 'package:flowy_editor/render/rich_text/heading_text.dart';
import 'package:flowy_editor/render/rich_text/number_list_text.dart'; import 'package:flowy_editor/render/rich_text/number_list_text.dart';
import 'package:flowy_editor/render/rich_text/quoted_text.dart'; import 'package:flowy_editor/render/rich_text/quoted_text.dart';
import 'package:flowy_editor/render/rich_text/rich_text.dart';
import 'package:flowy_editor/service/input_service.dart'; import 'package:flowy_editor/service/input_service.dart';
import 'package:flowy_editor/service/internal_key_event_handlers/arrow_keys_handler.dart'; import 'package:flowy_editor/service/internal_key_event_handlers/arrow_keys_handler.dart';
import 'package:flowy_editor/service/internal_key_event_handlers/copy_paste_handler.dart'; import 'package:flowy_editor/service/internal_key_event_handlers/copy_paste_handler.dart';

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flowy_editor/document/attributes.dart';
import 'package:flowy_editor/document/node.dart'; import 'package:flowy_editor/document/node.dart';
import 'package:flowy_editor/document/position.dart'; import 'package:flowy_editor/document/position.dart';
import 'package:flowy_editor/document/selection.dart'; import 'package:flowy_editor/document/selection.dart';
@ -91,7 +92,8 @@ FlowyKeyEventHandler enterWithoutShiftInTextNodesHandler =
..insertNode( ..insertNode(
textNode.path.next, textNode.path.next,
textNode.copyWith( textNode.copyWith(
attributes: needCopyAttributes ? textNode.attributes : {}, attributes:
needCopyAttributes ? Attributes.from(textNode.attributes) : {},
delta: textNode.delta.slice(selection.end.offset), delta: textNode.delta.slice(selection.end.offset),
), ),
) )

View File

@ -23,6 +23,18 @@ FlowyKeyEventHandler updateTextStyleByCommandXHandler = (editorState, event) {
case 'b': case 'b':
formatBold(editorState); formatBold(editorState);
return KeyEventResult.handled; return KeyEventResult.handled;
case 'I':
case 'i':
formatItalic(editorState);
return KeyEventResult.handled;
case 'U':
case 'u':
formatUnderline(editorState);
return KeyEventResult.handled;
case 'S':
case 's':
formatStrikethrough(editorState);
return KeyEventResult.handled;
default: default:
break; break;
} }