mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: customize text font (#3467)
* feat: update UI in settings page * feat: customzing font in document page * fix: flutter analyze and format issues
This commit is contained in:
parent
4b9b723521
commit
9c59e1487e
@ -80,7 +80,8 @@ void main() {
|
|||||||
await tester.openSettingsPage(SettingsPage.files);
|
await tester.openSettingsPage(SettingsPage.files);
|
||||||
await tester.openSettingsPage(SettingsPage.appearance);
|
await tester.openSettingsPage(SettingsPage.appearance);
|
||||||
|
|
||||||
expect(find.textContaining(DefaultAppearanceSettings.kDefaultFontFamily),
|
expect(
|
||||||
|
find.textContaining(DefaultAppearanceSettings.kDefaultFontFamily),
|
||||||
findsOneWidget,
|
findsOneWidget,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -12,8 +12,11 @@ class MyMockClient extends Mock implements http.Client {
|
|||||||
final requestType = request.method;
|
final requestType = request.method;
|
||||||
final requestUri = request.url;
|
final requestUri = request.url;
|
||||||
|
|
||||||
if (requestType == 'POST' && requestUri == OpenAIRequestType.textCompletion.uri) {
|
if (requestType == 'POST' &&
|
||||||
final responseHeaders = <String, String>{'content-type': 'text/event-stream'};
|
requestUri == OpenAIRequestType.textCompletion.uri) {
|
||||||
|
final responseHeaders = <String, String>{
|
||||||
|
'content-type': 'text/event-stream'
|
||||||
|
};
|
||||||
final responseBody = Stream.fromIterable([
|
final responseBody = Stream.fromIterable([
|
||||||
utf8.encode(
|
utf8.encode(
|
||||||
'{ "choices": [{"text": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula ", "index": 0, "logprobs": null, "finish_reason": null}]}',
|
'{ "choices": [{"text": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula ", "index": 0, "logprobs": null, "finish_reason": null}]}',
|
||||||
@ -51,7 +54,9 @@ class MockOpenAIRepository extends HttpOpenAIRepository {
|
|||||||
|
|
||||||
var previousSyntax = '';
|
var previousSyntax = '';
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
await for (final chunk in response.stream.transform(const Utf8Decoder()).transform(const LineSplitter())) {
|
await for (final chunk in response.stream
|
||||||
|
.transform(const Utf8Decoder())
|
||||||
|
.transform(const LineSplitter())) {
|
||||||
await onStart();
|
await onStart();
|
||||||
final data = chunk.trim().split('data: ');
|
final data = chunk.trim().split('data: ');
|
||||||
if (data[0] != '[DONE]') {
|
if (data[0] != '[DONE]') {
|
||||||
|
@ -382,8 +382,10 @@ Widget? _buildHeaderIcon(GroupData customData) {
|
|||||||
case FieldType.Checkbox:
|
case FieldType.Checkbox:
|
||||||
final group = customData.asCheckboxGroup()!;
|
final group = customData.asCheckboxGroup()!;
|
||||||
if (group.isCheck) {
|
if (group.isCheck) {
|
||||||
widget =
|
widget = const FlowySvg(
|
||||||
const FlowySvg(FlowySvgs.check_filled_s, blendMode: BlendMode.dst,);
|
FlowySvgs.check_filled_s,
|
||||||
|
blendMode: BlendMode.dst,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
widget = const FlowySvg(FlowySvgs.uncheck_s);
|
widget = const FlowySvg(FlowySvgs.uncheck_s);
|
||||||
}
|
}
|
||||||
|
@ -75,8 +75,8 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
alignToolbarItem,
|
alignToolbarItem,
|
||||||
buildTextColorItem(),
|
buildTextColorItem(),
|
||||||
buildHighlightColorItem(),
|
buildHighlightColorItem(),
|
||||||
// TODO: enable it in version 0.3.3
|
customizeFontToolbarItem,
|
||||||
// ...textDirectionItems,
|
...textDirectionItems,
|
||||||
];
|
];
|
||||||
|
|
||||||
late final List<SelectionMenuItem> slashMenuItems;
|
late final List<SelectionMenuItem> slashMenuItems;
|
||||||
|
@ -61,13 +61,10 @@ SelectionMenuItem calloutItem = SelectionMenuItem.node(
|
|||||||
// building the callout block widget
|
// building the callout block widget
|
||||||
class CalloutBlockComponentBuilder extends BlockComponentBuilder {
|
class CalloutBlockComponentBuilder extends BlockComponentBuilder {
|
||||||
CalloutBlockComponentBuilder({
|
CalloutBlockComponentBuilder({
|
||||||
this.configuration = const BlockComponentConfiguration(),
|
super.configuration,
|
||||||
required this.defaultColor,
|
required this.defaultColor,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
|
||||||
final BlockComponentConfiguration configuration;
|
|
||||||
|
|
||||||
final Color defaultColor;
|
final Color defaultColor;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -51,13 +51,10 @@ SelectionMenuItem codeBlockItem = SelectionMenuItem.node(
|
|||||||
|
|
||||||
class CodeBlockComponentBuilder extends BlockComponentBuilder {
|
class CodeBlockComponentBuilder extends BlockComponentBuilder {
|
||||||
CodeBlockComponentBuilder({
|
CodeBlockComponentBuilder({
|
||||||
this.configuration = const BlockComponentConfiguration(),
|
super.configuration,
|
||||||
this.padding = const EdgeInsets.all(0),
|
this.padding = const EdgeInsets.all(0),
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
|
||||||
final BlockComponentConfiguration configuration;
|
|
||||||
|
|
||||||
final EdgeInsets padding;
|
final EdgeInsets padding;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -17,12 +17,9 @@ class DatabaseBlockKeys {
|
|||||||
|
|
||||||
class DatabaseViewBlockComponentBuilder extends BlockComponentBuilder {
|
class DatabaseViewBlockComponentBuilder extends BlockComponentBuilder {
|
||||||
DatabaseViewBlockComponentBuilder({
|
DatabaseViewBlockComponentBuilder({
|
||||||
this.configuration = const BlockComponentConfiguration(),
|
super.configuration,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
|
||||||
final BlockComponentConfiguration configuration;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BlockComponentWidget build(BlockComponentContext blockComponentContext) {
|
BlockComponentWidget build(BlockComponentContext blockComponentContext) {
|
||||||
final node = blockComponentContext.node;
|
final node = blockComponentContext.node;
|
||||||
|
@ -8,26 +8,26 @@ import 'emoji_picker.dart';
|
|||||||
/// Config for customizations
|
/// Config for customizations
|
||||||
class Config {
|
class Config {
|
||||||
/// Constructor
|
/// Constructor
|
||||||
const Config(
|
const Config({
|
||||||
{this.columns = 7,
|
this.columns = 7,
|
||||||
this.emojiSizeMax = 32.0,
|
this.emojiSizeMax = 32.0,
|
||||||
this.verticalSpacing = 0,
|
this.verticalSpacing = 0,
|
||||||
this.horizontalSpacing = 0,
|
this.horizontalSpacing = 0,
|
||||||
this.initCategory = Category.RECENT,
|
this.initCategory = Category.RECENT,
|
||||||
this.bgColor = const Color(0xFFEBEFF2),
|
this.bgColor = const Color(0xFFEBEFF2),
|
||||||
this.indicatorColor = Colors.blue,
|
this.indicatorColor = Colors.blue,
|
||||||
this.iconColor = Colors.grey,
|
this.iconColor = Colors.grey,
|
||||||
this.iconColorSelected = Colors.blue,
|
this.iconColorSelected = Colors.blue,
|
||||||
this.progressIndicatorColor = Colors.blue,
|
this.progressIndicatorColor = Colors.blue,
|
||||||
this.backspaceColor = Colors.blue,
|
this.backspaceColor = Colors.blue,
|
||||||
this.showRecentsTab = true,
|
this.showRecentsTab = true,
|
||||||
this.recentsLimit = 28,
|
this.recentsLimit = 28,
|
||||||
this.noRecentsText = 'No Recents',
|
this.noRecentsText = 'No Recents',
|
||||||
this.noRecentsStyle =
|
this.noRecentsStyle = const TextStyle(fontSize: 20, color: Colors.black26),
|
||||||
const TextStyle(fontSize: 20, color: Colors.black26),
|
this.tabIndicatorAnimDuration = kTabScrollDuration,
|
||||||
this.tabIndicatorAnimDuration = kTabScrollDuration,
|
this.categoryIcons = const CategoryIcons(),
|
||||||
this.categoryIcons = const CategoryIcons(),
|
this.buttonMode = ButtonMode.MATERIAL,
|
||||||
this.buttonMode = ButtonMode.MATERIAL,});
|
});
|
||||||
|
|
||||||
/// Number of emojis per row
|
/// Number of emojis per row
|
||||||
final int columns;
|
final int columns;
|
||||||
|
@ -27,14 +27,16 @@ class DefaultEmojiPickerViewState extends State<DefaultEmojiPickerView>
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
var initCategory = widget.state.categoryEmoji.indexWhere(
|
var initCategory = widget.state.categoryEmoji.indexWhere(
|
||||||
(element) => element.category == widget.config.initCategory,);
|
(element) => element.category == widget.config.initCategory,
|
||||||
|
);
|
||||||
if (initCategory == -1) {
|
if (initCategory == -1) {
|
||||||
initCategory = 0;
|
initCategory = 0;
|
||||||
}
|
}
|
||||||
_tabController = TabController(
|
_tabController = TabController(
|
||||||
initialIndex: initCategory,
|
initialIndex: initCategory,
|
||||||
length: widget.state.categoryEmoji.length,
|
length: widget.state.categoryEmoji.length,
|
||||||
vsync: this,);
|
vsync: this,
|
||||||
|
);
|
||||||
_pageController = PageController(initialPage: initCategory);
|
_pageController = PageController(initialPage: initCategory);
|
||||||
_emojiFocusNode.requestFocus();
|
_emojiFocusNode.requestFocus();
|
||||||
|
|
||||||
@ -72,14 +74,15 @@ class DefaultEmojiPickerViewState extends State<DefaultEmojiPickerView>
|
|||||||
return Material(
|
return Material(
|
||||||
type: MaterialType.transparency,
|
type: MaterialType.transparency,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
padding: const EdgeInsets.only(bottom: 2),
|
padding: const EdgeInsets.only(bottom: 2),
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.backspace,
|
Icons.backspace,
|
||||||
color: widget.config.backspaceColor,
|
color: widget.config.backspaceColor,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
widget.state.onBackspacePressed!();
|
widget.state.onBackspacePressed!();
|
||||||
},),
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Container();
|
return Container();
|
||||||
@ -160,8 +163,12 @@ class DefaultEmojiPickerViewState extends State<DefaultEmojiPickerView>
|
|||||||
: widget.state.categoryEmoji
|
: widget.state.categoryEmoji
|
||||||
.asMap()
|
.asMap()
|
||||||
.entries
|
.entries
|
||||||
.map<Widget>((item) => _buildCategory(
|
.map<Widget>(
|
||||||
item.value.category, emojiSize,),)
|
(item) => _buildCategory(
|
||||||
|
item.value.category,
|
||||||
|
emojiSize,
|
||||||
|
),
|
||||||
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -206,8 +213,10 @@ class DefaultEmojiPickerViewState extends State<DefaultEmojiPickerView>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildButtonWidget(
|
Widget _buildButtonWidget({
|
||||||
{required VoidCallback onPressed, required Widget child,}) {
|
required VoidCallback onPressed,
|
||||||
|
required Widget child,
|
||||||
|
}) {
|
||||||
if (widget.config.buttonMode == ButtonMode.MATERIAL) {
|
if (widget.config.buttonMode == ButtonMode.MATERIAL) {
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: onPressed,
|
onTap: onPressed,
|
||||||
@ -266,29 +275,31 @@ class DefaultEmojiPickerViewState extends State<DefaultEmojiPickerView>
|
|||||||
Emoji emoji,
|
Emoji emoji,
|
||||||
) {
|
) {
|
||||||
return _buildButtonWidget(
|
return _buildButtonWidget(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
widget.state.onEmojiSelected(categoryEmoji.category, emoji);
|
widget.state.onEmojiSelected(categoryEmoji.category, emoji);
|
||||||
},
|
},
|
||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
child: Text(
|
child: Text(
|
||||||
emoji.emoji,
|
emoji.emoji,
|
||||||
textScaleFactor: 1.0,
|
textScaleFactor: 1.0,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: emojiSize,
|
fontSize: emojiSize,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),);
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildNoRecent() {
|
Widget _buildNoRecent() {
|
||||||
return Center(
|
return Center(
|
||||||
child: FlowyText.regular(
|
child: FlowyText.regular(
|
||||||
widget.config.noRecentsText,
|
widget.config.noRecentsText,
|
||||||
color: Theme.of(context).colorScheme.tertiary.withAlpha(77),
|
color: Theme.of(context).colorScheme.tertiary.withAlpha(77),
|
||||||
fontSize: widget.config.noRecentsStyle.fontSize,
|
fontSize: widget.config.noRecentsStyle.fontSize,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),);
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -190,30 +190,49 @@ class EmojiPickerState extends State<EmojiPicker> {
|
|||||||
categoryEmoji.add(CategoryEmoji(Category.RECENT, recentEmojiMap));
|
categoryEmoji.add(CategoryEmoji(Category.RECENT, recentEmojiMap));
|
||||||
}
|
}
|
||||||
categoryEmoji.addAll([
|
categoryEmoji.addAll([
|
||||||
CategoryEmoji(Category.SMILEYS,
|
|
||||||
await _getAvailableEmojis(emoji_list.smileys, title: 'smileys'),),
|
|
||||||
CategoryEmoji(Category.ANIMALS,
|
|
||||||
await _getAvailableEmojis(emoji_list.animals, title: 'animals'),),
|
|
||||||
CategoryEmoji(Category.FOODS,
|
|
||||||
await _getAvailableEmojis(emoji_list.foods, title: 'foods'),),
|
|
||||||
CategoryEmoji(
|
CategoryEmoji(
|
||||||
Category.ACTIVITIES,
|
Category.SMILEYS,
|
||||||
await _getAvailableEmojis(emoji_list.activities,
|
await _getAvailableEmojis(emoji_list.smileys, title: 'smileys'),
|
||||||
title: 'activities',),),
|
),
|
||||||
CategoryEmoji(Category.TRAVEL,
|
CategoryEmoji(
|
||||||
await _getAvailableEmojis(emoji_list.travel, title: 'travel'),),
|
Category.ANIMALS,
|
||||||
CategoryEmoji(Category.OBJECTS,
|
await _getAvailableEmojis(emoji_list.animals, title: 'animals'),
|
||||||
await _getAvailableEmojis(emoji_list.objects, title: 'objects'),),
|
),
|
||||||
CategoryEmoji(Category.SYMBOLS,
|
CategoryEmoji(
|
||||||
await _getAvailableEmojis(emoji_list.symbols, title: 'symbols'),),
|
Category.FOODS,
|
||||||
CategoryEmoji(Category.FLAGS,
|
await _getAvailableEmojis(emoji_list.foods, title: 'foods'),
|
||||||
await _getAvailableEmojis(emoji_list.flags, title: 'flags'),)
|
),
|
||||||
|
CategoryEmoji(
|
||||||
|
Category.ACTIVITIES,
|
||||||
|
await _getAvailableEmojis(
|
||||||
|
emoji_list.activities,
|
||||||
|
title: 'activities',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CategoryEmoji(
|
||||||
|
Category.TRAVEL,
|
||||||
|
await _getAvailableEmojis(emoji_list.travel, title: 'travel'),
|
||||||
|
),
|
||||||
|
CategoryEmoji(
|
||||||
|
Category.OBJECTS,
|
||||||
|
await _getAvailableEmojis(emoji_list.objects, title: 'objects'),
|
||||||
|
),
|
||||||
|
CategoryEmoji(
|
||||||
|
Category.SYMBOLS,
|
||||||
|
await _getAvailableEmojis(emoji_list.symbols, title: 'symbols'),
|
||||||
|
),
|
||||||
|
CategoryEmoji(
|
||||||
|
Category.FLAGS,
|
||||||
|
await _getAvailableEmojis(emoji_list.flags, title: 'flags'),
|
||||||
|
)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get available emoji for given category title
|
// Get available emoji for given category title
|
||||||
Future<List<Emoji>> _getAvailableEmojis(Map<String, String> map,
|
Future<List<Emoji>> _getAvailableEmojis(
|
||||||
{required String title,}) async {
|
Map<String, String> map, {
|
||||||
|
required String title,
|
||||||
|
}) async {
|
||||||
Map<String, String>? newMap;
|
Map<String, String>? newMap;
|
||||||
|
|
||||||
// Get Emojis cached locally if available
|
// Get Emojis cached locally if available
|
||||||
@ -236,15 +255,18 @@ class EmojiPickerState extends State<EmojiPicker> {
|
|||||||
|
|
||||||
// Check if emoji is available on current platform
|
// Check if emoji is available on current platform
|
||||||
Future<Map<String, String>?> _getPlatformAvailableEmoji(
|
Future<Map<String, String>?> _getPlatformAvailableEmoji(
|
||||||
Map<String, String> emoji,) async {
|
Map<String, String> emoji,
|
||||||
|
) async {
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
Map<String, String>? filtered = {};
|
Map<String, String>? filtered = {};
|
||||||
const delimiter = '|';
|
const delimiter = '|';
|
||||||
try {
|
try {
|
||||||
final entries = emoji.values.join(delimiter);
|
final entries = emoji.values.join(delimiter);
|
||||||
final keys = emoji.keys.join(delimiter);
|
final keys = emoji.keys.join(delimiter);
|
||||||
final result = (await platform.invokeMethod<String>('checkAvailability',
|
final result = (await platform.invokeMethod<String>(
|
||||||
{'emojiKeys': keys, 'emojiEntries': entries},)) as String;
|
'checkAvailability',
|
||||||
|
{'emojiKeys': keys, 'emojiEntries': entries},
|
||||||
|
)) as String;
|
||||||
final resultKeys = result.split(delimiter);
|
final resultKeys = result.split(delimiter);
|
||||||
for (var i = 0; i < resultKeys.length; i++) {
|
for (var i = 0; i < resultKeys.length; i++) {
|
||||||
filtered[resultKeys[i]] = emoji[resultKeys[i]]!;
|
filtered[resultKeys[i]] = emoji[resultKeys[i]]!;
|
||||||
@ -272,7 +294,9 @@ class EmojiPickerState extends State<EmojiPicker> {
|
|||||||
|
|
||||||
// Stores filtered emoji locally for faster access next time
|
// Stores filtered emoji locally for faster access next time
|
||||||
Future<void> _cacheFilteredEmojis(
|
Future<void> _cacheFilteredEmojis(
|
||||||
String title, Map<String, String> emojis,) async {
|
String title,
|
||||||
|
Map<String, String> emojis,
|
||||||
|
) async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
final emojiJson = jsonEncode(emojis);
|
final emojiJson = jsonEncode(emojis);
|
||||||
prefs.setString(title, emojiJson);
|
prefs.setString(title, emojiJson);
|
||||||
@ -305,7 +329,9 @@ class EmojiPickerState extends State<EmojiPicker> {
|
|||||||
recentEmoji.sort((a, b) => b.counter - a.counter);
|
recentEmoji.sort((a, b) => b.counter - a.counter);
|
||||||
// Limit entries to recentsLimit
|
// Limit entries to recentsLimit
|
||||||
recentEmoji = recentEmoji.sublist(
|
recentEmoji = recentEmoji.sublist(
|
||||||
0, min(widget.config.recentsLimit, recentEmoji.length),);
|
0,
|
||||||
|
min(widget.config.recentsLimit, recentEmoji.length),
|
||||||
|
);
|
||||||
// save locally
|
// save locally
|
||||||
prefs.setString('recent', jsonEncode(recentEmoji));
|
prefs.setString('recent', jsonEncode(recentEmoji));
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/font_family_setting.dart';
|
||||||
|
import 'package:appflowy_backend/log.dart';
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
||||||
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
final customizeFontToolbarItem = ToolbarItem(
|
||||||
|
id: 'editor.font',
|
||||||
|
group: 4,
|
||||||
|
isActive: onlyShowInTextType,
|
||||||
|
builder: (context, editorState, highlightColor) {
|
||||||
|
final selection = editorState.selection!;
|
||||||
|
final popoverController = PopoverController();
|
||||||
|
return MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.click,
|
||||||
|
child: FontFamilyDropDown(
|
||||||
|
currentFontFamily: '',
|
||||||
|
popoverController: popoverController,
|
||||||
|
onOpen: () => keepEditorFocusNotifier.value += 1,
|
||||||
|
onClose: () => keepEditorFocusNotifier.value -= 1,
|
||||||
|
onFontFamilyChanged: (fontFamily) async {
|
||||||
|
await popoverController.close();
|
||||||
|
try {
|
||||||
|
await editorState.formatDelta(selection, {
|
||||||
|
AppFlowyRichTextKeys.fontFamily: fontFamily,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
Log.error('Failed to set font family: $e');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 4.0),
|
||||||
|
child: FlowySvg(
|
||||||
|
FlowySvgs.font_family_s,
|
||||||
|
size: Size.square(16.0),
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
@ -54,12 +54,9 @@ SelectionMenuItem mathEquationItem = SelectionMenuItem.node(
|
|||||||
|
|
||||||
class MathEquationBlockComponentBuilder extends BlockComponentBuilder {
|
class MathEquationBlockComponentBuilder extends BlockComponentBuilder {
|
||||||
MathEquationBlockComponentBuilder({
|
MathEquationBlockComponentBuilder({
|
||||||
this.configuration = const BlockComponentConfiguration(),
|
super.configuration,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
|
||||||
final BlockComponentConfiguration configuration;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BlockComponentWidget build(BlockComponentContext blockComponentContext) {
|
BlockComponentWidget build(BlockComponentContext blockComponentContext) {
|
||||||
final node = blockComponentContext.node;
|
final node = blockComponentContext.node;
|
||||||
|
@ -32,12 +32,9 @@ Node outlineBlockNode() {
|
|||||||
|
|
||||||
class OutlineBlockComponentBuilder extends BlockComponentBuilder {
|
class OutlineBlockComponentBuilder extends BlockComponentBuilder {
|
||||||
OutlineBlockComponentBuilder({
|
OutlineBlockComponentBuilder({
|
||||||
this.configuration = const BlockComponentConfiguration(),
|
super.configuration,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
|
||||||
final BlockComponentConfiguration configuration;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BlockComponentWidget build(BlockComponentContext blockComponentContext) {
|
BlockComponentWidget build(BlockComponentContext blockComponentContext) {
|
||||||
final node = blockComponentContext.node;
|
final node = blockComponentContext.node;
|
||||||
|
@ -14,6 +14,7 @@ export 'database/inline_database_menu_item.dart';
|
|||||||
export 'database/referenced_database_menu_item.dart';
|
export 'database/referenced_database_menu_item.dart';
|
||||||
export 'emoji_picker/emoji_menu_item.dart';
|
export 'emoji_picker/emoji_menu_item.dart';
|
||||||
export 'extensions/flowy_tint_extension.dart';
|
export 'extensions/flowy_tint_extension.dart';
|
||||||
|
export 'font/customize_font_toolbar_item.dart';
|
||||||
export 'header/cover_editor_bloc.dart';
|
export 'header/cover_editor_bloc.dart';
|
||||||
export 'header/custom_cover_picker.dart';
|
export 'header/custom_cover_picker.dart';
|
||||||
export 'header/document_header_node_widget.dart';
|
export 'header/document_header_node_widget.dart';
|
||||||
|
@ -55,13 +55,10 @@ SelectionMenuItem toggleListBlockItem = SelectionMenuItem.node(
|
|||||||
|
|
||||||
class ToggleListBlockComponentBuilder extends BlockComponentBuilder {
|
class ToggleListBlockComponentBuilder extends BlockComponentBuilder {
|
||||||
ToggleListBlockComponentBuilder({
|
ToggleListBlockComponentBuilder({
|
||||||
this.configuration = const BlockComponentConfiguration(),
|
super.configuration,
|
||||||
this.padding = const EdgeInsets.all(0),
|
this.padding = const EdgeInsets.all(0),
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
|
||||||
final BlockComponentConfiguration configuration;
|
|
||||||
|
|
||||||
final EdgeInsets padding;
|
final EdgeInsets padding;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -2,6 +2,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/inline_mat
|
|||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/inline_page/inline_page_reference.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/inline_page/inline_page_reference.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
||||||
|
import 'package:appflowy/util/google_font_family_extension.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -193,6 +194,15 @@ class EditorStyleCustomizer {
|
|||||||
return textSpan;
|
return textSpan;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// try to refresh font here.
|
||||||
|
if (attributes.fontFamily != null) {
|
||||||
|
try {
|
||||||
|
GoogleFonts.getFont(attributes.fontFamily!.parseFontFamilyName());
|
||||||
|
} catch (e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// customize the inline mention block, like inline page
|
// customize the inline mention block, like inline page
|
||||||
final mention = attributes[MentionBlockKeys.mention] as Map?;
|
final mention = attributes[MentionBlockKeys.mention] as Map?;
|
||||||
if (mention != null) {
|
if (mention != null) {
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
extension GoogleFontsParser on String {
|
||||||
|
String parseFontFamilyName() {
|
||||||
|
final camelCase = RegExp('(?<=[a-z])[A-Z]');
|
||||||
|
return replaceAll('_regular', '')
|
||||||
|
.replaceAllMapped(camelCase, (m) => ' ${m.group(0)}');
|
||||||
|
}
|
||||||
|
}
|
@ -300,10 +300,6 @@ class AppearanceSettingsState with _$AppearanceSettingsState {
|
|||||||
ThemeData get lightTheme => _getThemeData(Brightness.light);
|
ThemeData get lightTheme => _getThemeData(Brightness.light);
|
||||||
ThemeData get darkTheme => _getThemeData(Brightness.dark);
|
ThemeData get darkTheme => _getThemeData(Brightness.dark);
|
||||||
|
|
||||||
// only support LTR layout in version 0.3.2, enable it in version 0.3.3
|
|
||||||
LayoutDirectionPB get layoutDirectionPB => LayoutDirectionPB.LTRLayout;
|
|
||||||
TextDirectionPB get textDirectionPB => TextDirectionPB.LTR;
|
|
||||||
|
|
||||||
ThemeData _getThemeData(Brightness brightness) {
|
ThemeData _getThemeData(Brightness brightness) {
|
||||||
// Poppins and SF Mono are not well supported in some languages, so use the
|
// Poppins and SF Mono are not well supported in some languages, so use the
|
||||||
// built-in font for the following languages.
|
// built-in font for the following languages.
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
||||||
|
import 'package:appflowy/util/google_font_family_extension.dart';
|
||||||
import 'package:appflowy/workspace/application/appearance.dart';
|
import 'package:appflowy/workspace/application/appearance.dart';
|
||||||
import 'package:appflowy/workspace/application/appearance_defaults.dart';
|
import 'package:appflowy/workspace/application/appearance_defaults.dart';
|
||||||
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
@ -29,9 +31,6 @@ class ThemeFontFamilySetting extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ThemeFontFamilySettingState extends State<ThemeFontFamilySetting> {
|
class _ThemeFontFamilySettingState extends State<ThemeFontFamilySetting> {
|
||||||
final List<String> availableFonts = GoogleFonts.asMap().keys.toList();
|
|
||||||
final ValueNotifier<String> query = ValueNotifier('');
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ThemeSettingEntryTemplateWidget(
|
return ThemeSettingEntryTemplateWidget(
|
||||||
@ -44,61 +43,101 @@ class _ThemeFontFamilySettingState extends State<ThemeFontFamilySetting> {
|
|||||||
.syncFontFamily(DefaultAppearanceSettings.kDefaultFontFamily);
|
.syncFontFamily(DefaultAppearanceSettings.kDefaultFontFamily);
|
||||||
},
|
},
|
||||||
trailing: [
|
trailing: [
|
||||||
ThemeValueDropDown(
|
FontFamilyDropDown(
|
||||||
popoverKey: ThemeFontFamilySetting.popoverKey,
|
currentFontFamily: widget.currentFontFamily,
|
||||||
currentValue: parseFontFamilyName(widget.currentFontFamily),
|
)
|
||||||
onClose: () {
|
],
|
||||||
query.value = '';
|
);
|
||||||
},
|
}
|
||||||
popupBuilder: (_) => CustomScrollView(
|
}
|
||||||
shrinkWrap: true,
|
|
||||||
slivers: [
|
class FontFamilyDropDown extends StatefulWidget {
|
||||||
SliverPadding(
|
const FontFamilyDropDown({
|
||||||
padding: const EdgeInsets.only(right: 8),
|
super.key,
|
||||||
sliver: SliverToBoxAdapter(
|
required this.currentFontFamily,
|
||||||
child: FlowyTextField(
|
this.onOpen,
|
||||||
key: ThemeFontFamilySetting.textFieldKey,
|
this.onClose,
|
||||||
hintText:
|
this.onFontFamilyChanged,
|
||||||
LocaleKeys.settings_appearance_fontFamily_search.tr(),
|
this.child,
|
||||||
autoFocus: false,
|
this.popoverController,
|
||||||
debounceDuration: const Duration(milliseconds: 300),
|
});
|
||||||
onChanged: (value) {
|
|
||||||
query.value = value;
|
final String currentFontFamily;
|
||||||
},
|
final VoidCallback? onOpen;
|
||||||
),
|
final VoidCallback? onClose;
|
||||||
|
final void Function(String fontFamily)? onFontFamilyChanged;
|
||||||
|
final Widget? child;
|
||||||
|
final PopoverController? popoverController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<FontFamilyDropDown> createState() => _FontFamilyDropDownState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FontFamilyDropDownState extends State<FontFamilyDropDown> {
|
||||||
|
final List<String> availableFonts = GoogleFonts.asMap().keys.toList();
|
||||||
|
final ValueNotifier<String> query = ValueNotifier('');
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ThemeValueDropDown(
|
||||||
|
popoverKey: ThemeFontFamilySetting.popoverKey,
|
||||||
|
popoverController: widget.popoverController,
|
||||||
|
currentValue: parseFontFamilyName(widget.currentFontFamily),
|
||||||
|
onClose: () {
|
||||||
|
query.value = '';
|
||||||
|
widget.onClose?.call();
|
||||||
|
},
|
||||||
|
child: widget.child,
|
||||||
|
popupBuilder: (_) {
|
||||||
|
widget.onOpen?.call();
|
||||||
|
return CustomScrollView(
|
||||||
|
shrinkWrap: true,
|
||||||
|
slivers: [
|
||||||
|
SliverPadding(
|
||||||
|
padding: const EdgeInsets.only(right: 8),
|
||||||
|
sliver: SliverToBoxAdapter(
|
||||||
|
child: FlowyTextField(
|
||||||
|
key: ThemeFontFamilySetting.textFieldKey,
|
||||||
|
hintText:
|
||||||
|
LocaleKeys.settings_appearance_fontFamily_search.tr(),
|
||||||
|
autoFocus: false,
|
||||||
|
debounceDuration: const Duration(milliseconds: 300),
|
||||||
|
onChanged: (value) {
|
||||||
|
query.value = value;
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SliverToBoxAdapter(
|
),
|
||||||
child: SizedBox(height: 4),
|
const SliverToBoxAdapter(
|
||||||
),
|
child: SizedBox(height: 4),
|
||||||
ValueListenableBuilder(
|
),
|
||||||
valueListenable: query,
|
ValueListenableBuilder(
|
||||||
builder: (context, value, child) {
|
valueListenable: query,
|
||||||
var displayed = availableFonts;
|
builder: (context, value, child) {
|
||||||
if (value.isNotEmpty) {
|
var displayed = availableFonts;
|
||||||
displayed = availableFonts
|
if (value.isNotEmpty) {
|
||||||
.where(
|
displayed = availableFonts
|
||||||
(font) => font
|
.where(
|
||||||
.toLowerCase()
|
(font) => font
|
||||||
.contains(value.toLowerCase().toString()),
|
.toLowerCase()
|
||||||
)
|
.contains(value.toLowerCase().toString()),
|
||||||
.sorted((a, b) => levenshtein(a, b))
|
)
|
||||||
.toList();
|
.sorted((a, b) => levenshtein(a, b))
|
||||||
}
|
.toList();
|
||||||
return SliverFixedExtentList.builder(
|
}
|
||||||
itemBuilder: (context, index) => _fontFamilyItemButton(
|
return SliverFixedExtentList.builder(
|
||||||
context,
|
itemBuilder: (context, index) => _fontFamilyItemButton(
|
||||||
GoogleFonts.getFont(displayed[index]),
|
context,
|
||||||
),
|
GoogleFonts.getFont(displayed[index]),
|
||||||
itemCount: displayed.length,
|
),
|
||||||
itemExtent: 32,
|
itemCount: displayed.length,
|
||||||
);
|
itemExtent: 32,
|
||||||
},
|
);
|
||||||
),
|
},
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
);
|
||||||
],
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,14 +167,17 @@ class _ThemeFontFamilySettingState extends State<ThemeFontFamilySetting> {
|
|||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (parseFontFamilyName(widget.currentFontFamily) !=
|
if (widget.onFontFamilyChanged != null) {
|
||||||
buttonFontFamily) {
|
widget.onFontFamilyChanged!(style.fontFamily!);
|
||||||
context
|
} else {
|
||||||
.read<AppearanceSettingsCubit>()
|
final fontFamily = style.fontFamily!.parseFontFamilyName();
|
||||||
.setFontFamily(parseFontFamilyName(style.fontFamily!));
|
if (parseFontFamilyName(widget.currentFontFamily) !=
|
||||||
context
|
buttonFontFamily) {
|
||||||
.read<DocumentAppearanceCubit>()
|
context.read<AppearanceSettingsCubit>().setFontFamily(fontFamily);
|
||||||
.syncFontFamily(parseFontFamilyName(style.fontFamily!));
|
context
|
||||||
|
.read<DocumentAppearanceCubit>()
|
||||||
|
.syncFontFamily(fontFamily);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -31,6 +31,7 @@ class ThemeSettingEntryTemplateWidget extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
FlowyText.medium(
|
FlowyText.medium(
|
||||||
label,
|
label,
|
||||||
|
fontSize: 14,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
if (hint != null)
|
if (hint != null)
|
||||||
@ -71,12 +72,16 @@ class ThemeValueDropDown extends StatefulWidget {
|
|||||||
required this.popupBuilder,
|
required this.popupBuilder,
|
||||||
this.popoverKey,
|
this.popoverKey,
|
||||||
this.onClose,
|
this.onClose,
|
||||||
|
this.child,
|
||||||
|
this.popoverController,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String currentValue;
|
final String currentValue;
|
||||||
final Key? popoverKey;
|
final Key? popoverKey;
|
||||||
final Widget Function(BuildContext) popupBuilder;
|
final Widget Function(BuildContext) popupBuilder;
|
||||||
final void Function()? onClose;
|
final void Function()? onClose;
|
||||||
|
final Widget? child;
|
||||||
|
final PopoverController? popoverController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ThemeValueDropDown> createState() => _ThemeValueDropDownState();
|
State<ThemeValueDropDown> createState() => _ThemeValueDropDownState();
|
||||||
@ -87,6 +92,7 @@ class _ThemeValueDropDownState extends State<ThemeValueDropDown> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AppFlowyPopover(
|
return AppFlowyPopover(
|
||||||
key: widget.popoverKey,
|
key: widget.popoverKey,
|
||||||
|
controller: widget.popoverController,
|
||||||
direction: PopoverDirection.bottomWithRightAligned,
|
direction: PopoverDirection.bottomWithRightAligned,
|
||||||
popupBuilder: widget.popupBuilder,
|
popupBuilder: widget.popupBuilder,
|
||||||
constraints: const BoxConstraints(
|
constraints: const BoxConstraints(
|
||||||
@ -95,11 +101,12 @@ class _ThemeValueDropDownState extends State<ThemeValueDropDown> {
|
|||||||
maxHeight: 400,
|
maxHeight: 400,
|
||||||
),
|
),
|
||||||
onClose: widget.onClose,
|
onClose: widget.onClose,
|
||||||
child: FlowyTextButton(
|
child: widget.child ??
|
||||||
widget.currentValue,
|
FlowyTextButton(
|
||||||
fontColor: Theme.of(context).colorScheme.onBackground,
|
widget.currentValue,
|
||||||
fillColor: Colors.transparent,
|
fontColor: Theme.of(context).colorScheme.onBackground,
|
||||||
),
|
fillColor: Colors.transparent,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,23 +19,25 @@ class SettingsAppearanceView extends StatelessWidget {
|
|||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
BrightnessSetting(
|
|
||||||
currentThemeMode: state.themeMode,
|
|
||||||
),
|
|
||||||
ColorSchemeSetting(
|
ColorSchemeSetting(
|
||||||
currentTheme: state.appTheme.themeName,
|
currentTheme: state.appTheme.themeName,
|
||||||
bloc: context.read<DynamicPluginBloc>(),
|
bloc: context.read<DynamicPluginBloc>(),
|
||||||
),
|
),
|
||||||
|
BrightnessSetting(
|
||||||
|
currentThemeMode: state.themeMode,
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
ThemeFontFamilySetting(
|
ThemeFontFamilySetting(
|
||||||
currentFontFamily: state.font,
|
currentFontFamily: state.font,
|
||||||
),
|
),
|
||||||
// TODO: enablt them in version 0.3.3
|
const Divider(),
|
||||||
// LayoutDirectionSetting(
|
LayoutDirectionSetting(
|
||||||
// currentLayoutDirection: state.layoutDirection,
|
currentLayoutDirection: state.layoutDirection,
|
||||||
// ),
|
),
|
||||||
// TextDirectionSetting(
|
TextDirectionSetting(
|
||||||
// currentTextDirection: state.textDirection,
|
currentTextDirection: state.textDirection,
|
||||||
// ),
|
),
|
||||||
|
const Divider(),
|
||||||
CreateFileSettings(),
|
CreateFileSettings(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -54,11 +54,11 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: "4a87ec4"
|
ref: a0ff609
|
||||||
resolved-ref: "4a87ec4bd440344b8f51dd61ab84e2c68d4196d2"
|
resolved-ref: a0ff609cb1ac53e5d167489f43452074860dd80e
|
||||||
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
||||||
source: git
|
source: git
|
||||||
version: "1.3.0"
|
version: "1.4.0"
|
||||||
appflowy_popover:
|
appflowy_popover:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -47,7 +47,7 @@ dependencies:
|
|||||||
appflowy_editor:
|
appflowy_editor:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
||||||
ref: 4a87ec4
|
ref: a0ff609
|
||||||
appflowy_popover:
|
appflowy_popover:
|
||||||
path: packages/appflowy_popover
|
path: packages/appflowy_popover
|
||||||
|
|
||||||
|
3
frontend/resources/flowy_icons/16x/font_family.svg
Normal file
3
frontend/resources/flowy_icons/16x/font_family.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24">
|
||||||
|
<path d="M420-160v-520H200v-120h560v120H540v520H420Z" fill="white" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 167 B |
@ -265,13 +265,13 @@
|
|||||||
},
|
},
|
||||||
"layoutDirection": {
|
"layoutDirection": {
|
||||||
"label": "Layout Direction",
|
"label": "Layout Direction",
|
||||||
"hint": "To start aligning elements from left or right of the screen.",
|
"hint": "Control the flow of content on your screen, from left to right or right to left.",
|
||||||
"ltr": "LTR",
|
"ltr": "LTR",
|
||||||
"rtl": "RTL"
|
"rtl": "RTL"
|
||||||
},
|
},
|
||||||
"textDirection": {
|
"textDirection": {
|
||||||
"label": "Default text direction",
|
"label": "Default text direction",
|
||||||
"hint": "Default text direction when the text direction is not set on the element.",
|
"hint": "Specify whether text should start from left or right as the default.",
|
||||||
"ltr": "LTR",
|
"ltr": "LTR",
|
||||||
"rtl": "RTL",
|
"rtl": "RTL",
|
||||||
"auto": "AUTO",
|
"auto": "AUTO",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user