feat: keep the toolbar the same height as the keyboard to optimize the editing experience (#3947)
@ -59,6 +59,8 @@ PODS:
|
||||
- Flutter
|
||||
- irondash_engine_context (0.0.1):
|
||||
- Flutter
|
||||
- keyboard_height_plugin (0.0.1):
|
||||
- Flutter
|
||||
- package_info_plus (0.4.5):
|
||||
- Flutter
|
||||
- path_provider_foundation (0.0.1):
|
||||
@ -100,6 +102,7 @@ DEPENDENCIES:
|
||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
||||
- irondash_engine_context (from `.symlinks/plugins/irondash_engine_context/ios`)
|
||||
- keyboard_height_plugin (from `.symlinks/plugins/keyboard_height_plugin/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- rich_clipboard_ios (from `.symlinks/plugins/rich_clipboard_ios/ios`)
|
||||
@ -145,6 +148,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/integration_test/ios"
|
||||
irondash_engine_context:
|
||||
:path: ".symlinks/plugins/irondash_engine_context/ios"
|
||||
keyboard_height_plugin:
|
||||
:path: ".symlinks/plugins/keyboard_height_plugin/ios"
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
path_provider_foundation:
|
||||
@ -180,6 +185,7 @@ SPEC CHECKSUMS:
|
||||
image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5
|
||||
integration_test: 13825b8a9334a850581300559b8839134b124670
|
||||
irondash_engine_context: 3458bf979b90d616ffb8ae03a150bafe2e860cc9
|
||||
keyboard_height_plugin: 43fa8bba20fd5c4fdeed5076466b8b9d43cc6b86
|
||||
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
|
||||
path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8
|
||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||
|
@ -4,6 +4,7 @@ import 'package:flowy_infra/colorscheme/colorscheme.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
const _primaryColor = Color(0xFF2DA2F6); //primary 100
|
||||
const _onBackgroundColor = Color(0xff2F3030); // text/title color
|
||||
@ -17,6 +18,8 @@ ThemeData getMobileThemeData(
|
||||
String fontFamily,
|
||||
String monospaceFontFamily,
|
||||
) {
|
||||
fontFamily = GoogleFonts.getFont(fontFamily).fontFamily ?? fontFamily;
|
||||
|
||||
final mobileColorTheme = (brightness == Brightness.light)
|
||||
? ColorScheme(
|
||||
brightness: brightness,
|
||||
@ -140,14 +143,14 @@ ThemeData getMobileThemeData(
|
||||
textButtonTheme: TextButtonThemeData(
|
||||
style: ButtonStyle(
|
||||
textStyle: MaterialStateProperty.all(
|
||||
const TextStyle(
|
||||
fontFamily: 'Poppins',
|
||||
TextStyle(
|
||||
fontFamily: fontFamily,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// text
|
||||
fontFamily: 'Poppins',
|
||||
fontFamily: fontFamily,
|
||||
textTheme: TextTheme(
|
||||
displayLarge: const TextStyle(
|
||||
color: Color(0xFF57B5F8),
|
||||
@ -157,6 +160,7 @@ ThemeData getMobileThemeData(
|
||||
letterSpacing: 0.16,
|
||||
),
|
||||
displayMedium: TextStyle(
|
||||
fontFamily: fontFamily,
|
||||
color: mobileColorTheme.onBackground,
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w600,
|
||||
@ -172,6 +176,7 @@ ThemeData getMobileThemeData(
|
||||
),
|
||||
// body2 14 Regular
|
||||
bodyMedium: TextStyle(
|
||||
fontFamily: fontFamily,
|
||||
color: mobileColorTheme.onBackground,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BottomSheetActionWidget extends StatelessWidget {
|
||||
@ -26,7 +27,7 @@ class BottomSheetActionWidget extends StatelessWidget {
|
||||
size: const Size.square(22.0),
|
||||
color: iconColor,
|
||||
),
|
||||
label: Text(text),
|
||||
label: FlowyText(text),
|
||||
style: Theme.of(context)
|
||||
.outlinedButtonTheme
|
||||
.style
|
||||
|
@ -18,7 +18,7 @@ import 'home.dart';
|
||||
class MobileHomeScreen extends StatelessWidget {
|
||||
const MobileHomeScreen({super.key});
|
||||
|
||||
static const routeName = "/MobileHomeScreen";
|
||||
static const routeName = '/home';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -1,10 +1,13 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/mobile_home_setting_page.dart';
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart';
|
||||
import 'package:appflowy/plugins/base/icon/icon_picker.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/workspace/application/user/settings_user_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class MobileHomePageHeader extends StatelessWidget {
|
||||
@ -18,77 +21,76 @@ class MobileHomePageHeader extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
// TODO: implement the details later.
|
||||
return SizedBox(
|
||||
height: 80,
|
||||
child: Row(
|
||||
children: [
|
||||
const FlowyText(
|
||||
'🐻',
|
||||
fontSize: 26,
|
||||
),
|
||||
const HSpace(14),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
return BlocProvider(
|
||||
create: (context) => getIt<SettingsUserViewBloc>(param1: userProfile)
|
||||
..add(const SettingsUserEvent.initial()),
|
||||
child: BlocBuilder<SettingsUserViewBloc, SettingsUserState>(
|
||||
builder: (context, state) {
|
||||
final userIcon = state.userProfile.iconUrl;
|
||||
return SizedBox(
|
||||
height: 48,
|
||||
child: Row(
|
||||
children: [
|
||||
// TODO: replace with the real data
|
||||
Row(
|
||||
children: [
|
||||
const FlowyText.medium(
|
||||
'AppFlowy',
|
||||
fontSize: 18,
|
||||
),
|
||||
// temporary placeholder for log out icon button
|
||||
// needs to be replaced with workspace switcher and log out
|
||||
IconButton(
|
||||
onPressed: () => showDialog<String>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => AlertDialog(
|
||||
title: const Text('Log out'),
|
||||
content:
|
||||
const Text('Are you sure you want to log out?'),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, 'Cancel'),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await getIt<AuthService>().signOut();
|
||||
runAppFlowy();
|
||||
},
|
||||
child: const Text('OK'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
icon: const Icon(
|
||||
Icons.arrow_drop_down,
|
||||
),
|
||||
),
|
||||
],
|
||||
FlowyButton(
|
||||
useIntrinsicWidth: true,
|
||||
text: FlowyText(
|
||||
// replace with user icon
|
||||
userIcon.isNotEmpty ? userIcon : '🐻',
|
||||
fontSize: 26,
|
||||
),
|
||||
onTap: () async {
|
||||
final icon = await context.push<EmojiPickerResult>(
|
||||
Uri(
|
||||
path: MobileEmojiPickerScreen.routeName,
|
||||
queryParameters: {
|
||||
MobileEmojiPickerScreen.pageTitle: 'User icon',
|
||||
},
|
||||
).toString(),
|
||||
);
|
||||
if (icon != null) {
|
||||
if (context.mounted) {
|
||||
context.read<SettingsUserViewBloc>().add(
|
||||
SettingsUserEvent.updateUserIcon(
|
||||
iconUrl: icon.emoji,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
FlowyText.regular(
|
||||
userProfile.email.isNotEmpty
|
||||
? userProfile.email
|
||||
: userProfile.name,
|
||||
fontSize: 12,
|
||||
color: theme.colorScheme.onSurface,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
const HSpace(12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
const FlowyText.medium(
|
||||
'AppFlowy',
|
||||
fontSize: 18,
|
||||
),
|
||||
FlowyText.regular(
|
||||
userProfile.email.isNotEmpty
|
||||
? userProfile.email
|
||||
: userProfile.name,
|
||||
fontSize: 12,
|
||||
color: theme.colorScheme.onSurface,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
context.push(MobileHomeSettingPage.routeName);
|
||||
},
|
||||
icon: const FlowySvg(
|
||||
FlowySvgs.m_setting_m,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
context.push(MobileHomeSettingPage.routeName);
|
||||
},
|
||||
icon: const FlowySvg(
|
||||
FlowySvgs.m_setting_m,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/presentation.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/logout_setting_group.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
@ -11,7 +12,7 @@ class MobileHomeSettingPage extends StatefulWidget {
|
||||
super.key,
|
||||
});
|
||||
|
||||
static const routeName = '/MobileHomeSettingPage';
|
||||
static const routeName = '/settings';
|
||||
|
||||
@override
|
||||
State<MobileHomeSettingPage> createState() => _MobileHomeSettingPageState();
|
||||
@ -58,8 +59,10 @@ class _MobileHomeSettingPageState extends State<MobileHomeSettingPage> {
|
||||
// TODO(yijing): implement this along with Notification Page
|
||||
const NotificationsSettingGroup(),
|
||||
const AppearanceSettingGroup(),
|
||||
const LanguageSettingGroup(),
|
||||
const SupportSettingGroup(),
|
||||
const AboutSettingGroup(),
|
||||
const LogoutSettingGroup(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -13,7 +13,7 @@ import 'package:go_router/go_router.dart';
|
||||
class MobileHomeTrashPage extends StatelessWidget {
|
||||
const MobileHomeTrashPage({super.key});
|
||||
|
||||
static const routeName = "/MobileHomeTrashPage";
|
||||
static const routeName = '/trash';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -0,0 +1,23 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/appearance/theme_setting.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../setting.dart';
|
||||
|
||||
class AppearanceSettingGroup extends StatelessWidget {
|
||||
const AppearanceSettingGroup({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MobileSettingGroup(
|
||||
groupTitle: LocaleKeys.settings_menu_appearance.tr(),
|
||||
settingItemList: const [
|
||||
ThemeSetting(),
|
||||
FontSetting(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_bottom_sheet.dart';
|
||||
import 'package:appflowy/util/theme_mode_extension.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../setting.dart';
|
||||
|
||||
class ThemeSetting extends StatelessWidget {
|
||||
const ThemeSetting({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final themeMode = context.watch<AppearanceSettingsCubit>().state.themeMode;
|
||||
return MobileSettingItem(
|
||||
name: LocaleKeys.settings_appearance_themeMode_label.tr(),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FlowyText(
|
||||
themeMode.labelText,
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
const Icon(Icons.chevron_right),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
showFlowyMobileBottomSheet(
|
||||
context,
|
||||
title: LocaleKeys.settings_appearance_themeMode_label.tr(),
|
||||
builder: (_) {
|
||||
return Column(
|
||||
children: [
|
||||
_ThemeModeRadioListTile(
|
||||
title: LocaleKeys.settings_appearance_themeMode_system.tr(),
|
||||
value: ThemeMode.system,
|
||||
),
|
||||
_ThemeModeRadioListTile(
|
||||
title: LocaleKeys.settings_appearance_themeMode_light.tr(),
|
||||
value: ThemeMode.light,
|
||||
),
|
||||
_ThemeModeRadioListTile(
|
||||
title: LocaleKeys.settings_appearance_themeMode_dark.tr(),
|
||||
value: ThemeMode.dark,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ThemeModeRadioListTile extends StatelessWidget {
|
||||
const _ThemeModeRadioListTile({
|
||||
required this.title,
|
||||
required this.value,
|
||||
});
|
||||
final String title;
|
||||
final ThemeMode value;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return RadioListTile<ThemeMode>(
|
||||
dense: true,
|
||||
contentPadding: const EdgeInsets.fromLTRB(0, 0, 4, 0),
|
||||
controlAffinity: ListTileControlAffinity.trailing,
|
||||
title: Text(
|
||||
title,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
groupValue: context.read<AppearanceSettingsCubit>().state.themeMode,
|
||||
value: value,
|
||||
onChanged: (selectedThemeMode) {
|
||||
if (selectedThemeMode == null) return;
|
||||
context.read<AppearanceSettingsCubit>().setThemeMode(selectedThemeMode);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_bottom_sheet.dart';
|
||||
import 'package:appflowy/util/theme_mode_extension.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'setting.dart';
|
||||
|
||||
class AppearanceSettingGroup extends StatefulWidget {
|
||||
const AppearanceSettingGroup({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AppearanceSettingGroup> createState() => _AppearanceSettingGroupState();
|
||||
}
|
||||
|
||||
class _AppearanceSettingGroupState extends State<AppearanceSettingGroup> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocSelector<AppearanceSettingsCubit, AppearanceSettingsState,
|
||||
ThemeMode>(
|
||||
selector: (state) {
|
||||
return state.themeMode;
|
||||
},
|
||||
builder: (context, themeMode) {
|
||||
final theme = Theme.of(context);
|
||||
return MobileSettingGroup(
|
||||
groupTitle: LocaleKeys.settings_menu_appearance.tr(),
|
||||
settingItemList: [
|
||||
MobileSettingItem(
|
||||
name: LocaleKeys.settings_appearance_themeMode_label.tr(),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
themeMode.labelText,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const Icon(Icons.chevron_right),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
showFlowyMobileBottomSheet(
|
||||
context,
|
||||
title: LocaleKeys.settings_appearance_themeMode_label.tr(),
|
||||
builder: (_) {
|
||||
return Column(
|
||||
children: [
|
||||
_ThemeModeRadioListTile(
|
||||
title: LocaleKeys.settings_appearance_themeMode_system
|
||||
.tr(),
|
||||
value: ThemeMode.system,
|
||||
),
|
||||
_ThemeModeRadioListTile(
|
||||
title: LocaleKeys.settings_appearance_themeMode_light
|
||||
.tr(),
|
||||
value: ThemeMode.light,
|
||||
),
|
||||
_ThemeModeRadioListTile(
|
||||
title: LocaleKeys.settings_appearance_themeMode_dark
|
||||
.tr(),
|
||||
value: ThemeMode.dark,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ThemeModeRadioListTile extends StatelessWidget {
|
||||
const _ThemeModeRadioListTile({
|
||||
required this.title,
|
||||
required this.value,
|
||||
});
|
||||
final String title;
|
||||
final ThemeMode value;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return RadioListTile<ThemeMode>(
|
||||
dense: true,
|
||||
contentPadding: const EdgeInsets.fromLTRB(0, 0, 4, 0),
|
||||
controlAffinity: ListTileControlAffinity.trailing,
|
||||
title: Text(
|
||||
title,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
groupValue: context.read<AppearanceSettingsCubit>().state.themeMode,
|
||||
value: value,
|
||||
onChanged: (selectedThemeMode) {
|
||||
if (selectedThemeMode == null) return;
|
||||
context.read<AppearanceSettingsCubit>().setThemeMode(selectedThemeMode);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_search_bar.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
final List<String> _availableFonts = GoogleFonts.asMap().keys.toList();
|
||||
|
||||
class FontPickerScreen extends StatelessWidget {
|
||||
static const routeName = '/font_picker';
|
||||
|
||||
const FontPickerScreen({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const LanguagePickerPage();
|
||||
}
|
||||
}
|
||||
|
||||
class LanguagePickerPage extends StatefulWidget {
|
||||
const LanguagePickerPage({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<LanguagePickerPage> createState() => _LanguagePickerPageState();
|
||||
}
|
||||
|
||||
class _LanguagePickerPageState extends State<LanguagePickerPage> {
|
||||
late List<String> availableFonts;
|
||||
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
|
||||
availableFonts = _availableFonts;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectedFontFamilyName =
|
||||
context.watch<AppearanceSettingsCubit>().state.font;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
titleSpacing: 0,
|
||||
title: FlowyText.semibold(
|
||||
LocaleKeys.titleBar_font.tr(),
|
||||
fontSize: 14.0,
|
||||
),
|
||||
leading: AppBarBackButton(
|
||||
onTap: () => context.pop(),
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: ListView.separated(
|
||||
itemBuilder: (context, index) {
|
||||
if (index == 0) {
|
||||
// search bar
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: FlowyMobileSearchTextField(
|
||||
onKeywordChanged: (keyword) {
|
||||
setState(() {
|
||||
availableFonts = _availableFonts
|
||||
.where(
|
||||
(element) => parseFontFamilyName(element)
|
||||
.toLowerCase()
|
||||
.contains(keyword.toLowerCase()),
|
||||
)
|
||||
.toList();
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
final fontFamilyName = availableFonts[index - 1];
|
||||
final displayName = parseFontFamilyName(fontFamilyName);
|
||||
return InkWell(
|
||||
onTap: () => context.pop(fontFamilyName),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4.0,
|
||||
vertical: 12.0,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const HSpace(12.0),
|
||||
FlowyText(
|
||||
displayName,
|
||||
fontFamily:
|
||||
GoogleFonts.getFont(fontFamilyName).fontFamily,
|
||||
),
|
||||
const Spacer(),
|
||||
if (selectedFontFamilyName == fontFamilyName)
|
||||
const Icon(
|
||||
Icons.check,
|
||||
size: 16,
|
||||
),
|
||||
const HSpace(12.0),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, __) => const Divider(
|
||||
height: 1,
|
||||
),
|
||||
itemCount: availableFonts.length + 1, // with search bar
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String parseFontFamilyName(String fontFamilyName) {
|
||||
final camelCase = RegExp('(?<=[a-z])[A-Z]');
|
||||
return fontFamilyName
|
||||
.replaceAll('_regular', '')
|
||||
.replaceAllMapped(camelCase, (m) => ' ${m.group(0)}');
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/font/font_picker_screen.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
import '../setting.dart';
|
||||
|
||||
class FontSetting extends StatelessWidget {
|
||||
const FontSetting({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final selectedFont = context.watch<AppearanceSettingsCubit>().state.font;
|
||||
return MobileSettingItem(
|
||||
name: LocaleKeys.settings_appearance_fontFamily_label.tr(),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FlowyText(
|
||||
selectedFont,
|
||||
fontFamily: GoogleFonts.getFont(selectedFont).fontFamily,
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
const Icon(Icons.chevron_right),
|
||||
],
|
||||
),
|
||||
onTap: () async {
|
||||
final newFont = await context.push<String>(FontPickerScreen.routeName);
|
||||
if (newFont != null && newFont != selectedFont) {
|
||||
if (context.mounted) {
|
||||
context.read<AppearanceSettingsCubit>().setFontFamily(newFont);
|
||||
context.read<DocumentAppearanceCubit>().syncFontFamily(newFont);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/language.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class LanguagePickerScreen extends StatelessWidget {
|
||||
static const routeName = '/language_picker';
|
||||
|
||||
const LanguagePickerScreen({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const LanguagePickerPage();
|
||||
}
|
||||
}
|
||||
|
||||
class LanguagePickerPage extends StatefulWidget {
|
||||
const LanguagePickerPage({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<LanguagePickerPage> createState() => _LanguagePickerPageState();
|
||||
}
|
||||
|
||||
class _LanguagePickerPageState extends State<LanguagePickerPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final supportedLocales = EasyLocalization.of(context)!.supportedLocales;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
titleSpacing: 0,
|
||||
title: FlowyText.semibold(
|
||||
LocaleKeys.titleBar_language.tr(),
|
||||
fontSize: 14.0,
|
||||
),
|
||||
leading: AppBarBackButton(
|
||||
onTap: () => context.pop(),
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: ListView.separated(
|
||||
itemBuilder: (context, index) {
|
||||
final locale = supportedLocales[index];
|
||||
return InkWell(
|
||||
onTap: () => context.pop(locale),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4.0,
|
||||
vertical: 12.0,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const HSpace(12.0),
|
||||
FlowyText(
|
||||
languageFromLocale(locale),
|
||||
),
|
||||
const Spacer(),
|
||||
if (EasyLocalization.of(context)!.locale == locale)
|
||||
const Icon(
|
||||
Icons.check,
|
||||
size: 16,
|
||||
),
|
||||
const HSpace(12.0),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, __) => const Divider(
|
||||
height: 1,
|
||||
),
|
||||
itemCount: supportedLocales.length,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/language/language_picker_screen.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/language.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'setting.dart';
|
||||
|
||||
class LanguageSettingGroup extends StatefulWidget {
|
||||
const LanguageSettingGroup({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<LanguageSettingGroup> createState() => _LanguageSettingGroupState();
|
||||
}
|
||||
|
||||
class _LanguageSettingGroupState extends State<LanguageSettingGroup> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocSelector<AppearanceSettingsCubit, AppearanceSettingsState,
|
||||
Locale>(
|
||||
selector: (state) {
|
||||
return state.locale;
|
||||
},
|
||||
builder: (context, locale) {
|
||||
final theme = Theme.of(context);
|
||||
return MobileSettingGroup(
|
||||
groupTitle: LocaleKeys.settings_menu_language.tr(),
|
||||
settingItemList: [
|
||||
MobileSettingItem(
|
||||
name: LocaleKeys.settings_menu_language.tr(),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FlowyText(
|
||||
languageFromLocale(locale),
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
const Icon(Icons.chevron_right),
|
||||
],
|
||||
),
|
||||
onTap: () async {
|
||||
final newLocale =
|
||||
await context.push<Locale>(LanguagePickerScreen.routeName);
|
||||
if (newLocale != null && newLocale != locale) {
|
||||
if (mounted) {
|
||||
context
|
||||
.read<AppearanceSettingsCubit>()
|
||||
.setLocale(context, newLocale);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LogoutSettingGroup extends StatelessWidget {
|
||||
const LogoutSettingGroup({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
const Divider(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4),
|
||||
child: FlowyButton(
|
||||
margin: const EdgeInsets.symmetric(
|
||||
vertical: 16.0,
|
||||
),
|
||||
text: FlowyText.medium(
|
||||
LocaleKeys.settings_menu_logout.tr(),
|
||||
textAlign: TextAlign.center,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
onTap: () async {
|
||||
await getIt<AuthService>().signOut();
|
||||
runAppFlowy();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
export 'personal_info/personal_info.dart';
|
||||
export 'notifications_setting_group.dart';
|
||||
export 'appearance_setting_group.dart';
|
||||
export 'support_setting_group.dart';
|
||||
export 'about/about.dart';
|
||||
export 'appearance/appearance_setting_group.dart';
|
||||
export 'font/font_setting.dart';
|
||||
export 'language_setting_group.dart';
|
||||
export 'notifications_setting_group.dart';
|
||||
export 'personal_info/personal_info.dart';
|
||||
export 'support_setting_group.dart';
|
||||
export 'widgets/widgets.dart';
|
||||
|
@ -1,7 +1,6 @@
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'mobile_setting_item_widget.dart';
|
||||
|
||||
class MobileSettingGroup extends StatelessWidget {
|
||||
const MobileSettingGroup({
|
||||
required this.groupTitle,
|
||||
@ -9,26 +8,21 @@ class MobileSettingGroup extends StatelessWidget {
|
||||
this.showDivider = true,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String groupTitle;
|
||||
final List<MobileSettingItem> settingItemList;
|
||||
final List<Widget> settingItemList;
|
||||
final bool showDivider;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Text(
|
||||
const VSpace(4.0),
|
||||
FlowyText.semibold(
|
||||
groupTitle,
|
||||
style: theme.textTheme.labelSmall,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
const VSpace(4.0),
|
||||
...settingItemList,
|
||||
showDivider ? const Divider() : const SizedBox.shrink(),
|
||||
],
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MobileSettingItem extends StatelessWidget {
|
||||
@ -15,13 +16,12 @@ class MobileSettingItem extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
padding: const EdgeInsets.only(bottom: 4),
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
title: FlowyText.medium(
|
||||
name,
|
||||
style: theme.textTheme.labelMedium,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
subtitle: subtitle,
|
||||
trailing: trailing,
|
||||
|
@ -0,0 +1,77 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class FlowyMobileSearchTextField extends StatefulWidget {
|
||||
const FlowyMobileSearchTextField({
|
||||
super.key,
|
||||
required this.onKeywordChanged,
|
||||
});
|
||||
|
||||
final void Function(String keyword) onKeywordChanged;
|
||||
|
||||
@override
|
||||
State<FlowyMobileSearchTextField> createState() =>
|
||||
_FlowyMobileSearchTextFieldState();
|
||||
}
|
||||
|
||||
class _FlowyMobileSearchTextFieldState
|
||||
extends State<FlowyMobileSearchTextField> {
|
||||
final TextEditingController controller = TextEditingController();
|
||||
final FocusNode focusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller.dispose();
|
||||
focusNode.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxHeight: 36.0,
|
||||
),
|
||||
child: FlowyTextField(
|
||||
focusNode: focusNode,
|
||||
hintText: LocaleKeys.emoji_search.tr(),
|
||||
controller: controller,
|
||||
onChanged: widget.onKeywordChanged,
|
||||
prefixIcon: const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 8.0,
|
||||
right: 4.0,
|
||||
),
|
||||
child: FlowySvg(
|
||||
FlowySvgs.search_s,
|
||||
),
|
||||
),
|
||||
prefixIconConstraints: const BoxConstraints(
|
||||
maxHeight: 18.0,
|
||||
),
|
||||
suffixIcon: Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: FlowyButton(
|
||||
text: const FlowySvg(
|
||||
FlowySvgs.close_lg,
|
||||
),
|
||||
margin: EdgeInsets.zero,
|
||||
useIntrinsicWidth: true,
|
||||
onTap: () {
|
||||
if (controller.text.isNotEmpty) {
|
||||
controller.clear();
|
||||
widget.onKeywordChanged('');
|
||||
} else {
|
||||
focusNode.unfocus();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -12,9 +12,11 @@ class FlowyEmojiPicker extends StatefulWidget {
|
||||
const FlowyEmojiPicker({
|
||||
super.key,
|
||||
required this.onEmojiSelected,
|
||||
this.emojiPerLine = 9,
|
||||
});
|
||||
|
||||
final EmojiSelectedCallback onEmojiSelected;
|
||||
final int emojiPerLine;
|
||||
|
||||
@override
|
||||
State<FlowyEmojiPicker> createState() => _FlowyEmojiPickerState();
|
||||
@ -61,6 +63,7 @@ class _FlowyEmojiPickerState extends State<FlowyEmojiPicker> {
|
||||
showSectionHeader: true,
|
||||
showTabs: false,
|
||||
defaultSkinTone: lastSelectedEmojiSkinTone ?? EmojiSkinTone.none,
|
||||
perLine: widget.emojiPerLine,
|
||||
),
|
||||
onEmojiSelected: widget.onEmojiSelected,
|
||||
headerBuilder: (context, category) {
|
||||
|
@ -5,14 +5,19 @@ import 'package:go_router/go_router.dart';
|
||||
|
||||
class MobileEmojiPickerScreen extends StatelessWidget {
|
||||
static const routeName = '/emoji_picker';
|
||||
static const pageTitle = 'title';
|
||||
|
||||
const MobileEmojiPickerScreen({
|
||||
super.key,
|
||||
this.title,
|
||||
});
|
||||
|
||||
final String? title;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconPickerPage(
|
||||
title: title,
|
||||
onSelected: (result) {
|
||||
context.pop<EmojiPickerResult>(result);
|
||||
},
|
||||
|
@ -105,10 +105,12 @@ class _SearchTextField extends StatefulWidget {
|
||||
|
||||
class _SearchTextFieldState extends State<_SearchTextField> {
|
||||
final TextEditingController controller = TextEditingController();
|
||||
final FocusNode focusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller.dispose();
|
||||
focusNode.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
@ -120,7 +122,7 @@ class _SearchTextFieldState extends State<_SearchTextField> {
|
||||
maxHeight: 32.0,
|
||||
),
|
||||
child: FlowyTextField(
|
||||
autoFocus: true,
|
||||
focusNode: focusNode,
|
||||
hintText: LocaleKeys.emoji_search.tr(),
|
||||
controller: controller,
|
||||
onChanged: widget.onKeywordChanged,
|
||||
@ -145,8 +147,12 @@ class _SearchTextFieldState extends State<_SearchTextField> {
|
||||
margin: EdgeInsets.zero,
|
||||
useIntrinsicWidth: true,
|
||||
onTap: () {
|
||||
controller.clear();
|
||||
widget.onKeywordChanged('');
|
||||
if (controller.text.isNotEmpty) {
|
||||
controller.clear();
|
||||
widget.onKeywordChanged('');
|
||||
} else {
|
||||
focusNode.unfocus();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -72,6 +72,7 @@ class _FlowyIconPickerState extends State<FlowyIconPicker>
|
||||
child: TabBarView(
|
||||
children: [
|
||||
FlowyEmojiPicker(
|
||||
emojiPerLine: _getEmojiPerLine(),
|
||||
onEmojiSelected: (_, emoji) {
|
||||
widget.onSelected(
|
||||
EmojiPickerResult(
|
||||
@ -116,6 +117,11 @@ class _FlowyIconPickerState extends State<FlowyIconPicker>
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
int _getEmojiPerLine() {
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
return width ~/ 46.0; // the size of the emoji
|
||||
}
|
||||
}
|
||||
|
||||
class _RemoveIconButton extends StatelessWidget {
|
||||
|
@ -6,26 +6,23 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class IconPickerPage extends StatefulWidget {
|
||||
class IconPickerPage extends StatelessWidget {
|
||||
const IconPickerPage({
|
||||
super.key,
|
||||
this.title,
|
||||
required this.onSelected,
|
||||
});
|
||||
|
||||
final void Function(EmojiPickerResult) onSelected;
|
||||
final String? title;
|
||||
|
||||
@override
|
||||
State<IconPickerPage> createState() => _IconPickerPageState();
|
||||
}
|
||||
|
||||
class _IconPickerPageState extends State<IconPickerPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
titleSpacing: 0,
|
||||
title: FlowyText.semibold(
|
||||
LocaleKeys.titleBar_pageIcon.tr(),
|
||||
title ?? LocaleKeys.titleBar_pageIcon.tr(),
|
||||
fontSize: 14.0,
|
||||
),
|
||||
leading: AppBarBackButton(
|
||||
@ -34,7 +31,7 @@ class _IconPickerPageState extends State<IconPickerPage> {
|
||||
),
|
||||
body: SafeArea(
|
||||
child: FlowyIconPicker(
|
||||
onSelected: widget.onSelected,
|
||||
onSelected: onSelected,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -255,7 +255,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () async {
|
||||
// if the last one isn't a empty node, insert a new empty node.
|
||||
await _ensureLastNodeIsEmptyParagraph();
|
||||
await _focusOnLastEmptyParagraph();
|
||||
},
|
||||
child: VSpace(PlatformExtension.isDesktopOrWeb ? 200 : 400),
|
||||
),
|
||||
@ -266,51 +266,49 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
_setInitialSelection(editorScrollController);
|
||||
|
||||
if (PlatformExtension.isMobile) {
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: MobileFloatingToolbar(
|
||||
editorState: editorState,
|
||||
editorScrollController: editorScrollController,
|
||||
toolbarBuilder: (context, anchor, closeToolbar) {
|
||||
return AdaptiveTextSelectionToolbar.editable(
|
||||
clipboardStatus: ClipboardStatus.pasteable,
|
||||
onCopy: () {
|
||||
copyCommand.execute(editorState);
|
||||
closeToolbar();
|
||||
},
|
||||
onCut: () => cutCommand.execute(editorState),
|
||||
onPaste: () => pasteCommand.execute(editorState),
|
||||
onSelectAll: () => selectAllCommand.execute(editorState),
|
||||
onLiveTextInput: null,
|
||||
anchors: TextSelectionToolbarAnchors(
|
||||
primaryAnchor: anchor,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: editor,
|
||||
),
|
||||
),
|
||||
MobileToolbar(
|
||||
editorState: editorState,
|
||||
toolbarItems: [
|
||||
textDecorationMobileToolbarItem,
|
||||
buildTextAndBackgroundColorMobileToolbarItem(),
|
||||
headingMobileToolbarItem,
|
||||
mobileBlocksToolbarItem,
|
||||
linkMobileToolbarItem,
|
||||
dividerMobileToolbarItem,
|
||||
imageMobileToolbarItem,
|
||||
mathEquationMobileToolbarItem,
|
||||
codeMobileToolbarItem,
|
||||
mobileAlignToolbarItem,
|
||||
mobileIndentToolbarItem,
|
||||
mobileOutdentToolbarItem,
|
||||
undoMobileToolbarItem,
|
||||
redoMobileToolbarItem,
|
||||
],
|
||||
),
|
||||
return MobileToolbarV2(
|
||||
toolbarHeight: 48.0,
|
||||
editorState: editorState,
|
||||
toolbarItems: [
|
||||
customTextDecorationMobileToolbarItem,
|
||||
buildTextAndBackgroundColorMobileToolbarItem(),
|
||||
mobileAddBlockToolbarItem,
|
||||
mobileConvertBlockToolbarItem,
|
||||
linkMobileToolbarItem,
|
||||
imageMobileToolbarItem,
|
||||
mobileAlignToolbarItem,
|
||||
mobileIndentToolbarItem,
|
||||
mobileOutdentToolbarItem,
|
||||
undoMobileToolbarItem,
|
||||
redoMobileToolbarItem,
|
||||
],
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: MobileFloatingToolbar(
|
||||
editorState: editorState,
|
||||
editorScrollController: editorScrollController,
|
||||
toolbarBuilder: (context, anchor, closeToolbar) {
|
||||
return AdaptiveTextSelectionToolbar.editable(
|
||||
clipboardStatus: ClipboardStatus.pasteable,
|
||||
onCopy: () {
|
||||
copyCommand.execute(editorState);
|
||||
closeToolbar();
|
||||
},
|
||||
onCut: () => cutCommand.execute(editorState),
|
||||
onPaste: () => pasteCommand.execute(editorState),
|
||||
onSelectAll: () => selectAllCommand.execute(editorState),
|
||||
onLiveTextInput: null,
|
||||
anchors: TextSelectionToolbarAnchors(
|
||||
primaryAnchor: anchor,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: editor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -482,19 +480,23 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
AppFlowyEditorL10n.current = EditorI18n();
|
||||
}
|
||||
|
||||
Future<void> _ensureLastNodeIsEmptyParagraph() async {
|
||||
Future<void> _focusOnLastEmptyParagraph() async {
|
||||
final editorState = widget.editorState;
|
||||
final root = editorState.document.root;
|
||||
final lastNode = root.children.lastOrNull;
|
||||
final transaction = editorState.transaction;
|
||||
if (lastNode == null ||
|
||||
lastNode.delta?.isEmpty == false ||
|
||||
lastNode.type != ParagraphBlockKeys.type) {
|
||||
final transaction = editorState.transaction;
|
||||
transaction.insertNode([root.children.length], paragraphNode());
|
||||
transaction.afterSelection = Selection.collapsed(
|
||||
Position(path: [root.children.length]),
|
||||
);
|
||||
await editorState.apply(transaction);
|
||||
} else {
|
||||
transaction.afterSelection = Selection.collapsed(
|
||||
Position(path: lastNode.path),
|
||||
);
|
||||
}
|
||||
await editorState.apply(transaction);
|
||||
}
|
||||
}
|
||||
|
@ -229,7 +229,11 @@ class _DocumentHeaderToolbarState extends State<DocumentHeaderToolbar> {
|
||||
Widget child = Container(
|
||||
alignment: Alignment.bottomLeft,
|
||||
width: double.infinity,
|
||||
padding: EditorStyleCustomizer.documentPadding,
|
||||
padding: PlatformExtension.isDesktopOrWeb
|
||||
? EditorStyleCustomizer.documentPadding
|
||||
: EdgeInsets.symmetric(
|
||||
horizontal: EditorStyleCustomizer.documentPadding.left - 6.0,
|
||||
),
|
||||
child: SizedBox(
|
||||
height: 28,
|
||||
child: Row(
|
||||
@ -420,48 +424,50 @@ class DocumentCoverState extends State<DocumentCover> {
|
||||
right: 12,
|
||||
child: Row(
|
||||
children: [
|
||||
RoundedTextButton(
|
||||
onPressed: () {
|
||||
showFlowyMobileBottomSheet(
|
||||
context,
|
||||
title: LocaleKeys.document_plugins_cover_changeCover.tr(),
|
||||
builder: (context) {
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxHeight: 340,
|
||||
minHeight: 80,
|
||||
),
|
||||
child: UploadImageMenu(
|
||||
supportTypes: const [
|
||||
UploadImageType.color,
|
||||
UploadImageType.local,
|
||||
UploadImageType.url,
|
||||
UploadImageType.unsplash,
|
||||
],
|
||||
onSelectedLocalImage: (path) async {
|
||||
context.pop();
|
||||
widget.onCoverChanged(CoverType.file, path);
|
||||
},
|
||||
onSelectedAIImage: (_) {
|
||||
throw UnimplementedError();
|
||||
},
|
||||
onSelectedNetworkImage: (url) async {
|
||||
context.pop();
|
||||
widget.onCoverChanged(CoverType.file, url);
|
||||
},
|
||||
onSelectedColor: (color) {
|
||||
context.pop();
|
||||
widget.onCoverChanged(CoverType.color, color);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
fillColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
width: 120,
|
||||
height: 32,
|
||||
title: LocaleKeys.document_plugins_cover_changeCover.tr(),
|
||||
IntrinsicWidth(
|
||||
child: RoundedTextButton(
|
||||
onPressed: () {
|
||||
showFlowyMobileBottomSheet(
|
||||
context,
|
||||
title:
|
||||
LocaleKeys.document_plugins_cover_changeCover.tr(),
|
||||
builder: (context) {
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxHeight: 340,
|
||||
minHeight: 80,
|
||||
),
|
||||
child: UploadImageMenu(
|
||||
supportTypes: const [
|
||||
UploadImageType.color,
|
||||
UploadImageType.local,
|
||||
UploadImageType.url,
|
||||
UploadImageType.unsplash,
|
||||
],
|
||||
onSelectedLocalImage: (path) async {
|
||||
context.pop();
|
||||
widget.onCoverChanged(CoverType.file, path);
|
||||
},
|
||||
onSelectedAIImage: (_) {
|
||||
throw UnimplementedError();
|
||||
},
|
||||
onSelectedNetworkImage: (url) async {
|
||||
context.pop();
|
||||
widget.onCoverChanged(CoverType.file, url);
|
||||
},
|
||||
onSelectedColor: (color) {
|
||||
context.pop();
|
||||
widget.onCoverChanged(CoverType.color, color);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
fillColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
height: 32,
|
||||
title: LocaleKeys.document_plugins_cover_changeCover.tr(),
|
||||
),
|
||||
),
|
||||
const HSpace(8.0),
|
||||
SizedBox.square(
|
||||
@ -530,14 +536,16 @@ class DocumentCoverState extends State<DocumentCover> {
|
||||
constraints: BoxConstraints.loose(const Size(380, 450)),
|
||||
margin: EdgeInsets.zero,
|
||||
onClose: () => isPopoverOpen = false,
|
||||
child: RoundedTextButton(
|
||||
onPressed: () => popoverController.show(),
|
||||
hoverColor: Theme.of(context).colorScheme.surface,
|
||||
textColor: Theme.of(context).colorScheme.tertiary,
|
||||
fillColor: Theme.of(context).colorScheme.surface.withOpacity(0.5),
|
||||
width: 120,
|
||||
height: 28,
|
||||
title: LocaleKeys.document_plugins_cover_changeCover.tr(),
|
||||
child: IntrinsicWidth(
|
||||
child: RoundedTextButton(
|
||||
height: 28.0,
|
||||
onPressed: () => popoverController.show(),
|
||||
hoverColor: Theme.of(context).colorScheme.surface,
|
||||
textColor: Theme.of(context).colorScheme.tertiary,
|
||||
fillColor:
|
||||
Theme.of(context).colorScheme.surface.withOpacity(0.5),
|
||||
title: LocaleKeys.document_plugins_cover_changeCover.tr(),
|
||||
),
|
||||
),
|
||||
popupBuilder: (BuildContext popoverContext) {
|
||||
isPopoverOpen = true;
|
||||
@ -575,7 +583,7 @@ class DeleteCoverButton extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyIconButton(
|
||||
hoverColor: Theme.of(context).colorScheme.surface,
|
||||
fillColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
fillColor: Theme.of(context).colorScheme.surface.withOpacity(0.5),
|
||||
iconPadding: const EdgeInsets.all(5),
|
||||
width: 28,
|
||||
icon: FlowySvg(
|
||||
|
@ -1,5 +1,7 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
@ -22,8 +24,8 @@ class _ImagePickerPageState extends State<ImagePickerPage> {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
titleSpacing: 0,
|
||||
title: const FlowyText.semibold(
|
||||
'Page icon',
|
||||
title: FlowyText.semibold(
|
||||
LocaleKeys.titleBar_pageIcon.tr(),
|
||||
fontSize: 14.0,
|
||||
),
|
||||
leading: AppBarBackButton(
|
||||
|
@ -5,8 +5,8 @@ import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
final imageMobileToolbarItem = MobileToolbarItem.action(
|
||||
itemIconBuilder: (_, __) => const FlowySvg(FlowySvgs.m_toolbar_imae_lg),
|
||||
actionHandler: (editorState, selection) async {
|
||||
itemIconBuilder: (_, __, ___) => const FlowySvg(FlowySvgs.m_toolbar_imae_lg),
|
||||
actionHandler: (_, editorState) async {
|
||||
final imagePlaceholderKey = GlobalKey<ImagePlaceholderState>();
|
||||
await editorState.insertEmptyImageBlock(imagePlaceholderKey);
|
||||
|
||||
|
@ -4,10 +4,13 @@ import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
final mathEquationMobileToolbarItem = MobileToolbarItem.action(
|
||||
itemIconBuilder: (_, __) =>
|
||||
const SizedBox(width: 22, child: FlowySvg(FlowySvgs.math_lg)),
|
||||
actionHandler: (editorState, selection) async {
|
||||
if (!selection.isCollapsed) {
|
||||
itemIconBuilder: (_, __, ___) => const SizedBox(
|
||||
width: 22,
|
||||
child: FlowySvg(FlowySvgs.math_lg),
|
||||
),
|
||||
actionHandler: (_, editorState) async {
|
||||
final selection = editorState.selection;
|
||||
if (selection == null || !selection.isCollapsed) {
|
||||
return;
|
||||
}
|
||||
final path = selection.start.path;
|
||||
|
@ -0,0 +1,327 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_blocks_menu.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// convert the current block to other block types
|
||||
// only show in single selection and text type
|
||||
final mobileAddBlockToolbarItem = MobileToolbarItem.withMenu(
|
||||
itemIconBuilder: (_, editorState, ___) {
|
||||
if (!onlyShowInSingleSelectionAndTextType(editorState)) {
|
||||
return null;
|
||||
}
|
||||
return const FlowySvg(
|
||||
FlowySvgs.add_m,
|
||||
size: Size.square(48),
|
||||
);
|
||||
},
|
||||
itemMenuBuilder: (_, editorState, service) {
|
||||
final selection = editorState.selection;
|
||||
if (selection == null) {
|
||||
return null;
|
||||
}
|
||||
return BlocksMenu(
|
||||
items: _addBlockMenuItems,
|
||||
editorState: editorState,
|
||||
service: service,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
final _addBlockMenuItems = [
|
||||
// paragraph
|
||||
BlockMenuItem(
|
||||
blockType: ParagraphBlockKeys.type,
|
||||
icon: const FlowySvg(FlowySvgs.m_text_decoration_m),
|
||||
label: LocaleKeys.editor_text.tr(),
|
||||
isSelected: _unSelectable,
|
||||
onTap: (editorState, selection, service) async {
|
||||
await editorState.insertBlockOrReplaceCurrentBlock(
|
||||
selection,
|
||||
paragraphNode(),
|
||||
);
|
||||
service.closeItemMenu();
|
||||
},
|
||||
),
|
||||
|
||||
// to-do list
|
||||
BlockMenuItem(
|
||||
blockType: TodoListBlockKeys.type,
|
||||
icon: const FlowySvg(FlowySvgs.m_checkbox_m),
|
||||
label: LocaleKeys.editor_checkbox.tr(),
|
||||
isSelected: _unSelectable,
|
||||
onTap: (editorState, selection, service) async {
|
||||
await editorState.insertBlockOrReplaceCurrentBlock(
|
||||
selection,
|
||||
todoListNode(checked: false),
|
||||
);
|
||||
service.closeItemMenu();
|
||||
},
|
||||
),
|
||||
|
||||
// heading 1 - 3
|
||||
BlockMenuItem(
|
||||
blockType: HeadingBlockKeys.type,
|
||||
icon: const FlowySvg(FlowySvgs.m_h1_m),
|
||||
label: LocaleKeys.editor_heading1.tr(),
|
||||
isSelected: _unSelectable,
|
||||
onTap: (editorState, selection, service) async {
|
||||
await editorState.insertBlockOrReplaceCurrentBlock(
|
||||
selection,
|
||||
headingNode(level: 1),
|
||||
);
|
||||
service.closeItemMenu();
|
||||
},
|
||||
),
|
||||
BlockMenuItem(
|
||||
blockType: HeadingBlockKeys.type,
|
||||
icon: const FlowySvg(FlowySvgs.m_h2_m),
|
||||
label: LocaleKeys.editor_heading2.tr(),
|
||||
isSelected: _unSelectable,
|
||||
onTap: (editorState, selection, service) async {
|
||||
await editorState.insertBlockOrReplaceCurrentBlock(
|
||||
selection,
|
||||
headingNode(level: 2),
|
||||
);
|
||||
service.closeItemMenu();
|
||||
},
|
||||
),
|
||||
BlockMenuItem(
|
||||
blockType: HeadingBlockKeys.type,
|
||||
icon: const FlowySvg(FlowySvgs.m_h3_m),
|
||||
label: LocaleKeys.editor_heading3.tr(),
|
||||
isSelected: _unSelectable,
|
||||
onTap: (editorState, selection, service) async {
|
||||
await editorState.insertBlockOrReplaceCurrentBlock(
|
||||
selection,
|
||||
headingNode(level: 3),
|
||||
);
|
||||
service.closeItemMenu();
|
||||
},
|
||||
),
|
||||
|
||||
// bulleted list
|
||||
BlockMenuItem(
|
||||
blockType: BulletedListBlockKeys.type,
|
||||
icon: const FlowySvg(FlowySvgs.m_bulleted_list_m),
|
||||
label: LocaleKeys.editor_bulletedList.tr(),
|
||||
isSelected: _unSelectable,
|
||||
onTap: (editorState, selection, service) async {
|
||||
await editorState.insertBlockOrReplaceCurrentBlock(
|
||||
selection,
|
||||
bulletedListNode(),
|
||||
);
|
||||
service.closeItemMenu();
|
||||
},
|
||||
),
|
||||
|
||||
// numbered list
|
||||
BlockMenuItem(
|
||||
blockType: NumberedListBlockKeys.type,
|
||||
icon: const FlowySvg(FlowySvgs.m_numbered_list_m),
|
||||
label: LocaleKeys.editor_numberedList.tr(),
|
||||
isSelected: _unSelectable,
|
||||
onTap: (editorState, selection, service) async {
|
||||
await editorState.insertBlockOrReplaceCurrentBlock(
|
||||
selection,
|
||||
numberedListNode(),
|
||||
);
|
||||
service.closeItemMenu();
|
||||
},
|
||||
),
|
||||
|
||||
// toggle list
|
||||
BlockMenuItem(
|
||||
blockType: ToggleListBlockKeys.type,
|
||||
icon: const FlowySvg(FlowySvgs.m_toggle_list_m),
|
||||
label: LocaleKeys.document_plugins_toggleList.tr(),
|
||||
isSelected: _unSelectable,
|
||||
onTap: (editorState, selection, service) async {
|
||||
await editorState.insertBlockOrReplaceCurrentBlock(
|
||||
selection,
|
||||
toggleListBlockNode(),
|
||||
);
|
||||
service.closeItemMenu();
|
||||
},
|
||||
),
|
||||
|
||||
// quote
|
||||
BlockMenuItem(
|
||||
blockType: QuoteBlockKeys.type,
|
||||
icon: const FlowySvg(FlowySvgs.m_quote_m),
|
||||
label: LocaleKeys.editor_quote.tr(),
|
||||
isSelected: _unSelectable,
|
||||
onTap: (editorState, selection, service) async {
|
||||
await editorState.insertBlockOrReplaceCurrentBlock(
|
||||
selection,
|
||||
quoteNode(),
|
||||
);
|
||||
service.closeItemMenu();
|
||||
},
|
||||
),
|
||||
|
||||
// callout
|
||||
BlockMenuItem(
|
||||
blockType: CalloutBlockKeys.type,
|
||||
// FIXME: update icon
|
||||
icon: const Icon(Icons.note_rounded),
|
||||
label: LocaleKeys.document_plugins_callout.tr(),
|
||||
isSelected: _unSelectable,
|
||||
onTap: (editorState, selection, service) async {
|
||||
await editorState.insertBlockOrReplaceCurrentBlock(
|
||||
selection,
|
||||
calloutNode(),
|
||||
);
|
||||
service.closeItemMenu();
|
||||
},
|
||||
),
|
||||
|
||||
// code
|
||||
BlockMenuItem(
|
||||
blockType: CodeBlockKeys.type,
|
||||
icon: const FlowySvg(FlowySvgs.m_code_m),
|
||||
label: LocaleKeys.document_selectionMenu_codeBlock.tr(),
|
||||
isSelected: _unSelectable,
|
||||
onTap: (editorState, selection, service) async {
|
||||
await editorState.insertBlockOrReplaceCurrentBlock(
|
||||
selection,
|
||||
codeBlockNode(),
|
||||
);
|
||||
service.closeItemMenu();
|
||||
},
|
||||
),
|
||||
|
||||
// divider
|
||||
BlockMenuItem(
|
||||
blockType: DividerBlockKeys.type,
|
||||
icon: const FlowySvg(FlowySvgs.m_divider_m),
|
||||
label: LocaleKeys.editor_divider.tr(),
|
||||
isSelected: _unSelectable,
|
||||
onTap: (editorState, selection, service) async {
|
||||
await editorState.insertDivider(selection);
|
||||
service.closeItemMenu();
|
||||
},
|
||||
),
|
||||
|
||||
// math equation
|
||||
BlockMenuItem(
|
||||
blockType: MathEquationBlockKeys.type,
|
||||
icon: const FlowySvg(
|
||||
FlowySvgs.math_lg,
|
||||
size: Size.square(22),
|
||||
),
|
||||
label: LocaleKeys.document_plugins_mathEquation_name.tr(),
|
||||
isSelected: _unSelectable,
|
||||
onTap: (editorState, selection, service) async {
|
||||
await editorState.insertMathEquation(selection);
|
||||
service.closeItemMenu();
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
bool _unSelectable(
|
||||
EditorState editorState,
|
||||
Selection selection,
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
extension on EditorState {
|
||||
Future<void> insertBlockOrReplaceCurrentBlock(
|
||||
Selection selection,
|
||||
Node insertedNode,
|
||||
) async {
|
||||
// If the current block is not an empty paragraph block,
|
||||
// then insert a new block below the current block.
|
||||
final node = getNodeAtPath(selection.start.path);
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
final transaction = this.transaction;
|
||||
if (node.type != ParagraphBlockKeys.type ||
|
||||
(node.delta?.isNotEmpty ?? true)) {
|
||||
final path = node.path.next;
|
||||
// insert the block below the current empty paragraph block
|
||||
transaction
|
||||
..insertNode(path, insertedNode)
|
||||
..afterSelection = Selection.collapsed(
|
||||
Position(path: path, offset: 0),
|
||||
);
|
||||
} else {
|
||||
final path = node.path;
|
||||
// replace the current empty paragraph block with the inserted block
|
||||
transaction
|
||||
..insertNode(path, insertedNode)
|
||||
..deleteNode(node)
|
||||
..afterSelection = Selection.collapsed(
|
||||
Position(path: path, offset: 0),
|
||||
);
|
||||
}
|
||||
await apply(transaction);
|
||||
}
|
||||
|
||||
Future<void> insertMathEquation(
|
||||
Selection selection,
|
||||
) async {
|
||||
final path = selection.start.path;
|
||||
final node = getNodeAtPath(path);
|
||||
final delta = node?.delta;
|
||||
if (node == null || delta == null) {
|
||||
return;
|
||||
}
|
||||
final transaction = this.transaction;
|
||||
final insertedNode = mathEquationNode();
|
||||
if (delta.isEmpty) {
|
||||
transaction
|
||||
..insertNode(path, insertedNode)
|
||||
..deleteNode(node);
|
||||
} else {
|
||||
transaction.insertNode(
|
||||
path.next,
|
||||
insertedNode,
|
||||
);
|
||||
}
|
||||
|
||||
await apply(transaction);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
final mathEquationState = getNodeAtPath(path)?.key.currentState;
|
||||
if (mathEquationState != null &&
|
||||
mathEquationState is MathEquationBlockComponentWidgetState) {
|
||||
mathEquationState.showEditingDialog();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> insertDivider(Selection selection) async {
|
||||
// same as the [handler] of [dividerMenuItem] in Desktop
|
||||
|
||||
final path = selection.end.path;
|
||||
final node = getNodeAtPath(path);
|
||||
final delta = node?.delta;
|
||||
if (node == null || delta == null) {
|
||||
return;
|
||||
}
|
||||
final insertedPath = delta.isEmpty ? path : path.next;
|
||||
final transaction = this.transaction;
|
||||
transaction.insertNode(insertedPath, dividerNode());
|
||||
// only insert a new paragraph node when the next node is not a paragraph node
|
||||
// and its delta is not empty.
|
||||
final next = node.next;
|
||||
if (next == null ||
|
||||
next.type != ParagraphBlockKeys.type ||
|
||||
next.delta?.isNotEmpty == true) {
|
||||
transaction.insertNode(
|
||||
insertedPath,
|
||||
paragraphNode(),
|
||||
);
|
||||
}
|
||||
transaction.afterSelection = Selection.collapsed(
|
||||
Position(path: insertedPath.next),
|
||||
);
|
||||
await apply(transaction);
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
final mobileAlignToolbarItem = MobileToolbarItem.withMenu(
|
||||
itemIconBuilder: (_, editorState) {
|
||||
itemIconBuilder: (_, editorState, __) {
|
||||
return onlyShowInTextType(editorState)
|
||||
? const FlowySvg(
|
||||
FlowySvgs.toolbar_align_center_s,
|
||||
@ -14,7 +14,11 @@ final mobileAlignToolbarItem = MobileToolbarItem.withMenu(
|
||||
)
|
||||
: null;
|
||||
},
|
||||
itemMenuBuilder: (editorState, selection, _) {
|
||||
itemMenuBuilder: (_, editorState, ___) {
|
||||
final selection = editorState.selection;
|
||||
if (selection == null) {
|
||||
return null;
|
||||
}
|
||||
return _MobileAlignMenu(
|
||||
editorState: editorState,
|
||||
selection: selection,
|
||||
@ -34,6 +38,7 @@ class _MobileAlignMenu extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GridView.count(
|
||||
padding: EdgeInsets.zero,
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
|
@ -0,0 +1,90 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BlockMenuItem {
|
||||
const BlockMenuItem({
|
||||
required this.blockType,
|
||||
required this.icon,
|
||||
required this.label,
|
||||
required this.onTap,
|
||||
this.isSelected,
|
||||
});
|
||||
|
||||
// block type
|
||||
final String blockType;
|
||||
final Widget icon;
|
||||
final String label;
|
||||
// callback
|
||||
final void Function(
|
||||
EditorState editorState,
|
||||
Selection selection,
|
||||
// used to control the open or close the menu
|
||||
MobileToolbarWidgetService service,
|
||||
) onTap;
|
||||
|
||||
final bool Function(
|
||||
EditorState editorState,
|
||||
Selection selection,
|
||||
)? isSelected;
|
||||
}
|
||||
|
||||
class BlocksMenu extends StatelessWidget {
|
||||
const BlocksMenu({
|
||||
super.key,
|
||||
required this.editorState,
|
||||
required this.items,
|
||||
required this.service,
|
||||
});
|
||||
|
||||
final EditorState editorState;
|
||||
final List<BlockMenuItem> items;
|
||||
final MobileToolbarWidgetService service;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GridView.count(
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
childAspectRatio: 4,
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 36.0,
|
||||
),
|
||||
shrinkWrap: true,
|
||||
children: items.map((item) {
|
||||
final selection = editorState.selection;
|
||||
if (selection == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
bool isSelected = false;
|
||||
if (item.isSelected != null) {
|
||||
isSelected = item.isSelected!(editorState, selection);
|
||||
} else {
|
||||
isSelected = _isSelected(editorState, selection, item.blockType);
|
||||
}
|
||||
return MobileToolbarItemMenuBtn(
|
||||
icon: item.icon,
|
||||
label: FlowyText(item.label),
|
||||
isSelected: isSelected,
|
||||
onPressed: () async {
|
||||
item.onTap(editorState, selection, service);
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
bool _isSelected(
|
||||
EditorState editorState,
|
||||
Selection selection,
|
||||
String blockType,
|
||||
) {
|
||||
final node = editorState.getNodeAtPath(selection.start.path);
|
||||
final type = node?.type;
|
||||
if (node == null || type == null) {
|
||||
return false;
|
||||
}
|
||||
return type == blockType;
|
||||
}
|
||||
}
|
@ -1,142 +0,0 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
final mobileBlocksToolbarItem = MobileToolbarItem.withMenu(
|
||||
itemIconBuilder: (_, __) =>
|
||||
const AFMobileIcon(afMobileIcons: AFMobileIcons.list),
|
||||
itemMenuBuilder: (editorState, selection, _) {
|
||||
return _MobileListMenu(
|
||||
editorState: editorState,
|
||||
selection: selection,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
class _MobileListMenu extends StatelessWidget {
|
||||
const _MobileListMenu({
|
||||
required this.editorState,
|
||||
required this.selection,
|
||||
});
|
||||
|
||||
final Selection selection;
|
||||
final EditorState editorState;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GridView.count(
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
childAspectRatio: 5,
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
// bulleted list, numbered list
|
||||
_buildListButton(
|
||||
context,
|
||||
BulletedListBlockKeys.type,
|
||||
const AFMobileIcon(afMobileIcons: AFMobileIcons.bulletedList),
|
||||
LocaleKeys.document_plugins_bulletedList.tr(),
|
||||
),
|
||||
_buildListButton(
|
||||
context,
|
||||
NumberedListBlockKeys.type,
|
||||
const AFMobileIcon(afMobileIcons: AFMobileIcons.numberedList),
|
||||
LocaleKeys.document_plugins_numberedList.tr(),
|
||||
),
|
||||
|
||||
// todo list, quote list
|
||||
_buildListButton(
|
||||
context,
|
||||
TodoListBlockKeys.type,
|
||||
const AFMobileIcon(afMobileIcons: AFMobileIcons.checkbox),
|
||||
LocaleKeys.document_plugins_todoList.tr(),
|
||||
),
|
||||
_buildListButton(
|
||||
context,
|
||||
QuoteBlockKeys.type,
|
||||
const AFMobileIcon(afMobileIcons: AFMobileIcons.quote),
|
||||
LocaleKeys.document_plugins_quoteList.tr(),
|
||||
),
|
||||
|
||||
// toggle list, callout
|
||||
_buildListButton(
|
||||
context,
|
||||
ToggleListBlockKeys.type,
|
||||
const FlowySvg(
|
||||
FlowySvgs.toggle_list_s,
|
||||
size: Size.square(24),
|
||||
),
|
||||
LocaleKeys.document_plugins_toggleList.tr(),
|
||||
),
|
||||
_buildListButton(
|
||||
context,
|
||||
CalloutBlockKeys.type,
|
||||
const Icon(Icons.note_rounded),
|
||||
LocaleKeys.document_plugins_callout.tr(),
|
||||
),
|
||||
_buildListButton(
|
||||
context,
|
||||
CodeBlockKeys.type,
|
||||
const Icon(Icons.abc),
|
||||
LocaleKeys.document_selectionMenu_codeBlock.tr(),
|
||||
),
|
||||
// code block
|
||||
_buildListButton(
|
||||
context,
|
||||
CodeBlockKeys.type,
|
||||
const Icon(Icons.abc),
|
||||
LocaleKeys.document_selectionMenu_codeBlock.tr(),
|
||||
),
|
||||
// outline
|
||||
_buildListButton(
|
||||
context,
|
||||
OutlineBlockKeys.type,
|
||||
const Icon(Icons.list_alt),
|
||||
LocaleKeys.document_selectionMenu_outline.tr(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildListButton(
|
||||
BuildContext context,
|
||||
String listBlockType,
|
||||
Widget icon,
|
||||
String label,
|
||||
) {
|
||||
final node = editorState.getNodeAtPath(selection.start.path);
|
||||
final type = node?.type;
|
||||
if (node == null || type == null) {
|
||||
const SizedBox.shrink();
|
||||
}
|
||||
final isSelected = type == listBlockType;
|
||||
return MobileToolbarItemMenuBtn(
|
||||
icon: icon,
|
||||
label: FlowyText(label),
|
||||
isSelected: isSelected,
|
||||
onPressed: () async {
|
||||
await editorState.formatNode(
|
||||
selection,
|
||||
(node) {
|
||||
final attributes = {
|
||||
ParagraphBlockKeys.delta: (node.delta ?? Delta()).toJson(),
|
||||
if (listBlockType == TodoListBlockKeys.type)
|
||||
TodoListBlockKeys.checked: false,
|
||||
if (listBlockType == CalloutBlockKeys.type)
|
||||
CalloutBlockKeys.icon: '📌',
|
||||
};
|
||||
return node.copyWith(
|
||||
type: isSelected ? ParagraphBlockKeys.type : listBlockType,
|
||||
attributes: attributes,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,251 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_blocks_menu.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// convert the current block to other block types
|
||||
// only show in single selection and text type
|
||||
final mobileConvertBlockToolbarItem = MobileToolbarItem.withMenu(
|
||||
itemIconBuilder: (_, editorState, ___) {
|
||||
if (!onlyShowInSingleSelectionAndTextType(editorState)) {
|
||||
return null;
|
||||
}
|
||||
return const FlowySvg(
|
||||
FlowySvgs.convert_s,
|
||||
size: Size.square(22),
|
||||
);
|
||||
},
|
||||
itemMenuBuilder: (_, editorState, service) {
|
||||
final selection = editorState.selection;
|
||||
if (selection == null) {
|
||||
return null;
|
||||
}
|
||||
return BlocksMenu(
|
||||
items: _convertToBlockMenuItems,
|
||||
editorState: editorState,
|
||||
service: service,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
final _convertToBlockMenuItems = [
|
||||
// paragraph
|
||||
BlockMenuItem(
|
||||
blockType: ParagraphBlockKeys.type,
|
||||
icon: const FlowySvg(FlowySvgs.m_text_decoration_m),
|
||||
label: LocaleKeys.editor_text.tr(),
|
||||
onTap: (editorState, selection, _) => editorState.convertBlockType(
|
||||
selection,
|
||||
ParagraphBlockKeys.type,
|
||||
),
|
||||
),
|
||||
|
||||
// to-do list
|
||||
BlockMenuItem(
|
||||
blockType: TodoListBlockKeys.type,
|
||||
icon: const FlowySvg(FlowySvgs.m_checkbox_m),
|
||||
label: LocaleKeys.editor_checkbox.tr(),
|
||||
onTap: (editorState, selection, _) => editorState.convertBlockType(
|
||||
selection,
|
||||
TodoListBlockKeys.type,
|
||||
extraAttributes: {
|
||||
TodoListBlockKeys.checked: false,
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// heading 1 - 3
|
||||
BlockMenuItem(
|
||||
blockType: HeadingBlockKeys.type,
|
||||
icon: const FlowySvg(FlowySvgs.m_h1_m),
|
||||
label: LocaleKeys.editor_heading1.tr(),
|
||||
isSelected: (editorState, selection) => _isHeadingSelected(
|
||||
editorState,
|
||||
selection,
|
||||
1,
|
||||
),
|
||||
onTap: (editorState, selection, _) {
|
||||
final isSelected = _isHeadingSelected(
|
||||
editorState,
|
||||
selection,
|
||||
1,
|
||||
);
|
||||
editorState.convertBlockType(
|
||||
selection,
|
||||
HeadingBlockKeys.type,
|
||||
isSelected: isSelected,
|
||||
extraAttributes: {
|
||||
HeadingBlockKeys.level: 1,
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
BlockMenuItem(
|
||||
blockType: HeadingBlockKeys.type,
|
||||
icon: const FlowySvg(FlowySvgs.m_h2_m),
|
||||
label: LocaleKeys.editor_heading2.tr(),
|
||||
isSelected: (editorState, selection) => _isHeadingSelected(
|
||||
editorState,
|
||||
selection,
|
||||
2,
|
||||
),
|
||||
onTap: (editorState, selection, _) {
|
||||
final isSelected = _isHeadingSelected(
|
||||
editorState,
|
||||
selection,
|
||||
2,
|
||||
);
|
||||
editorState.convertBlockType(
|
||||
selection,
|
||||
HeadingBlockKeys.type,
|
||||
isSelected: isSelected,
|
||||
extraAttributes: {
|
||||
HeadingBlockKeys.level: 2,
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
BlockMenuItem(
|
||||
blockType: HeadingBlockKeys.type,
|
||||
icon: const FlowySvg(FlowySvgs.m_h3_m),
|
||||
label: LocaleKeys.editor_heading3.tr(),
|
||||
isSelected: (editorState, selection) => _isHeadingSelected(
|
||||
editorState,
|
||||
selection,
|
||||
3,
|
||||
),
|
||||
onTap: (editorState, selection, _) {
|
||||
final isSelected = _isHeadingSelected(
|
||||
editorState,
|
||||
selection,
|
||||
3,
|
||||
);
|
||||
editorState.convertBlockType(
|
||||
selection,
|
||||
HeadingBlockKeys.type,
|
||||
isSelected: isSelected,
|
||||
extraAttributes: {
|
||||
HeadingBlockKeys.level: 3,
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
// bulleted list
|
||||
BlockMenuItem(
|
||||
blockType: BulletedListBlockKeys.type,
|
||||
icon: const FlowySvg(FlowySvgs.m_bulleted_list_m),
|
||||
label: LocaleKeys.editor_bulletedList.tr(),
|
||||
onTap: (editorState, selection, _) => editorState.convertBlockType(
|
||||
selection,
|
||||
BulletedListBlockKeys.type,
|
||||
),
|
||||
),
|
||||
|
||||
// numbered list
|
||||
BlockMenuItem(
|
||||
blockType: NumberedListBlockKeys.type,
|
||||
icon: const FlowySvg(FlowySvgs.m_numbered_list_m),
|
||||
label: LocaleKeys.editor_numberedList.tr(),
|
||||
onTap: (editorState, selection, _) => editorState.convertBlockType(
|
||||
selection,
|
||||
NumberedListBlockKeys.type,
|
||||
),
|
||||
),
|
||||
|
||||
// toggle list
|
||||
BlockMenuItem(
|
||||
blockType: ToggleListBlockKeys.type,
|
||||
icon: const FlowySvg(FlowySvgs.m_toggle_list_m),
|
||||
label: LocaleKeys.document_plugins_toggleList.tr(),
|
||||
onTap: (editorState, selection, _) => editorState.convertBlockType(
|
||||
selection,
|
||||
ToggleListBlockKeys.type,
|
||||
),
|
||||
),
|
||||
|
||||
// quote
|
||||
BlockMenuItem(
|
||||
blockType: QuoteBlockKeys.type,
|
||||
icon: const FlowySvg(FlowySvgs.m_quote_m),
|
||||
label: LocaleKeys.editor_quote.tr(),
|
||||
onTap: (editorState, selection, _) => editorState.convertBlockType(
|
||||
selection,
|
||||
QuoteBlockKeys.type,
|
||||
),
|
||||
),
|
||||
|
||||
// callout
|
||||
BlockMenuItem(
|
||||
blockType: CalloutBlockKeys.type,
|
||||
// FIXME: update icon
|
||||
icon: const Icon(Icons.note_rounded),
|
||||
label: LocaleKeys.document_plugins_callout.tr(),
|
||||
onTap: (editorState, selection, _) => editorState.convertBlockType(
|
||||
selection,
|
||||
CalloutBlockKeys.type,
|
||||
extraAttributes: {
|
||||
CalloutBlockKeys.icon: '📌',
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// code
|
||||
BlockMenuItem(
|
||||
blockType: CodeBlockKeys.type,
|
||||
icon: const FlowySvg(FlowySvgs.m_code_m),
|
||||
label: LocaleKeys.document_selectionMenu_codeBlock.tr(),
|
||||
onTap: (editorState, selection, _) => editorState.convertBlockType(
|
||||
selection,
|
||||
CodeBlockKeys.type,
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
extension on EditorState {
|
||||
Future<void> convertBlockType(
|
||||
Selection selection,
|
||||
String newBlockType, {
|
||||
Attributes? extraAttributes,
|
||||
bool? isSelected,
|
||||
}) async {
|
||||
final node = getNodeAtPath(selection.start.path);
|
||||
final type = node?.type;
|
||||
if (node == null || type == null) {
|
||||
assert(false, 'node or type is null');
|
||||
return;
|
||||
}
|
||||
final selected = isSelected ?? type == newBlockType;
|
||||
await formatNode(
|
||||
selection,
|
||||
(node) {
|
||||
final attributes = {
|
||||
ParagraphBlockKeys.delta: (node.delta ?? Delta()).toJson(),
|
||||
// for some block types, they have extra attributes, like todo list has checked attribute, callout has icon attribute, etc.
|
||||
if (!selected && extraAttributes != null) ...extraAttributes,
|
||||
};
|
||||
return node.copyWith(
|
||||
type: selected ? ParagraphBlockKeys.type : newBlockType,
|
||||
attributes: attributes,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool _isHeadingSelected(
|
||||
EditorState editorState,
|
||||
Selection selection,
|
||||
int level,
|
||||
) {
|
||||
final node = editorState.getNodeAtPath(selection.start.path);
|
||||
final type = node?.type;
|
||||
if (node == null || type == null) {
|
||||
return false;
|
||||
}
|
||||
return type == HeadingBlockKeys.type &&
|
||||
node.attributes[HeadingBlockKeys.level] == level;
|
||||
}
|
@ -2,23 +2,23 @@ import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
final mobileIndentToolbarItem = MobileToolbarItem.action(
|
||||
itemIconBuilder: (_, editorState) {
|
||||
itemIconBuilder: (_, editorState, __) {
|
||||
return onlyShowInTextType(editorState)
|
||||
? const Icon(Icons.format_indent_increase_rounded)
|
||||
: null;
|
||||
},
|
||||
actionHandler: (editorState, selection) {
|
||||
actionHandler: (_, editorState) {
|
||||
indentCommand.execute(editorState);
|
||||
},
|
||||
);
|
||||
|
||||
final mobileOutdentToolbarItem = MobileToolbarItem.action(
|
||||
itemIconBuilder: (_, editorState) {
|
||||
itemIconBuilder: (_, editorState, __) {
|
||||
return onlyShowInTextType(editorState)
|
||||
? const Icon(Icons.format_indent_decrease_rounded)
|
||||
: null;
|
||||
},
|
||||
actionHandler: (editorState, selection) {
|
||||
actionHandler: (_, editorState) {
|
||||
outdentCommand.execute(editorState);
|
||||
},
|
||||
);
|
||||
|
@ -0,0 +1,106 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
final customTextDecorationMobileToolbarItem = MobileToolbarItem.withMenu(
|
||||
itemIconBuilder: (_, __, ___) => const AFMobileIcon(
|
||||
afMobileIcons: AFMobileIcons.textDecoration,
|
||||
),
|
||||
itemMenuBuilder: (_, editorState, __) {
|
||||
final selection = editorState.selection;
|
||||
if (selection == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return _TextDecorationMenu(editorState, selection);
|
||||
},
|
||||
);
|
||||
|
||||
class _TextDecorationMenu extends StatefulWidget {
|
||||
const _TextDecorationMenu(
|
||||
this.editorState,
|
||||
this.selection,
|
||||
);
|
||||
|
||||
final EditorState editorState;
|
||||
final Selection selection;
|
||||
|
||||
@override
|
||||
State<_TextDecorationMenu> createState() => _TextDecorationMenuState();
|
||||
}
|
||||
|
||||
class _TextDecorationMenuState extends State<_TextDecorationMenu> {
|
||||
final textDecorations = [
|
||||
// BIUS
|
||||
TextDecorationUnit(
|
||||
icon: AFMobileIcons.bold,
|
||||
label: AppFlowyEditorL10n.current.bold,
|
||||
name: AppFlowyRichTextKeys.bold,
|
||||
),
|
||||
TextDecorationUnit(
|
||||
icon: AFMobileIcons.italic,
|
||||
label: AppFlowyEditorL10n.current.italic,
|
||||
name: AppFlowyRichTextKeys.italic,
|
||||
),
|
||||
TextDecorationUnit(
|
||||
icon: AFMobileIcons.underline,
|
||||
label: AppFlowyEditorL10n.current.underline,
|
||||
name: AppFlowyRichTextKeys.underline,
|
||||
),
|
||||
TextDecorationUnit(
|
||||
icon: AFMobileIcons.strikethrough,
|
||||
label: AppFlowyEditorL10n.current.strikethrough,
|
||||
name: AppFlowyRichTextKeys.strikethrough,
|
||||
),
|
||||
|
||||
// Code
|
||||
TextDecorationUnit(
|
||||
icon: AFMobileIcons.code,
|
||||
label: AppFlowyEditorL10n.current.embedCode,
|
||||
name: AppFlowyRichTextKeys.code,
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bius = textDecorations.map((currentDecoration) {
|
||||
// Check current decoration is active or not
|
||||
final selection = widget.selection;
|
||||
final nodes = widget.editorState.getNodesInSelection(selection);
|
||||
final bool isSelected;
|
||||
if (selection.isCollapsed) {
|
||||
isSelected = widget.editorState.toggledStyle.containsKey(
|
||||
currentDecoration.name,
|
||||
);
|
||||
} else {
|
||||
isSelected = nodes.allSatisfyInSelection(selection, (delta) {
|
||||
return delta.everyAttributes(
|
||||
(attributes) => attributes[currentDecoration.name] == true,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return MobileToolbarItemMenuBtn(
|
||||
icon: AFMobileIcon(
|
||||
afMobileIcons: currentDecoration.icon,
|
||||
),
|
||||
label: FlowyText(currentDecoration.label),
|
||||
isSelected: isSelected,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
widget.editorState.toggleAttribute(currentDecoration.name);
|
||||
});
|
||||
},
|
||||
);
|
||||
}).toList();
|
||||
|
||||
return GridView.count(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.zero,
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
childAspectRatio: 4,
|
||||
children: bius,
|
||||
);
|
||||
}
|
||||
}
|
@ -26,9 +26,11 @@ export 'inline_math_equation/inline_math_equation.dart';
|
||||
export 'inline_math_equation/inline_math_equation_toolbar_item.dart';
|
||||
export 'math_equation/math_equation_block_component.dart';
|
||||
export 'math_equation/mobile_math_equation_toolbar_item.dart';
|
||||
export 'mobile_toolbar_item/mobile_add_block_toolbar_item.dart';
|
||||
export 'mobile_toolbar_item/mobile_align_toolbar_item.dart';
|
||||
export 'mobile_toolbar_item/mobile_blocks_toolbar_item.dart';
|
||||
export 'mobile_toolbar_item/mobile_convert_block_toolbar_item.dart';
|
||||
export 'mobile_toolbar_item/mobile_indent_toolbar_items.dart';
|
||||
export 'mobile_toolbar_item/mobile_text_decoration_item.dart';
|
||||
export 'openai/widgets/auto_completion_node_widget.dart';
|
||||
export 'openai/widgets/smart_edit_node_widget.dart';
|
||||
export 'openai/widgets/smart_edit_toolbar_item.dart';
|
||||
|
@ -2,8 +2,10 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
final redoMobileToolbarItem = MobileToolbarItem.action(
|
||||
itemIconBuilder: (_, __) => const FlowySvg(FlowySvgs.m_redo_m),
|
||||
actionHandler: (editorState, selection) async {
|
||||
itemIconBuilder: (_, __, ___) => const FlowySvg(
|
||||
FlowySvgs.m_redo_m,
|
||||
),
|
||||
actionHandler: (_, editorState) async {
|
||||
editorState.undoManager.redo();
|
||||
},
|
||||
);
|
||||
|
@ -2,8 +2,10 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
final undoMobileToolbarItem = MobileToolbarItem.action(
|
||||
itemIconBuilder: (_, __) => const FlowySvg(FlowySvgs.m_undo_m),
|
||||
actionHandler: (editorState, selection) async {
|
||||
itemIconBuilder: (_, __, ___) => const FlowySvg(
|
||||
FlowySvgs.m_undo_m,
|
||||
),
|
||||
actionHandler: (_, editorState) async {
|
||||
editorState.undoManager.undo();
|
||||
},
|
||||
);
|
||||
|
@ -83,11 +83,14 @@ class EditorStyleCustomizer {
|
||||
|
||||
EditorStyle mobile() {
|
||||
final theme = Theme.of(context);
|
||||
const fontSize = 14.0;
|
||||
final fontFamily = GoogleFonts.poppins().fontFamily ?? 'Poppins';
|
||||
final fontSize = context.read<DocumentAppearanceCubit>().state.fontSize;
|
||||
final fontFamily = context.read<DocumentAppearanceCubit>().state.fontFamily;
|
||||
final defaultTextDirection =
|
||||
context.read<DocumentAppearanceCubit>().state.defaultTextDirection;
|
||||
final codeFontSize = max(0.0, fontSize - 2);
|
||||
return EditorStyle.mobile(
|
||||
padding: padding,
|
||||
defaultTextDirection: defaultTextDirection,
|
||||
textStyleConfiguration: TextStyleConfiguration(
|
||||
text: baseTextStyle(fontFamily).copyWith(
|
||||
fontSize: fontSize,
|
||||
|
@ -3,6 +3,8 @@ import 'package:appflowy/mobile/presentation/database/mobile_calendar_screen.dar
|
||||
import 'package:appflowy/mobile/presentation/database/mobile_grid_screen.dart';
|
||||
import 'package:appflowy/mobile/presentation/favorite/mobile_favorite_page.dart';
|
||||
import 'package:appflowy/mobile/presentation/presentation.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/font/font_picker_screen.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/language/language_picker_screen.dart';
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/code_block/code_language_screen.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_picker_screen.dart';
|
||||
@ -57,6 +59,8 @@ GoRouter generateRouter(Widget child) {
|
||||
|
||||
// code language picker
|
||||
_mobileCodeLanguagePickerPageRoute(),
|
||||
_mobileLanguagePickerPageRoute(),
|
||||
_mobileFontPickerPageRoute(),
|
||||
],
|
||||
|
||||
// Desktop and Mobile
|
||||
@ -215,8 +219,12 @@ GoRoute _mobileEmojiPickerPageRoute() {
|
||||
parentNavigatorKey: AppGlobals.rootNavKey,
|
||||
path: MobileEmojiPickerScreen.routeName,
|
||||
pageBuilder: (context, state) {
|
||||
return const MaterialPage(
|
||||
child: MobileEmojiPickerScreen(),
|
||||
final title =
|
||||
state.uri.queryParameters[MobileEmojiPickerScreen.pageTitle];
|
||||
return MaterialPage(
|
||||
child: MobileEmojiPickerScreen(
|
||||
title: title,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -246,6 +254,30 @@ GoRoute _mobileCodeLanguagePickerPageRoute() {
|
||||
);
|
||||
}
|
||||
|
||||
GoRoute _mobileLanguagePickerPageRoute() {
|
||||
return GoRoute(
|
||||
parentNavigatorKey: AppGlobals.rootNavKey,
|
||||
path: LanguagePickerScreen.routeName,
|
||||
pageBuilder: (context, state) {
|
||||
return const MaterialPage(
|
||||
child: LanguagePickerScreen(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
GoRoute _mobileFontPickerPageRoute() {
|
||||
return GoRoute(
|
||||
parentNavigatorKey: AppGlobals.rootNavKey,
|
||||
path: FontPickerScreen.routeName,
|
||||
pageBuilder: (context, state) {
|
||||
return const MaterialPage(
|
||||
child: FontPickerScreen(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
GoRoute _desktopHomeScreenRoute() {
|
||||
return GoRoute(
|
||||
path: DesktopHomeScreen.routeName,
|
||||
|
@ -5,10 +5,11 @@ import 'package:appflowy/env/env.dart';
|
||||
import 'package:appflowy/user/application/supabase_realtime.dart';
|
||||
import 'package:appflowy/workspace/application/settings/application_data_storage.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
import 'package:url_protocol/url_protocol.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
import 'package:url_protocol/url_protocol.dart';
|
||||
|
||||
import '../startup.dart';
|
||||
|
||||
// ONLY supports in macOS and Windows now.
|
||||
@ -58,7 +59,9 @@ class InitSupabaseTask extends LaunchTask {
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
await realtimeService?.dispose();
|
||||
realtimeService = null;
|
||||
supabase?.dispose();
|
||||
supabase = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,6 +56,14 @@ class FlowyButton extends StatelessWidget {
|
||||
final alpha = (255 * disableOpacity).toInt();
|
||||
color.withAlpha(alpha);
|
||||
|
||||
if (Platform.isIOS || Platform.isAndroid) {
|
||||
return InkWell(
|
||||
onTap: disable ? null : onTap,
|
||||
onSecondaryTap: disable ? null : onSecondaryTap,
|
||||
child: _render(context),
|
||||
);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: disable ? null : onTap,
|
||||
|
@ -54,8 +54,8 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "4f073f3"
|
||||
resolved-ref: "4f073f3381a05a2379144f282c6f65462c4ce9c6"
|
||||
ref: "2617f7"
|
||||
resolved-ref: "2617f766a5a4aa83c9f4d5c4d7221c12dbe23b66"
|
||||
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
||||
source: git
|
||||
version: "2.0.0-beta.1"
|
||||
@ -970,6 +970,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.1"
|
||||
keyboard_height_plugin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: keyboard_height_plugin
|
||||
sha256: "9fd5cd9e3e80d8f530aaa26ad17b4d18d34a63956cf0d530920a54c228200510"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.4"
|
||||
linked_scroll_controller:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -47,7 +47,7 @@ dependencies:
|
||||
appflowy_editor:
|
||||
git:
|
||||
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
||||
ref: 4f073f3
|
||||
ref: '2617f7'
|
||||
|
||||
appflowy_popover:
|
||||
path: packages/appflowy_popover
|
||||
|
1
frontend/resources/flowy_icons/16x/convert.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="50px" height="50px"><path d="M 20 4 C 14.507813 4 10 8.507813 10 14 L 10 31.75 L 7.125 28.875 L 4.3125 31.71875 L 12 39.40625 L 19.6875 31.71875 L 16.875 28.90625 L 14 31.75 L 14 14 C 14 10.691406 16.691406 8 20 8 L 31 8 L 31 4 Z M 38 10.59375 L 30.28125 18.3125 L 33.125 21.125 L 36 18.25 L 36 36 C 36 39.308594 33.308594 42 30 42 L 19 42 L 19 46 L 30 46 C 35.492188 46 40 41.492188 40 36 L 40 18.25 L 42.875 21.125 L 45.6875 18.28125 Z"/></svg>
|
After Width: | Height: | Size: 514 B |
3
frontend/resources/flowy_icons/24x/m_bold.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.48828 3.90625C7.65988 3.90625 6.98828 4.53823 6.98828 5.31823C6.98828 6.02423 6.98828 10.2592 6.98828 10.9652V18.4942C6.98828 19.2742 7.65988 19.9062 8.48828 19.9062C8.71218 19.9062 10.8837 19.9062 12.4883 19.9062C15.5259 19.9062 17.9883 17.5882 17.9883 14.7302C17.9883 13.1162 17.1954 11.6862 16.0192 10.7762C16.6546 10.0452 16.9883 9.15922 16.9883 8.14122C16.9883 5.80223 14.9735 3.90625 12.4883 3.90625C11.7348 3.90625 9.02118 3.90625 8.48828 3.90625ZM9.98828 6.73021C10.9796 6.73021 12.0174 6.73021 12.4883 6.73021C13.3167 6.73021 13.9883 7.36222 13.9883 8.14122C13.9883 8.92122 13.3167 9.5532 12.4883 9.5532C12.03 9.5532 10.9805 9.5532 9.98828 9.5532C9.98828 8.53821 9.98828 7.74421 9.98828 6.73021ZM9.98828 12.3772C10.9821 12.3772 12.0241 12.3772 12.4883 12.3772C13.869 12.3772 14.9883 13.4302 14.9883 14.7302C14.9883 16.0292 13.869 17.0822 12.4883 17.0822C11.4854 17.0822 10.8202 17.0822 9.98828 17.0822V12.3772Z" fill="#2F3030"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
3
frontend/resources/flowy_icons/24x/m_bulleted_list.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.99902 6.10352C2.44702 6.10352 1.99902 6.55152 1.99902 7.10352C1.99902 7.65552 2.44702 8.10352 2.99902 8.10352C3.55102 8.10352 3.99902 7.65552 3.99902 7.10352C3.99902 6.55152 3.55102 6.10352 2.99902 6.10352ZM6.99902 6.10352C6.44702 6.10352 5.99902 6.55152 5.99902 7.10352C5.99902 7.65552 6.44702 8.10352 6.99902 8.10352H20.999C21.551 8.10352 21.999 7.65552 21.999 7.10352C21.999 6.55152 21.551 6.10352 20.999 6.10352H6.99902ZM2.99902 11.1035C2.44702 11.1035 1.99902 11.5515 1.99902 12.1035C1.99902 12.6555 2.44702 13.1035 2.99902 13.1035C3.55102 13.1035 3.99902 12.6555 3.99902 12.1035C3.99902 11.5515 3.55102 11.1035 2.99902 11.1035ZM6.99902 11.1035C6.44702 11.1035 5.99902 11.5515 5.99902 12.1035C5.99902 12.6555 6.44702 13.1035 6.99902 13.1035H20.999C21.551 13.1035 21.999 12.6555 21.999 12.1035C21.999 11.5515 21.551 11.1035 20.999 11.1035H6.99902ZM2.99902 16.1035C2.44702 16.1035 1.99902 16.5515 1.99902 17.1035C1.99902 17.6555 2.44702 18.1035 2.99902 18.1035C3.55102 18.1035 3.99902 17.6555 3.99902 17.1035C3.99902 16.5515 3.55102 16.1035 2.99902 16.1035ZM6.99902 16.1035C6.44702 16.1035 5.99902 16.5515 5.99902 17.1035C5.99902 17.6555 6.44702 18.1035 6.99902 18.1035H20.999C21.551 18.1035 21.999 17.6555 21.999 17.1035C21.999 16.5515 21.551 16.1035 20.999 16.1035H6.99902Z" fill="#2F3030"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
3
frontend/resources/flowy_icons/24x/m_checkbox.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.99072 2.99829C4.78162 2.99829 2.99072 4.78929 2.99072 6.99828V16.9983C2.99072 19.2073 4.78162 20.9983 6.99072 20.9983H16.9907C19.1997 20.9983 20.9907 19.2073 20.9907 16.9983V6.99828C20.9907 4.78929 19.1997 2.99829 16.9907 2.99829H6.99072ZM6.99072 4.99829H16.9907C18.0957 4.99829 18.9907 5.89329 18.9907 6.99828V16.9983C18.9907 18.1033 18.0957 18.9983 16.9907 18.9983H6.99072C5.88612 18.9983 4.99072 18.1033 4.99072 16.9983V6.99828C4.99072 5.89329 5.88612 4.99829 6.99072 4.99829ZM15.9907 8.81029C15.7347 8.81029 15.4677 8.89728 15.2717 9.09228L11.5527 12.8103C11.2957 13.0683 11.0367 13.0513 10.8347 12.7483L9.83453 11.2483C9.52813 10.7883 8.88782 10.6603 8.42822 10.9673C7.96872 11.2733 7.84062 11.9133 8.14692 12.3733L9.14692 13.8733C10.0497 15.2263 11.8097 15.3983 12.9597 14.2483L16.7097 10.4983C17.0997 10.1073 17.0997 9.48227 16.7097 9.09228C16.5137 8.89628 16.2467 8.81129 15.9907 8.81029Z" fill="#2F3030"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
3
frontend/resources/flowy_icons/24x/m_close.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.00586 2.92969C4.79686 2.92969 3.00586 4.72069 3.00586 6.92969V16.9297C3.00586 19.1387 4.79686 20.9297 7.00586 20.9297H17.0059C19.2149 20.9297 21.0059 19.1387 21.0059 16.9297V6.92969C21.0059 4.72069 19.2149 2.92969 17.0059 2.92969H7.00586ZM9.00586 7.92969C9.26186 7.92969 9.52885 8.01569 9.72485 8.21069L12.0059 10.4917L14.2869 8.21069C14.4819 8.01569 14.7499 7.92969 15.0059 7.92969C15.2619 7.92969 15.5289 8.01569 15.7249 8.21069C16.1149 8.60169 16.1149 9.25768 15.7249 9.64868L13.4438 11.9297L15.7249 14.2107C16.1149 14.6017 16.1149 15.2577 15.7249 15.6487C15.3339 16.0387 14.6779 16.0387 14.2869 15.6487L12.0059 13.3677L9.72485 15.6487C9.33385 16.0387 8.67787 16.0387 8.28687 15.6487C7.89687 15.2577 7.89687 14.6017 8.28687 14.2107L10.5679 11.9297L8.28687 9.64868C7.89687 9.25768 7.89687 8.60169 8.28687 8.21069C8.48187 8.01569 8.74986 7.92969 9.00586 7.92969Z" fill="#2F3030"/>
|
||||
</svg>
|
After Width: | Height: | Size: 997 B |
3
frontend/resources/flowy_icons/24x/m_code.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.3071 4.02869C13.7761 3.87769 13.2089 4.18468 13.0571 4.71668L9.05711 18.7167C8.90541 19.2477 9.21361 19.8147 9.74471 19.9667C10.2757 20.1177 10.8429 19.8107 10.9947 19.2787L14.9947 5.27869C15.1464 4.74769 14.8382 4.18069 14.3071 4.02869ZM6.83841 5.02869C6.58741 5.07869 6.33531 5.20567 6.18211 5.43567L2.18211 11.4357C1.98311 11.7337 1.95921 12.1147 2.11971 12.4357L5.11971 18.4357C5.36661 18.9287 5.96941 19.1507 6.46341 18.9037C6.95741 18.6567 7.17911 18.0537 6.93211 17.5597L4.18211 12.0917L7.86971 6.55969C8.17601 6.10069 8.04791 5.46069 7.58841 5.15369C7.35861 5.00069 7.08941 4.97869 6.83841 5.02869ZM18.3384 5.05969C18.0956 4.97969 17.8354 4.96768 17.5884 5.09168C17.0944 5.33868 16.8727 5.94167 17.1197 6.43567L19.8697 11.9037L16.1821 17.4357C15.8758 17.8947 16.0039 18.5347 16.4634 18.8417C16.9229 19.1477 17.5633 19.0197 17.8697 18.5597L21.8697 12.5597C22.0687 12.2617 22.0926 11.8807 21.9321 11.5597L18.9321 5.55969C18.8087 5.31369 18.5812 5.14169 18.3384 5.05969Z" fill="#2F3030"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
3
frontend/resources/flowy_icons/24x/m_color.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.999 1.95312C6.47602 1.95312 1.99902 6.43012 1.99902 11.9531C1.99902 17.4761 6.47602 21.9531 11.999 21.9531C12.08 21.9531 12.695 21.9661 12.905 21.9531C13.185 21.9351 13.437 21.8911 13.718 21.8281C15.05 21.5281 15.941 20.6311 16.187 19.4221C16.279 18.9641 16.23 18.6641 16.093 17.8281C16.037 17.4871 16.009 17.3241 15.999 17.1411C15.977 16.7211 16.064 16.4761 16.28 16.2651C16.472 16.0791 16.651 15.9791 16.874 15.9531C17.105 15.9261 17.341 15.9771 17.843 16.0781C18.473 16.2051 18.683 16.2131 19.03 16.2031C20.516 16.1611 21.705 15.0451 21.905 13.5151C21.975 12.9841 21.999 12.4771 21.999 11.9531C21.999 6.43012 17.522 1.95312 11.999 1.95312ZM11.999 3.95312C16.417 3.95312 19.999 7.53511 19.999 11.9531C19.999 12.3881 19.964 12.8181 19.905 13.2651C19.83 13.8431 19.447 14.1891 18.968 14.2031C18.835 14.2071 18.708 14.2021 18.249 14.1091C17.549 13.9681 17.159 13.9251 16.655 13.9841C16.001 14.0611 15.387 14.3281 14.874 14.8281C14.196 15.4881 13.98 16.2861 14.03 17.2341C14.045 17.5131 14.056 17.7611 14.124 18.1721C14.216 18.7281 14.242 18.8981 14.218 19.0151C14.129 19.4531 13.844 19.7321 13.28 19.8591C13.106 19.8991 12.957 19.9421 12.78 19.9531C12.629 19.9631 12.489 19.9541 12.155 19.9531C12.075 19.9531 12.084 19.9531 11.999 19.9531C7.58102 19.9531 3.99902 16.3711 3.99902 11.9531C3.99902 7.53511 7.58102 3.95312 11.999 3.95312ZM12.999 6.07812C12.171 6.07812 11.499 6.75012 11.499 7.57811C11.499 8.40611 12.171 9.07811 12.999 9.07811C13.827 9.07811 14.499 8.40611 14.499 7.57811C14.499 6.75012 13.827 6.07812 12.999 6.07812ZM8.81104 7.26513C7.98304 7.26613 7.31104 7.93712 7.31104 8.76512C7.31104 9.59412 7.98304 10.2651 8.81104 10.2651C9.64004 10.2651 10.311 9.59412 10.311 8.76512C10.312 7.93712 9.64004 7.26513 8.81104 7.26513ZM16.218 8.98412C15.389 8.98412 14.718 9.65612 14.718 10.4841C14.718 11.3131 15.389 11.9841 16.218 11.9841C17.046 11.9841 17.718 11.3131 17.718 10.4841C17.718 9.65612 17.046 8.98412 16.218 8.98412ZM7.59302 11.4221C6.76402 11.4221 6.09302 12.0931 6.09302 12.9221C6.09302 13.7501 6.76402 14.4221 7.59302 14.4221C8.42102 14.4221 9.09302 13.7501 9.09302 12.9221C9.09302 12.0931 8.42102 11.4221 7.59302 11.4221ZM10.499 14.6721C9.67102 14.6721 8.99902 15.3431 8.99902 16.1721C8.99902 17.0001 9.67102 17.6721 10.499 17.6721C11.327 17.6721 11.999 17.0001 11.999 16.1721C11.999 15.3431 11.327 14.6721 10.499 14.6721Z" fill="#2F3030"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
3
frontend/resources/flowy_icons/24x/m_divider.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.99805 11.0142C3.44605 11.0142 2.99805 11.4622 2.99805 12.0142C2.99805 12.5662 3.44605 13.0142 3.99805 13.0142H19.998C20.55 13.0142 20.998 12.5662 20.998 12.0142C20.998 11.4622 20.55 11.0142 19.998 11.0142H3.99805Z" fill="#2F3030"/>
|
||||
</svg>
|
After Width: | Height: | Size: 347 B |
3
frontend/resources/flowy_icons/24x/m_h1.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.99121 3.92166C3.43891 3.92166 2.99121 4.36966 2.99121 4.92166V18.9216C2.99121 19.4736 3.43891 19.9216 3.99121 19.9216C4.54351 19.9216 4.99121 19.4736 4.99121 18.9216V12.9216H10.9912V18.9216C10.9912 19.4736 11.4389 19.9216 11.9912 19.9216C12.5435 19.9216 12.9912 19.4736 12.9912 18.9216V4.92166C12.9912 4.36966 12.5435 3.92166 11.9912 3.92166C11.4389 3.92166 10.9912 4.36966 10.9912 4.92166V10.9216H4.99121V4.92166C4.99121 4.36966 4.54351 3.92166 3.99121 3.92166ZM20.3662 3.98368C20.0182 3.83968 19.5875 3.88767 19.2724 4.20267L15.2724 8.20266C14.8819 8.59366 14.8819 9.24965 15.2724 9.64065C15.663 10.0306 16.3194 10.0306 16.71 9.64065L18.9912 7.35867V18.9216C18.9912 19.4736 19.4389 19.9216 19.9912 19.9216C20.5435 19.9216 20.9912 19.4736 20.9912 18.9216V4.92166C20.9912 4.47666 20.7142 4.12868 20.3662 3.98368Z" fill="#2F3030"/>
|
||||
</svg>
|
After Width: | Height: | Size: 946 B |
3
frontend/resources/flowy_icons/24x/m_h2.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.01367 3.90625C2.46137 3.90625 2.01367 4.35425 2.01367 4.90625V18.9062C2.01367 19.4582 2.46137 19.9062 3.01367 19.9062C3.56597 19.9062 4.01367 19.4582 4.01367 18.9062V12.9062H10.0137V18.9062C10.0137 19.4582 10.4617 19.9062 11.0137 19.9062C11.5657 19.9062 12.0137 19.4582 12.0137 18.9062V4.90625C12.0137 4.35425 11.5657 3.90625 11.0137 3.90625C10.4617 3.90625 10.0137 4.35425 10.0137 4.90625V10.9062H4.01367V4.90625C4.01367 4.35425 3.56597 3.90625 3.01367 3.90625ZM18.0137 3.90625C15.8047 3.90625 14.0137 5.69724 14.0137 7.90623C14.0137 8.45823 14.4617 8.90623 15.0137 8.90623C15.5657 8.90623 16.0137 8.45823 16.0137 7.90623C16.0137 6.80124 16.9087 5.90624 18.0137 5.90624C19.1187 5.90624 20.0137 6.80124 20.0137 7.90623C20.0137 9.64023 19.3687 10.6142 17.2947 12.6872C14.8687 15.1142 14.0137 16.4732 14.0137 18.9062C14.0137 19.4582 14.4617 19.9062 15.0137 19.9062C16.1247 19.9062 20.1247 19.9062 21.0137 19.9062C21.5657 19.9062 22.0137 19.4582 22.0137 18.9062C22.0137 18.3542 21.5657 17.9062 21.0137 17.9062C20.3097 17.9062 17.8897 17.9062 16.2637 17.9062C16.5107 16.7402 17.1137 15.7432 18.7327 14.1252C21.1587 11.6982 22.0137 10.3392 22.0137 7.90623C22.0137 5.69724 20.2227 3.90625 18.0137 3.90625Z" fill="#2F3030"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
3
frontend/resources/flowy_icons/24x/m_h3.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.01465 3.90625C2.46265 3.90625 2.01465 4.35425 2.01465 4.90625V18.9062C2.01465 19.4582 2.46265 19.9062 3.01465 19.9062C3.56665 19.9062 4.01465 19.4582 4.01465 18.9062V12.9062H10.0146V18.9062C10.0146 19.4582 10.4626 19.9062 11.0146 19.9062C11.5666 19.9062 12.0146 19.4582 12.0146 18.9062V4.90625C12.0146 4.35425 11.5666 3.90625 11.0146 3.90625C10.4626 3.90625 10.0146 4.35425 10.0146 4.90625V10.9062H4.01465V4.90625C4.01465 4.35425 3.56665 3.90625 3.01465 3.90625ZM18.0146 3.90625C15.8056 3.90625 14.0146 5.69724 14.0146 7.90623C14.0146 8.45823 14.4626 8.90623 15.0146 8.90623C15.5666 8.90623 16.0146 8.45823 16.0146 7.90623C16.0146 6.80124 16.9096 5.90624 18.0146 5.90624C19.1196 5.90624 20.0146 6.80124 20.0146 7.90623C20.0146 9.14923 19.2616 10.1632 17.5766 11.0002C17.1826 11.1962 17.0226 11.6112 17.0136 11.9092C17.0046 12.2082 17.1346 12.5842 17.5766 12.8122C19.2496 13.6742 20.0146 14.6632 20.0146 15.9062C20.0146 17.0112 19.1196 17.9062 18.0146 17.9062C16.9096 17.9062 16.0146 17.0112 16.0146 15.9062C16.0146 15.3542 15.5666 14.9062 15.0146 14.9062C14.4626 14.9062 14.0146 15.3542 14.0146 15.9062C14.0146 18.1152 15.8056 19.9062 18.0146 19.9062C20.2236 19.9062 22.0146 18.1152 22.0146 15.9062C22.0146 14.2792 21.2666 12.9522 19.9196 11.9022C21.2276 10.9272 22.0146 9.53323 22.0146 7.90623C22.0146 5.69724 20.2236 3.90625 18.0146 3.90625Z" fill="#2F3030"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
3
frontend/resources/flowy_icons/24x/m_heading.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 4C7.4477 4 7 4.448 7 5V19C7 19.552 7.4477 20 8 20C8.5523 20 9 19.552 9 19V13H15V19C15 19.552 15.4477 20 16 20C16.5523 20 17 19.552 17 19V5C17 4.448 16.5523 4 16 4C15.4477 4 15 4.448 15 5V11H9V5C9 4.448 8.5523 4 8 4Z" fill="#2F3030"/>
|
||||
</svg>
|
After Width: | Height: | Size: 349 B |
3
frontend/resources/flowy_icons/24x/m_italic.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.0117 3.90625C10.4597 3.90625 10.0117 4.35425 10.0117 4.90625C10.0117 5.45824 10.4597 5.90624 11.0117 5.90624H12.6677L9.26171 17.9062H7.01172C6.45972 17.9062 6.01172 18.3542 6.01172 18.9062C6.01172 19.4582 6.45972 19.9062 7.01172 19.9062H13.0117C13.5637 19.9062 14.0117 19.4582 14.0117 18.9062C14.0117 18.3542 13.5637 17.9062 13.0117 17.9062H11.3557L14.7617 5.90624H17.0117C17.5637 5.90624 18.0117 5.45824 18.0117 4.90625C18.0117 4.35425 17.5637 3.90625 17.0117 3.90625H11.0117Z" fill="#2F3030"/>
|
||||
</svg>
|
After Width: | Height: | Size: 612 B |
3
frontend/resources/flowy_icons/24x/m_link.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.43 1.95449C15.142 1.98749 13.899 2.58247 12.81 3.67247L11.281 5.20247C10.891 5.59247 10.891 6.21747 11.281 6.60747C11.671 6.99747 12.327 6.99747 12.717 6.60747L14.214 5.07747C15.663 3.62747 17.27 3.48247 18.863 5.07747C20.457 6.67147 20.312 8.27948 18.863 9.72948L15.837 12.7585C14.388 14.2085 12.781 14.3525 11.188 12.7585L10.751 12.3215C9.81001 11.3795 8.40502 12.7845 9.34702 13.7265L9.78402 14.1635C12.22 16.6015 15.063 16.3425 17.241 14.1635L20.267 11.1345C22.445 8.95548 22.704 6.11047 20.267 3.67247C19.049 2.45347 17.717 1.92249 16.43 1.95449ZM10.189 8.01247C8.98802 8.09047 7.81901 8.66648 6.75701 9.72948L3.73101 12.7585C1.29401 15.1965 1.55301 18.0415 3.73101 20.2205C5.90801 22.3995 8.75201 22.6585 11.188 20.2205L12.717 18.6905C13.107 18.3005 13.107 17.6755 12.717 17.2855C12.327 16.8955 11.671 16.8955 11.281 17.2855L9.78402 18.8155C8.19002 20.4105 6.58402 20.2655 5.13502 18.8155C3.68602 17.3655 3.54102 15.7585 5.13502 14.1635L8.16102 11.1345C8.89602 10.3995 9.62202 10.0555 10.314 10.0105C11.151 9.95549 12.029 10.3525 12.81 11.1345L13.247 11.5715C13.637 11.9615 14.261 11.9615 14.651 11.5715C15.041 11.1815 15.041 10.5565 14.651 10.1665L14.214 9.72948C13.057 8.57148 11.663 7.91547 10.189 8.01247Z" fill="#2F3030"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
3
frontend/resources/flowy_icons/24x/m_list.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.99902 6.10352C2.44702 6.10352 1.99902 6.55152 1.99902 7.10352C1.99902 7.65552 2.44702 8.10352 2.99902 8.10352H20.999C21.551 8.10352 21.999 7.65552 21.999 7.10352C21.999 6.55152 21.551 6.10352 20.999 6.10352H2.99902ZM2.99902 11.1035C2.44702 11.1035 1.99902 11.5515 1.99902 12.1035C1.99902 12.6555 2.44702 13.1035 2.99902 13.1035H20.999C21.551 13.1035 21.999 12.6555 21.999 12.1035C21.999 11.5515 21.551 11.1035 20.999 11.1035H2.99902ZM2.99902 16.1035C2.44702 16.1035 1.99902 16.5515 1.99902 17.1035C1.99902 17.6555 2.44702 18.1035 2.99902 18.1035H20.999C21.551 18.1035 21.999 17.6555 21.999 17.1035C21.999 16.5515 21.551 16.1035 20.999 16.1035H2.99902Z" fill="#2F3030"/>
|
||||
</svg>
|
After Width: | Height: | Size: 785 B |
3
frontend/resources/flowy_icons/24x/m_numbered_list.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.21685 5.21492L3.09185 6.33992C2.98045 6.45092 2.98045 6.66593 3.09185 6.77693C3.20315 6.88893 3.38675 6.88893 3.49805 6.77693L4.15435 6.12093V8.83992C4.15435 8.99692 4.27815 9.12093 4.43555 9.12093C4.59295 9.12093 4.71685 8.99692 4.71685 8.83992V5.40193C4.71685 5.14793 4.39635 5.03492 4.21685 5.21492ZM7.99805 6.12093C7.44575 6.12093 6.99805 6.56893 6.99805 7.12093C6.99805 7.67293 7.44575 8.12093 7.99805 8.12093H20.9981C21.5504 8.12093 21.9981 7.67293 21.9981 7.12093C21.9981 6.56893 21.5504 6.12093 20.9981 6.12093H7.99805ZM4.12305 10.1209C3.49335 10.1209 2.99805 10.6419 2.99805 11.2769C2.99805 11.4359 3.12185 11.5579 3.27935 11.5589C3.43675 11.5589 3.56055 11.4359 3.56055 11.2769C3.56055 10.9599 3.80825 10.6829 4.12305 10.6829C4.43895 10.6829 4.71935 10.9629 4.71685 11.2769C4.71415 11.6069 4.55555 11.7449 4.12305 12.0269C4.04635 12.0769 4.07845 12.0679 3.99805 12.1209C3.32265 12.5659 2.99635 12.9859 2.99805 13.8089C2.99835 13.9679 3.12165 14.1219 3.27935 14.1209H4.99805C5.15545 14.1199 5.27995 13.9989 5.27935 13.8399C5.27865 13.6809 5.15545 13.5579 4.99805 13.5589L3.74805 13.5269C3.82535 13.1799 3.87905 12.9159 4.27935 12.6519C4.35575 12.6019 4.38815 12.5779 4.46685 12.5269C5.04465 12.1499 5.27465 11.8519 5.27935 11.2769C5.28445 10.6419 4.75425 10.1209 4.12305 10.1209ZM7.99805 11.1209C7.44575 11.1209 6.99805 11.5689 6.99805 12.1209C6.99805 12.6729 7.44575 13.1209 7.99805 13.1209H20.9981C21.5504 13.1209 21.9981 12.6729 21.9981 12.1209C21.9981 11.5689 21.5504 11.1209 20.9981 11.1209H7.99805ZM4.15435 15.1209C3.66235 15.1209 3.21775 15.4439 3.06055 15.9019C3.01985 16.0209 2.99805 16.1499 2.99805 16.2769C2.99805 16.4349 3.12155 16.5579 3.27935 16.5589C3.43705 16.5589 3.56055 16.4349 3.56055 16.2769C3.56055 16.2129 3.57155 16.1489 3.59185 16.0899C3.67035 15.8609 3.90825 15.6829 4.15435 15.6829C4.46985 15.6829 4.71685 15.9619 4.71685 16.2769C4.71685 16.5929 4.46985 16.8399 4.15435 16.8399C3.99655 16.8399 3.84185 16.9629 3.84185 17.1209C3.84185 17.2789 3.99645 17.4019 4.15435 17.4019C4.46985 17.4019 4.71685 17.6489 4.71685 17.9649C4.71685 18.2799 4.46985 18.5589 4.15435 18.5589C3.83875 18.5589 3.56055 18.2799 3.56055 17.9649C3.56055 17.8069 3.43715 17.684 3.27935 17.683C3.12155 17.683 2.99805 17.8069 2.99805 17.9649C2.99805 18.5959 3.52305 19.1209 4.15435 19.1209C4.78545 19.1209 5.27935 18.5959 5.27935 17.9649C5.27935 17.6089 5.09695 17.3309 4.84185 17.1209C5.09695 16.9109 5.27935 16.6329 5.27935 16.2769C5.27935 15.6459 4.78545 15.1209 4.15435 15.1209ZM7.99805 16.1209C7.44575 16.1209 6.99805 16.5689 6.99805 17.1209C6.99805 17.6729 7.44575 18.1209 7.99805 18.1209H20.9981C21.5504 18.1209 21.9981 17.6729 21.9981 17.1209C21.9981 16.5689 21.5504 16.1209 20.9981 16.1209H7.99805Z" fill="#2F3030"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.8 KiB |
3
frontend/resources/flowy_icons/24x/m_quote.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.01416 5.00488C2.46216 5.00488 2.01416 5.45288 2.01416 6.00488C2.01416 6.55688 2.46216 7.00488 3.01416 7.00488H21.0142C21.5662 7.00488 22.0142 6.55688 22.0142 6.00488C22.0142 5.45288 21.5662 5.00488 21.0142 5.00488H3.01416ZM13.0142 9.00488C12.4622 9.00488 12.0142 9.45288 12.0142 10.0049C12.0142 10.5569 12.4622 11.0049 13.0142 11.0049H21.0142C21.5662 11.0049 22.0142 10.5569 22.0142 10.0049C22.0142 9.45288 21.5662 9.00488 21.0142 9.00488H13.0142ZM5.17017 9.09888C3.82317 10.1339 3.01416 11.7419 3.01416 13.4739C3.01416 13.4839 3.01416 13.4949 3.01416 13.5049C3.01416 14.3339 3.68516 15.0049 4.51416 15.0049C5.34316 15.0049 6.01416 14.3339 6.01416 13.5049C6.01416 12.6759 5.34316 12.0049 4.51416 12.0049C4.44316 12.0049 4.33316 11.9959 4.26416 12.0049C4.54416 11.1559 5.04816 10.4299 5.76416 9.87988C5.98316 9.71188 6.02615 9.41187 5.85815 9.19287C5.68915 8.97287 5.38917 8.92988 5.17017 9.09888ZM9.17017 9.09888C7.82317 10.1339 7.01416 11.7419 7.01416 13.4739C7.01416 13.4839 7.01416 13.4949 7.01416 13.5049C7.01416 14.3339 7.68516 15.0049 8.51416 15.0049C9.34316 15.0049 10.0142 14.3339 10.0142 13.5049C10.0142 12.6759 9.34316 12.0049 8.51416 12.0049C8.44316 12.0049 8.33316 12.0269 8.26416 12.0359C8.54416 11.1869 9.04816 10.4299 9.76416 9.87988C9.98316 9.71188 10.0262 9.41187 9.85815 9.19287C9.68915 8.97287 9.38917 8.92988 9.17017 9.09888ZM13.0142 13.0049C12.4622 13.0049 12.0142 13.4529 12.0142 14.0049C12.0142 14.5569 12.4622 15.0049 13.0142 15.0049H21.0142C21.5662 15.0049 22.0142 14.5569 22.0142 14.0049C22.0142 13.4529 21.5662 13.0049 21.0142 13.0049H13.0142ZM3.01416 17.0049C2.46216 17.0049 2.01416 17.4529 2.01416 18.0049C2.01416 18.5569 2.46216 19.0049 3.01416 19.0049H21.0142C21.5662 19.0049 22.0142 18.5569 22.0142 18.0049C22.0142 17.4529 21.5662 17.0049 21.0142 17.0049H3.01416Z" fill="#2F3030"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
3
frontend/resources/flowy_icons/24x/m_strikethrough.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.0049 3.90625C9.79588 3.90625 8.00488 5.69724 8.00488 7.90623C8.00488 8.53823 8.16288 9.16424 8.44288 9.71824C8.62788 10.0862 8.92987 10.4732 9.34887 10.9062H6.00488C5.45288 10.9062 5.00488 11.3542 5.00488 11.9062C5.00488 12.4582 5.45288 12.9062 6.00488 12.9062H11.6609C11.7169 12.9552 11.7459 12.9732 11.8169 13.0312C12.1339 13.2902 12.4629 13.5712 12.7549 13.8432C12.9619 14.0372 13.1239 14.2002 13.2859 14.3752C13.5739 14.6852 13.7829 14.9742 13.8489 15.1252C13.9569 15.3732 14.0049 15.6272 14.0049 15.9062C14.0049 17.0112 13.1099 17.9062 12.0049 17.9062C10.8999 17.9062 10.0049 17.0112 10.0049 15.9062C10.0049 15.3542 9.55688 14.9062 9.00488 14.9062C8.45288 14.9062 8.00488 15.3542 8.00488 15.9062C8.00488 18.1152 9.79588 19.9062 12.0049 19.9062C14.2139 19.9062 16.0049 18.1152 16.0049 15.9062C16.0049 15.3522 15.8779 14.8112 15.6609 14.3122C15.4859 13.9082 15.1839 13.4932 14.7549 13.0312C14.7179 12.9912 14.6689 12.9472 14.6299 12.9062H18.0049C18.5569 12.9062 19.0049 12.4582 19.0049 11.9062C19.0049 11.3542 18.5569 10.9062 18.0049 10.9062H12.3489C12.2919 10.8572 12.2629 10.8382 12.1929 10.7812C11.8809 10.5272 11.5739 10.2642 11.2859 10.0002C11.1109 9.83822 10.9609 9.67823 10.8169 9.53123C10.5109 9.21623 10.3009 8.96624 10.2239 8.81224C10.0849 8.53624 10.0049 8.22423 10.0049 7.90623C10.0049 6.80124 10.8999 5.90624 12.0049 5.90624C13.1099 5.90624 14.0049 6.80124 14.0049 7.90623C14.0049 8.45823 14.4529 8.90623 15.0049 8.90623C15.5569 8.90623 16.0049 8.45823 16.0049 7.90623C16.0049 5.69724 14.2139 3.90625 12.0049 3.90625Z" fill="#2F3030"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
3
frontend/resources/flowy_icons/24x/m_text_decoration.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.47028 3.90625C7.06928 3.90725 6.64926 4.13024 6.50226 4.59424L2.06427 18.5942C1.89727 19.1202 2.16327 19.6762 2.68927 19.8442C3.21527 20.0112 3.77227 19.7452 3.93927 19.2192L5.31427 14.9062H9.65826L11.0643 19.2192C11.2353 19.7442 11.7893 20.0142 12.3143 19.8442C12.8393 19.6732 13.1093 19.1192 12.9393 18.5942L8.40827 4.59424C8.25827 4.13124 7.87228 3.90525 7.47028 3.90625ZM17.5643 7.90625C16.0363 7.90625 14.5733 9.07324 14.0643 10.5942C13.8893 11.1172 14.1653 11.6682 14.6893 11.8442C15.2133 12.0192 15.7643 11.7422 15.9393 11.2192C16.1853 10.4842 16.9313 9.90625 17.5643 9.90625H18.4393C19.2703 9.90625 20.0023 10.7702 20.0023 11.9062V12.9062C19.1163 12.9062 18.0203 12.9062 17.5643 12.9062C15.5983 12.9062 14.0023 14.4672 14.0023 16.4062C14.0023 18.3462 15.5983 19.9092 17.5643 19.9062C17.6143 19.9062 17.9343 19.9072 18.0333 19.9062C18.8003 19.8982 19.5023 19.6422 20.1273 19.2502C20.2743 19.6192 20.5803 19.9062 21.0023 19.9062C21.5543 19.9062 22.0023 19.4582 22.0023 18.9062V15.9062C22.0043 15.6982 22.0023 13.9832 22.0023 13.9062V11.9062C22.0023 9.72825 20.4493 7.90625 18.4393 7.90625H17.5643ZM7.47028 8.18725L9.00226 12.9062H5.97028L7.47028 8.18725ZM17.5643 14.9062C18.0203 14.9062 19.1193 14.9062 20.0023 14.9062C20.0023 15.3602 20.0033 15.7782 20.0023 15.8752C19.9853 17.1102 19.0653 17.8952 18.0023 17.9062C17.9333 17.9072 17.6473 17.9062 17.5643 17.9062C16.6923 17.9072 16.0023 17.2292 16.0023 16.4062C16.0023 15.5842 16.6903 14.9062 17.5643 14.9062Z" fill="#2F3030"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
4
frontend/resources/flowy_icons/24x/m_toggle_list.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.00586 2.99072C4.79686 2.99072 3.00586 4.78172 3.00586 6.99072V16.9907C3.00586 19.1997 4.79686 20.9907 7.00586 20.9907H17.0059C19.2149 20.9907 21.0059 19.1997 21.0059 16.9907V6.99072C21.0059 4.78172 19.2149 2.99072 17.0059 2.99072H7.00586ZM7.00586 4.99072H17.0059C18.1109 4.99072 19.0059 5.88572 19.0059 6.99072V16.9907C19.0059 18.0957 18.1109 18.9907 17.0059 18.9907H7.00586C5.90086 18.9907 5.00586 18.0957 5.00586 16.9907V6.99072C5.00586 5.88572 5.90086 4.99072 7.00586 4.99072Z" fill="#2F3030"/>
|
||||
<path d="M9.04616 10.1891C8.97814 10.3025 8.98028 10.4504 9.08637 10.5681C9.40678 10.9246 11.3322 13.0635 11.6531 13.42C11.8238 13.61 12.1767 13.61 12.3478 13.42L14.9145 10.5681C15.1263 10.3332 14.9197 9.99984 14.5672 9.99984L9.43374 9.99984C9.25749 9.99984 9.11418 10.0761 9.04616 10.1891Z" fill="#2F3030"/>
|
||||
</svg>
|
After Width: | Height: | Size: 922 B |
3
frontend/resources/flowy_icons/24x/m_underline.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.98828 3.90625C7.43628 3.90625 6.98828 4.35425 6.98828 4.90625V10.9062C6.98828 13.6672 9.22727 15.9062 11.9883 15.9062C14.7493 15.9062 16.9883 13.6672 16.9883 10.9062V4.90625C16.9883 4.35425 16.5403 3.90625 15.9883 3.90625C15.4363 3.90625 14.9883 4.35425 14.9883 4.90625V10.9062C14.9883 12.5632 13.6453 13.9062 11.9883 13.9062C10.3313 13.9062 8.98828 12.5632 8.98828 10.9062V4.90625C8.98828 4.35425 8.54028 3.90625 7.98828 3.90625ZM7.98828 17.9062C7.43628 17.9062 6.98828 18.3542 6.98828 18.9062C6.98828 19.4582 7.43628 19.9062 7.98828 19.9062H15.9883C16.5403 19.9062 16.9883 19.4582 16.9883 18.9062C16.9883 18.3542 16.5403 17.9062 15.9883 17.9062H7.98828Z" fill="#2F3030"/>
|
||||
</svg>
|
After Width: | Height: | Size: 789 B |
@ -650,6 +650,7 @@
|
||||
"alertDialogConfirmation": "Are you sure, you want to continue?"
|
||||
},
|
||||
"mathEquation": {
|
||||
"name": "Math Equation",
|
||||
"addMathEquation": "Add a TeX equation",
|
||||
"editMathEquation": "Edit Math Equation"
|
||||
},
|
||||
@ -1064,6 +1065,7 @@
|
||||
},
|
||||
"titleBar": {
|
||||
"pageIcon": "Page icon",
|
||||
"language": "Language"
|
||||
"language": "Language",
|
||||
"font": "Font"
|
||||
}
|
||||
}
|