mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge branch 'main' into workspace-rename-icon
This commit is contained in:
commit
891543653d
@ -1,4 +1,3 @@
|
|||||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:integration_test/integration_test.dart';
|
import 'package:integration_test/integration_test.dart';
|
||||||
@ -10,7 +9,7 @@ void main() {
|
|||||||
|
|
||||||
group('text direction', () {
|
group('text direction', () {
|
||||||
testWidgets(
|
testWidgets(
|
||||||
'''no text direction items will be displayed in the default/LTR mode,and three text direction items will be displayed in the RTL mode.''',
|
'''no text direction items will be displayed in the default/LTR mode, and three text direction items will be displayed when toggle is enabled.''',
|
||||||
(tester) async {
|
(tester) async {
|
||||||
// combine the two tests into one to avoid the time-consuming process of initializing the app
|
// combine the two tests into one to avoid the time-consuming process of initializing the app
|
||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
@ -32,7 +31,7 @@ void main() {
|
|||||||
'toolbar/text_direction_ltr',
|
'toolbar/text_direction_ltr',
|
||||||
'toolbar/text_direction_rtl',
|
'toolbar/text_direction_rtl',
|
||||||
];
|
];
|
||||||
// no text direction items in default/LTR mode
|
// no text direction items by default
|
||||||
var button = find.byWidgetPredicate(
|
var button = find.byWidgetPredicate(
|
||||||
(widget) =>
|
(widget) =>
|
||||||
widget is SVGIconItemWidget &&
|
widget is SVGIconItemWidget &&
|
||||||
@ -41,7 +40,7 @@ void main() {
|
|||||||
expect(button, findsNothing);
|
expect(button, findsNothing);
|
||||||
|
|
||||||
// switch to the RTL mode
|
// switch to the RTL mode
|
||||||
await tester.switchLayoutDirectionMode(LayoutDirection.rtlLayout);
|
await tester.toggleEnableRTLToolbarItems();
|
||||||
|
|
||||||
await tester.editor.tapLineOfEditorAt(0);
|
await tester.editor.tapLineOfEditorAt(0);
|
||||||
await tester.editor.updateSelection(selection);
|
await tester.editor.updateSelection(selection);
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
|
||||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_user.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_user.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart';
|
import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/direction_setting.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu_element.dart';
|
import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu_element.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_user_view.dart';
|
import 'package:appflowy/workspace/presentation/settings/widgets/settings_user_view.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
import 'base.dart';
|
import 'base.dart';
|
||||||
@ -76,31 +75,15 @@ extension AppFlowySettings on WidgetTester {
|
|||||||
await pumpAndSettle();
|
await pumpAndSettle();
|
||||||
}
|
}
|
||||||
|
|
||||||
// go to settings page and switch the layout direction
|
// go to settings page and toggle enable RTL toolbar items
|
||||||
Future<void> switchLayoutDirectionMode(
|
Future<void> toggleEnableRTLToolbarItems() async {
|
||||||
LayoutDirection layoutDirection,
|
|
||||||
) async {
|
|
||||||
await openSettings();
|
await openSettings();
|
||||||
await openSettingsPage(SettingsPage.appearance);
|
await openSettingsPage(SettingsPage.appearance);
|
||||||
|
|
||||||
final button = find.byKey(const ValueKey('layout_direction_option_button'));
|
final switchButton =
|
||||||
expect(button, findsOneWidget);
|
find.byKey(EnableRTLToolbarItemsSetting.enableRTLSwitchKey);
|
||||||
await tapButton(button);
|
expect(switchButton, findsOneWidget);
|
||||||
|
await tapButton(switchButton);
|
||||||
switch (layoutDirection) {
|
|
||||||
case LayoutDirection.ltrLayout:
|
|
||||||
final ltrButton = find.text(
|
|
||||||
LocaleKeys.settings_appearance_layoutDirection_ltr.tr(),
|
|
||||||
);
|
|
||||||
await tapButton(ltrButton);
|
|
||||||
break;
|
|
||||||
case LayoutDirection.rtlLayout:
|
|
||||||
final rtlButton = find.text(
|
|
||||||
LocaleKeys.settings_appearance_layoutDirection_rtl.tr(),
|
|
||||||
);
|
|
||||||
await tapButton(rtlButton);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// tap anywhere to close the settings page
|
// tap anywhere to close the settings page
|
||||||
await tapAt(Offset.zero);
|
await tapAt(Offset.zero);
|
||||||
|
@ -127,11 +127,13 @@ Future<T?> showMobileBottomSheet<T>(
|
|||||||
children: [
|
children: [
|
||||||
...children,
|
...children,
|
||||||
Expanded(
|
Expanded(
|
||||||
|
child: Scrollbar(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
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/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||||
|
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart';
|
||||||
import 'package:appflowy/plugins/database/board/application/board_bloc.dart';
|
import 'package:appflowy/plugins/database/board/application/board_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/header/field_type_extension.dart';
|
import 'package:appflowy/plugins/database/grid/presentation/widgets/header/field_type_extension.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
|
||||||
@ -8,7 +11,6 @@ import 'package:appflowy_backend/protobuf/flowy-database2/group.pb.dart';
|
|||||||
import 'package:appflowy_board/appflowy_board.dart';
|
import 'package:appflowy_board/appflowy_board.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';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
@ -107,16 +109,18 @@ class _GroupCardHeaderState extends State<GroupCardHeader> {
|
|||||||
splashRadius: 5,
|
splashRadius: 5,
|
||||||
onPressed: () => showMobileBottomSheet(
|
onPressed: () => showMobileBottomSheet(
|
||||||
context,
|
context,
|
||||||
title: LocaleKeys.board_column_groupActions.tr(),
|
showDragHandle: true,
|
||||||
showHeader: true,
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||||
showCloseButton: true,
|
builder: (_) => SeparatedColumn(
|
||||||
builder: (_) {
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
return Row(
|
separatorBuilder: () => const Divider(
|
||||||
|
height: 8.5,
|
||||||
|
thickness: 0.5,
|
||||||
|
),
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
MobileQuickActionButton(
|
||||||
child: BottomSheetActionWidget(
|
|
||||||
svg: FlowySvgs.edit_s,
|
|
||||||
text: LocaleKeys.board_column_renameColumn.tr(),
|
text: LocaleKeys.board_column_renameColumn.tr(),
|
||||||
|
icon: FlowySvgs.edit_s,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.read<BoardBloc>().add(
|
context.read<BoardBloc>().add(
|
||||||
BoardEvent.startEditingHeader(
|
BoardEvent.startEditingHeader(
|
||||||
@ -126,12 +130,9 @@ class _GroupCardHeaderState extends State<GroupCardHeader> {
|
|||||||
context.pop();
|
context.pop();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
MobileQuickActionButton(
|
||||||
const HSpace(8),
|
|
||||||
Expanded(
|
|
||||||
child: BottomSheetActionWidget(
|
|
||||||
svg: FlowySvgs.hide_s,
|
|
||||||
text: LocaleKeys.board_column_hideColumn.tr(),
|
text: LocaleKeys.board_column_hideColumn.tr(),
|
||||||
|
icon: FlowySvgs.hide_s,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.read<BoardBloc>().add(
|
context.read<BoardBloc>().add(
|
||||||
BoardEvent.toggleGroupVisibility(
|
BoardEvent.toggleGroupVisibility(
|
||||||
@ -143,10 +144,8 @@ class _GroupCardHeaderState extends State<GroupCardHeader> {
|
|||||||
context.pop();
|
context.pop();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
),
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:appflowy/env/cloud_env.dart';
|
import 'package:appflowy/env/cloud_env.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||||
@ -5,10 +7,10 @@ import 'package:appflowy/startup/startup.dart';
|
|||||||
import 'package:appflowy/workspace/application/user/prelude.dart';
|
import 'package:appflowy/workspace/application/user/prelude.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
import '../widgets/widgets.dart';
|
import '../widgets/widgets.dart';
|
||||||
|
|
||||||
import 'personal_info.dart';
|
import 'personal_info.dart';
|
||||||
|
|
||||||
class PersonalInfoSettingGroup extends StatelessWidget {
|
class PersonalInfoSettingGroup extends StatelessWidget {
|
||||||
@ -34,7 +36,7 @@ class PersonalInfoSettingGroup extends StatelessWidget {
|
|||||||
settingItemList: [
|
settingItemList: [
|
||||||
MobileSettingItem(
|
MobileSettingItem(
|
||||||
name: userName,
|
name: userName,
|
||||||
subtitle: isAuthEnabled
|
subtitle: isAuthEnabled && userProfile.email.isNotEmpty
|
||||||
? Text(
|
? Text(
|
||||||
userProfile.email,
|
userProfile.email,
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
@ -7,16 +7,16 @@ class CalendarSize {
|
|||||||
static double get headerContainerPadding => 12 * scale;
|
static double get headerContainerPadding => 12 * scale;
|
||||||
|
|
||||||
static EdgeInsets get contentInsets => EdgeInsets.fromLTRB(
|
static EdgeInsets get contentInsets => EdgeInsets.fromLTRB(
|
||||||
GridSize.leadingHeaderPadding,
|
GridSize.horizontalHeaderPadding,
|
||||||
CalendarSize.headerContainerPadding,
|
CalendarSize.headerContainerPadding,
|
||||||
GridSize.leadingHeaderPadding,
|
GridSize.horizontalHeaderPadding,
|
||||||
CalendarSize.headerContainerPadding,
|
CalendarSize.headerContainerPadding,
|
||||||
);
|
);
|
||||||
|
|
||||||
static EdgeInsets get contentInsetsMobile => EdgeInsets.fromLTRB(
|
static EdgeInsets get contentInsetsMobile => EdgeInsets.fromLTRB(
|
||||||
GridSize.leadingHeaderPadding / 2,
|
GridSize.horizontalHeaderPadding / 2,
|
||||||
0,
|
0,
|
||||||
GridSize.leadingHeaderPadding / 2,
|
GridSize.horizontalHeaderPadding / 2,
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ class GridLayout {
|
|||||||
.reduce((value, element) => value + element);
|
.reduce((value, element) => value + element);
|
||||||
|
|
||||||
return fieldsWidth +
|
return fieldsWidth +
|
||||||
GridSize.leadingHeaderPadding +
|
GridSize.horizontalHeaderPadding +
|
||||||
GridSize.trailHeaderPadding;
|
GridSize.trailHeaderPadding;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,19 +7,14 @@ class GridSize {
|
|||||||
static double get scrollBarSize => 8 * scale;
|
static double get scrollBarSize => 8 * scale;
|
||||||
static double get headerHeight => 40 * scale;
|
static double get headerHeight => 40 * scale;
|
||||||
static double get footerHeight => 40 * scale;
|
static double get footerHeight => 40 * scale;
|
||||||
static double get leadingHeaderPadding =>
|
static double get horizontalHeaderPadding =>
|
||||||
PlatformExtension.isDesktop ? 40 * scale : 20 * scale;
|
PlatformExtension.isDesktop ? 40 * scale : 16 * scale;
|
||||||
static double get trailHeaderPadding => 140 * scale;
|
static double get trailHeaderPadding => 140 * scale;
|
||||||
static double get headerContainerPadding => 0 * scale;
|
|
||||||
static double get cellHPadding => 10 * scale;
|
static double get cellHPadding => 10 * scale;
|
||||||
static double get cellVPadding => 10 * scale;
|
static double get cellVPadding => 10 * scale;
|
||||||
static double get popoverItemHeight => 26 * scale;
|
static double get popoverItemHeight => 26 * scale;
|
||||||
static double get typeOptionSeparatorHeight => 4 * scale;
|
static double get typeOptionSeparatorHeight => 4 * scale;
|
||||||
|
|
||||||
static EdgeInsets get headerContentInsets => EdgeInsets.symmetric(
|
|
||||||
horizontal: GridSize.headerContainerPadding,
|
|
||||||
vertical: GridSize.headerContainerPadding,
|
|
||||||
);
|
|
||||||
static EdgeInsets get cellContentInsets => EdgeInsets.symmetric(
|
static EdgeInsets get cellContentInsets => EdgeInsets.symmetric(
|
||||||
horizontal: GridSize.cellHPadding,
|
horizontal: GridSize.cellHPadding,
|
||||||
vertical: GridSize.cellVPadding,
|
vertical: GridSize.cellVPadding,
|
||||||
@ -36,18 +31,13 @@ class GridSize {
|
|||||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 2);
|
const EdgeInsets.symmetric(horizontal: 8, vertical: 2);
|
||||||
|
|
||||||
static EdgeInsets get footerContentInsets => EdgeInsets.fromLTRB(
|
static EdgeInsets get footerContentInsets => EdgeInsets.fromLTRB(
|
||||||
GridSize.leadingHeaderPadding,
|
GridSize.horizontalHeaderPadding,
|
||||||
GridSize.headerContainerPadding,
|
0,
|
||||||
PlatformExtension.isMobile
|
PlatformExtension.isMobile ? GridSize.horizontalHeaderPadding : 0,
|
||||||
? GridSize.leadingHeaderPadding
|
PlatformExtension.isMobile ? 100 : 0,
|
||||||
: GridSize.headerContainerPadding,
|
|
||||||
PlatformExtension.isMobile ? 100 : GridSize.headerContainerPadding,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
static EdgeInsets get contentInsets => EdgeInsets.fromLTRB(
|
static EdgeInsets get contentInsets => EdgeInsets.symmetric(
|
||||||
GridSize.leadingHeaderPadding,
|
horizontal: GridSize.horizontalHeaderPadding,
|
||||||
GridSize.headerContainerPadding,
|
|
||||||
GridSize.leadingHeaderPadding,
|
|
||||||
GridSize.headerContainerPadding,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -157,12 +157,14 @@ class _GridPageContentState extends State<GridPageContent> {
|
|||||||
final _scrollController = GridScrollController(
|
final _scrollController = GridScrollController(
|
||||||
scrollGroupController: LinkedScrollControllerGroup(),
|
scrollGroupController: LinkedScrollControllerGroup(),
|
||||||
);
|
);
|
||||||
late final ScrollController headerScrollController;
|
late final ScrollController contentScrollController;
|
||||||
|
late final ScrollController reorderableController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
headerScrollController = _scrollController.linkHorizontalController();
|
contentScrollController = _scrollController.linkHorizontalController();
|
||||||
|
reorderableController = _scrollController.linkHorizontalController();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -196,7 +198,8 @@ class _GridPageContentState extends State<GridPageContent> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_GridHeader(
|
_GridHeader(
|
||||||
headerScrollController: headerScrollController,
|
contentScrollController: contentScrollController,
|
||||||
|
reorderableController: reorderableController,
|
||||||
),
|
),
|
||||||
_GridRows(
|
_GridRows(
|
||||||
viewId: widget.view.id,
|
viewId: widget.view.id,
|
||||||
@ -205,8 +208,8 @@ class _GridPageContentState extends State<GridPageContent> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 20,
|
bottom: 16,
|
||||||
right: 20,
|
right: 16,
|
||||||
child: getGridFabs(context),
|
child: getGridFabs(context),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -216,9 +219,13 @@ class _GridPageContentState extends State<GridPageContent> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _GridHeader extends StatelessWidget {
|
class _GridHeader extends StatelessWidget {
|
||||||
const _GridHeader({required this.headerScrollController});
|
const _GridHeader({
|
||||||
|
required this.contentScrollController,
|
||||||
|
required this.reorderableController,
|
||||||
|
});
|
||||||
|
|
||||||
final ScrollController headerScrollController;
|
final ScrollController contentScrollController;
|
||||||
|
final ScrollController reorderableController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -226,7 +233,8 @@ class _GridHeader extends StatelessWidget {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return MobileGridHeader(
|
return MobileGridHeader(
|
||||||
viewId: state.viewId,
|
viewId: state.viewId,
|
||||||
anchorScrollController: headerScrollController,
|
contentScrollController: contentScrollController,
|
||||||
|
reorderableController: reorderableController,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -247,7 +255,7 @@ class _GridRows extends StatelessWidget {
|
|||||||
return BlocBuilder<GridBloc, GridState>(
|
return BlocBuilder<GridBloc, GridState>(
|
||||||
buildWhen: (previous, current) => previous.fields != current.fields,
|
buildWhen: (previous, current) => previous.fields != current.fields,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final double contentWidth = _getContentWidth(state.fields);
|
final double contentWidth = getMobileGridContentWidth(state.fields);
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: _WrapScrollView(
|
child: _WrapScrollView(
|
||||||
scrollController: scrollController,
|
scrollController: scrollController,
|
||||||
@ -277,14 +285,6 @@ class _GridRows extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
double _getContentWidth(List<FieldInfo> fields) {
|
|
||||||
final visibleFields = fields.where(
|
|
||||||
(field) =>
|
|
||||||
field.fieldSettings?.visibility != FieldVisibility.AlwaysHidden,
|
|
||||||
);
|
|
||||||
return (visibleFields.length + 1) * 200 + GridSize.leadingHeaderPadding * 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _renderList(
|
Widget _renderList(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
GridState state,
|
GridState state,
|
||||||
@ -438,3 +438,11 @@ class _AddRowButton extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double getMobileGridContentWidth(List<FieldInfo> fields) {
|
||||||
|
final visibleFields = fields.where(
|
||||||
|
(field) => field.fieldSettings?.visibility != FieldVisibility.AlwaysHidden,
|
||||||
|
);
|
||||||
|
return (visibleFields.length + 1) * 200 +
|
||||||
|
GridSize.horizontalHeaderPadding * 2;
|
||||||
|
}
|
||||||
|
@ -139,7 +139,7 @@ class _GridHeaderState extends State<_GridHeader> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _cellLeading() {
|
Widget _cellLeading() {
|
||||||
return SizedBox(width: GridSize.leadingHeaderPadding);
|
return SizedBox(width: GridSize.horizontalHeaderPadding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,7 +158,6 @@ class _CellTrailing extends StatelessWidget {
|
|||||||
bottom: BorderSide(color: Theme.of(context).dividerColor),
|
bottom: BorderSide(color: Theme.of(context).dividerColor),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
padding: GridSize.headerContentInsets,
|
|
||||||
child: CreateFieldButton(
|
child: CreateFieldButton(
|
||||||
viewId: viewId,
|
viewId: viewId,
|
||||||
onFieldCreated: (fieldId) => context
|
onFieldCreated: (fieldId) => context
|
||||||
|
@ -13,17 +13,22 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
import '../../layout/sizes.dart';
|
import '../../layout/sizes.dart';
|
||||||
|
import '../../mobile_grid_page.dart';
|
||||||
import 'mobile_field_button.dart';
|
import 'mobile_field_button.dart';
|
||||||
|
|
||||||
|
const double _kGridHeaderHeight = 50.0;
|
||||||
|
|
||||||
class MobileGridHeader extends StatefulWidget {
|
class MobileGridHeader extends StatefulWidget {
|
||||||
const MobileGridHeader({
|
const MobileGridHeader({
|
||||||
super.key,
|
super.key,
|
||||||
required this.viewId,
|
required this.viewId,
|
||||||
required this.anchorScrollController,
|
required this.contentScrollController,
|
||||||
|
required this.reorderableController,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String viewId;
|
final String viewId;
|
||||||
final ScrollController anchorScrollController;
|
final ScrollController contentScrollController;
|
||||||
|
final ScrollController reorderableController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<MobileGridHeader> createState() => _MobileGridHeaderState();
|
State<MobileGridHeader> createState() => _MobileGridHeaderState();
|
||||||
@ -41,30 +46,46 @@ class _MobileGridHeaderState extends State<MobileGridHeader> {
|
|||||||
fieldController: fieldController,
|
fieldController: fieldController,
|
||||||
)..add(const GridHeaderEvent.initial());
|
)..add(const GridHeaderEvent.initial());
|
||||||
},
|
},
|
||||||
child: SingleChildScrollView(
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
BlocBuilder<GridHeaderBloc, GridHeaderState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return SingleChildScrollView(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
controller: widget.anchorScrollController,
|
controller: widget.contentScrollController,
|
||||||
child: Row(
|
child: Stack(
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
children: [
|
||||||
HSpace(GridSize.leadingHeaderPadding),
|
Positioned(
|
||||||
Stack(
|
top: 0,
|
||||||
children: [
|
left: GridSize.horizontalHeaderPadding + 24,
|
||||||
Positioned(top: 0, left: 24, right: 24, child: _divider()),
|
right: GridSize.horizontalHeaderPadding + 24,
|
||||||
Positioned(bottom: 0, left: 0, right: 0, child: _divider()),
|
child: _divider(),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: 0,
|
||||||
|
left: GridSize.horizontalHeaderPadding,
|
||||||
|
right: GridSize.horizontalHeaderPadding,
|
||||||
|
child: _divider(),
|
||||||
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 50,
|
height: _kGridHeaderHeight,
|
||||||
|
width: getMobileGridContentWidth(state.fields),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: _kGridHeaderHeight,
|
||||||
child: _GridHeader(
|
child: _GridHeader(
|
||||||
viewId: widget.viewId,
|
viewId: widget.viewId,
|
||||||
fieldController: fieldController,
|
fieldController: fieldController,
|
||||||
|
scrollController: widget.reorderableController,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const HSpace(20),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,10 +102,12 @@ class _GridHeader extends StatefulWidget {
|
|||||||
const _GridHeader({
|
const _GridHeader({
|
||||||
required this.viewId,
|
required this.viewId,
|
||||||
required this.fieldController,
|
required this.fieldController,
|
||||||
|
required this.scrollController,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String viewId;
|
final String viewId;
|
||||||
final FieldController fieldController;
|
final FieldController fieldController;
|
||||||
|
final ScrollController scrollController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_GridHeader> createState() => _GridHeaderState();
|
State<_GridHeader> createState() => _GridHeaderState();
|
||||||
@ -114,13 +137,16 @@ class _GridHeaderState extends State<_GridHeader> {
|
|||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
return ReorderableListView.builder(
|
return ReorderableListView.builder(
|
||||||
scrollController: ScrollController(),
|
scrollController: widget.scrollController,
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
proxyDecorator: (child, index, anim) => Material(
|
proxyDecorator: (child, index, anim) => Material(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: GridSize.horizontalHeaderPadding,
|
||||||
|
),
|
||||||
header: firstField != null
|
header: firstField != null
|
||||||
? MobileFieldButton.first(
|
? MobileFieldButton.first(
|
||||||
viewId: widget.viewId,
|
viewId: widget.viewId,
|
||||||
|
@ -68,7 +68,7 @@ class _MobileGridRowState extends State<MobileGridRow> {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(width: GridSize.leadingHeaderPadding),
|
SizedBox(width: GridSize.horizontalHeaderPadding),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: RowContent(
|
child: RowContent(
|
||||||
fieldController: widget.databaseController.fieldController,
|
fieldController: widget.databaseController.fieldController,
|
||||||
@ -163,7 +163,6 @@ class RowContent extends StatelessWidget {
|
|||||||
Widget _finalCellDecoration(BuildContext context) {
|
Widget _finalCellDecoration(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
width: 200,
|
width: 200,
|
||||||
padding: GridSize.headerContentInsets,
|
|
||||||
constraints: const BoxConstraints(minHeight: 46),
|
constraints: const BoxConstraints(minHeight: 46),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
|
@ -115,7 +115,7 @@ class _RowLeadingState extends State<_RowLeading> {
|
|||||||
child: Consumer<RegionStateNotifier>(
|
child: Consumer<RegionStateNotifier>(
|
||||||
builder: (context, state, _) {
|
builder: (context, state, _) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: GridSize.leadingHeaderPadding,
|
width: GridSize.horizontalHeaderPadding,
|
||||||
child: state.onEnter ? _activeWidget() : null,
|
child: state.onEnter ? _activeWidget() : null,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -283,7 +283,6 @@ class RowContent extends StatelessWidget {
|
|||||||
cursor: SystemMouseCursors.basic,
|
cursor: SystemMouseCursors.basic,
|
||||||
child: Container(
|
child: Container(
|
||||||
width: GridSize.trailHeaderPadding,
|
width: GridSize.trailHeaderPadding,
|
||||||
padding: GridSize.headerContentInsets,
|
|
||||||
constraints: const BoxConstraints(minHeight: 46),
|
constraints: const BoxConstraints(minHeight: 46),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
|
@ -57,7 +57,7 @@ class _DatabaseViewSettingContent extends StatelessWidget {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
horizontal: GridSize.leadingHeaderPadding,
|
horizontal: GridSize.horizontalHeaderPadding,
|
||||||
),
|
),
|
||||||
child: DecoratedBox(
|
child: DecoratedBox(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
@ -21,8 +22,11 @@ class TabBarHeader extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox(
|
return Container(
|
||||||
height: 30,
|
height: 30,
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: GridSize.horizontalHeaderPadding,
|
||||||
|
),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Positioned(
|
Positioned(
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
|
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/mobile/presentation/bottom_sheet/show_transition_bottom_sheet.dart';
|
import 'package:appflowy/mobile/presentation/bottom_sheet/show_transition_bottom_sheet.dart';
|
||||||
import 'package:appflowy/mobile/presentation/database/view/database_view_list.dart';
|
import 'package:appflowy/mobile/presentation/database/view/database_view_list.dart';
|
||||||
import 'package:appflowy/plugins/base/emoji/emoji_text.dart';
|
import 'package:appflowy/plugins/base/emoji/emoji_text.dart';
|
||||||
import 'package:appflowy/plugins/database/application/tab_bar_bloc.dart';
|
import 'package:appflowy/plugins/database/application/tab_bar_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
|
||||||
import 'package:appflowy/plugins/database/widgets/setting/mobile_database_controls.dart';
|
import 'package:appflowy/plugins/database/widgets/setting/mobile_database_controls.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
import '../../grid/presentation/grid_page.dart';
|
import '../../grid/presentation/grid_page.dart';
|
||||||
@ -26,11 +27,14 @@ class _MobileTabBarHeaderState extends State<MobileTabBarHeader> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(top: 14.0),
|
padding: EdgeInsets.only(
|
||||||
|
left: GridSize.horizontalHeaderPadding,
|
||||||
|
top: 14.0,
|
||||||
|
right: GridSize.horizontalHeaderPadding - 5.0,
|
||||||
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
HSpace(GridSize.leadingHeaderPadding),
|
|
||||||
const _DatabaseViewSelectorButton(),
|
const _DatabaseViewSelectorButton(),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
BlocBuilder<DatabaseTabBarBloc, DatabaseTabBarState>(
|
BlocBuilder<DatabaseTabBarBloc, DatabaseTabBarState>(
|
||||||
|
@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
||||||
import 'package:appflowy/plugins/database/application/tab_bar_bloc.dart';
|
import 'package:appflowy/plugins/database/application/tab_bar_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
|
||||||
import 'package:appflowy/plugins/database/widgets/share_button.dart';
|
import 'package:appflowy/plugins/database/widgets/share_button.dart';
|
||||||
import 'package:appflowy/plugins/util.dart';
|
import 'package:appflowy/plugins/util.dart';
|
||||||
import 'package:appflowy/startup/plugin/plugin.dart';
|
import 'package:appflowy/startup/plugin/plugin.dart';
|
||||||
@ -112,19 +111,9 @@ class _DatabaseTabBarViewState extends State<DatabaseTabBarView> {
|
|||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PlatformExtension.isDesktop) {
|
return PlatformExtension.isDesktop
|
||||||
return Padding(
|
? const TabBarHeader()
|
||||||
padding: EdgeInsets.symmetric(
|
: const MobileTabBarHeader();
|
||||||
horizontal: GridSize.leadingHeaderPadding,
|
|
||||||
),
|
|
||||||
child: const TabBarHeader(),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return const Padding(
|
|
||||||
padding: EdgeInsets.only(right: 8),
|
|
||||||
child: MobileTabBarHeader(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -259,7 +259,9 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
LayoutDirection.rtlLayout;
|
LayoutDirection.rtlLayout;
|
||||||
final textDirection = isRTL ? TextDirection.rtl : TextDirection.ltr;
|
final textDirection = isRTL ? TextDirection.rtl : TextDirection.ltr;
|
||||||
|
|
||||||
_setRTLToolbarItems(isRTL);
|
_setRTLToolbarItems(
|
||||||
|
context.read<AppearanceSettingsCubit>().state.enableRtlToolbarItems,
|
||||||
|
);
|
||||||
|
|
||||||
final editor = Directionality(
|
final editor = Directionality(
|
||||||
textDirection: textDirection,
|
textDirection: textDirection,
|
||||||
@ -412,12 +414,12 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setRTLToolbarItems(bool isRTL) {
|
void _setRTLToolbarItems(bool enableRtlToolbarItems) {
|
||||||
final textDirectionItemIds = textDirectionItems.map((e) => e.id);
|
final textDirectionItemIds = textDirectionItems.map((e) => e.id);
|
||||||
// clear all the text direction items
|
// clear all the text direction items
|
||||||
toolbarItems.removeWhere((item) => textDirectionItemIds.contains(item.id));
|
toolbarItems.removeWhere((item) => textDirectionItemIds.contains(item.id));
|
||||||
// only show the rtl item when the layout direction is ltr.
|
// only show the rtl item when the layout direction is ltr.
|
||||||
if (isRTL) {
|
if (enableRtlToolbarItems) {
|
||||||
toolbarItems.addAll(textDirectionItems);
|
toolbarItems.addAll(textDirectionItems);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_date_block.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_date_block.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
|
||||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/reminder_selector.dart';
|
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/reminder_selector.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
enum MentionType {
|
enum MentionType {
|
||||||
|
@ -141,7 +141,7 @@ class EditorMigration {
|
|||||||
}
|
}
|
||||||
const backgroundColor = 'backgroundColor';
|
const backgroundColor = 'backgroundColor';
|
||||||
if (attributes.containsKey(backgroundColor)) {
|
if (attributes.containsKey(backgroundColor)) {
|
||||||
attributes[AppFlowyRichTextKeys.highlightColor] =
|
attributes[AppFlowyRichTextKeys.backgroundColor] =
|
||||||
attributes[backgroundColor];
|
attributes[backgroundColor];
|
||||||
attributes.remove(backgroundColor);
|
attributes.remove(backgroundColor);
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,9 @@ Future<T?> showEditLinkBottomSheet<T>(
|
|||||||
return showMobileBottomSheet(
|
return showMobileBottomSheet(
|
||||||
context,
|
context,
|
||||||
showHeader: false,
|
showHeader: false,
|
||||||
|
showCloseButton: false,
|
||||||
|
showDragHandle: true,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return MobileBottomSheetEditLinkWidget(
|
return MobileBottomSheetEditLinkWidget(
|
||||||
text: text,
|
text: text,
|
||||||
|
@ -71,7 +71,8 @@ class BIUSItems extends StatelessWidget {
|
|||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
icon: icon,
|
icon: icon,
|
||||||
isSelected: editorState.isTextDecorationSelected(richTextKey),
|
isSelected: editorState.isTextDecorationSelected(richTextKey) &&
|
||||||
|
editorState.toggledStyle[richTextKey] != false,
|
||||||
iconPadding: const EdgeInsets.symmetric(
|
iconPadding: const EdgeInsets.symmetric(
|
||||||
vertical: 14.0,
|
vertical: 14.0,
|
||||||
),
|
),
|
||||||
|
@ -68,7 +68,7 @@ class BlockItems extends StatelessWidget {
|
|||||||
enableTopRightRadius: false,
|
enableTopRightRadius: false,
|
||||||
enableBottomRightRadius: false,
|
enableBottomRightRadius: false,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await editorState.convertBlockType(blockType);
|
await _convert(blockType);
|
||||||
},
|
},
|
||||||
backgroundColor: theme.toolbarMenuItemBackgroundColor,
|
backgroundColor: theme.toolbarMenuItemBackgroundColor,
|
||||||
icon: icon,
|
icon: icon,
|
||||||
@ -196,4 +196,23 @@ class BlockItems extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
editorState.service.keyboardService?.closeKeyboard();
|
editorState.service.keyboardService?.closeKeyboard();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _convert(String blockType) async {
|
||||||
|
await editorState.convertBlockType(
|
||||||
|
blockType,
|
||||||
|
selectionExtraInfo: {
|
||||||
|
selectionExtraInfoDoNotAttachTextService: true,
|
||||||
|
selectionExtraInfoDisableFloatingToolbar: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
unawaited(
|
||||||
|
editorState.updateSelectionWithReason(
|
||||||
|
editorState.selection,
|
||||||
|
extraInfo: {
|
||||||
|
selectionExtraInfoDisableFloatingToolbar: true,
|
||||||
|
selectionExtraInfoDoNotAttachTextService: true,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,8 @@ class ColorItem extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = ToolbarColorExtension.of(context);
|
final theme = ToolbarColorExtension.of(context);
|
||||||
|
final selectedBackgroundColor = _getBackgroundColor(context);
|
||||||
|
|
||||||
return MobileToolbarMenuItemWrapper(
|
return MobileToolbarMenuItemWrapper(
|
||||||
size: const Size(82, 52),
|
size: const Size(82, 52),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
@ -42,10 +44,11 @@ class ColorItem extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
icon: FlowySvgs.m_aa_color_s,
|
icon: FlowySvgs.m_aa_color_s,
|
||||||
backgroundColor: theme.toolbarMenuItemBackgroundColor,
|
backgroundColor:
|
||||||
isSelected: false,
|
selectedBackgroundColor ?? theme.toolbarMenuItemBackgroundColor,
|
||||||
|
selectedBackgroundColor: selectedBackgroundColor,
|
||||||
|
isSelected: selectedBackgroundColor != null,
|
||||||
showRightArrow: true,
|
showRightArrow: true,
|
||||||
enable: editorState.selection?.isCollapsed == false,
|
|
||||||
iconPadding: const EdgeInsets.only(
|
iconPadding: const EdgeInsets.only(
|
||||||
top: 14.0,
|
top: 14.0,
|
||||||
bottom: 14.0,
|
bottom: 14.0,
|
||||||
@ -53,4 +56,33 @@ class ColorItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Color? _getBackgroundColor(BuildContext context) {
|
||||||
|
final selection = editorState.selection;
|
||||||
|
if (selection == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String? backgroundColor =
|
||||||
|
editorState.toggledStyle[AppFlowyRichTextKeys.backgroundColor];
|
||||||
|
if (backgroundColor == null) {
|
||||||
|
if (selection.isCollapsed && selection.startIndex != 0) {
|
||||||
|
backgroundColor = editorState.getDeltaAttributeValueInSelection<String>(
|
||||||
|
AppFlowyRichTextKeys.backgroundColor,
|
||||||
|
selection.copyWith(
|
||||||
|
start: selection.start.copyWith(
|
||||||
|
offset: selection.startIndex - 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
backgroundColor = editorState.getDeltaAttributeValueInSelection<String>(
|
||||||
|
AppFlowyRichTextKeys.backgroundColor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (backgroundColor != null && int.tryParse(backgroundColor) != null) {
|
||||||
|
return Color(int.parse(backgroundColor));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,16 +63,9 @@ class _TextColorAndBackgroundColorState
|
|||||||
extends State<_TextColorAndBackgroundColor> {
|
extends State<_TextColorAndBackgroundColor> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final String? selectedTextColor =
|
final String? selectedTextColor = _getColor(AppFlowyRichTextKeys.textColor);
|
||||||
widget.editorState.getDeltaAttributeValueInSelection(
|
|
||||||
AppFlowyRichTextKeys.textColor,
|
|
||||||
widget.selection,
|
|
||||||
);
|
|
||||||
final String? selectedBackgroundColor =
|
final String? selectedBackgroundColor =
|
||||||
widget.editorState.getDeltaAttributeValueInSelection(
|
_getColor(AppFlowyRichTextKeys.backgroundColor);
|
||||||
AppFlowyRichTextKeys.highlightColor,
|
|
||||||
widget.selection,
|
|
||||||
);
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -90,6 +83,13 @@ class _TextColorAndBackgroundColorState
|
|||||||
selectedColor: selectedTextColor?.tryToColor(),
|
selectedColor: selectedTextColor?.tryToColor(),
|
||||||
onSelectedColor: (textColor) async {
|
onSelectedColor: (textColor) async {
|
||||||
final hex = textColor.alpha == 0 ? null : textColor.toHex();
|
final hex = textColor.alpha == 0 ? null : textColor.toHex();
|
||||||
|
final selection = widget.selection;
|
||||||
|
if (selection.isCollapsed) {
|
||||||
|
widget.editorState.updateToggledStyle(
|
||||||
|
AppFlowyRichTextKeys.textColor,
|
||||||
|
hex ?? '',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
await widget.editorState.formatDelta(
|
await widget.editorState.formatDelta(
|
||||||
widget.selection,
|
widget.selection,
|
||||||
{
|
{
|
||||||
@ -101,6 +101,7 @@ class _TextColorAndBackgroundColorState
|
|||||||
selectionExtraInfoDoNotAttachTextService: true,
|
selectionExtraInfoDoNotAttachTextService: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
}
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -119,10 +120,17 @@ class _TextColorAndBackgroundColorState
|
|||||||
onSelectedColor: (backgroundColor) async {
|
onSelectedColor: (backgroundColor) async {
|
||||||
final hex =
|
final hex =
|
||||||
backgroundColor.alpha == 0 ? null : backgroundColor.toHex();
|
backgroundColor.alpha == 0 ? null : backgroundColor.toHex();
|
||||||
|
final selection = widget.selection;
|
||||||
|
if (selection.isCollapsed) {
|
||||||
|
widget.editorState.updateToggledStyle(
|
||||||
|
AppFlowyRichTextKeys.backgroundColor,
|
||||||
|
hex ?? '',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
await widget.editorState.formatDelta(
|
await widget.editorState.formatDelta(
|
||||||
widget.selection,
|
widget.selection,
|
||||||
{
|
{
|
||||||
AppFlowyRichTextKeys.highlightColor: hex,
|
AppFlowyRichTextKeys.backgroundColor: hex,
|
||||||
},
|
},
|
||||||
selectionExtraInfo: {
|
selectionExtraInfo: {
|
||||||
selectionExtraInfoDisableFloatingToolbar: true,
|
selectionExtraInfoDisableFloatingToolbar: true,
|
||||||
@ -130,12 +138,35 @@ class _TextColorAndBackgroundColorState
|
|||||||
selectionExtraInfoDoNotAttachTextService: true,
|
selectionExtraInfoDoNotAttachTextService: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
}
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String? _getColor(String key) {
|
||||||
|
final selection = widget.selection;
|
||||||
|
String? color = widget.editorState.toggledStyle[key];
|
||||||
|
if (color == null) {
|
||||||
|
if (selection.isCollapsed && selection.startIndex != 0) {
|
||||||
|
color = widget.editorState.getDeltaAttributeValueInSelection<String>(
|
||||||
|
key,
|
||||||
|
selection.copyWith(
|
||||||
|
start: selection.start.copyWith(
|
||||||
|
offset: selection.startIndex - 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
color = widget.editorState.getDeltaAttributeValueInSelection<String>(
|
||||||
|
key,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _BackgroundColors extends StatelessWidget {
|
class _BackgroundColors extends StatelessWidget {
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:appflowy/mobile/presentation/setting/font/font_picker_screen.dart';
|
import 'package:appflowy/mobile/presentation/setting/font/font_picker_screen.dart';
|
||||||
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
|
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/_toolbar_theme.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/_toolbar_theme.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||||
import 'package:appflowy/util/google_font_family_extension.dart';
|
import 'package:appflowy/util/google_font_family_extension.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
@ -23,15 +22,16 @@ class FontFamilyItem extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = ToolbarColorExtension.of(context);
|
final theme = ToolbarColorExtension.of(context);
|
||||||
final fontFamily = editorState.getDeltaAttributeValueInSelection<String>(
|
final fontFamily = _getCurrentSelectedFontFamilyName();
|
||||||
AppFlowyRichTextKeys.fontFamily,
|
|
||||||
);
|
|
||||||
final systemFonFamily =
|
final systemFonFamily =
|
||||||
context.read<DocumentAppearanceCubit>().state.fontFamily;
|
context.read<DocumentAppearanceCubit>().state.fontFamily;
|
||||||
return MobileToolbarMenuItemWrapper(
|
return MobileToolbarMenuItemWrapper(
|
||||||
size: const Size(144, 52),
|
size: const Size(144, 52),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final selection = editorState.selection;
|
final selection = editorState.selection;
|
||||||
|
if (selection == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// disable the floating toolbar
|
// disable the floating toolbar
|
||||||
unawaited(
|
unawaited(
|
||||||
editorState.updateSelectionWithReason(
|
editorState.updateSelectionWithReason(
|
||||||
@ -46,12 +46,17 @@ class FontFamilyItem extends StatelessWidget {
|
|||||||
final newFont = await context
|
final newFont = await context
|
||||||
.read<GoRouter>()
|
.read<GoRouter>()
|
||||||
.push<String>(FontPickerScreen.routeName);
|
.push<String>(FontPickerScreen.routeName);
|
||||||
if (newFont != null && newFont != fontFamily) {
|
|
||||||
|
// if the selection is not collapsed, apply the font to the selection.
|
||||||
|
if (newFont != null && !selection.isCollapsed) {
|
||||||
|
if (newFont != fontFamily) {
|
||||||
await editorState.formatDelta(selection, {
|
await editorState.formatDelta(selection, {
|
||||||
AppFlowyRichTextKeys.fontFamily:
|
AppFlowyRichTextKeys.fontFamily:
|
||||||
GoogleFonts.getFont(newFont).fontFamily,
|
GoogleFonts.getFont(newFont).fontFamily,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// wait for the font picker screen to be dismissed.
|
// wait for the font picker screen to be dismissed.
|
||||||
Future.delayed(const Duration(milliseconds: 250), () {
|
Future.delayed(const Duration(milliseconds: 250), () {
|
||||||
// highlight the selected text again.
|
// highlight the selected text again.
|
||||||
@ -62,13 +67,20 @@ class FontFamilyItem extends StatelessWidget {
|
|||||||
selectionExtraInfoDisableMobileToolbarKey: false,
|
selectionExtraInfoDisableMobileToolbarKey: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
// if the selection is collapsed, save the font for the next typing.
|
||||||
|
if (newFont != null && selection.isCollapsed) {
|
||||||
|
editorState.updateToggledStyle(
|
||||||
|
AppFlowyRichTextKeys.fontFamily,
|
||||||
|
GoogleFonts.getFont(newFont).fontFamily,
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
text: (fontFamily ?? systemFonFamily).parseFontFamilyName(),
|
text: (fontFamily ?? systemFonFamily).parseFontFamilyName(),
|
||||||
fontFamily: fontFamily ?? systemFonFamily,
|
fontFamily: fontFamily ?? systemFonFamily,
|
||||||
backgroundColor: theme.toolbarMenuItemBackgroundColor,
|
backgroundColor: theme.toolbarMenuItemBackgroundColor,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
enable: editorState.selection?.isCollapsed == false,
|
enable: true,
|
||||||
showRightArrow: true,
|
showRightArrow: true,
|
||||||
iconPadding: const EdgeInsets.only(
|
iconPadding: const EdgeInsets.only(
|
||||||
top: 14.0,
|
top: 14.0,
|
||||||
@ -81,4 +93,28 @@ class FontFamilyItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String? _getCurrentSelectedFontFamilyName() {
|
||||||
|
final toggleFontFamily =
|
||||||
|
editorState.toggledStyle[AppFlowyRichTextKeys.fontFamily];
|
||||||
|
if (toggleFontFamily is String && toggleFontFamily.isNotEmpty) {
|
||||||
|
return toggleFontFamily;
|
||||||
|
}
|
||||||
|
final selection = editorState.selection;
|
||||||
|
if (selection != null &&
|
||||||
|
selection.isCollapsed &&
|
||||||
|
selection.startIndex != 0) {
|
||||||
|
return editorState.getDeltaAttributeValueInSelection<String>(
|
||||||
|
AppFlowyRichTextKeys.fontFamily,
|
||||||
|
selection.copyWith(
|
||||||
|
start: selection.start.copyWith(
|
||||||
|
offset: selection.startIndex - 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return editorState.getDeltaAttributeValueInSelection<String>(
|
||||||
|
AppFlowyRichTextKeys.fontFamily,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ final aaToolbarItem = AppFlowyMobileToolbarItem(
|
|||||||
pilotAtExpandedSelection: true,
|
pilotAtExpandedSelection: true,
|
||||||
itemBuilder: (context, editorState, service, onMenu, _) {
|
itemBuilder: (context, editorState, service, onMenu, _) {
|
||||||
return AppFlowyMobileToolbarIconItem(
|
return AppFlowyMobileToolbarIconItem(
|
||||||
|
editorState: editorState,
|
||||||
isSelected: () => service.showMenuNotifier.value,
|
isSelected: () => service.showMenuNotifier.value,
|
||||||
keepSelectedStatus: true,
|
keepSelectedStatus: true,
|
||||||
icon: FlowySvgs.m_toolbar_aa_s,
|
icon: FlowySvgs.m_toolbar_aa_s,
|
||||||
|
@ -18,6 +18,7 @@ import 'package:go_router/go_router.dart';
|
|||||||
final addBlockToolbarItem = AppFlowyMobileToolbarItem(
|
final addBlockToolbarItem = AppFlowyMobileToolbarItem(
|
||||||
itemBuilder: (context, editorState, service, __, onAction) {
|
itemBuilder: (context, editorState, service, __, onAction) {
|
||||||
return AppFlowyMobileToolbarIconItem(
|
return AppFlowyMobileToolbarIconItem(
|
||||||
|
editorState: editorState,
|
||||||
icon: FlowySvgs.m_toolbar_add_s,
|
icon: FlowySvgs.m_toolbar_add_s,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
final selection = editorState.selection;
|
final selection = editorState.selection;
|
||||||
@ -83,7 +84,7 @@ Future<bool?> showAddBlockMenu(
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _AddBlockMenu extends StatelessWidget {
|
class _AddBlockMenu extends StatelessWidget {
|
||||||
_AddBlockMenu({
|
const _AddBlockMenu({
|
||||||
required this.selection,
|
required this.selection,
|
||||||
required this.editorState,
|
required this.editorState,
|
||||||
});
|
});
|
||||||
@ -91,25 +92,47 @@ class _AddBlockMenu extends StatelessWidget {
|
|||||||
final Selection selection;
|
final Selection selection;
|
||||||
final EditorState editorState;
|
final EditorState editorState;
|
||||||
|
|
||||||
late final List<TypeOptionMenuItemValue<String>> typeOptionMenuItemValue = [
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return TypeOptionMenu<String>(
|
||||||
|
values: buildTypeOptionMenuItemValues(context),
|
||||||
|
scaleFactor: context.scale,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _insertBlock(Node node) async {
|
||||||
|
AppGlobals.rootNavKey.currentContext?.pop(true);
|
||||||
|
Future.delayed(const Duration(milliseconds: 100), () {
|
||||||
|
editorState.insertBlockAfterCurrentSelection(
|
||||||
|
selection,
|
||||||
|
node,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TypeOptionMenuItemValue<String>> buildTypeOptionMenuItemValues(
|
||||||
|
BuildContext context,
|
||||||
|
) {
|
||||||
|
final colorMap = _colorMap(context);
|
||||||
|
return [
|
||||||
// heading 1 - 3
|
// heading 1 - 3
|
||||||
TypeOptionMenuItemValue(
|
TypeOptionMenuItemValue(
|
||||||
value: HeadingBlockKeys.type,
|
value: HeadingBlockKeys.type,
|
||||||
backgroundColor: const Color(0xFFBAC9FF),
|
backgroundColor: colorMap[HeadingBlockKeys.type]!,
|
||||||
text: LocaleKeys.editor_heading1.tr(),
|
text: LocaleKeys.editor_heading1.tr(),
|
||||||
icon: FlowySvgs.m_add_block_h1_s,
|
icon: FlowySvgs.m_add_block_h1_s,
|
||||||
onTap: (_, __) => _insertBlock(headingNode(level: 1)),
|
onTap: (_, __) => _insertBlock(headingNode(level: 1)),
|
||||||
),
|
),
|
||||||
TypeOptionMenuItemValue(
|
TypeOptionMenuItemValue(
|
||||||
value: HeadingBlockKeys.type,
|
value: HeadingBlockKeys.type,
|
||||||
backgroundColor: const Color(0xFFBAC9FF),
|
backgroundColor: colorMap[HeadingBlockKeys.type]!,
|
||||||
text: LocaleKeys.editor_heading2.tr(),
|
text: LocaleKeys.editor_heading2.tr(),
|
||||||
icon: FlowySvgs.m_add_block_h2_s,
|
icon: FlowySvgs.m_add_block_h2_s,
|
||||||
onTap: (_, __) => _insertBlock(headingNode(level: 2)),
|
onTap: (_, __) => _insertBlock(headingNode(level: 2)),
|
||||||
),
|
),
|
||||||
TypeOptionMenuItemValue(
|
TypeOptionMenuItemValue(
|
||||||
value: HeadingBlockKeys.type,
|
value: HeadingBlockKeys.type,
|
||||||
backgroundColor: const Color(0xFFBAC9FF),
|
backgroundColor: colorMap[HeadingBlockKeys.type]!,
|
||||||
text: LocaleKeys.editor_heading3.tr(),
|
text: LocaleKeys.editor_heading3.tr(),
|
||||||
icon: FlowySvgs.m_add_block_h3_s,
|
icon: FlowySvgs.m_add_block_h3_s,
|
||||||
onTap: (_, __) => _insertBlock(headingNode(level: 3)),
|
onTap: (_, __) => _insertBlock(headingNode(level: 3)),
|
||||||
@ -118,7 +141,7 @@ class _AddBlockMenu extends StatelessWidget {
|
|||||||
// paragraph
|
// paragraph
|
||||||
TypeOptionMenuItemValue(
|
TypeOptionMenuItemValue(
|
||||||
value: ParagraphBlockKeys.type,
|
value: ParagraphBlockKeys.type,
|
||||||
backgroundColor: const Color(0xFFBAC9FF),
|
backgroundColor: colorMap[ParagraphBlockKeys.type]!,
|
||||||
text: LocaleKeys.editor_text.tr(),
|
text: LocaleKeys.editor_text.tr(),
|
||||||
icon: FlowySvgs.m_add_block_paragraph_s,
|
icon: FlowySvgs.m_add_block_paragraph_s,
|
||||||
onTap: (_, __) => _insertBlock(paragraphNode()),
|
onTap: (_, __) => _insertBlock(paragraphNode()),
|
||||||
@ -127,7 +150,7 @@ class _AddBlockMenu extends StatelessWidget {
|
|||||||
// checkbox
|
// checkbox
|
||||||
TypeOptionMenuItemValue(
|
TypeOptionMenuItemValue(
|
||||||
value: TodoListBlockKeys.type,
|
value: TodoListBlockKeys.type,
|
||||||
backgroundColor: const Color(0xFF98F4CD),
|
backgroundColor: colorMap[TodoListBlockKeys.type]!,
|
||||||
text: LocaleKeys.editor_checkbox.tr(),
|
text: LocaleKeys.editor_checkbox.tr(),
|
||||||
icon: FlowySvgs.m_add_block_checkbox_s,
|
icon: FlowySvgs.m_add_block_checkbox_s,
|
||||||
onTap: (_, __) => _insertBlock(todoListNode(checked: false)),
|
onTap: (_, __) => _insertBlock(todoListNode(checked: false)),
|
||||||
@ -136,7 +159,7 @@ class _AddBlockMenu extends StatelessWidget {
|
|||||||
// quote
|
// quote
|
||||||
TypeOptionMenuItemValue(
|
TypeOptionMenuItemValue(
|
||||||
value: QuoteBlockKeys.type,
|
value: QuoteBlockKeys.type,
|
||||||
backgroundColor: const Color(0xFFFDEDA7),
|
backgroundColor: colorMap[QuoteBlockKeys.type]!,
|
||||||
text: LocaleKeys.editor_quote.tr(),
|
text: LocaleKeys.editor_quote.tr(),
|
||||||
icon: FlowySvgs.m_add_block_quote_s,
|
icon: FlowySvgs.m_add_block_quote_s,
|
||||||
onTap: (_, __) => _insertBlock(quoteNode()),
|
onTap: (_, __) => _insertBlock(quoteNode()),
|
||||||
@ -145,21 +168,21 @@ class _AddBlockMenu extends StatelessWidget {
|
|||||||
// bulleted list, numbered list, toggle list
|
// bulleted list, numbered list, toggle list
|
||||||
TypeOptionMenuItemValue(
|
TypeOptionMenuItemValue(
|
||||||
value: BulletedListBlockKeys.type,
|
value: BulletedListBlockKeys.type,
|
||||||
backgroundColor: const Color(0xFFFFB9EF),
|
backgroundColor: colorMap[BulletedListBlockKeys.type]!,
|
||||||
text: LocaleKeys.editor_bulletedListShortForm.tr(),
|
text: LocaleKeys.editor_bulletedListShortForm.tr(),
|
||||||
icon: FlowySvgs.m_add_block_bulleted_list_s,
|
icon: FlowySvgs.m_add_block_bulleted_list_s,
|
||||||
onTap: (_, __) => _insertBlock(bulletedListNode()),
|
onTap: (_, __) => _insertBlock(bulletedListNode()),
|
||||||
),
|
),
|
||||||
TypeOptionMenuItemValue(
|
TypeOptionMenuItemValue(
|
||||||
value: NumberedListBlockKeys.type,
|
value: NumberedListBlockKeys.type,
|
||||||
backgroundColor: const Color(0xFFFFB9EF),
|
backgroundColor: colorMap[NumberedListBlockKeys.type]!,
|
||||||
text: LocaleKeys.editor_numberedListShortForm.tr(),
|
text: LocaleKeys.editor_numberedListShortForm.tr(),
|
||||||
icon: FlowySvgs.m_add_block_numbered_list_s,
|
icon: FlowySvgs.m_add_block_numbered_list_s,
|
||||||
onTap: (_, __) => _insertBlock(numberedListNode()),
|
onTap: (_, __) => _insertBlock(numberedListNode()),
|
||||||
),
|
),
|
||||||
TypeOptionMenuItemValue(
|
TypeOptionMenuItemValue(
|
||||||
value: ToggleListBlockKeys.type,
|
value: ToggleListBlockKeys.type,
|
||||||
backgroundColor: const Color(0xFFFFB9EF),
|
backgroundColor: colorMap[ToggleListBlockKeys.type]!,
|
||||||
text: LocaleKeys.editor_toggleListShortForm.tr(),
|
text: LocaleKeys.editor_toggleListShortForm.tr(),
|
||||||
icon: FlowySvgs.m_add_block_toggle_s,
|
icon: FlowySvgs.m_add_block_toggle_s,
|
||||||
onTap: (_, __) => _insertBlock(toggleListBlockNode()),
|
onTap: (_, __) => _insertBlock(toggleListBlockNode()),
|
||||||
@ -167,8 +190,8 @@ class _AddBlockMenu extends StatelessWidget {
|
|||||||
|
|
||||||
// image
|
// image
|
||||||
TypeOptionMenuItemValue(
|
TypeOptionMenuItemValue(
|
||||||
value: DividerBlockKeys.type,
|
value: ImageBlockKeys.type,
|
||||||
backgroundColor: const Color(0xFF98F4CD),
|
backgroundColor: colorMap[ImageBlockKeys.type]!,
|
||||||
text: LocaleKeys.editor_image.tr(),
|
text: LocaleKeys.editor_image.tr(),
|
||||||
icon: FlowySvgs.m_add_block_image_s,
|
icon: FlowySvgs.m_add_block_image_s,
|
||||||
onTap: (_, __) async {
|
onTap: (_, __) async {
|
||||||
@ -183,7 +206,7 @@ class _AddBlockMenu extends StatelessWidget {
|
|||||||
// date
|
// date
|
||||||
TypeOptionMenuItemValue(
|
TypeOptionMenuItemValue(
|
||||||
value: ParagraphBlockKeys.type,
|
value: ParagraphBlockKeys.type,
|
||||||
backgroundColor: const Color(0xFF91EAF5),
|
backgroundColor: colorMap['date']!,
|
||||||
text: LocaleKeys.editor_date.tr(),
|
text: LocaleKeys.editor_date.tr(),
|
||||||
icon: FlowySvgs.m_add_block_date_s,
|
icon: FlowySvgs.m_add_block_date_s,
|
||||||
onTap: (_, __) => _insertBlock(dateMentionNode()),
|
onTap: (_, __) => _insertBlock(dateMentionNode()),
|
||||||
@ -192,7 +215,7 @@ class _AddBlockMenu extends StatelessWidget {
|
|||||||
// divider
|
// divider
|
||||||
TypeOptionMenuItemValue(
|
TypeOptionMenuItemValue(
|
||||||
value: DividerBlockKeys.type,
|
value: DividerBlockKeys.type,
|
||||||
backgroundColor: const Color(0xFF98F4CD),
|
backgroundColor: colorMap[DividerBlockKeys.type]!,
|
||||||
text: LocaleKeys.editor_divider.tr(),
|
text: LocaleKeys.editor_divider.tr(),
|
||||||
icon: FlowySvgs.m_add_block_divider_s,
|
icon: FlowySvgs.m_add_block_divider_s,
|
||||||
onTap: (_, __) {
|
onTap: (_, __) {
|
||||||
@ -206,21 +229,21 @@ class _AddBlockMenu extends StatelessWidget {
|
|||||||
// callout, code, math equation
|
// callout, code, math equation
|
||||||
TypeOptionMenuItemValue(
|
TypeOptionMenuItemValue(
|
||||||
value: CalloutBlockKeys.type,
|
value: CalloutBlockKeys.type,
|
||||||
backgroundColor: const Color(0xFFCABDFF),
|
backgroundColor: colorMap[CalloutBlockKeys.type]!,
|
||||||
text: LocaleKeys.document_plugins_callout.tr(),
|
text: LocaleKeys.document_plugins_callout.tr(),
|
||||||
icon: FlowySvgs.m_add_block_callout_s,
|
icon: FlowySvgs.m_add_block_callout_s,
|
||||||
onTap: (_, __) => _insertBlock(calloutNode()),
|
onTap: (_, __) => _insertBlock(calloutNode()),
|
||||||
),
|
),
|
||||||
TypeOptionMenuItemValue(
|
TypeOptionMenuItemValue(
|
||||||
value: CodeBlockKeys.type,
|
value: CodeBlockKeys.type,
|
||||||
backgroundColor: const Color(0xFFCABDFF),
|
backgroundColor: colorMap[CodeBlockKeys.type]!,
|
||||||
text: LocaleKeys.editor_codeBlockShortForm.tr(),
|
text: LocaleKeys.editor_codeBlockShortForm.tr(),
|
||||||
icon: FlowySvgs.m_add_block_code_s,
|
icon: FlowySvgs.m_add_block_code_s,
|
||||||
onTap: (_, __) => _insertBlock(codeBlockNode()),
|
onTap: (_, __) => _insertBlock(codeBlockNode()),
|
||||||
),
|
),
|
||||||
TypeOptionMenuItemValue(
|
TypeOptionMenuItemValue(
|
||||||
value: MathEquationBlockKeys.type,
|
value: MathEquationBlockKeys.type,
|
||||||
backgroundColor: const Color(0xFFCABDFF),
|
backgroundColor: colorMap[MathEquationBlockKeys.type]!,
|
||||||
text: LocaleKeys.editor_mathEquationShortForm.tr(),
|
text: LocaleKeys.editor_mathEquationShortForm.tr(),
|
||||||
icon: FlowySvgs.m_add_block_formula_s,
|
icon: FlowySvgs.m_add_block_formula_s,
|
||||||
onTap: (_, __) {
|
onTap: (_, __) {
|
||||||
@ -231,23 +254,42 @@ class _AddBlockMenu extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return TypeOptionMenu<String>(
|
|
||||||
values: typeOptionMenuItemValue,
|
|
||||||
scaleFactor: context.scale,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _insertBlock(Node node) async {
|
Map<String, Color> _colorMap(BuildContext context) {
|
||||||
AppGlobals.rootNavKey.currentContext?.pop(true);
|
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
||||||
Future.delayed(const Duration(milliseconds: 100), () {
|
if (isDarkMode) {
|
||||||
editorState.insertBlockAfterCurrentSelection(
|
return {
|
||||||
selection,
|
HeadingBlockKeys.type: const Color(0xFF5465A1),
|
||||||
node,
|
ParagraphBlockKeys.type: const Color(0xFF5465A1),
|
||||||
);
|
TodoListBlockKeys.type: const Color(0xFF4BB299),
|
||||||
});
|
QuoteBlockKeys.type: const Color(0xFFBAAC74),
|
||||||
|
BulletedListBlockKeys.type: const Color(0xFFA35F94),
|
||||||
|
NumberedListBlockKeys.type: const Color(0xFFA35F94),
|
||||||
|
ToggleListBlockKeys.type: const Color(0xFFA35F94),
|
||||||
|
ImageBlockKeys.type: const Color(0xFFBAAC74),
|
||||||
|
'date': const Color(0xFF40AAB8),
|
||||||
|
DividerBlockKeys.type: const Color(0xFF4BB299),
|
||||||
|
CalloutBlockKeys.type: const Color(0xFF66599B),
|
||||||
|
CodeBlockKeys.type: const Color(0xFF66599B),
|
||||||
|
MathEquationBlockKeys.type: const Color(0xFF66599B),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
HeadingBlockKeys.type: const Color(0xFFBECCFF),
|
||||||
|
ParagraphBlockKeys.type: const Color(0xFFBECCFF),
|
||||||
|
TodoListBlockKeys.type: const Color(0xFF98F4CD),
|
||||||
|
QuoteBlockKeys.type: const Color(0xFFFDEDA7),
|
||||||
|
BulletedListBlockKeys.type: const Color(0xFFFFB9EF),
|
||||||
|
NumberedListBlockKeys.type: const Color(0xFFFFB9EF),
|
||||||
|
ToggleListBlockKeys.type: const Color(0xFFFFB9EF),
|
||||||
|
ImageBlockKeys.type: const Color(0xFFFDEDA7),
|
||||||
|
'date': const Color(0xFF91EAF5),
|
||||||
|
DividerBlockKeys.type: const Color(0xFF98F4CD),
|
||||||
|
CalloutBlockKeys.type: const Color(0xFFCABDFF),
|
||||||
|
CodeBlockKeys.type: const Color(0xFFCABDFF),
|
||||||
|
MathEquationBlockKeys.type: const Color(0xFFCABDFF),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,6 +270,11 @@ class _MobileToolbarState extends State<_MobileToolbar>
|
|||||||
widget.editorState.selection = null;
|
widget.editorState.selection = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the menu is shown and the height is not 0, we need to close the menu
|
||||||
|
if (showMenuNotifier.value && height != 0) {
|
||||||
|
closeItemMenu();
|
||||||
|
}
|
||||||
|
|
||||||
if (canUpdateCachedKeyboardHeight) {
|
if (canUpdateCachedKeyboardHeight) {
|
||||||
cachedKeyboardHeight.value = height;
|
cachedKeyboardHeight.value = height;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/_toolbar_theme.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/_toolbar_theme.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar.dart';
|
||||||
@ -42,7 +44,9 @@ class AppFlowyMobileToolbarIconItem extends StatefulWidget {
|
|||||||
this.keepSelectedStatus = false,
|
this.keepSelectedStatus = false,
|
||||||
this.iconBuilder,
|
this.iconBuilder,
|
||||||
this.isSelected,
|
this.isSelected,
|
||||||
|
this.shouldListenToToggledStyle = false,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
|
required this.editorState,
|
||||||
});
|
});
|
||||||
|
|
||||||
final FlowySvgData? icon;
|
final FlowySvgData? icon;
|
||||||
@ -50,6 +54,8 @@ class AppFlowyMobileToolbarIconItem extends StatefulWidget {
|
|||||||
final VoidCallback onTap;
|
final VoidCallback onTap;
|
||||||
final WidgetBuilder? iconBuilder;
|
final WidgetBuilder? iconBuilder;
|
||||||
final bool Function()? isSelected;
|
final bool Function()? isSelected;
|
||||||
|
final bool shouldListenToToggledStyle;
|
||||||
|
final EditorState editorState;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AppFlowyMobileToolbarIconItem> createState() =>
|
State<AppFlowyMobileToolbarIconItem> createState() =>
|
||||||
@ -59,12 +65,28 @@ class AppFlowyMobileToolbarIconItem extends StatefulWidget {
|
|||||||
class _AppFlowyMobileToolbarIconItemState
|
class _AppFlowyMobileToolbarIconItemState
|
||||||
extends State<AppFlowyMobileToolbarIconItem> {
|
extends State<AppFlowyMobileToolbarIconItem> {
|
||||||
bool isSelected = false;
|
bool isSelected = false;
|
||||||
|
StreamSubscription? _subscription;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
isSelected = widget.isSelected?.call() ?? false;
|
isSelected = widget.isSelected?.call() ?? false;
|
||||||
|
if (widget.shouldListenToToggledStyle) {
|
||||||
|
widget.editorState.toggledStyleNotifier.addListener(_rebuild);
|
||||||
|
_subscription = widget.editorState.transactionStream.listen((_) {
|
||||||
|
_rebuild();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
if (widget.shouldListenToToggledStyle) {
|
||||||
|
widget.editorState.toggledStyleNotifier.removeListener(_rebuild);
|
||||||
|
_subscription?.cancel();
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -85,15 +107,7 @@ class _AppFlowyMobileToolbarIconItemState
|
|||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
widget.onTap();
|
widget.onTap();
|
||||||
if (widget.keepSelectedStatus && widget.isSelected == null) {
|
_rebuild();
|
||||||
setState(() {
|
|
||||||
isSelected = !isSelected;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setState(() {
|
|
||||||
isSelected = widget.isSelected?.call() ?? false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 48,
|
width: 48,
|
||||||
@ -111,4 +125,12 @@ class _AppFlowyMobileToolbarIconItemState
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _rebuild() {
|
||||||
|
setState(() {
|
||||||
|
isSelected = (widget.keepSelectedStatus && widget.isSelected == null)
|
||||||
|
? !isSelected
|
||||||
|
: widget.isSelected?.call() ?? false;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,13 @@ import 'package:appflowy_editor/appflowy_editor.dart';
|
|||||||
final boldToolbarItem = AppFlowyMobileToolbarItem(
|
final boldToolbarItem = AppFlowyMobileToolbarItem(
|
||||||
itemBuilder: (context, editorState, _, __, onAction) {
|
itemBuilder: (context, editorState, _, __, onAction) {
|
||||||
return AppFlowyMobileToolbarIconItem(
|
return AppFlowyMobileToolbarIconItem(
|
||||||
isSelected: () => editorState.isTextDecorationSelected(
|
editorState: editorState,
|
||||||
|
shouldListenToToggledStyle: true,
|
||||||
|
isSelected: () =>
|
||||||
|
editorState.isTextDecorationSelected(
|
||||||
AppFlowyRichTextKeys.bold,
|
AppFlowyRichTextKeys.bold,
|
||||||
),
|
) &&
|
||||||
|
editorState.toggledStyle[AppFlowyRichTextKeys.bold] != false,
|
||||||
icon: FlowySvgs.m_toolbar_bold_s,
|
icon: FlowySvgs.m_toolbar_bold_s,
|
||||||
onTap: () async => editorState.toggleAttribute(
|
onTap: () async => editorState.toggleAttribute(
|
||||||
AppFlowyRichTextKeys.bold,
|
AppFlowyRichTextKeys.bold,
|
||||||
@ -23,7 +27,8 @@ final boldToolbarItem = AppFlowyMobileToolbarItem(
|
|||||||
final italicToolbarItem = AppFlowyMobileToolbarItem(
|
final italicToolbarItem = AppFlowyMobileToolbarItem(
|
||||||
itemBuilder: (context, editorState, _, __, onAction) {
|
itemBuilder: (context, editorState, _, __, onAction) {
|
||||||
return AppFlowyMobileToolbarIconItem(
|
return AppFlowyMobileToolbarIconItem(
|
||||||
// keepSelectedStatus: true,
|
editorState: editorState,
|
||||||
|
shouldListenToToggledStyle: true,
|
||||||
isSelected: () => editorState.isTextDecorationSelected(
|
isSelected: () => editorState.isTextDecorationSelected(
|
||||||
AppFlowyRichTextKeys.italic,
|
AppFlowyRichTextKeys.italic,
|
||||||
),
|
),
|
||||||
@ -41,6 +46,8 @@ final italicToolbarItem = AppFlowyMobileToolbarItem(
|
|||||||
final underlineToolbarItem = AppFlowyMobileToolbarItem(
|
final underlineToolbarItem = AppFlowyMobileToolbarItem(
|
||||||
itemBuilder: (context, editorState, _, __, onAction) {
|
itemBuilder: (context, editorState, _, __, onAction) {
|
||||||
return AppFlowyMobileToolbarIconItem(
|
return AppFlowyMobileToolbarIconItem(
|
||||||
|
editorState: editorState,
|
||||||
|
shouldListenToToggledStyle: true,
|
||||||
isSelected: () => editorState.isTextDecorationSelected(
|
isSelected: () => editorState.isTextDecorationSelected(
|
||||||
AppFlowyRichTextKeys.underline,
|
AppFlowyRichTextKeys.underline,
|
||||||
),
|
),
|
||||||
@ -58,6 +65,8 @@ final underlineToolbarItem = AppFlowyMobileToolbarItem(
|
|||||||
final colorToolbarItem = AppFlowyMobileToolbarItem(
|
final colorToolbarItem = AppFlowyMobileToolbarItem(
|
||||||
itemBuilder: (context, editorState, service, __, onAction) {
|
itemBuilder: (context, editorState, service, __, onAction) {
|
||||||
return AppFlowyMobileToolbarIconItem(
|
return AppFlowyMobileToolbarIconItem(
|
||||||
|
editorState: editorState,
|
||||||
|
shouldListenToToggledStyle: true,
|
||||||
icon: FlowySvgs.m_toolbar_color_s,
|
icon: FlowySvgs.m_toolbar_color_s,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
service.closeKeyboard();
|
service.closeKeyboard();
|
||||||
|
@ -6,6 +6,8 @@ final todoListToolbarItem = AppFlowyMobileToolbarItem(
|
|||||||
itemBuilder: (context, editorState, _, __, onAction) {
|
itemBuilder: (context, editorState, _, __, onAction) {
|
||||||
final isSelected = editorState.isBlockTypeSelected(TodoListBlockKeys.type);
|
final isSelected = editorState.isBlockTypeSelected(TodoListBlockKeys.type);
|
||||||
return AppFlowyMobileToolbarIconItem(
|
return AppFlowyMobileToolbarIconItem(
|
||||||
|
editorState: editorState,
|
||||||
|
shouldListenToToggledStyle: true,
|
||||||
keepSelectedStatus: true,
|
keepSelectedStatus: true,
|
||||||
isSelected: () => isSelected,
|
isSelected: () => isSelected,
|
||||||
icon: FlowySvgs.m_toolbar_checkbox_s,
|
icon: FlowySvgs.m_toolbar_checkbox_s,
|
||||||
|
@ -4,6 +4,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.da
|
|||||||
final moreToolbarItem = AppFlowyMobileToolbarItem(
|
final moreToolbarItem = AppFlowyMobileToolbarItem(
|
||||||
itemBuilder: (context, editorState, _, __, onAction) {
|
itemBuilder: (context, editorState, _, __, onAction) {
|
||||||
return AppFlowyMobileToolbarIconItem(
|
return AppFlowyMobileToolbarIconItem(
|
||||||
|
editorState: editorState,
|
||||||
icon: FlowySvgs.m_toolbar_more_s,
|
icon: FlowySvgs.m_toolbar_more_s,
|
||||||
onTap: () {},
|
onTap: () {},
|
||||||
);
|
);
|
||||||
|
@ -8,6 +8,7 @@ final undoToolbarItem = AppFlowyMobileToolbarItem(
|
|||||||
itemBuilder: (context, editorState, _, __, onAction) {
|
itemBuilder: (context, editorState, _, __, onAction) {
|
||||||
final theme = ToolbarColorExtension.of(context);
|
final theme = ToolbarColorExtension.of(context);
|
||||||
return AppFlowyMobileToolbarIconItem(
|
return AppFlowyMobileToolbarIconItem(
|
||||||
|
editorState: editorState,
|
||||||
iconBuilder: (context) {
|
iconBuilder: (context) {
|
||||||
final canUndo = editorState.undoManager.undoStack.isNonEmpty;
|
final canUndo = editorState.undoManager.undoStack.isNonEmpty;
|
||||||
return FlowySvg(
|
return FlowySvg(
|
||||||
@ -26,6 +27,7 @@ final redoToolbarItem = AppFlowyMobileToolbarItem(
|
|||||||
itemBuilder: (context, editorState, _, __, onAction) {
|
itemBuilder: (context, editorState, _, __, onAction) {
|
||||||
final theme = ToolbarColorExtension.of(context);
|
final theme = ToolbarColorExtension.of(context);
|
||||||
return AppFlowyMobileToolbarIconItem(
|
return AppFlowyMobileToolbarIconItem(
|
||||||
|
editorState: editorState,
|
||||||
iconBuilder: (context) {
|
iconBuilder: (context) {
|
||||||
final canRedo = editorState.undoManager.redoStack.isNonEmpty;
|
final canRedo = editorState.undoManager.redoStack.isNonEmpty;
|
||||||
return FlowySvg(
|
return FlowySvg(
|
||||||
|
@ -11,6 +11,7 @@ class MobileToolbarMenuItemWrapper extends StatelessWidget {
|
|||||||
this.icon,
|
this.icon,
|
||||||
this.text,
|
this.text,
|
||||||
this.backgroundColor,
|
this.backgroundColor,
|
||||||
|
this.selectedBackgroundColor,
|
||||||
this.enable,
|
this.enable,
|
||||||
this.fontFamily,
|
this.fontFamily,
|
||||||
required this.isSelected,
|
required this.isSelected,
|
||||||
@ -40,6 +41,7 @@ class MobileToolbarMenuItemWrapper extends StatelessWidget {
|
|||||||
final bool showDownArrow;
|
final bool showDownArrow;
|
||||||
final bool showRightArrow;
|
final bool showRightArrow;
|
||||||
final Color? backgroundColor;
|
final Color? backgroundColor;
|
||||||
|
final Color? selectedBackgroundColor;
|
||||||
final EdgeInsets textPadding;
|
final EdgeInsets textPadding;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -90,7 +92,8 @@ class MobileToolbarMenuItemWrapper extends StatelessWidget {
|
|||||||
alignment: text != null ? Alignment.centerLeft : Alignment.center,
|
alignment: text != null ? Alignment.centerLeft : Alignment.center,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? theme.toolbarMenuItemSelectedBackgroundColor
|
? (selectedBackgroundColor ??
|
||||||
|
theme.toolbarMenuItemSelectedBackgroundColor)
|
||||||
: backgroundColor,
|
: backgroundColor,
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
topLeft: enableTopLeftRadius ? radius : Radius.zero,
|
topLeft: enableTopLeftRadius ? radius : Radius.zero,
|
||||||
|
@ -42,6 +42,7 @@ class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
|
|||||||
appearanceSettings.monospaceFont,
|
appearanceSettings.monospaceFont,
|
||||||
appearanceSettings.layoutDirection,
|
appearanceSettings.layoutDirection,
|
||||||
appearanceSettings.textDirection,
|
appearanceSettings.textDirection,
|
||||||
|
appearanceSettings.enableRtlToolbarItems,
|
||||||
appearanceSettings.locale,
|
appearanceSettings.locale,
|
||||||
appearanceSettings.isMenuCollapsed,
|
appearanceSettings.isMenuCollapsed,
|
||||||
appearanceSettings.menuOffset,
|
appearanceSettings.menuOffset,
|
||||||
@ -134,6 +135,12 @@ class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
|
|||||||
emit(state.copyWith(textDirection: textDirection));
|
emit(state.copyWith(textDirection: textDirection));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setEnableRTLToolbarItems(bool value) {
|
||||||
|
_appearanceSettings.enableRtlToolbarItems = value;
|
||||||
|
_saveAppearanceSettings();
|
||||||
|
emit(state.copyWith(enableRtlToolbarItems: value));
|
||||||
|
}
|
||||||
|
|
||||||
/// Update selected font in the user's settings and emit an updated state
|
/// Update selected font in the user's settings and emit an updated state
|
||||||
/// with the font name.
|
/// with the font name.
|
||||||
void setFontFamily(String fontFamilyName) {
|
void setFontFamily(String fontFamilyName) {
|
||||||
@ -365,6 +372,7 @@ class AppearanceSettingsState with _$AppearanceSettingsState {
|
|||||||
required String monospaceFont,
|
required String monospaceFont,
|
||||||
required LayoutDirection layoutDirection,
|
required LayoutDirection layoutDirection,
|
||||||
required AppFlowyTextDirection? textDirection,
|
required AppFlowyTextDirection? textDirection,
|
||||||
|
required bool enableRtlToolbarItems,
|
||||||
required Locale locale,
|
required Locale locale,
|
||||||
required bool isMenuCollapsed,
|
required bool isMenuCollapsed,
|
||||||
required double menuOffset,
|
required double menuOffset,
|
||||||
@ -383,6 +391,7 @@ class AppearanceSettingsState with _$AppearanceSettingsState {
|
|||||||
String monospaceFont,
|
String monospaceFont,
|
||||||
LayoutDirectionPB layoutDirectionPB,
|
LayoutDirectionPB layoutDirectionPB,
|
||||||
TextDirectionPB? textDirectionPB,
|
TextDirectionPB? textDirectionPB,
|
||||||
|
bool enableRtlToolbarItems,
|
||||||
LocaleSettingsPB localePB,
|
LocaleSettingsPB localePB,
|
||||||
bool isMenuCollapsed,
|
bool isMenuCollapsed,
|
||||||
double menuOffset,
|
double menuOffset,
|
||||||
@ -399,6 +408,7 @@ class AppearanceSettingsState with _$AppearanceSettingsState {
|
|||||||
monospaceFont: monospaceFont,
|
monospaceFont: monospaceFont,
|
||||||
layoutDirection: LayoutDirection.fromLayoutDirectionPB(layoutDirectionPB),
|
layoutDirection: LayoutDirection.fromLayoutDirectionPB(layoutDirectionPB),
|
||||||
textDirection: AppFlowyTextDirection.fromTextDirectionPB(textDirectionPB),
|
textDirection: AppFlowyTextDirection.fromTextDirectionPB(textDirectionPB),
|
||||||
|
enableRtlToolbarItems: enableRtlToolbarItems,
|
||||||
themeMode: _themeModeFromPB(themeModePB),
|
themeMode: _themeModeFromPB(themeModePB),
|
||||||
locale: Locale(localePB.languageCode, localePB.countryCode),
|
locale: Locale(localePB.languageCode, localePB.countryCode),
|
||||||
isMenuCollapsed: isMenuCollapsed,
|
isMenuCollapsed: isMenuCollapsed,
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
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/application/document_appearance_cubit.dart';
|
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
|
||||||
@ -7,6 +5,7 @@ import 'package:appflowy/workspace/application/settings/appearance/appearance_cu
|
|||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.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';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
import 'theme_setting_entry_template.dart';
|
import 'theme_setting_entry_template.dart';
|
||||||
@ -26,7 +25,6 @@ class LayoutDirectionSetting extends StatelessWidget {
|
|||||||
hint: LocaleKeys.settings_appearance_layoutDirection_hint.tr(),
|
hint: LocaleKeys.settings_appearance_layoutDirection_hint.tr(),
|
||||||
trailing: [
|
trailing: [
|
||||||
FlowySettingValueDropDown(
|
FlowySettingValueDropDown(
|
||||||
key: const ValueKey('layout_direction_option_button'),
|
|
||||||
currentValue: _layoutDirectionLabelText(currentLayoutDirection),
|
currentValue: _layoutDirectionLabelText(currentLayoutDirection),
|
||||||
popupBuilder: (context) => Column(
|
popupBuilder: (context) => Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@ -142,3 +140,34 @@ class TextDirectionSetting extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class EnableRTLToolbarItemsSetting extends StatelessWidget {
|
||||||
|
const EnableRTLToolbarItemsSetting({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
static const enableRTLSwitchKey = ValueKey('enable_rtl_toolbar_items_switch');
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FlowySettingListTile(
|
||||||
|
label: LocaleKeys.settings_appearance_enableRTLToolbarItems.tr(),
|
||||||
|
trailing: [
|
||||||
|
Switch(
|
||||||
|
key: enableRTLSwitchKey,
|
||||||
|
value: context
|
||||||
|
.read<AppearanceSettingsCubit>()
|
||||||
|
.state
|
||||||
|
.enableRtlToolbarItems,
|
||||||
|
splashRadius: 0,
|
||||||
|
activeColor: Theme.of(context).colorScheme.primary,
|
||||||
|
onChanged: (value) {
|
||||||
|
context
|
||||||
|
.read<AppearanceSettingsCubit>()
|
||||||
|
.setEnableRTLToolbarItems(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -53,6 +53,7 @@ class SettingsAppearanceView extends StatelessWidget {
|
|||||||
TextDirectionSetting(
|
TextDirectionSetting(
|
||||||
currentTextDirection: state.textDirection,
|
currentTextDirection: state.textDirection,
|
||||||
),
|
),
|
||||||
|
const EnableRTLToolbarItemsSetting(),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
DateFormatSetting(
|
DateFormatSetting(
|
||||||
currentFormat: state.dateFormat,
|
currentFormat: state.dateFormat,
|
||||||
|
@ -8,7 +8,6 @@ import 'package:appflowy/workspace/presentation/widgets/more_view_actions/widget
|
|||||||
import 'package:appflowy/workspace/presentation/widgets/more_view_actions/widgets/font_size_action.dart';
|
import 'package:appflowy/workspace/presentation/widgets/more_view_actions/widgets/font_size_action.dart';
|
||||||
import 'package:appflowy/workspace/presentation/widgets/more_view_actions/widgets/view_meta_info.dart';
|
import 'package:appflowy/workspace/presentation/widgets/more_view_actions/widgets/view_meta_info.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/date_time.pbenum.dart';
|
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.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';
|
||||||
@ -35,13 +34,11 @@ class MoreViewActions extends StatefulWidget {
|
|||||||
|
|
||||||
class _MoreViewActionsState extends State<MoreViewActions> {
|
class _MoreViewActionsState extends State<MoreViewActions> {
|
||||||
late final List<Widget> viewActions;
|
late final List<Widget> viewActions;
|
||||||
late final UserDateFormatPB dateFormat;
|
|
||||||
final popoverMutex = PopoverMutex();
|
final popoverMutex = PopoverMutex();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
dateFormat = context.read<AppearanceSettingsCubit>().state.dateFormat;
|
|
||||||
viewActions = ViewActionType.values
|
viewActions = ViewActionType.values
|
||||||
.map(
|
.map(
|
||||||
(type) => ViewAction(
|
(type) => ViewAction(
|
||||||
@ -61,11 +58,15 @@ class _MoreViewActionsState extends State<MoreViewActions> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final appearanceSettings = context.watch<AppearanceSettingsCubit>().state;
|
||||||
|
final dateFormat = appearanceSettings.dateFormat;
|
||||||
|
final timeFormat = appearanceSettings.timeFormat;
|
||||||
|
|
||||||
return BlocBuilder<ViewInfoBloc, ViewInfoState>(
|
return BlocBuilder<ViewInfoBloc, ViewInfoState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return AppFlowyPopover(
|
return AppFlowyPopover(
|
||||||
mutex: popoverMutex,
|
mutex: popoverMutex,
|
||||||
constraints: BoxConstraints.loose(const Size(200, 400)),
|
constraints: BoxConstraints.loose(const Size(210, 400)),
|
||||||
offset: const Offset(0, 30),
|
offset: const Offset(0, 30),
|
||||||
popupBuilder: (_) {
|
popupBuilder: (_) {
|
||||||
final actions = [
|
final actions = [
|
||||||
@ -79,6 +80,7 @@ class _MoreViewActionsState extends State<MoreViewActions> {
|
|||||||
const Divider(height: 4),
|
const Divider(height: 4),
|
||||||
ViewMetaInfo(
|
ViewMetaInfo(
|
||||||
dateFormat: dateFormat,
|
dateFormat: dateFormat,
|
||||||
|
timeFormat: timeFormat,
|
||||||
documentCounters: state.documentCounters,
|
documentCounters: state.documentCounters,
|
||||||
createdAt: state.createdAt,
|
createdAt: state.createdAt,
|
||||||
),
|
),
|
||||||
|
@ -12,11 +12,13 @@ class ViewMetaInfo extends StatelessWidget {
|
|||||||
const ViewMetaInfo({
|
const ViewMetaInfo({
|
||||||
super.key,
|
super.key,
|
||||||
required this.dateFormat,
|
required this.dateFormat,
|
||||||
|
required this.timeFormat,
|
||||||
this.documentCounters,
|
this.documentCounters,
|
||||||
this.createdAt,
|
this.createdAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
final UserDateFormatPB dateFormat;
|
final UserDateFormatPB dateFormat;
|
||||||
|
final UserTimeFormatPB timeFormat;
|
||||||
final Counters? documentCounters;
|
final Counters? documentCounters;
|
||||||
final DateTime? createdAt;
|
final DateTime? createdAt;
|
||||||
|
|
||||||
@ -47,8 +49,9 @@ class ViewMetaInfo extends StatelessWidget {
|
|||||||
if (documentCounters != null) const VSpace(2),
|
if (documentCounters != null) const VSpace(2),
|
||||||
FlowyText.regular(
|
FlowyText.regular(
|
||||||
LocaleKeys.moreAction_createdAt.tr(
|
LocaleKeys.moreAction_createdAt.tr(
|
||||||
args: [dateFormat.formatDate(createdAt!, false)],
|
args: [dateFormat.formatDate(createdAt!, true, timeFormat)],
|
||||||
),
|
),
|
||||||
|
maxLines: 2,
|
||||||
color: Theme.of(context).hintColor,
|
color: Theme.of(context).hintColor,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -53,8 +53,8 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: "9d90053"
|
ref: d4d35c0
|
||||||
resolved-ref: "9d9005366ba8383e93029c4f2f006f9ff8d1cb08"
|
resolved-ref: d4d35c0d103a5d1bddf68181fcfaf9f75b0fccb5
|
||||||
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
||||||
source: git
|
source: git
|
||||||
version: "2.3.2"
|
version: "2.3.2"
|
||||||
|
@ -167,7 +167,7 @@ dependency_overrides:
|
|||||||
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: "9d90053"
|
ref: "d4d35c0"
|
||||||
|
|
||||||
sheet:
|
sheet:
|
||||||
git:
|
git:
|
||||||
|
@ -106,6 +106,13 @@ export const useFiltersCount = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function useStaticTypeOption<T>(fieldId: string) {
|
||||||
|
const context = useContext(DatabaseContext);
|
||||||
|
const typeOptions = context.typeOptions;
|
||||||
|
|
||||||
|
return typeOptions[fieldId] as T;
|
||||||
|
}
|
||||||
|
|
||||||
export function useTypeOption<T>(fieldId: string) {
|
export function useTypeOption<T>(fieldId: string) {
|
||||||
const context = useContext(DatabaseContext);
|
const context = useContext(DatabaseContext);
|
||||||
const typeOptions = useSnapshot(context.typeOptions);
|
const typeOptions = useSnapshot(context.typeOptions);
|
||||||
|
@ -15,6 +15,7 @@ import ExpandRecordModal from '$app/components/database/components/edit_record/E
|
|||||||
import { subscribeNotifications } from '$app/application/notification';
|
import { subscribeNotifications } from '$app/application/notification';
|
||||||
import { Page } from '$app_reducers/pages/slice';
|
import { Page } from '$app_reducers/pages/slice';
|
||||||
import { getPage } from '$app/application/folder/page.service';
|
import { getPage } from '$app/application/folder/page.service';
|
||||||
|
import './database.scss';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
selectedViewId?: string;
|
selectedViewId?: string;
|
||||||
|
@ -8,9 +8,11 @@ interface Props {
|
|||||||
|
|
||||||
export const DatabaseCollection = ({ open }: Props) => {
|
export const DatabaseCollection = ({ open }: Props) => {
|
||||||
return (
|
return (
|
||||||
<div className={`flex items-center gap-2 px-16 ${!open ? 'hidden' : 'py-3'}`}>
|
<div className={`database-collection w-full px-[64px] ${!open ? 'hidden' : 'py-3'}`}>
|
||||||
|
<div className={'flex w-full items-center gap-2 overflow-x-auto overflow-y-hidden '}>
|
||||||
<Sorts />
|
<Sorts />
|
||||||
<Filters />
|
<Filters />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
import React, { FC, useMemo, useState } from 'react';
|
import React, { FC, useMemo, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Filter as FilterType,
|
|
||||||
Field as FieldData,
|
|
||||||
UndeterminedFilter,
|
|
||||||
TextFilterData,
|
|
||||||
SelectFilterData,
|
|
||||||
NumberFilterData,
|
|
||||||
CheckboxFilterData,
|
CheckboxFilterData,
|
||||||
ChecklistFilterData,
|
ChecklistFilterData,
|
||||||
DateFilterData,
|
DateFilterData,
|
||||||
|
Field as FieldData,
|
||||||
|
Filter as FilterType,
|
||||||
|
NumberFilterData,
|
||||||
|
SelectFilterData,
|
||||||
|
TextFilterData,
|
||||||
|
UndeterminedFilter,
|
||||||
} from '$app/application/database';
|
} from '$app/application/database';
|
||||||
import { Chip, Popover } from '@mui/material';
|
import { Chip, Popover } from '@mui/material';
|
||||||
import { Property } from '$app/components/database/components/property';
|
import { Property } from '$app/components/database/components/property';
|
||||||
import { ReactComponent as DropDownSvg } from '$app/assets/dropdown.svg';
|
import { ReactComponent as DropDownSvg } from '$app/assets/dropdown.svg';
|
||||||
import TextFilter from './text_filter/TextFilter';
|
import TextFilter from './text_filter/TextFilter';
|
||||||
import { FieldType } from '@/services/backend';
|
import { CheckboxFilterConditionPB, ChecklistFilterConditionPB, FieldType } from '@/services/backend';
|
||||||
import FilterActions from '$app/components/database/components/filter/FilterActions';
|
import FilterActions from '$app/components/database/components/filter/FilterActions';
|
||||||
import { updateFilter } from '$app/application/database/filter/filter_service';
|
import { updateFilter } from '$app/application/database/filter/filter_service';
|
||||||
import { useViewId } from '$app/hooks';
|
import { useViewId } from '$app/hooks';
|
||||||
@ -22,6 +22,11 @@ import SelectFilter from './select_filter/SelectFilter';
|
|||||||
|
|
||||||
import DateFilter from '$app/components/database/components/filter/date_filter/DateFilter';
|
import DateFilter from '$app/components/database/components/filter/date_filter/DateFilter';
|
||||||
import FilterConditionSelect from '$app/components/database/components/filter/FilterConditionSelect';
|
import FilterConditionSelect from '$app/components/database/components/filter/FilterConditionSelect';
|
||||||
|
import TextFilterValue from '$app/components/database/components/filter/text_filter/TextFilterValue';
|
||||||
|
import SelectFilterValue from '$app/components/database/components/filter/select_filter/SelectFilterValue';
|
||||||
|
import NumberFilterValue from '$app/components/database/components/filter/number_filter/NumberFilterValue';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import DateFilterValue from '$app/components/database/components/filter/date_filter/DateFilterValue';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
filter: FilterType;
|
filter: FilterType;
|
||||||
@ -57,6 +62,7 @@ const getFilterComponent = (field: FieldData) => {
|
|||||||
|
|
||||||
function Filter({ filter, field }: Props) {
|
function Filter({ filter, field }: Props) {
|
||||||
const viewId = useViewId();
|
const viewId = useViewId();
|
||||||
|
const { t } = useTranslation();
|
||||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||||
const open = Boolean(anchorEl);
|
const open = Boolean(anchorEl);
|
||||||
const handleClick = (e: React.MouseEvent<HTMLElement>) => {
|
const handleClick = (e: React.MouseEvent<HTMLElement>) => {
|
||||||
@ -70,7 +76,10 @@ function Filter({ filter, field }: Props) {
|
|||||||
const onDataChange = async (data: UndeterminedFilter['data']) => {
|
const onDataChange = async (data: UndeterminedFilter['data']) => {
|
||||||
const newFilter = {
|
const newFilter = {
|
||||||
...filter,
|
...filter,
|
||||||
data,
|
data: {
|
||||||
|
...(filter.data || {}),
|
||||||
|
...data,
|
||||||
|
},
|
||||||
} as UndeterminedFilter;
|
} as UndeterminedFilter;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -105,14 +114,42 @@ function Filter({ filter, field }: Props) {
|
|||||||
}
|
}
|
||||||
}, [field, filter]);
|
}, [field, filter]);
|
||||||
|
|
||||||
|
const conditionValue = useMemo(() => {
|
||||||
|
switch (field.type) {
|
||||||
|
case FieldType.RichText:
|
||||||
|
case FieldType.URL:
|
||||||
|
return <TextFilterValue data={filter.data as TextFilterData} />;
|
||||||
|
case FieldType.SingleSelect:
|
||||||
|
case FieldType.MultiSelect:
|
||||||
|
return <SelectFilterValue data={filter.data as SelectFilterData} fieldId={field.id} />;
|
||||||
|
case FieldType.Number:
|
||||||
|
return <NumberFilterValue data={filter.data as NumberFilterData} />;
|
||||||
|
case FieldType.Checkbox:
|
||||||
|
return (filter.data as CheckboxFilterData).condition === CheckboxFilterConditionPB.IsChecked
|
||||||
|
? t('grid.checkboxFilter.isChecked')
|
||||||
|
: t('grid.checkboxFilter.isUnchecked');
|
||||||
|
case FieldType.Checklist:
|
||||||
|
return (filter.data as ChecklistFilterData).condition === ChecklistFilterConditionPB.IsComplete
|
||||||
|
? t('grid.checklistFilter.isComplete')
|
||||||
|
: t('grid.checklistFilter.isIncomplted');
|
||||||
|
case FieldType.DateTime:
|
||||||
|
case FieldType.LastEditedTime:
|
||||||
|
case FieldType.CreatedTime:
|
||||||
|
return <DateFilterValue data={filter.data as DateFilterData} />;
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}, [field.id, field.type, filter.data, t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Chip
|
<Chip
|
||||||
clickable
|
clickable
|
||||||
variant='outlined'
|
variant='outlined'
|
||||||
label={
|
label={
|
||||||
<div className={'flex items-center justify-center gap-1'}>
|
<div className={'flex items-center justify-between gap-1'}>
|
||||||
<Property field={field} />
|
<Property className={'flex flex-1 items-center'} field={field} />
|
||||||
|
<span className={'max-w-[120px] truncate'}>{conditionValue}</span>
|
||||||
<DropDownSvg className={'h-6 w-6'} />
|
<DropDownSvg className={'h-6 w-6'} />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -29,9 +29,9 @@ function Filters() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'flex items-center justify-center gap-2 text-text-title'}>
|
<div className={'flex flex-1 items-center gap-2 text-text-title'}>
|
||||||
{options.map(({ filter, field }) => (field ? <Filter key={filter.id} filter={filter} field={field} /> : null))}
|
{options.map(({ filter, field }) => (field ? <Filter key={filter.id} filter={filter} field={field} /> : null))}
|
||||||
<Button size={'small'} onClick={handleClick} color={'inherit'} startIcon={<AddSvg />}>
|
<Button size={'small'} className={'min-w-[100px]'} onClick={handleClick} color={'inherit'} startIcon={<AddSvg />}>
|
||||||
{t('grid.settings.addFilter')}
|
{t('grid.settings.addFilter')}
|
||||||
</Button>
|
</Button>
|
||||||
<FilterFieldsMenu
|
<FilterFieldsMenu
|
||||||
|
@ -27,23 +27,19 @@ function DateFilter({ filter, field, onChange }: Props) {
|
|||||||
const condition = filter.data.condition;
|
const condition = filter.data.condition;
|
||||||
const isRange = condition === DateFilterConditionPB.DateWithIn;
|
const isRange = condition === DateFilterConditionPB.DateWithIn;
|
||||||
const timestamp = useMemo(() => {
|
const timestamp = useMemo(() => {
|
||||||
const now = Date.now() / 1000;
|
|
||||||
|
|
||||||
if (isRange) {
|
if (isRange) {
|
||||||
return filter.data.start ? filter.data.start : now;
|
return filter.data.start;
|
||||||
}
|
}
|
||||||
|
|
||||||
return filter.data.timestamp ? filter.data.timestamp : now;
|
return filter.data.timestamp;
|
||||||
}, [filter.data.start, filter.data.timestamp, isRange]);
|
}, [filter.data.start, filter.data.timestamp, isRange]);
|
||||||
|
|
||||||
const endTimestamp = useMemo(() => {
|
const endTimestamp = useMemo(() => {
|
||||||
const now = Date.now() / 1000;
|
|
||||||
|
|
||||||
if (isRange) {
|
if (isRange) {
|
||||||
return filter.data.end ? filter.data.end : now;
|
return filter.data.end;
|
||||||
}
|
}
|
||||||
|
|
||||||
return now;
|
return;
|
||||||
}, [filter.data.end, isRange]);
|
}, [filter.data.end, isRange]);
|
||||||
|
|
||||||
const timeFormat = useMemo(() => {
|
const timeFormat = useMemo(() => {
|
||||||
@ -64,7 +60,7 @@ function DateFilter({ filter, field, onChange }: Props) {
|
|||||||
onChange({
|
onChange({
|
||||||
condition,
|
condition,
|
||||||
timestamp: date,
|
timestamp: date,
|
||||||
start: date,
|
start: endDate ? date : undefined,
|
||||||
end: endDate,
|
end: endDate,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@ -81,7 +77,7 @@ function DateFilter({ filter, field, onChange }: Props) {
|
|||||||
onChange({
|
onChange({
|
||||||
condition,
|
condition,
|
||||||
timestamp: date,
|
timestamp: date,
|
||||||
start: date,
|
start: endDate ? date : undefined,
|
||||||
end: endDate,
|
end: endDate,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { DateFilterData } from '$app/application/database';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { DateFilterConditionPB } from '@/services/backend';
|
||||||
|
|
||||||
|
function DateFilterValue({ data }: { data: DateFilterData }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const value = useMemo(() => {
|
||||||
|
if (!data.timestamp) return '';
|
||||||
|
|
||||||
|
let startStr = '';
|
||||||
|
let endStr = '';
|
||||||
|
|
||||||
|
if (data.start) {
|
||||||
|
const end = data.end ?? data.start;
|
||||||
|
const moreThanOneYear = dayjs.unix(end).diff(dayjs.unix(data.start), 'year') > 1;
|
||||||
|
const format = moreThanOneYear ? 'MMM D, YYYY' : 'MMM D';
|
||||||
|
|
||||||
|
startStr = dayjs.unix(data.start).format(format);
|
||||||
|
endStr = dayjs.unix(end).format(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
const timestamp = dayjs.unix(data.timestamp).format('MMM D');
|
||||||
|
|
||||||
|
switch (data.condition) {
|
||||||
|
case DateFilterConditionPB.DateIs:
|
||||||
|
return `: ${timestamp}`;
|
||||||
|
case DateFilterConditionPB.DateBefore:
|
||||||
|
return `: ${t('grid.dateFilter.choicechipPrefix.before')} ${timestamp}`;
|
||||||
|
case DateFilterConditionPB.DateAfter:
|
||||||
|
return `: ${t('grid.dateFilter.choicechipPrefix.after')} ${timestamp}`;
|
||||||
|
case DateFilterConditionPB.DateOnOrBefore:
|
||||||
|
return `: ${t('grid.dateFilter.choicechipPrefix.onOrBefore')} ${timestamp}`;
|
||||||
|
case DateFilterConditionPB.DateOnOrAfter:
|
||||||
|
return `: ${t('grid.dateFilter.choicechipPrefix.onOrAfter')} ${timestamp}`;
|
||||||
|
case DateFilterConditionPB.DateWithIn:
|
||||||
|
return `: ${startStr} - ${endStr}`;
|
||||||
|
case DateFilterConditionPB.DateIsEmpty:
|
||||||
|
return `: ${t('grid.dateFilter.choicechipPrefix.isEmpty')}`;
|
||||||
|
case DateFilterConditionPB.DateIsNotEmpty:
|
||||||
|
return `: ${t('grid.dateFilter.choicechipPrefix.isNotEmpty')}`;
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}, [data, t]);
|
||||||
|
|
||||||
|
return <>{value}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DateFilterValue;
|
@ -0,0 +1,39 @@
|
|||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { NumberFilterData } from '$app/application/database';
|
||||||
|
import { NumberFilterConditionPB } from '@/services/backend';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
function NumberFilterValue({ data }: { data: NumberFilterData }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const value = useMemo(() => {
|
||||||
|
if (!data.content) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = parseInt(data.content);
|
||||||
|
|
||||||
|
switch (data.condition) {
|
||||||
|
case NumberFilterConditionPB.Equal:
|
||||||
|
return `= ${content}`;
|
||||||
|
case NumberFilterConditionPB.NotEqual:
|
||||||
|
return `!= ${content}`;
|
||||||
|
case NumberFilterConditionPB.GreaterThan:
|
||||||
|
return `> ${content}`;
|
||||||
|
case NumberFilterConditionPB.GreaterThanOrEqualTo:
|
||||||
|
return `>= ${content}`;
|
||||||
|
case NumberFilterConditionPB.LessThan:
|
||||||
|
return `< ${content}`;
|
||||||
|
case NumberFilterConditionPB.LessThanOrEqualTo:
|
||||||
|
return `<= ${content}`;
|
||||||
|
case NumberFilterConditionPB.NumberIsEmpty:
|
||||||
|
return t('grid.textFilter.isEmpty');
|
||||||
|
case NumberFilterConditionPB.NumberIsNotEmpty:
|
||||||
|
return t('grid.textFilter.isNotEmpty');
|
||||||
|
}
|
||||||
|
}, [data.condition, data.content, t]);
|
||||||
|
|
||||||
|
return <>{value}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NumberFilterValue;
|
@ -0,0 +1,38 @@
|
|||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { SelectFilterData, SelectTypeOption } from '$app/application/database';
|
||||||
|
import { useStaticTypeOption } from '$app/components/database';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { SelectOptionConditionPB } from '@/services/backend';
|
||||||
|
|
||||||
|
function SelectFilterValue({ data, fieldId }: { data: SelectFilterData; fieldId: string }) {
|
||||||
|
const typeOption = useStaticTypeOption<SelectTypeOption>(fieldId);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const value = useMemo(() => {
|
||||||
|
if (!data.optionIds?.length) return '';
|
||||||
|
|
||||||
|
const options = data.optionIds
|
||||||
|
.map((optionId) => {
|
||||||
|
const option = typeOption?.options?.find((option) => option.id === optionId);
|
||||||
|
|
||||||
|
return option?.name;
|
||||||
|
})
|
||||||
|
.join(', ');
|
||||||
|
|
||||||
|
switch (data.condition) {
|
||||||
|
case SelectOptionConditionPB.OptionIs:
|
||||||
|
return `: ${options}`;
|
||||||
|
case SelectOptionConditionPB.OptionIsNot:
|
||||||
|
return `: ${t('grid.textFilter.choicechipPrefix.isNot')} ${options}`;
|
||||||
|
case SelectOptionConditionPB.OptionIsEmpty:
|
||||||
|
return `: ${t('grid.textFilter.choicechipPrefix.isEmpty')}`;
|
||||||
|
case SelectOptionConditionPB.OptionIsNotEmpty:
|
||||||
|
return `: ${t('grid.textFilter.choicechipPrefix.isNotEmpty')}`;
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}, [data.condition, data.optionIds, t, typeOption?.options]);
|
||||||
|
|
||||||
|
return <>{value}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SelectFilterValue;
|
@ -0,0 +1,34 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import { TextFilterData } from '$app/application/database';
|
||||||
|
import { TextFilterConditionPB } from '@/services/backend';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
function TextFilterValue({ data }: { data: TextFilterData }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const value = useMemo(() => {
|
||||||
|
if (!data.content) return '';
|
||||||
|
switch (data.condition) {
|
||||||
|
case TextFilterConditionPB.Contains:
|
||||||
|
case TextFilterConditionPB.Is:
|
||||||
|
return `: ${data.content}`;
|
||||||
|
case TextFilterConditionPB.DoesNotContain:
|
||||||
|
case TextFilterConditionPB.IsNot:
|
||||||
|
return `: ${t('grid.textFilter.choicechipPrefix.isNot')} ${data.content}`;
|
||||||
|
case TextFilterConditionPB.StartsWith:
|
||||||
|
return `: ${t('grid.textFilter.choicechipPrefix.startWith')} ${data.content}`;
|
||||||
|
case TextFilterConditionPB.EndsWith:
|
||||||
|
return `: ${t('grid.textFilter.choicechipPrefix.endWith')} ${data.content}`;
|
||||||
|
case TextFilterConditionPB.TextIsEmpty:
|
||||||
|
return `: ${t('grid.textFilter.choicechipPrefix.isEmpty')}`;
|
||||||
|
case TextFilterConditionPB.TextIsNotEmpty:
|
||||||
|
return `: ${t('grid.textFilter.choicechipPrefix.isNotEmpty')}`;
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}, [t, data]);
|
||||||
|
|
||||||
|
return <>{value}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TextFilterValue;
|
@ -10,6 +10,7 @@ export interface FieldProps {
|
|||||||
menuOpened?: boolean;
|
menuOpened?: boolean;
|
||||||
onOpenMenu?: (id: string) => void;
|
onOpenMenu?: (id: string) => void;
|
||||||
onCloseMenu?: (id: string) => void;
|
onCloseMenu?: (id: string) => void;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialAnchorOrigin: PopoverOrigin = {
|
const initialAnchorOrigin: PopoverOrigin = {
|
||||||
@ -22,7 +23,7 @@ const initialTransformOrigin: PopoverOrigin = {
|
|||||||
horizontal: 'left',
|
horizontal: 'left',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Property: FC<FieldProps> = ({ field, onCloseMenu, menuOpened }) => {
|
export const Property: FC<FieldProps> = ({ field, onCloseMenu, className, menuOpened }) => {
|
||||||
const ref = useRef<HTMLDivElement | null>(null);
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
const [anchorPosition, setAnchorPosition] = useState<
|
const [anchorPosition, setAnchorPosition] = useState<
|
||||||
| {
|
| {
|
||||||
@ -63,7 +64,7 @@ export const Property: FC<FieldProps> = ({ field, onCloseMenu, menuOpened }) =>
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div ref={ref} className='flex w-full items-center px-2'>
|
<div ref={ref} className={className ? className : `flex w-full items-center px-2`}>
|
||||||
<ProppertyTypeSvg className='mr-1 text-base' type={field.type} />
|
<ProppertyTypeSvg className='mr-1 text-base' type={field.type} />
|
||||||
<span className='flex-1 truncate text-left text-xs'>{field.name}</span>
|
<span className='flex-1 truncate text-left text-xs'>{field.name}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
.database-collection {
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 0px;
|
||||||
|
height: 0px;
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
import { ReactEditor } from 'slate-react';
|
import { ReactEditor } from 'slate-react';
|
||||||
import { Editor, Element as SlateElement, NodeEntry, Range, Transforms } from 'slate';
|
import { Editor, Element, Element as SlateElement, NodeEntry, Range, Transforms } from 'slate';
|
||||||
import { EditorInlineNodeType, FormulaNode } from '$app/application/document/document.types';
|
import { EditorInlineNodeType, FormulaNode } from '$app/application/document/document.types';
|
||||||
import { isMarkActive } from '$app/components/editor/command/mark';
|
|
||||||
|
|
||||||
export function insertFormula(editor: ReactEditor, formula?: string) {
|
export function insertFormula(editor: ReactEditor, formula?: string) {
|
||||||
if (editor.selection) {
|
if (editor.selection) {
|
||||||
@ -80,5 +79,11 @@ export function unwrapFormula(editor: ReactEditor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isFormulaActive(editor: ReactEditor) {
|
export function isFormulaActive(editor: ReactEditor) {
|
||||||
return isMarkActive(editor, EditorInlineNodeType.Formula);
|
const [match] = editor.nodes({
|
||||||
|
match: (n) => {
|
||||||
|
return !Editor.isEditor(n) && Element.isElement(n) && n.type === EditorInlineNodeType.Formula;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return Boolean(match);
|
||||||
}
|
}
|
||||||
|
@ -229,6 +229,16 @@ export const CustomEditor = {
|
|||||||
return !!match;
|
return !!match;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
formulaActiveNode(editor: ReactEditor) {
|
||||||
|
const [match] = editor.nodes({
|
||||||
|
match: (n) => {
|
||||||
|
return !Editor.isEditor(n) && Element.isElement(n) && n.type === EditorInlineNodeType.Formula;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return match ? (match as NodeEntry<FormulaNode>) : undefined;
|
||||||
|
},
|
||||||
|
|
||||||
isMentionActive(editor: ReactEditor) {
|
isMentionActive(editor: ReactEditor) {
|
||||||
const [match] = editor.nodes({
|
const [match] = editor.nodes({
|
||||||
match: (n) => {
|
match: (n) => {
|
||||||
@ -519,6 +529,14 @@ export const CustomEditor = {
|
|||||||
return editor.isEmpty(textNode);
|
return editor.isEmpty(textNode);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
includeInlineBlocks: (editor: ReactEditor) => {
|
||||||
|
const [match] = Editor.nodes(editor, {
|
||||||
|
match: (n) => Element.isElement(n) && editor.isInline(n),
|
||||||
|
});
|
||||||
|
|
||||||
|
return Boolean(match);
|
||||||
|
},
|
||||||
|
|
||||||
getNodeTextContent(node: Node): string {
|
getNodeTextContent(node: Node): string {
|
||||||
if (Element.isElement(node) && node.type === EditorInlineNodeType.Formula) {
|
if (Element.isElement(node) && node.type === EditorInlineNodeType.Formula) {
|
||||||
return (node as FormulaNode).data || '';
|
return (node as FormulaNode).data || '';
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { ReactEditor } from 'slate-react';
|
import { ReactEditor } from 'slate-react';
|
||||||
import { Editor, Text, Range } from 'slate';
|
import { Editor, Text, Range, Element } from 'slate';
|
||||||
import { EditorInlineNodeType, EditorMarkFormat } from '$app/application/document/document.types';
|
import { EditorInlineNodeType, EditorMarkFormat } from '$app/application/document/document.types';
|
||||||
|
|
||||||
export function toggleMark(
|
export function toggleMark(
|
||||||
@ -33,18 +33,13 @@ export function isMarkActive(editor: ReactEditor, format: EditorMarkFormat | Edi
|
|||||||
const isExpanded = Range.isExpanded(selection);
|
const isExpanded = Range.isExpanded(selection);
|
||||||
|
|
||||||
if (isExpanded) {
|
if (isExpanded) {
|
||||||
const matches = Array.from(getSelectionNodeEntry(editor) || []);
|
const texts = getSelectionTexts(editor);
|
||||||
|
|
||||||
return matches.every((match) => {
|
|
||||||
const [node] = match;
|
|
||||||
|
|
||||||
|
return texts.every((node) => {
|
||||||
const { text, ...attributes } = node;
|
const { text, ...attributes } = node;
|
||||||
|
|
||||||
if (!text) {
|
if (!text) return true;
|
||||||
return true;
|
return Boolean((attributes as Record<string, boolean | string>)[format]);
|
||||||
}
|
|
||||||
|
|
||||||
return !!(attributes as Record<string, boolean | string>)[format];
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,10 +48,12 @@ export function isMarkActive(editor: ReactEditor, format: EditorMarkFormat | Edi
|
|||||||
return marks ? !!marks[format] : false;
|
return marks ? !!marks[format] : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSelectionNodeEntry(editor: ReactEditor) {
|
function getSelectionTexts(editor: ReactEditor) {
|
||||||
const selection = editor.selection;
|
const selection = editor.selection;
|
||||||
|
|
||||||
if (!selection) return null;
|
if (!selection) return [];
|
||||||
|
|
||||||
|
const texts: Text[] = [];
|
||||||
|
|
||||||
const isExpanded = Range.isExpanded(selection);
|
const isExpanded = Range.isExpanded(selection);
|
||||||
|
|
||||||
@ -73,16 +70,25 @@ function getSelectionNodeEntry(editor: ReactEditor) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Editor.nodes(editor, {
|
Array.from(
|
||||||
match: Text.isText,
|
Editor.nodes(editor, {
|
||||||
at: {
|
at: {
|
||||||
anchor,
|
anchor,
|
||||||
focus,
|
focus,
|
||||||
},
|
},
|
||||||
|
})
|
||||||
|
).forEach((match) => {
|
||||||
|
const node = match[0] as Element;
|
||||||
|
|
||||||
|
if (Text.isText(node)) {
|
||||||
|
texts.push(node);
|
||||||
|
} else if (Editor.isInline(editor, node)) {
|
||||||
|
texts.push(...(node.children as Text[]));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return texts;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -97,13 +103,11 @@ export function getAllMarks(editor: ReactEditor) {
|
|||||||
const isExpanded = Range.isExpanded(selection);
|
const isExpanded = Range.isExpanded(selection);
|
||||||
|
|
||||||
if (isExpanded) {
|
if (isExpanded) {
|
||||||
const matches = Array.from(getSelectionNodeEntry(editor) || []);
|
const texts = getSelectionTexts(editor);
|
||||||
|
|
||||||
const marks: Record<string, string | boolean> = {};
|
const marks: Record<string, string | boolean> = {};
|
||||||
|
|
||||||
matches.forEach((match) => {
|
texts.forEach((node) => {
|
||||||
const [node] = match;
|
|
||||||
|
|
||||||
Object.entries(node).forEach(([key, value]) => {
|
Object.entries(node).forEach(([key, value]) => {
|
||||||
if (key !== 'text') {
|
if (key !== 'text') {
|
||||||
marks[key] = value;
|
marks[key] = value;
|
||||||
|
@ -8,7 +8,7 @@ function BulletedListIcon({ block: _, className }: { block: BulletedListNode; cl
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}}
|
}}
|
||||||
contentEditable={false}
|
contentEditable={false}
|
||||||
className={`${className} bulleted-icon flex w-[23px] justify-center pr-1 font-medium`}
|
className={`${className} bulleted-icon flex min-w-[23px] justify-center pr-1 font-medium`}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ function NumberListIcon({ block, className }: { block: NumberedListNode; classNa
|
|||||||
}}
|
}}
|
||||||
contentEditable={false}
|
contentEditable={false}
|
||||||
data-number={index}
|
data-number={index}
|
||||||
className={`${className} numbered-icon flex w-[23px] justify-center pr-1 font-medium`}
|
className={`${className} numbered-icon flex w-[23px] min-w-[23px] justify-center pr-1 font-medium`}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ export const InlineFormula = memo(
|
|||||||
const { popoverOpen = false, setRange, openPopover, closePopover } = useEditorInlineBlockState('formula');
|
const { popoverOpen = false, setRange, openPopover, closePopover } = useEditorInlineBlockState('formula');
|
||||||
const anchor = useRef<HTMLSpanElement | null>(null);
|
const anchor = useRef<HTMLSpanElement | null>(null);
|
||||||
const selected = useSelected();
|
const selected = useSelected();
|
||||||
const open = popoverOpen && selected;
|
const open = Boolean(popoverOpen && selected);
|
||||||
|
|
||||||
const handleClick = useCallback(
|
const handleClick = useCallback(
|
||||||
(e: MouseEvent<HTMLSpanElement>) => {
|
(e: MouseEvent<HTMLSpanElement>) => {
|
||||||
|
@ -3,7 +3,7 @@ import { MutableRefObject, useCallback, useEffect, useMemo, useRef, useState } f
|
|||||||
import { getSelectionPosition } from '$app/components/editor/components/tools/selection_toolbar/utils';
|
import { getSelectionPosition } from '$app/components/editor/components/tools/selection_toolbar/utils';
|
||||||
import debounce from 'lodash-es/debounce';
|
import debounce from 'lodash-es/debounce';
|
||||||
import { CustomEditor } from '$app/components/editor/command';
|
import { CustomEditor } from '$app/components/editor/command';
|
||||||
import { BaseRange, Editor, Range as SlateRange } from 'slate';
|
import { BaseRange, Range as SlateRange } from 'slate';
|
||||||
import { useDecorateDispatch } from '$app/components/editor/stores/decorate';
|
import { useDecorateDispatch } from '$app/components/editor/stores/decorate';
|
||||||
|
|
||||||
const DELAY = 300;
|
const DELAY = 300;
|
||||||
@ -118,9 +118,21 @@ export function useSelectionToolbar(ref: MutableRefObject<HTMLDivElement | null>
|
|||||||
|
|
||||||
const { selection } = editor;
|
const { selection } = editor;
|
||||||
|
|
||||||
if (!isFocusedEditor || !selection || SlateRange.isCollapsed(selection) || Editor.string(editor, selection) === '') {
|
const close = () => {
|
||||||
debounceRecalculatePosition.cancel();
|
debounceRecalculatePosition.cancel();
|
||||||
closeToolbar();
|
closeToolbar();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isFocusedEditor || !selection || SlateRange.isCollapsed(selection)) {
|
||||||
|
close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// There has a bug which the text of selection is empty when the selection include inline blocks
|
||||||
|
const isEmptyText = !CustomEditor.includeInlineBlocks(editor) && editor.string(selection) === '';
|
||||||
|
|
||||||
|
if (isEmptyText) {
|
||||||
|
close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ export function Bold() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const editor = useSlateStatic();
|
const editor = useSlateStatic();
|
||||||
const isActivated = CustomEditor.isMarkActive(editor, EditorMarkFormat.Bold);
|
const isActivated = CustomEditor.isMarkActive(editor, EditorMarkFormat.Bold);
|
||||||
|
|
||||||
const modifier = useMemo(() => getHotKey(EditorMarkFormat.Bold).modifier, []);
|
const modifier = useMemo(() => getHotKey(EditorMarkFormat.Bold).modifier, []);
|
||||||
const onClick = useCallback(() => {
|
const onClick = useCallback(() => {
|
||||||
CustomEditor.toggleMark(editor, {
|
CustomEditor.toggleMark(editor, {
|
||||||
|
@ -11,20 +11,28 @@ export function Formula() {
|
|||||||
const editor = useSlateStatic();
|
const editor = useSlateStatic();
|
||||||
const isActivatedMention = CustomEditor.isMentionActive(editor);
|
const isActivatedMention = CustomEditor.isMentionActive(editor);
|
||||||
|
|
||||||
|
const formulaMatch = CustomEditor.formulaActiveNode(editor);
|
||||||
const isActivated = !isActivatedMention && CustomEditor.isFormulaActive(editor);
|
const isActivated = !isActivatedMention && CustomEditor.isFormulaActive(editor);
|
||||||
|
|
||||||
const { setRange, openPopover } = useEditorInlineBlockState('formula');
|
const { setRange, openPopover } = useEditorInlineBlockState('formula');
|
||||||
const onClick = useCallback(() => {
|
const onClick = useCallback(() => {
|
||||||
const selection = editor.selection;
|
let selection = editor.selection;
|
||||||
|
|
||||||
if (!selection) return;
|
if (!selection) return;
|
||||||
|
if (formulaMatch) {
|
||||||
|
selection = editor.range(formulaMatch[1]);
|
||||||
|
editor.select(selection);
|
||||||
|
} else {
|
||||||
CustomEditor.toggleFormula(editor);
|
CustomEditor.toggleFormula(editor);
|
||||||
|
}
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
|
if (!selection) return;
|
||||||
|
|
||||||
setRange(selection);
|
setRange(selection);
|
||||||
openPopover();
|
openPopover();
|
||||||
});
|
});
|
||||||
}, [editor, setRange, openPopover]);
|
}, [editor, formulaMatch, setRange, openPopover]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
|
@ -4,7 +4,7 @@ import { generateId } from '$app/components/editor/provider/utils/convert';
|
|||||||
import { YDelta2Delta } from '$app/components/editor/provider/utils/delta';
|
import { YDelta2Delta } from '$app/components/editor/provider/utils/delta';
|
||||||
import { YDelta } from '$app/components/editor/provider/types/y_event';
|
import { YDelta } from '$app/components/editor/provider/types/y_event';
|
||||||
import { getInsertTarget, getYTarget } from '$app/components/editor/provider/utils/relation';
|
import { getInsertTarget, getYTarget } from '$app/components/editor/provider/utils/relation';
|
||||||
import { EditorNodeType } from '$app/application/document/document.types';
|
import { EditorInlineNodeType, EditorNodeType } from '$app/application/document/document.types';
|
||||||
import { Log } from '$app/utils/log';
|
import { Log } from '$app/utils/log';
|
||||||
|
|
||||||
export function YEvents2BlockActions(
|
export function YEvents2BlockActions(
|
||||||
@ -36,6 +36,30 @@ export function YEvent2BlockActions(
|
|||||||
const backupTarget = getYTarget(backupDoc, path) as Readonly<Y.XmlText>;
|
const backupTarget = getYTarget(backupDoc, path) as Readonly<Y.XmlText>;
|
||||||
const actions = [];
|
const actions = [];
|
||||||
|
|
||||||
|
if ([EditorInlineNodeType.Formula, EditorInlineNodeType.Mention].includes(yXmlText.getAttribute('type'))) {
|
||||||
|
const parentYXmlText = yXmlText.parent as Y.XmlText;
|
||||||
|
const parentDelta = parentYXmlText.toDelta() as YDelta;
|
||||||
|
const index = parentDelta.findIndex((op) => op.insert === yXmlText);
|
||||||
|
const ops = YDelta2Delta(parentDelta);
|
||||||
|
|
||||||
|
const retainIndex = ops.reduce((acc, op, currentIndex) => {
|
||||||
|
if (currentIndex < index) {
|
||||||
|
return acc + (op.insert as string).length ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
const newDelta = [
|
||||||
|
{
|
||||||
|
retain: retainIndex,
|
||||||
|
},
|
||||||
|
...delta,
|
||||||
|
];
|
||||||
|
|
||||||
|
actions.push(...generateApplyTextActions(parentYXmlText, newDelta));
|
||||||
|
}
|
||||||
|
|
||||||
if (yXmlText.getAttribute('type') === 'text') {
|
if (yXmlText.getAttribute('type') === 'text') {
|
||||||
actions.push(...textOps2BlockActions(rootId, yXmlText, delta));
|
actions.push(...textOps2BlockActions(rootId, yXmlText, delta));
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ export function transformToInlineElement(op: Op): Element | null {
|
|||||||
const attributes = op.attributes;
|
const attributes = op.attributes;
|
||||||
|
|
||||||
if (!attributes) return null;
|
if (!attributes) return null;
|
||||||
const formula = attributes.formula as string;
|
const { formula, mention, ...attrs } = attributes;
|
||||||
|
|
||||||
if (formula) {
|
if (formula) {
|
||||||
return {
|
return {
|
||||||
@ -20,23 +20,23 @@ export function transformToInlineElement(op: Op): Element | null {
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
text: op.insert as string,
|
text: op.insert as string,
|
||||||
|
...attrs,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const matchMention = attributes.mention as Mention;
|
if (mention) {
|
||||||
|
|
||||||
if (matchMention) {
|
|
||||||
return {
|
return {
|
||||||
type: EditorInlineNodeType.Mention,
|
type: EditorInlineNodeType.Mention,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
text: op.insert as string,
|
text: op.insert as string,
|
||||||
|
...attrs,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
data: {
|
data: {
|
||||||
...matchMention,
|
...(mention as Mention),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { createContext, useCallback, useContext, useMemo } from 'react';
|
import { createContext, useCallback, useContext, useMemo } from 'react';
|
||||||
import { BaseRange } from 'slate';
|
import { BaseRange, Path } from 'slate';
|
||||||
import { proxy, useSnapshot } from 'valtio';
|
import { proxy, useSnapshot } from 'valtio';
|
||||||
|
|
||||||
export interface EditorInlineBlockState {
|
export interface EditorInlineBlockState {
|
||||||
@ -43,8 +43,10 @@ export function useEditorInlineBlockState(key: 'formula') {
|
|||||||
}, [context, key]);
|
}, [context, key]);
|
||||||
|
|
||||||
const setRange = useCallback(
|
const setRange = useCallback(
|
||||||
(range: BaseRange) => {
|
(at: BaseRange | Path) => {
|
||||||
context[key].range = range;
|
const range = Path.isPath(at) ? { anchor: at, focus: at } : at;
|
||||||
|
|
||||||
|
context[key].range = range as BaseRange;
|
||||||
},
|
},
|
||||||
[context, key]
|
[context, key]
|
||||||
);
|
);
|
||||||
|
@ -70,12 +70,12 @@
|
|||||||
"copyLink": "نسخ الرابط"
|
"copyLink": "نسخ الرابط"
|
||||||
},
|
},
|
||||||
"moreAction": {
|
"moreAction": {
|
||||||
"fontSize": "حجم الخط",
|
|
||||||
"import": "استيراد",
|
|
||||||
"moreOptions": "المزيد من الخيارات",
|
|
||||||
"small": "صغير",
|
"small": "صغير",
|
||||||
"medium": "متوسط",
|
"medium": "متوسط",
|
||||||
"large": "كبير"
|
"large": "كبير",
|
||||||
|
"fontSize": "حجم الخط",
|
||||||
|
"import": "استيراد",
|
||||||
|
"moreOptions": "المزيد من الخيارات"
|
||||||
},
|
},
|
||||||
"importPanel": {
|
"importPanel": {
|
||||||
"textAndMarkdown": "نص و Markdown",
|
"textAndMarkdown": "نص و Markdown",
|
||||||
|
@ -69,12 +69,12 @@
|
|||||||
"copyLink": "Copiar l'enllaç"
|
"copyLink": "Copiar l'enllaç"
|
||||||
},
|
},
|
||||||
"moreAction": {
|
"moreAction": {
|
||||||
"fontSize": "Mida de la font",
|
|
||||||
"import": "Importar",
|
|
||||||
"moreOptions": "Més opcions",
|
|
||||||
"small": "petit",
|
"small": "petit",
|
||||||
"medium": "mitjà",
|
"medium": "mitjà",
|
||||||
"large": "gran"
|
"large": "gran",
|
||||||
|
"fontSize": "Mida de la font",
|
||||||
|
"import": "Importar",
|
||||||
|
"moreOptions": "Més opcions"
|
||||||
},
|
},
|
||||||
"importPanel": {
|
"importPanel": {
|
||||||
"textAndMarkdown": "Text i rebaixa",
|
"textAndMarkdown": "Text i rebaixa",
|
||||||
|
@ -69,12 +69,12 @@
|
|||||||
"copyLink": "Link kopieren"
|
"copyLink": "Link kopieren"
|
||||||
},
|
},
|
||||||
"moreAction": {
|
"moreAction": {
|
||||||
"fontSize": "Schriftgröße",
|
|
||||||
"import": "Importieren",
|
|
||||||
"moreOptions": "Mehr Optionen",
|
|
||||||
"small": "klein",
|
"small": "klein",
|
||||||
"medium": "mittel",
|
"medium": "mittel",
|
||||||
"large": "groß"
|
"large": "groß",
|
||||||
|
"fontSize": "Schriftgröße",
|
||||||
|
"import": "Importieren",
|
||||||
|
"moreOptions": "Mehr Optionen"
|
||||||
},
|
},
|
||||||
"importPanel": {
|
"importPanel": {
|
||||||
"textAndMarkdown": "Text & Markdown",
|
"textAndMarkdown": "Text & Markdown",
|
||||||
|
@ -394,7 +394,8 @@
|
|||||||
"twelveHour": "Twelve hour",
|
"twelveHour": "Twelve hour",
|
||||||
"twentyFourHour": "Twenty four hour"
|
"twentyFourHour": "Twenty four hour"
|
||||||
},
|
},
|
||||||
"showNamingDialogWhenCreatingPage": "Show naming dialog when creating a page"
|
"showNamingDialogWhenCreatingPage": "Show naming dialog when creating a page",
|
||||||
|
"enableRTLToolbarItems": "Enable RTL toolbar items"
|
||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"copy": "Copy",
|
"copy": "Copy",
|
||||||
@ -550,7 +551,15 @@
|
|||||||
"onOrAfter": "Is on or after",
|
"onOrAfter": "Is on or after",
|
||||||
"between": "Is between",
|
"between": "Is between",
|
||||||
"empty": "Is empty",
|
"empty": "Is empty",
|
||||||
"notEmpty": "Is not empty"
|
"notEmpty": "Is not empty",
|
||||||
|
"choicechipPrefix": {
|
||||||
|
"before": "Before",
|
||||||
|
"after": "After",
|
||||||
|
"onOrBefore": "On or before",
|
||||||
|
"onOrAfter": "On or after",
|
||||||
|
"isEmpty": "Is empty",
|
||||||
|
"isNotEmpty": "Is not empty"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"numberFilter": {
|
"numberFilter": {
|
||||||
"equal": "Equals",
|
"equal": "Equals",
|
||||||
@ -932,7 +941,6 @@
|
|||||||
"addToColumnBottomTooltip": "Add a new card at the bottom",
|
"addToColumnBottomTooltip": "Add a new card at the bottom",
|
||||||
"renameColumn": "Rename",
|
"renameColumn": "Rename",
|
||||||
"hideColumn": "Hide",
|
"hideColumn": "Hide",
|
||||||
"groupActions": "Group Actions",
|
|
||||||
"newGroup": "New Group",
|
"newGroup": "New Group",
|
||||||
"deleteColumn": "Delete",
|
"deleteColumn": "Delete",
|
||||||
"deleteColumnConfirmation": "This will delete this group and all the cards in it.\nAre you sure you want to continue?"
|
"deleteColumnConfirmation": "This will delete this group and all the cards in it.\nAre you sure you want to continue?"
|
||||||
|
@ -72,12 +72,12 @@
|
|||||||
"copyLink": "Copiar enlace"
|
"copyLink": "Copiar enlace"
|
||||||
},
|
},
|
||||||
"moreAction": {
|
"moreAction": {
|
||||||
"fontSize": "Tamaño de fuente",
|
|
||||||
"import": "Importar",
|
|
||||||
"moreOptions": "Mas opciones",
|
|
||||||
"small": "pequeño",
|
"small": "pequeño",
|
||||||
"medium": "medio",
|
"medium": "medio",
|
||||||
"large": "grande"
|
"large": "grande",
|
||||||
|
"fontSize": "Tamaño de fuente",
|
||||||
|
"import": "Importar",
|
||||||
|
"moreOptions": "Mas opciones"
|
||||||
},
|
},
|
||||||
"importPanel": {
|
"importPanel": {
|
||||||
"textAndMarkdown": "Texto y descuento",
|
"textAndMarkdown": "Texto y descuento",
|
||||||
|
@ -50,12 +50,12 @@
|
|||||||
"copyLink": "Esteka kopiatu"
|
"copyLink": "Esteka kopiatu"
|
||||||
},
|
},
|
||||||
"moreAction": {
|
"moreAction": {
|
||||||
"fontSize": "Letra tamaina",
|
|
||||||
"import": "Inportatu",
|
|
||||||
"moreOptions": "Aukera gehiago",
|
|
||||||
"small": "txikia",
|
"small": "txikia",
|
||||||
"medium": "ertaina",
|
"medium": "ertaina",
|
||||||
"large": "handia"
|
"large": "handia",
|
||||||
|
"fontSize": "Letra tamaina",
|
||||||
|
"import": "Inportatu",
|
||||||
|
"moreOptions": "Aukera gehiago"
|
||||||
},
|
},
|
||||||
"importPanel": {
|
"importPanel": {
|
||||||
"textAndMarkdown": "Testua eta Markdown",
|
"textAndMarkdown": "Testua eta Markdown",
|
||||||
@ -159,7 +159,9 @@
|
|||||||
"editContact": "Kontaktua editatu"
|
"editContact": "Kontaktua editatu"
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
|
"ok": "OK",
|
||||||
"done": "Eginda",
|
"done": "Eginda",
|
||||||
|
"cancel": "Ezteztatu",
|
||||||
"signIn": "Saioa hasi",
|
"signIn": "Saioa hasi",
|
||||||
"signOut": "Saioa itxi",
|
"signOut": "Saioa itxi",
|
||||||
"complete": "Burututa",
|
"complete": "Burututa",
|
||||||
@ -175,9 +177,7 @@
|
|||||||
"edit": "Editatu",
|
"edit": "Editatu",
|
||||||
"delete": "Ezabatu",
|
"delete": "Ezabatu",
|
||||||
"duplicate": "Bikoiztu",
|
"duplicate": "Bikoiztu",
|
||||||
"putback": "Jarri Atzera",
|
"putback": "Jarri Atzera"
|
||||||
"cancel": "Ezteztatu",
|
|
||||||
"ok": "OK"
|
|
||||||
},
|
},
|
||||||
"label": {
|
"label": {
|
||||||
"welcome": "Ongi etorri!",
|
"welcome": "Ongi etorri!",
|
||||||
|
@ -55,12 +55,12 @@
|
|||||||
"copyLink": "کپی کردن لینک"
|
"copyLink": "کپی کردن لینک"
|
||||||
},
|
},
|
||||||
"moreAction": {
|
"moreAction": {
|
||||||
"fontSize": "اندازه قلم",
|
|
||||||
"import": "اضافه کردن",
|
|
||||||
"moreOptions": "گزینه های بیشتر",
|
|
||||||
"small": "کوچک",
|
"small": "کوچک",
|
||||||
"medium": "متوسط",
|
"medium": "متوسط",
|
||||||
"large": "بزرگ"
|
"large": "بزرگ",
|
||||||
|
"fontSize": "اندازه قلم",
|
||||||
|
"import": "اضافه کردن",
|
||||||
|
"moreOptions": "گزینه های بیشتر"
|
||||||
},
|
},
|
||||||
"importPanel": {
|
"importPanel": {
|
||||||
"textAndMarkdown": "Text & Markdown",
|
"textAndMarkdown": "Text & Markdown",
|
||||||
@ -175,7 +175,9 @@
|
|||||||
"editContact": "ویرایش مخاطب"
|
"editContact": "ویرایش مخاطب"
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
|
"ok": "باشه",
|
||||||
"done": "انجام شد",
|
"done": "انجام شد",
|
||||||
|
"cancel": "لغو",
|
||||||
"signIn": "ورود",
|
"signIn": "ورود",
|
||||||
"signOut": "خروج",
|
"signOut": "خروج",
|
||||||
"complete": "کامل شد",
|
"complete": "کامل شد",
|
||||||
@ -191,9 +193,7 @@
|
|||||||
"edit": "ویرایش",
|
"edit": "ویرایش",
|
||||||
"delete": "حذف کردن",
|
"delete": "حذف کردن",
|
||||||
"duplicate": "تکرار کردن",
|
"duplicate": "تکرار کردن",
|
||||||
"putback": "بازگشت",
|
"putback": "بازگشت"
|
||||||
"cancel": "لغو",
|
|
||||||
"ok": "باشه"
|
|
||||||
},
|
},
|
||||||
"label": {
|
"label": {
|
||||||
"welcome": "خوش آمدید!",
|
"welcome": "خوش آمدید!",
|
||||||
|
@ -50,12 +50,12 @@
|
|||||||
"copyLink": "Link másolása"
|
"copyLink": "Link másolása"
|
||||||
},
|
},
|
||||||
"moreAction": {
|
"moreAction": {
|
||||||
"fontSize": "Betűméret",
|
|
||||||
"import": "Importálás",
|
|
||||||
"moreOptions": "Több lehetőség",
|
|
||||||
"small": "kicsi",
|
"small": "kicsi",
|
||||||
"medium": "közepes",
|
"medium": "közepes",
|
||||||
"large": "nagy"
|
"large": "nagy",
|
||||||
|
"fontSize": "Betűméret",
|
||||||
|
"import": "Importálás",
|
||||||
|
"moreOptions": "Több lehetőség"
|
||||||
},
|
},
|
||||||
"importPanel": {
|
"importPanel": {
|
||||||
"textAndMarkdown": "Szöveg & Markdown",
|
"textAndMarkdown": "Szöveg & Markdown",
|
||||||
@ -163,7 +163,9 @@
|
|||||||
"editContact": "Kontakt Szerkesztése"
|
"editContact": "Kontakt Szerkesztése"
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
|
"ok": "OK",
|
||||||
"done": "Kész",
|
"done": "Kész",
|
||||||
|
"cancel": "Mégse",
|
||||||
"signIn": "Bejelentkezés",
|
"signIn": "Bejelentkezés",
|
||||||
"signOut": "Kijelentkezés",
|
"signOut": "Kijelentkezés",
|
||||||
"complete": "Kész",
|
"complete": "Kész",
|
||||||
@ -179,9 +181,7 @@
|
|||||||
"edit": "Szerkesztés",
|
"edit": "Szerkesztés",
|
||||||
"delete": "Töröl",
|
"delete": "Töröl",
|
||||||
"duplicate": "Másolat",
|
"duplicate": "Másolat",
|
||||||
"putback": "Visszatesz",
|
"putback": "Visszatesz"
|
||||||
"cancel": "Mégse",
|
|
||||||
"ok": "OK"
|
|
||||||
},
|
},
|
||||||
"label": {
|
"label": {
|
||||||
"welcome": "Üdvözlünk!",
|
"welcome": "Üdvözlünk!",
|
||||||
|
@ -72,12 +72,12 @@
|
|||||||
"copyLink": "Salin tautan"
|
"copyLink": "Salin tautan"
|
||||||
},
|
},
|
||||||
"moreAction": {
|
"moreAction": {
|
||||||
"fontSize": "Ukuran huruf",
|
|
||||||
"import": "Impor",
|
|
||||||
"moreOptions": "Lebih banyak pilihan",
|
|
||||||
"small": "kecil",
|
"small": "kecil",
|
||||||
"medium": "sedang",
|
"medium": "sedang",
|
||||||
"large": "besar"
|
"large": "besar",
|
||||||
|
"fontSize": "Ukuran huruf",
|
||||||
|
"import": "Impor",
|
||||||
|
"moreOptions": "Lebih banyak pilihan"
|
||||||
},
|
},
|
||||||
"importPanel": {
|
"importPanel": {
|
||||||
"textAndMarkdown": "Teks & Markdown",
|
"textAndMarkdown": "Teks & Markdown",
|
||||||
|
@ -59,6 +59,8 @@
|
|||||||
"failedToLoad": "Qualcosa è andato storto! Impossibile caricare lo spazio di lavoro. Prova a chiudere qualsiasi istanza aperta di AppFlowy e riprova.",
|
"failedToLoad": "Qualcosa è andato storto! Impossibile caricare lo spazio di lavoro. Prova a chiudere qualsiasi istanza aperta di AppFlowy e riprova.",
|
||||||
"errorActions": {
|
"errorActions": {
|
||||||
"reportIssue": "Segnala un problema",
|
"reportIssue": "Segnala un problema",
|
||||||
|
"reportIssueOnGithub": "Segnalate un problema su Github",
|
||||||
|
"exportLogFiles": "Esporta i file di log",
|
||||||
"reachOut": "Contattaci su Discord"
|
"reachOut": "Contattaci su Discord"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -70,12 +72,16 @@
|
|||||||
"copyLink": "Copia Link"
|
"copyLink": "Copia Link"
|
||||||
},
|
},
|
||||||
"moreAction": {
|
"moreAction": {
|
||||||
|
"small": "piccolo",
|
||||||
|
"medium": "medio",
|
||||||
|
"large": "grande",
|
||||||
"fontSize": "Dimensione del font",
|
"fontSize": "Dimensione del font",
|
||||||
"import": "Importare",
|
"import": "Importare",
|
||||||
"moreOptions": "Più opzioni",
|
"moreOptions": "Più opzioni",
|
||||||
"small": "piccolo",
|
"wordCount": "Conteggio parole: {}",
|
||||||
"medium": "medio",
|
"charCount": "Numero di caratteri: {}",
|
||||||
"large": "grande"
|
"deleteView": "Cancella",
|
||||||
|
"duplicateView": "Duplica"
|
||||||
},
|
},
|
||||||
"importPanel": {
|
"importPanel": {
|
||||||
"textAndMarkdown": "Testo e markdown",
|
"textAndMarkdown": "Testo e markdown",
|
||||||
@ -231,7 +237,11 @@
|
|||||||
"rename": "Rinomina",
|
"rename": "Rinomina",
|
||||||
"helpCenter": "Centro assistenza",
|
"helpCenter": "Centro assistenza",
|
||||||
"add": "Aggiungi",
|
"add": "Aggiungi",
|
||||||
"yes": "SÌ"
|
"yes": "SÌ",
|
||||||
|
"Done": "Fatto",
|
||||||
|
"Cancel": "Annulla",
|
||||||
|
"remove": "Rimuovi",
|
||||||
|
"dontRemove": "Non rimuovere"
|
||||||
},
|
},
|
||||||
"label": {
|
"label": {
|
||||||
"welcome": "Benvenuto!",
|
"welcome": "Benvenuto!",
|
||||||
@ -276,7 +286,10 @@
|
|||||||
"cloudLocal": "Locale",
|
"cloudLocal": "Locale",
|
||||||
"cloudSupabase": "Supabase",
|
"cloudSupabase": "Supabase",
|
||||||
"cloudSupabaseUrl": "URL di Supabase",
|
"cloudSupabaseUrl": "URL di Supabase",
|
||||||
|
"cloudSupabaseUrlCanNotBeEmpty": "L'url di supabase non può essere vuoto",
|
||||||
"cloudAppFlowy": "AppFlowy Cloud",
|
"cloudAppFlowy": "AppFlowy Cloud",
|
||||||
|
"cloudAppFlowySelfHost": "AppFlowy Cloud Self-hosted (autogestito)",
|
||||||
|
"appFlowyCloudUrlCanNotBeEmpty": "L'url del cloud non può essere vuoto",
|
||||||
"clickToCopy": "Fare clic per copiare",
|
"clickToCopy": "Fare clic per copiare",
|
||||||
"selfHostStart": "Se non disponi di un server, fai riferimento a",
|
"selfHostStart": "Se non disponi di un server, fai riferimento a",
|
||||||
"selfHostContent": "documento",
|
"selfHostContent": "documento",
|
||||||
@ -286,6 +299,7 @@
|
|||||||
"cloudWSURLHint": "Inserisci l'indirizzo websocket del tuo server",
|
"cloudWSURLHint": "Inserisci l'indirizzo websocket del tuo server",
|
||||||
"restartApp": "Riavvia",
|
"restartApp": "Riavvia",
|
||||||
"restartAppTip": "Riavvia l'applicazione affinché le modifiche abbiano effetto. Tieni presente che ciò potrebbe disconnettere il tuo account corrente",
|
"restartAppTip": "Riavvia l'applicazione affinché le modifiche abbiano effetto. Tieni presente che ciò potrebbe disconnettere il tuo account corrente",
|
||||||
|
"changeServerTip": "Dopo aver modificato il server, è necessario fare clic sul pulsante di riavvio affinché le modifiche abbiano effetto.",
|
||||||
"enableEncryptPrompt": "Attiva la crittografia per proteggere i tuoi dati con questo segreto. Conservarlo in modo sicuro; una volta abilitato, non può essere spento. In caso di perdita, i tuoi dati diventano irrecuperabili. Fare clic per copiare",
|
"enableEncryptPrompt": "Attiva la crittografia per proteggere i tuoi dati con questo segreto. Conservarlo in modo sicuro; una volta abilitato, non può essere spento. In caso di perdita, i tuoi dati diventano irrecuperabili. Fare clic per copiare",
|
||||||
"inputEncryptPrompt": "Inserisci il tuo segreto di crittografia per",
|
"inputEncryptPrompt": "Inserisci il tuo segreto di crittografia per",
|
||||||
"clickToCopySecret": "Fare clic per copiare il segreto",
|
"clickToCopySecret": "Fare clic per copiare il segreto",
|
||||||
@ -297,6 +311,7 @@
|
|||||||
"openHistoricalUser": "Fare clic per aprire l'account anonimo",
|
"openHistoricalUser": "Fare clic per aprire l'account anonimo",
|
||||||
"customPathPrompt": "L'archiviazione della cartella dati di AppFlowy in una cartella sincronizzata sul cloud come Google Drive può comportare rischi. Se si accede o si modifica il database all'interno di questa cartella da più posizioni contemporaneamente, potrebbero verificarsi conflitti di sincronizzazione e potenziale danneggiamento dei dati",
|
"customPathPrompt": "L'archiviazione della cartella dati di AppFlowy in una cartella sincronizzata sul cloud come Google Drive può comportare rischi. Se si accede o si modifica il database all'interno di questa cartella da più posizioni contemporaneamente, potrebbero verificarsi conflitti di sincronizzazione e potenziale danneggiamento dei dati",
|
||||||
"importAppFlowyData": "Importa dati dalla cartella AppFlowy esterna",
|
"importAppFlowyData": "Importa dati dalla cartella AppFlowy esterna",
|
||||||
|
"importingAppFlowyDataTip": "L'importazione dei dati è in corso. Non chiudere l'applicazione",
|
||||||
"importAppFlowyDataDescription": "Copia i dati da una cartella dati AppFlowy esterna e importali nella cartella dati AppFlowy corrente",
|
"importAppFlowyDataDescription": "Copia i dati da una cartella dati AppFlowy esterna e importali nella cartella dati AppFlowy corrente",
|
||||||
"importSuccess": "Importazione della cartella dati AppFlowy riuscita",
|
"importSuccess": "Importazione della cartella dati AppFlowy riuscita",
|
||||||
"importFailed": "L'importazione della cartella dati di AppFlowy non è riuscita",
|
"importFailed": "L'importazione della cartella dati di AppFlowy non è riuscita",
|
||||||
@ -441,10 +456,12 @@
|
|||||||
"joinDiscord": "Unisciti a noi su Discord",
|
"joinDiscord": "Unisciti a noi su Discord",
|
||||||
"privacyPolicy": "Politica sulla riservatezza",
|
"privacyPolicy": "Politica sulla riservatezza",
|
||||||
"userAgreement": "Accordo per gli utenti",
|
"userAgreement": "Accordo per gli utenti",
|
||||||
|
"termsAndConditions": "Termini e Condizioni",
|
||||||
"userprofileError": "Impossibile caricare il profilo utente",
|
"userprofileError": "Impossibile caricare il profilo utente",
|
||||||
"userprofileErrorDescription": "Prova a disconnetterti e ad accedere nuovamente per verificare se il problema persiste.",
|
"userprofileErrorDescription": "Prova a disconnetterti e ad accedere nuovamente per verificare se il problema persiste.",
|
||||||
"selectLayout": "Seleziona disposizione",
|
"selectLayout": "Seleziona disposizione",
|
||||||
"selectStartingDay": "Seleziona il giorno di inizio"
|
"selectStartingDay": "Seleziona il giorno di inizio",
|
||||||
|
"version": "Versione"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"grid": {
|
"grid": {
|
||||||
@ -466,14 +483,14 @@
|
|||||||
"typeAValue": "Digita un valore...",
|
"typeAValue": "Digita un valore...",
|
||||||
"layout": "Disposizione",
|
"layout": "Disposizione",
|
||||||
"databaseLayout": "Disposizione",
|
"databaseLayout": "Disposizione",
|
||||||
|
"viewList": "Viste del database",
|
||||||
"editView": "Modifica vista",
|
"editView": "Modifica vista",
|
||||||
"boardSettings": "Impostazioni della bacheca",
|
"boardSettings": "Impostazioni della bacheca",
|
||||||
"calendarSettings": "Impostazioni del calendario",
|
"calendarSettings": "Impostazioni del calendario",
|
||||||
"createView": "Nuova vista",
|
"createView": "Nuova vista",
|
||||||
"duplicateView": "Duplica vista",
|
"duplicateView": "Duplica vista",
|
||||||
"deleteView": "Elimina vista",
|
"deleteView": "Elimina vista",
|
||||||
"numberOfVisibleFields": "{} mostrato",
|
"numberOfVisibleFields": "{} mostrato"
|
||||||
"viewList": "Viste del database"
|
|
||||||
},
|
},
|
||||||
"textFilter": {
|
"textFilter": {
|
||||||
"contains": "Contiene",
|
"contains": "Contiene",
|
||||||
@ -525,6 +542,16 @@
|
|||||||
"empty": "È vuoto",
|
"empty": "È vuoto",
|
||||||
"notEmpty": "Non è vuoto"
|
"notEmpty": "Non è vuoto"
|
||||||
},
|
},
|
||||||
|
"numberFilter": {
|
||||||
|
"equal": "Uguale",
|
||||||
|
"notEqual": "Non è uguale",
|
||||||
|
"lessThan": "È meno di",
|
||||||
|
"greaterThan": "È maggiore di",
|
||||||
|
"lessThanOrEqualTo": "È inferiore o uguale a",
|
||||||
|
"greaterThanOrEqualTo": "È maggiore o uguale a",
|
||||||
|
"isEmpty": "È vuoto",
|
||||||
|
"isNotEmpty": "Non è vuoto"
|
||||||
|
},
|
||||||
"field": {
|
"field": {
|
||||||
"hide": "Nascondere",
|
"hide": "Nascondere",
|
||||||
"show": "Mostra",
|
"show": "Mostra",
|
||||||
@ -556,6 +583,7 @@
|
|||||||
"timeFormatTwelveHour": "12 ore",
|
"timeFormatTwelveHour": "12 ore",
|
||||||
"timeFormatTwentyFourHour": "24 ore",
|
"timeFormatTwentyFourHour": "24 ore",
|
||||||
"clearDate": "Pulisci data",
|
"clearDate": "Pulisci data",
|
||||||
|
"dateTime": "Data e ora",
|
||||||
"failedToLoadDate": "Impossibile caricare il valore della data",
|
"failedToLoadDate": "Impossibile caricare il valore della data",
|
||||||
"selectTime": "Seleziona l'ora",
|
"selectTime": "Seleziona l'ora",
|
||||||
"selectDate": "Seleziona la data",
|
"selectDate": "Seleziona la data",
|
||||||
@ -569,7 +597,9 @@
|
|||||||
"newProperty": "Nuova proprietà",
|
"newProperty": "Nuova proprietà",
|
||||||
"deleteFieldPromptMessage": "Sei sicuro? Questa proprietà verrà eliminata",
|
"deleteFieldPromptMessage": "Sei sicuro? Questa proprietà verrà eliminata",
|
||||||
"newColumn": "Nuova colonna",
|
"newColumn": "Nuova colonna",
|
||||||
"format": "Formato"
|
"format": "Formato",
|
||||||
|
"reminderOnDateTooltip": "Questa cella ha un promemoria programmato",
|
||||||
|
"optionAlreadyExist": "L'opzione esiste già"
|
||||||
},
|
},
|
||||||
"rowPage": {
|
"rowPage": {
|
||||||
"newField": "Aggiungi un nuovo campo",
|
"newField": "Aggiungi un nuovo campo",
|
||||||
@ -588,6 +618,7 @@
|
|||||||
"sort": {
|
"sort": {
|
||||||
"ascending": "Ascendente",
|
"ascending": "Ascendente",
|
||||||
"descending": "Discendente",
|
"descending": "Discendente",
|
||||||
|
"cannotFindCreatableField": "Impossibile trovare un campo adatto per l'ordinamento",
|
||||||
"deleteAllSorts": "Elimina tutti gli ordinamenti",
|
"deleteAllSorts": "Elimina tutti gli ordinamenti",
|
||||||
"addSort": "Aggiungi ordinamento",
|
"addSort": "Aggiungi ordinamento",
|
||||||
"deleteSort": "Elimina ordinamento"
|
"deleteSort": "Elimina ordinamento"
|
||||||
@ -636,7 +667,16 @@
|
|||||||
"showComplete": "Mostra tutte le attività"
|
"showComplete": "Mostra tutte le attività"
|
||||||
},
|
},
|
||||||
"menuName": "Griglia",
|
"menuName": "Griglia",
|
||||||
"referencedGridPrefix": "Vista di"
|
"referencedGridPrefix": "Vista di",
|
||||||
|
"calculate": "Calcolare",
|
||||||
|
"calculationTypeLabel": {
|
||||||
|
"average": "Media",
|
||||||
|
"max": "Massimo",
|
||||||
|
"median": "Medio",
|
||||||
|
"min": "Minimo",
|
||||||
|
"sum": "Somma"
|
||||||
|
},
|
||||||
|
"removeSorting": "Si desidera rimuovere l'ordinamento?"
|
||||||
},
|
},
|
||||||
"document": {
|
"document": {
|
||||||
"menuName": "Documento",
|
"menuName": "Documento",
|
||||||
@ -742,13 +782,16 @@
|
|||||||
},
|
},
|
||||||
"image": {
|
"image": {
|
||||||
"copiedToPasteBoard": "Il link dell'immagine è stato copiato negli appunti",
|
"copiedToPasteBoard": "Il link dell'immagine è stato copiato negli appunti",
|
||||||
"addAnImage": "Aggiungi un'immagine"
|
"addAnImage": "Aggiungi un'immagine",
|
||||||
|
"imageUploadFailed": "Caricamento dell'immagine non riuscito"
|
||||||
},
|
},
|
||||||
"urlPreview": {
|
"urlPreview": {
|
||||||
"copiedToPasteBoard": "Il link è stato copiato negli appunti"
|
"copiedToPasteBoard": "Il link è stato copiato negli appunti",
|
||||||
|
"convertToLink": "Convertire in link da incorporare"
|
||||||
},
|
},
|
||||||
"outline": {
|
"outline": {
|
||||||
"addHeadingToCreateOutline": "Aggiungi intestazioni per creare un sommario."
|
"addHeadingToCreateOutline": "Aggiungi intestazioni per creare un sommario.",
|
||||||
|
"noMatchHeadings": "Non sono stati trovati titoli corrispondenti."
|
||||||
},
|
},
|
||||||
"table": {
|
"table": {
|
||||||
"addAfter": "Aggiungi dopo",
|
"addAfter": "Aggiungi dopo",
|
||||||
@ -771,7 +814,9 @@
|
|||||||
"toContinue": "continuare",
|
"toContinue": "continuare",
|
||||||
"newDatabase": "Nuova banca dati",
|
"newDatabase": "Nuova banca dati",
|
||||||
"linkToDatabase": "Collegamento alla banca dati"
|
"linkToDatabase": "Collegamento alla banca dati"
|
||||||
}
|
},
|
||||||
|
"date": "Data",
|
||||||
|
"emoji": "Emoji"
|
||||||
},
|
},
|
||||||
"textBlock": {
|
"textBlock": {
|
||||||
"placeholder": "Digita '/' per i comandi"
|
"placeholder": "Digita '/' per i comandi"
|
||||||
@ -817,7 +862,10 @@
|
|||||||
"saveImageToGallery": "Salva immagine",
|
"saveImageToGallery": "Salva immagine",
|
||||||
"failedToAddImageToGallery": "Impossibile aggiungere l'immagine alla galleria",
|
"failedToAddImageToGallery": "Impossibile aggiungere l'immagine alla galleria",
|
||||||
"successToAddImageToGallery": "Immagine aggiunta alla galleria con successo",
|
"successToAddImageToGallery": "Immagine aggiunta alla galleria con successo",
|
||||||
"unableToLoadImage": "Impossibile caricare l'immagine"
|
"unableToLoadImage": "Impossibile caricare l'immagine",
|
||||||
|
"maximumImageSize": "La dimensione massima supportata per il caricamento delle immagini è di 10 MB",
|
||||||
|
"uploadImageErrorImageSizeTooBig": "Le dimensioni dell'immagine devono essere inferiori a 10 MB",
|
||||||
|
"imageIsUploading": "L'immagine si sta caricando"
|
||||||
},
|
},
|
||||||
"codeBlock": {
|
"codeBlock": {
|
||||||
"language": {
|
"language": {
|
||||||
@ -844,7 +892,9 @@
|
|||||||
"page": {
|
"page": {
|
||||||
"label": "Collegamento alla pagina",
|
"label": "Collegamento alla pagina",
|
||||||
"tooltip": "Fare clic per aprire la pagina"
|
"tooltip": "Fare clic per aprire la pagina"
|
||||||
}
|
},
|
||||||
|
"deleted": "Eliminato",
|
||||||
|
"deletedContent": "Questo contenuto non esiste o è stato cancellato"
|
||||||
},
|
},
|
||||||
"toolbar": {
|
"toolbar": {
|
||||||
"resetToDefaultFont": "Ripristina alle condizioni predefinite"
|
"resetToDefaultFont": "Ripristina alle condizioni predefinite"
|
||||||
@ -876,6 +926,7 @@
|
|||||||
"cardDuplicated": "La carta è stata duplicata",
|
"cardDuplicated": "La carta è stata duplicata",
|
||||||
"cardDeleted": "La carta è stata eliminata",
|
"cardDeleted": "La carta è stata eliminata",
|
||||||
"showOnCard": "Mostra sui dettagli della carta",
|
"showOnCard": "Mostra sui dettagli della carta",
|
||||||
|
"setting": "Impostazioni",
|
||||||
"propertyName": "Nome della proprietà",
|
"propertyName": "Nome della proprietà",
|
||||||
"menuName": "Bacheca",
|
"menuName": "Bacheca",
|
||||||
"showUngrouped": "Mostra elementi non raggruppati",
|
"showUngrouped": "Mostra elementi non raggruppati",
|
||||||
@ -902,16 +953,21 @@
|
|||||||
"previousMonth": "Il mese scorso",
|
"previousMonth": "Il mese scorso",
|
||||||
"nextMonth": "Il prossimo mese"
|
"nextMonth": "Il prossimo mese"
|
||||||
},
|
},
|
||||||
|
"mobileEventScreen": {
|
||||||
|
"emptyTitle": "Non ci sono ancora eventi",
|
||||||
|
"emptyBody": "Premere il pulsante più per creare un evento in questo giorno."
|
||||||
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"showWeekNumbers": "Mostra i numeri della settimana",
|
"showWeekNumbers": "Mostra i numeri della settimana",
|
||||||
"showWeekends": "Mostra i fine settimana",
|
"showWeekends": "Mostra i fine settimana",
|
||||||
"firstDayOfWeek": "Inizia la settimana",
|
"firstDayOfWeek": "Inizia la settimana",
|
||||||
"layoutDateField": "Layout calendario per",
|
"layoutDateField": "Layout calendario per",
|
||||||
|
"changeLayoutDateField": "Modifica del campo di layout",
|
||||||
"noDateTitle": "Nessuna data",
|
"noDateTitle": "Nessuna data",
|
||||||
|
"noDateHint": "Gli eventi non programmati verranno visualizzati qui",
|
||||||
"unscheduledEventsTitle": "Eventi non programmati",
|
"unscheduledEventsTitle": "Eventi non programmati",
|
||||||
"clickToAdd": "Fare clic per aggiungere al calendario",
|
"clickToAdd": "Fare clic per aggiungere al calendario",
|
||||||
"name": "Disposizione del calendario",
|
"name": "Disposizione del calendario"
|
||||||
"noDateHint": "Gli eventi non programmati verranno visualizzati qui"
|
|
||||||
},
|
},
|
||||||
"referencedCalendarPrefix": "Vista di",
|
"referencedCalendarPrefix": "Vista di",
|
||||||
"quickJumpYear": "Salta a"
|
"quickJumpYear": "Salta a"
|
||||||
@ -983,9 +1039,14 @@
|
|||||||
},
|
},
|
||||||
"inlineActions": {
|
"inlineActions": {
|
||||||
"noResults": "Nessun risultato",
|
"noResults": "Nessun risultato",
|
||||||
|
"pageReference": "Riferimento della pagina",
|
||||||
|
"docReference": "Riferimento al documento",
|
||||||
|
"calReference": "Calendario di riferimento",
|
||||||
|
"gridReference": "Riferimento griglia",
|
||||||
"date": "Data",
|
"date": "Data",
|
||||||
"reminder": {
|
"reminder": {
|
||||||
"groupTitle": "Promemoria"
|
"groupTitle": "Promemoria",
|
||||||
|
"shortKeyword": "ricordare"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"datePicker": {
|
"datePicker": {
|
||||||
@ -994,7 +1055,23 @@
|
|||||||
"includeTime": "Includere l'orario",
|
"includeTime": "Includere l'orario",
|
||||||
"isRange": "Data di fine",
|
"isRange": "Data di fine",
|
||||||
"timeFormat": "Formato orario",
|
"timeFormat": "Formato orario",
|
||||||
"clearDate": "Rimuovi data"
|
"clearDate": "Rimuovi data",
|
||||||
|
"reminderLabel": "Promemoria",
|
||||||
|
"selectReminder": "Seleziona il promemoria",
|
||||||
|
"reminderOptions": {
|
||||||
|
"atTimeOfEvent": "Ora dell'evento",
|
||||||
|
"fiveMinsBefore": "5 minuti prima",
|
||||||
|
"tenMinsBefore": "10 minuti prima",
|
||||||
|
"fifteenMinsBefore": "15 minuti prima",
|
||||||
|
"thirtyMinsBefore": "30 minuti prima",
|
||||||
|
"oneHourBefore": "1 ora prima",
|
||||||
|
"twoHoursBefore": "2 hours before",
|
||||||
|
"onDayOfEvent": "Il giorno dell'evento",
|
||||||
|
"oneDayBefore": "1 giorno prima",
|
||||||
|
"twoDaysBefore": "2 giorni prima",
|
||||||
|
"oneWeekBefore": "1 settimana prima",
|
||||||
|
"custom": "Personalizzato"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"relativeDates": {
|
"relativeDates": {
|
||||||
"yesterday": "Ieri",
|
"yesterday": "Ieri",
|
||||||
@ -1010,6 +1087,7 @@
|
|||||||
"emptyTitle": "Tutto a posto!",
|
"emptyTitle": "Tutto a posto!",
|
||||||
"emptyBody": "Nessuna notifica o azione in sospeso. Goditi la calma.",
|
"emptyBody": "Nessuna notifica o azione in sospeso. Goditi la calma.",
|
||||||
"tabs": {
|
"tabs": {
|
||||||
|
"inbox": "Posta in arrivo",
|
||||||
"upcoming": "Prossimamente"
|
"upcoming": "Prossimamente"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
@ -1057,6 +1135,7 @@
|
|||||||
"highlight": "Evidenzia",
|
"highlight": "Evidenzia",
|
||||||
"color": "Colore",
|
"color": "Colore",
|
||||||
"image": "Immagine",
|
"image": "Immagine",
|
||||||
|
"date": "Data",
|
||||||
"italic": "Corsivo",
|
"italic": "Corsivo",
|
||||||
"link": "Link",
|
"link": "Link",
|
||||||
"numberedList": "Elenco numerato",
|
"numberedList": "Elenco numerato",
|
||||||
@ -1158,7 +1237,10 @@
|
|||||||
"colClear": "Rimuovi contenuto",
|
"colClear": "Rimuovi contenuto",
|
||||||
"rowClear": "Rimuovi contenuto",
|
"rowClear": "Rimuovi contenuto",
|
||||||
"slashPlaceHolder": "Digita \"/\" per inserire un blocco o inizia a digitare",
|
"slashPlaceHolder": "Digita \"/\" per inserire un blocco o inizia a digitare",
|
||||||
"typeSomething": "Scrivi qualcosa..."
|
"typeSomething": "Scrivi qualcosa...",
|
||||||
|
"quoteListShortForm": "Citazione",
|
||||||
|
"mathEquationShortForm": "Formula",
|
||||||
|
"codeBlockShortForm": "Codice"
|
||||||
},
|
},
|
||||||
"favorite": {
|
"favorite": {
|
||||||
"noFavorite": "Nessuna pagina preferita",
|
"noFavorite": "Nessuna pagina preferita",
|
||||||
@ -1182,5 +1264,6 @@
|
|||||||
"date": "Data",
|
"date": "Data",
|
||||||
"addField": "Aggiungi campo",
|
"addField": "Aggiungi campo",
|
||||||
"userIcon": "Icona utente"
|
"userIcon": "Icona utente"
|
||||||
}
|
},
|
||||||
|
"noLogFiles": "Non ci sono file di log"
|
||||||
}
|
}
|
||||||
|
@ -63,12 +63,12 @@
|
|||||||
"copyLink": "リンクをコピー"
|
"copyLink": "リンクをコピー"
|
||||||
},
|
},
|
||||||
"moreAction": {
|
"moreAction": {
|
||||||
"fontSize": "フォントサイズ",
|
|
||||||
"import": "取り込む",
|
|
||||||
"moreOptions": "より多くのオプション",
|
|
||||||
"small": "小さい",
|
"small": "小さい",
|
||||||
"medium": "中くらい",
|
"medium": "中くらい",
|
||||||
"large": "大きい"
|
"large": "大きい",
|
||||||
|
"fontSize": "フォントサイズ",
|
||||||
|
"import": "取り込む",
|
||||||
|
"moreOptions": "より多くのオプション"
|
||||||
},
|
},
|
||||||
"importPanel": {
|
"importPanel": {
|
||||||
"textAndMarkdown": "テキストとマークダウン",
|
"textAndMarkdown": "テキストとマークダウン",
|
||||||
|
@ -50,12 +50,12 @@
|
|||||||
"copyLink": "링크 복사"
|
"copyLink": "링크 복사"
|
||||||
},
|
},
|
||||||
"moreAction": {
|
"moreAction": {
|
||||||
"fontSize": "글꼴 크기",
|
|
||||||
"import": "수입",
|
|
||||||
"moreOptions": "추가 옵션",
|
|
||||||
"small": "작은",
|
"small": "작은",
|
||||||
"medium": "중간",
|
"medium": "중간",
|
||||||
"large": "크기가 큰"
|
"large": "크기가 큰",
|
||||||
|
"fontSize": "글꼴 크기",
|
||||||
|
"import": "수입",
|
||||||
|
"moreOptions": "추가 옵션"
|
||||||
},
|
},
|
||||||
"importPanel": {
|
"importPanel": {
|
||||||
"textAndMarkdown": "텍스트 및 마크다운",
|
"textAndMarkdown": "텍스트 및 마크다운",
|
||||||
@ -159,7 +159,9 @@
|
|||||||
"editContact": "연락처 편집"
|
"editContact": "연락처 편집"
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
|
"ok": "확인",
|
||||||
"done": "완료",
|
"done": "완료",
|
||||||
|
"cancel": "취소",
|
||||||
"signIn": "로그인",
|
"signIn": "로그인",
|
||||||
"signOut": "로그아웃",
|
"signOut": "로그아웃",
|
||||||
"complete": "완료",
|
"complete": "완료",
|
||||||
@ -175,9 +177,7 @@
|
|||||||
"edit": "편집하다",
|
"edit": "편집하다",
|
||||||
"delete": "삭제",
|
"delete": "삭제",
|
||||||
"duplicate": "복제하다",
|
"duplicate": "복제하다",
|
||||||
"putback": "다시 집어 넣어",
|
"putback": "다시 집어 넣어"
|
||||||
"cancel": "취소",
|
|
||||||
"ok": "확인"
|
|
||||||
},
|
},
|
||||||
"label": {
|
"label": {
|
||||||
"welcome": "환영합니다!",
|
"welcome": "환영합니다!",
|
||||||
|
@ -70,12 +70,12 @@
|
|||||||
"copyLink": "Skopiuj link"
|
"copyLink": "Skopiuj link"
|
||||||
},
|
},
|
||||||
"moreAction": {
|
"moreAction": {
|
||||||
"fontSize": "Rozmiar czcionki",
|
|
||||||
"import": "Import",
|
|
||||||
"moreOptions": "Więcej opcji",
|
|
||||||
"small": "mały",
|
"small": "mały",
|
||||||
"medium": "średni",
|
"medium": "średni",
|
||||||
"large": "duży"
|
"large": "duży",
|
||||||
|
"fontSize": "Rozmiar czcionki",
|
||||||
|
"import": "Import",
|
||||||
|
"moreOptions": "Więcej opcji"
|
||||||
},
|
},
|
||||||
"importPanel": {
|
"importPanel": {
|
||||||
"textAndMarkdown": "Tekst i Markdown",
|
"textAndMarkdown": "Tekst i Markdown",
|
||||||
|
@ -72,12 +72,12 @@
|
|||||||
"copyLink": "Copiar link"
|
"copyLink": "Copiar link"
|
||||||
},
|
},
|
||||||
"moreAction": {
|
"moreAction": {
|
||||||
"fontSize": "Tamanho da fonte",
|
|
||||||
"import": "Importar",
|
|
||||||
"moreOptions": "Mais opções",
|
|
||||||
"small": "pequeno",
|
"small": "pequeno",
|
||||||
"medium": "médio",
|
"medium": "médio",
|
||||||
"large": "grande"
|
"large": "grande",
|
||||||
|
"fontSize": "Tamanho da fonte",
|
||||||
|
"import": "Importar",
|
||||||
|
"moreOptions": "Mais opções"
|
||||||
},
|
},
|
||||||
"importPanel": {
|
"importPanel": {
|
||||||
"textAndMarkdown": "Texto e Remarcação",
|
"textAndMarkdown": "Texto e Remarcação",
|
||||||
|
@ -70,12 +70,12 @@
|
|||||||
"copyLink": "Copiar o link"
|
"copyLink": "Copiar o link"
|
||||||
},
|
},
|
||||||
"moreAction": {
|
"moreAction": {
|
||||||
"fontSize": "Tamanho da fonte",
|
|
||||||
"import": "Importar",
|
|
||||||
"moreOptions": "Mais opções",
|
|
||||||
"small": "pequeno",
|
"small": "pequeno",
|
||||||
"medium": "médio",
|
"medium": "médio",
|
||||||
"large": "grande"
|
"large": "grande",
|
||||||
|
"fontSize": "Tamanho da fonte",
|
||||||
|
"import": "Importar",
|
||||||
|
"moreOptions": "Mais opções"
|
||||||
},
|
},
|
||||||
"importPanel": {
|
"importPanel": {
|
||||||
"textAndMarkdown": "Texto e Remarcação",
|
"textAndMarkdown": "Texto e Remarcação",
|
||||||
@ -194,7 +194,9 @@
|
|||||||
"editContact": "Editar um conctato"
|
"editContact": "Editar um conctato"
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
|
"ok": "OK",
|
||||||
"done": "Feito",
|
"done": "Feito",
|
||||||
|
"cancel": "Cancelar",
|
||||||
"signIn": "Entrar",
|
"signIn": "Entrar",
|
||||||
"signOut": "Sair",
|
"signOut": "Sair",
|
||||||
"complete": "Completar",
|
"complete": "Completar",
|
||||||
@ -210,9 +212,7 @@
|
|||||||
"edit": "Editar",
|
"edit": "Editar",
|
||||||
"delete": "Excluir",
|
"delete": "Excluir",
|
||||||
"duplicate": "Duplicado",
|
"duplicate": "Duplicado",
|
||||||
"putback": "Por de volta",
|
"putback": "Por de volta"
|
||||||
"cancel": "Cancelar",
|
|
||||||
"ok": "OK"
|
|
||||||
},
|
},
|
||||||
"label": {
|
"label": {
|
||||||
"welcome": "Bem vindo!",
|
"welcome": "Bem vindo!",
|
||||||
|
@ -70,12 +70,12 @@
|
|||||||
"copyLink": "Скопировать ссылку"
|
"copyLink": "Скопировать ссылку"
|
||||||
},
|
},
|
||||||
"moreAction": {
|
"moreAction": {
|
||||||
"fontSize": "Размер шрифта",
|
|
||||||
"import": "Импорт",
|
|
||||||
"moreOptions": "Дополнительные опции",
|
|
||||||
"small": "маленький",
|
"small": "маленький",
|
||||||
"medium": "средний",
|
"medium": "средний",
|
||||||
"large": "большой"
|
"large": "большой",
|
||||||
|
"fontSize": "Размер шрифта",
|
||||||
|
"import": "Импорт",
|
||||||
|
"moreOptions": "Дополнительные опции"
|
||||||
},
|
},
|
||||||
"importPanel": {
|
"importPanel": {
|
||||||
"textAndMarkdown": "Текст и Markdown",
|
"textAndMarkdown": "Текст и Markdown",
|
||||||
|
@ -69,12 +69,12 @@
|
|||||||
"copyLink": "Bağlantıyı kopyala"
|
"copyLink": "Bağlantıyı kopyala"
|
||||||
},
|
},
|
||||||
"moreAction": {
|
"moreAction": {
|
||||||
"fontSize": "Yazı boyutu",
|
|
||||||
"import": "İçe aktar",
|
|
||||||
"moreOptions": "Diğer seçenekler",
|
|
||||||
"small": "Küçük",
|
"small": "Küçük",
|
||||||
"medium": "Orta",
|
"medium": "Orta",
|
||||||
"large": "Büyük"
|
"large": "Büyük",
|
||||||
|
"fontSize": "Yazı boyutu",
|
||||||
|
"import": "İçe aktar",
|
||||||
|
"moreOptions": "Diğer seçenekler"
|
||||||
},
|
},
|
||||||
"importPanel": {
|
"importPanel": {
|
||||||
"textAndMarkdown": "Metin ve Markdown",
|
"textAndMarkdown": "Metin ve Markdown",
|
||||||
|
@ -72,12 +72,12 @@
|
|||||||
"copyLink": "Sao chép đường dẫn"
|
"copyLink": "Sao chép đường dẫn"
|
||||||
},
|
},
|
||||||
"moreAction": {
|
"moreAction": {
|
||||||
"fontSize": "Cỡ chữ",
|
|
||||||
"import": "Import",
|
|
||||||
"moreOptions": "Lựa chọn khác",
|
|
||||||
"small": "nhỏ",
|
"small": "nhỏ",
|
||||||
"medium": "trung bình",
|
"medium": "trung bình",
|
||||||
"large": "lớn"
|
"large": "lớn",
|
||||||
|
"fontSize": "Cỡ chữ",
|
||||||
|
"import": "Import",
|
||||||
|
"moreOptions": "Lựa chọn khác"
|
||||||
},
|
},
|
||||||
"importPanel": {
|
"importPanel": {
|
||||||
"textAndMarkdown": "Văn bản & Markdown",
|
"textAndMarkdown": "Văn bản & Markdown",
|
||||||
|
@ -74,12 +74,12 @@
|
|||||||
"copyLink": "复制链接"
|
"copyLink": "复制链接"
|
||||||
},
|
},
|
||||||
"moreAction": {
|
"moreAction": {
|
||||||
"fontSize": "字体大小",
|
|
||||||
"import": "导入",
|
|
||||||
"moreOptions": "更多选项",
|
|
||||||
"small": "小",
|
"small": "小",
|
||||||
"medium": "中",
|
"medium": "中",
|
||||||
"large": "大"
|
"large": "大",
|
||||||
|
"fontSize": "字体大小",
|
||||||
|
"import": "导入",
|
||||||
|
"moreOptions": "更多选项"
|
||||||
},
|
},
|
||||||
"importPanel": {
|
"importPanel": {
|
||||||
"textAndMarkdown": "文本和Markdown",
|
"textAndMarkdown": "文本和Markdown",
|
||||||
|
@ -73,12 +73,12 @@
|
|||||||
"copyLink": "複製連結"
|
"copyLink": "複製連結"
|
||||||
},
|
},
|
||||||
"moreAction": {
|
"moreAction": {
|
||||||
"fontSize": "字型大小",
|
|
||||||
"import": "匯入",
|
|
||||||
"moreOptions": "更多選項",
|
|
||||||
"small": "小",
|
"small": "小",
|
||||||
"medium": "中",
|
"medium": "中",
|
||||||
"large": "大"
|
"large": "大",
|
||||||
|
"fontSize": "字型大小",
|
||||||
|
"import": "匯入",
|
||||||
|
"moreOptions": "更多選項"
|
||||||
},
|
},
|
||||||
"importPanel": {
|
"importPanel": {
|
||||||
"textAndMarkdown": "文字 & Markdown",
|
"textAndMarkdown": "文字 & Markdown",
|
||||||
|
@ -73,6 +73,7 @@ impl TypeOptionTransform for RichTextTypeOption {
|
|||||||
|| transformed_field_type.is_multi_select()
|
|| transformed_field_type.is_multi_select()
|
||||||
|| transformed_field_type.is_number()
|
|| transformed_field_type.is_number()
|
||||||
|| transformed_field_type.is_url()
|
|| transformed_field_type.is_url()
|
||||||
|
|| transformed_field_type.is_checklist()
|
||||||
{
|
{
|
||||||
Some(StrCellData::from(stringify_cell_data(
|
Some(StrCellData::from(stringify_cell_data(
|
||||||
cell,
|
cell,
|
||||||
|
@ -206,7 +206,7 @@ async fn grid_switch_from_multi_select_to_text_test() {
|
|||||||
// Test when switching the current field from Checkbox to Text test
|
// Test when switching the current field from Checkbox to Text test
|
||||||
// input:
|
// input:
|
||||||
// check -> "Yes"
|
// check -> "Yes"
|
||||||
// unchecked -> ""
|
// unchecked -> "No"
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn grid_switch_from_checkbox_to_text_test() {
|
async fn grid_switch_from_checkbox_to_text_test() {
|
||||||
let mut test = DatabaseFieldTest::new().await;
|
let mut test = DatabaseFieldTest::new().await;
|
||||||
@ -290,3 +290,24 @@ async fn grid_switch_from_number_to_text_test() {
|
|||||||
|
|
||||||
test.run_scripts(scripts).await;
|
test.run_scripts(scripts).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test when switching the current field from Checklist to Text test
|
||||||
|
#[tokio::test]
|
||||||
|
async fn grid_switch_from_checklist_to_text_test() {
|
||||||
|
let mut test = DatabaseFieldTest::new().await;
|
||||||
|
let field_rev = test.get_first_field(FieldType::Checklist);
|
||||||
|
|
||||||
|
let scripts = vec![
|
||||||
|
SwitchToField {
|
||||||
|
field_id: field_rev.id.clone(),
|
||||||
|
new_field_type: FieldType::RichText,
|
||||||
|
},
|
||||||
|
AssertCellContent {
|
||||||
|
field_id: field_rev.id.clone(),
|
||||||
|
row_index: 0,
|
||||||
|
from_field_type: FieldType::Checklist,
|
||||||
|
expected_content: "First thing".to_string(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
test.run_scripts(scripts).await;
|
||||||
|
}
|
||||||
|
@ -68,6 +68,10 @@ pub struct AppearanceSettingsPB {
|
|||||||
#[pb(index = 12)]
|
#[pb(index = 12)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub document_setting: DocumentSettingsPB,
|
pub document_setting: DocumentSettingsPB,
|
||||||
|
|
||||||
|
#[pb(index = 13)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub enable_rtl_toolbar_items: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_RESET_VALUE: fn() -> bool = || APPEARANCE_RESET_AS_DEFAULT;
|
const DEFAULT_RESET_VALUE: fn() -> bool = || APPEARANCE_RESET_AS_DEFAULT;
|
||||||
@ -129,6 +133,7 @@ pub const APPEARANCE_DEFAULT_MONOSPACE_FONT: &str = "SF Mono";
|
|||||||
const APPEARANCE_RESET_AS_DEFAULT: bool = true;
|
const APPEARANCE_RESET_AS_DEFAULT: bool = true;
|
||||||
const APPEARANCE_DEFAULT_IS_MENU_COLLAPSED: bool = false;
|
const APPEARANCE_DEFAULT_IS_MENU_COLLAPSED: bool = false;
|
||||||
const APPEARANCE_DEFAULT_MENU_OFFSET: f64 = 0.0;
|
const APPEARANCE_DEFAULT_MENU_OFFSET: f64 = 0.0;
|
||||||
|
const APPEARANCE_DEFAULT_ENABLE_RTL_TOOLBAR_ITEMS: bool = false;
|
||||||
|
|
||||||
impl std::default::Default for AppearanceSettingsPB {
|
impl std::default::Default for AppearanceSettingsPB {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
@ -144,6 +149,7 @@ impl std::default::Default for AppearanceSettingsPB {
|
|||||||
menu_offset: APPEARANCE_DEFAULT_MENU_OFFSET,
|
menu_offset: APPEARANCE_DEFAULT_MENU_OFFSET,
|
||||||
layout_direction: LayoutDirectionPB::default(),
|
layout_direction: LayoutDirectionPB::default(),
|
||||||
text_direction: TextDirectionPB::default(),
|
text_direction: TextDirectionPB::default(),
|
||||||
|
enable_rtl_toolbar_items: APPEARANCE_DEFAULT_ENABLE_RTL_TOOLBAR_ITEMS,
|
||||||
document_setting: DocumentSettingsPB::default(),
|
document_setting: DocumentSettingsPB::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user