feat: expose text span for text style builder (#1011)

* feat: expose textspan for text style builder

* fix: content menu will show when collaborating
This commit is contained in:
Lucas 2025-01-06 11:40:17 +08:00 • committed by GitHub
commit 2490f698c3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 47 additions and 31 deletions

View file

@ -85,7 +85,7 @@ class _CustomizeThemeForEditorState extends State<CustomizeThemeForEditor> {
} }
return const EdgeInsets.symmetric(vertical: 5); return const EdgeInsets.symmetric(vertical: 5);
}, },
textStyle: (node) { textStyle: (node, {TextSpan? textSpan}) {
if (HeadingBlockKeys.type == node.type) { if (HeadingBlockKeys.type == node.type) {
return const TextStyle(color: Colors.yellow); return const TextStyle(color: Colors.yellow);
} }

View file

@ -1,6 +1,11 @@
import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
typedef BlockComponentTextStyleBuilder = TextStyle Function(
Node node, {
TextSpan? textSpan,
});
/// only for the common config of block component /// only for the common config of block component
class BlockComponentConfiguration { class BlockComponentConfiguration {
const BlockComponentConfiguration({ const BlockComponentConfiguration({
@ -27,7 +32,7 @@ class BlockComponentConfiguration {
) indentPadding; ) indentPadding;
/// The text style of a block component. /// The text style of a block component.
final TextStyle Function(Node node) textStyle; final BlockComponentTextStyleBuilder textStyle;
/// The placeholder text of a block component. /// The placeholder text of a block component.
final String Function(Node node) placeholderText; final String Function(Node node) placeholderText;
@ -35,7 +40,7 @@ class BlockComponentConfiguration {
/// The placeholder text style of a block component. /// The placeholder text style of a block component.
/// ///
/// It inherits the style from [textStyle]. /// It inherits the style from [textStyle].
final TextStyle Function(Node node) placeholderTextStyle; final BlockComponentTextStyleBuilder placeholderTextStyle;
/// The padding of a block selection area. /// The padding of a block selection area.
final EdgeInsets Function(Node node) blockSelectionAreaMargin; final EdgeInsets Function(Node node) blockSelectionAreaMargin;
@ -48,9 +53,9 @@ class BlockComponentConfiguration {
BlockComponentConfiguration copyWith({ BlockComponentConfiguration copyWith({
EdgeInsets Function(Node node)? padding, EdgeInsets Function(Node node)? padding,
TextStyle Function(Node node)? textStyle, BlockComponentTextStyleBuilder? textStyle,
String Function(Node node)? placeholderText, String Function(Node node)? placeholderText,
TextStyle Function(Node node)? placeholderTextStyle, BlockComponentTextStyleBuilder? placeholderTextStyle,
EdgeInsets Function(Node node)? blockSelectionAreaMargin, EdgeInsets Function(Node node)? blockSelectionAreaMargin,
TextAlign Function(Node node)? textAlign, TextAlign Function(Node node)? textAlign,
}) { }) {
@ -72,13 +77,14 @@ mixin BlockComponentConfigurable<T extends StatefulWidget> on State<T> {
EdgeInsets get padding => configuration.padding(node); EdgeInsets get padding => configuration.padding(node);
TextStyle get textStyle => configuration.textStyle(node); TextStyle textStyleWithTextSpan({TextSpan? textSpan}) =>
configuration.textStyle(node, textSpan: textSpan);
TextStyle placeholderTextStyleWithTextSpan({TextSpan? textSpan}) =>
configuration.placeholderTextStyle(node, textSpan: textSpan);
String get placeholderText => configuration.placeholderText(node); String get placeholderText => configuration.placeholderText(node);
TextStyle get placeholderTextStyle =>
configuration.placeholderTextStyle(node);
TextAlign get textAlign => configuration.textAlign(node); TextAlign get textAlign => configuration.textAlign(node);
} }
@ -95,7 +101,7 @@ EdgeInsets _indentPadding(Node node, TextDirection textDirection) {
} }
} }
TextStyle _textStyle(Node node) { TextStyle _textStyle(Node node, {TextSpan? textSpan}) {
return const TextStyle(); return const TextStyle();
} }
@ -103,7 +109,7 @@ String _placeholderText(Node node) {
return ' '; return ' ';
} }
TextStyle _placeholderTextStyle(Node node) { TextStyle _placeholderTextStyle(Node node, {TextSpan? textSpan}) {
return const TextStyle( return const TextStyle(
color: Colors.grey, color: Colors.grey,
); );

View file

@ -129,7 +129,7 @@ class _BulletedListBlockComponentWidgetState
? widget.iconBuilder!(context, node) ? widget.iconBuilder!(context, node)
: _BulletedListIcon( : _BulletedListIcon(
node: widget.node, node: widget.node,
textStyle: textStyle, textStyle: textStyleWithTextSpan(),
), ),
Flexible( Flexible(
child: AppFlowyRichText( child: AppFlowyRichText(
@ -140,11 +140,11 @@ class _BulletedListBlockComponentWidgetState
textAlign: alignment?.toTextAlign ?? textAlign, textAlign: alignment?.toTextAlign ?? textAlign,
placeholderText: placeholderText, placeholderText: placeholderText,
textSpanDecorator: (textSpan) => textSpan.updateTextStyle( textSpanDecorator: (textSpan) => textSpan.updateTextStyle(
textStyle, textStyleWithTextSpan(textSpan: textSpan),
), ),
placeholderTextSpanDecorator: (textSpan) => placeholderTextSpanDecorator: (textSpan) =>
textSpan.updateTextStyle( textSpan.updateTextStyle(
placeholderTextStyle, placeholderTextStyleWithTextSpan(textSpan: textSpan),
), ),
textDirection: textDirection, textDirection: textDirection,
cursorColor: editorState.editorStyle.cursorColor, cursorColor: editorState.editorStyle.cursorColor,

View file

@ -139,7 +139,9 @@ class _HeadingBlockComponentWidgetState
editorState: editorState, editorState: editorState,
textAlign: alignment?.toTextAlign ?? textAlign, textAlign: alignment?.toTextAlign ?? textAlign,
textSpanDecorator: (textSpan) { textSpanDecorator: (textSpan) {
var result = textSpan.updateTextStyle(textStyle); var result = textSpan.updateTextStyle(
textStyleWithTextSpan(textSpan: textSpan),
);
result = result.updateTextStyle( result = result.updateTextStyle(
widget.textStyleBuilder?.call(level) ?? widget.textStyleBuilder?.call(level) ??
defaultTextStyle(level), defaultTextStyle(level),
@ -153,7 +155,7 @@ class _HeadingBlockComponentWidgetState
defaultTextStyle(level), defaultTextStyle(level),
) )
.updateTextStyle( .updateTextStyle(
placeholderTextStyle, placeholderTextStyleWithTextSpan(textSpan: textSpan),
), ),
textDirection: textDirection, textDirection: textDirection,
cursorColor: editorState.editorStyle.cursorColor, cursorColor: editorState.editorStyle.cursorColor,

View file

@ -143,7 +143,7 @@ class _NumberedListBlockComponentWidgetState
) )
: _NumberedListIcon( : _NumberedListIcon(
node: node, node: node,
textStyle: textStyle, textStyle: textStyleWithTextSpan(),
direction: textDirection, direction: textDirection,
), ),
Flexible( Flexible(
@ -155,11 +155,11 @@ class _NumberedListBlockComponentWidgetState
textAlign: alignment?.toTextAlign ?? textAlign, textAlign: alignment?.toTextAlign ?? textAlign,
placeholderText: placeholderText, placeholderText: placeholderText,
textSpanDecorator: (textSpan) => textSpan.updateTextStyle( textSpanDecorator: (textSpan) => textSpan.updateTextStyle(
textStyle, textStyleWithTextSpan(textSpan: textSpan),
), ),
placeholderTextSpanDecorator: (textSpan) => placeholderTextSpanDecorator: (textSpan) =>
textSpan.updateTextStyle( textSpan.updateTextStyle(
placeholderTextStyle, placeholderTextStyleWithTextSpan(textSpan: textSpan),
), ),
textDirection: textDirection, textDirection: textDirection,
cursorColor: editorState.editorStyle.cursorColor, cursorColor: editorState.editorStyle.cursorColor,

View file

@ -163,10 +163,15 @@ class _ParagraphBlockComponentWidgetState
editorState: editorState, editorState: editorState,
textAlign: alignment?.toTextAlign ?? textAlign, textAlign: alignment?.toTextAlign ?? textAlign,
placeholderText: _showPlaceholder ? placeholderText : ' ', placeholderText: _showPlaceholder ? placeholderText : ' ',
textSpanDecorator: (textSpan) => textSpanDecorator: (textSpan) => textSpan.updateTextStyle(
textSpan.updateTextStyle(textStyle), textStyleWithTextSpan(
textSpan: textSpan,
),
),
placeholderTextSpanDecorator: (textSpan) => placeholderTextSpanDecorator: (textSpan) =>
textSpan.updateTextStyle(placeholderTextStyle), textSpan.updateTextStyle(
placeholderTextStyleWithTextSpan(textSpan: textSpan),
),
textDirection: textDirection, textDirection: textDirection,
cursorColor: editorState.editorStyle.cursorColor, cursorColor: editorState.editorStyle.cursorColor,
selectionColor: editorState.editorStyle.selectionColor, selectionColor: editorState.editorStyle.selectionColor,

View file

@ -133,11 +133,11 @@ class _QuoteBlockComponentWidgetState extends State<QuoteBlockComponentWidget>
textAlign: alignment?.toTextAlign ?? textAlign, textAlign: alignment?.toTextAlign ?? textAlign,
placeholderText: placeholderText, placeholderText: placeholderText,
textSpanDecorator: (textSpan) => textSpan.updateTextStyle( textSpanDecorator: (textSpan) => textSpan.updateTextStyle(
textStyle, textStyleWithTextSpan(textSpan: textSpan),
), ),
placeholderTextSpanDecorator: (textSpan) => placeholderTextSpanDecorator: (textSpan) =>
textSpan.updateTextStyle( textSpan.updateTextStyle(
placeholderTextStyle, placeholderTextStyleWithTextSpan(textSpan: textSpan),
), ),
textDirection: textDirection, textDirection: textDirection,
cursorColor: editorState.editorStyle.cursorColor, cursorColor: editorState.editorStyle.cursorColor,

View file

@ -172,14 +172,15 @@ class _TodoListBlockComponentWidgetState
textAlign: alignment?.toTextAlign ?? textAlign, textAlign: alignment?.toTextAlign ?? textAlign,
placeholderText: placeholderText, placeholderText: placeholderText,
textDirection: textDirection, textDirection: textDirection,
textSpanDecorator: (textSpan) => textSpanDecorator: (textSpan) => textSpan
textSpan.updateTextStyle(textStyle).updateTextStyle( .updateTextStyle(textStyleWithTextSpan())
widget.textStyleBuilder?.call(checked) ?? .updateTextStyle(
defaultTextStyle(), widget.textStyleBuilder?.call(checked) ??
), defaultTextStyle(),
),
placeholderTextSpanDecorator: (textSpan) => placeholderTextSpanDecorator: (textSpan) =>
textSpan.updateTextStyle( textSpan.updateTextStyle(
placeholderTextStyle, placeholderTextStyleWithTextSpan(textSpan: textSpan),
), ),
cursorColor: editorState.editorStyle.cursorColor, cursorColor: editorState.editorStyle.cursorColor,
selectionColor: editorState.editorStyle.selectionColor, selectionColor: editorState.editorStyle.selectionColor,

View file

@ -204,7 +204,7 @@ class SelectionMenu extends SelectionMenuService {
// Workaround: We can customize the padding through the [EditorStyle], // Workaround: We can customize the padding through the [EditorStyle],
// but the coordinates of overlay are not properly converted currently. // but the coordinates of overlay are not properly converted currently.
// Just subtract the padding here as a result. // Just subtract the padding here as a result.
const menuHeight = 200.0; const menuHeight = 300.0;
const menuOffset = Offset(0, 10); const menuOffset = Offset(0, 10);
final editorOffset = final editorOffset =
editorState.renderBox?.localToGlobal(Offset.zero) ?? Offset.zero; editorState.renderBox?.localToGlobal(Offset.zero) ?? Offset.zero;

View file

@ -57,6 +57,7 @@ enum CursorUpdateReason {
enum SelectionUpdateReason { enum SelectionUpdateReason {
uiEvent, // like mouse click, keyboard event uiEvent, // like mouse click, keyboard event
transaction, // like insert, delete, format transaction, // like insert, delete, format
remote, // like remote selection
selectAll, selectAll,
searchHighlight, // Highlighting search results searchHighlight, // Highlighting search results
} }
@ -359,6 +360,7 @@ class EditorState {
final completer = Completer<void>(); final completer = Completer<void>();
if (isRemote) { if (isRemote) {
_selectionUpdateReason = SelectionUpdateReason.remote;
selection = _applyTransactionFromRemote(transaction); selection = _applyTransactionFromRemote(transaction);
} else { } else {
// broadcast to other users here, before applying the transaction // broadcast to other users here, before applying the transaction