feat: mobile toolbar revamp (#4194)
* feat: toolbar ui revamp * feat: implement bius and blocks * feat: implement converting heading block * feat: implement bius logic * feat: implement numbered/bulleted/quote list logic * feat: indent/outdent logic * feat: implement link logic * feat: toolbar ui improvement * feat: implement toolbar biu logic * feat: focus on biu if the selection is not collapsed * feat: implement changing font logic' * feat: implement color picker logic * feat: support customzing text and background color * fix: draghandler padding
@ -2,7 +2,6 @@ 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';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class AppBarBackButton extends StatelessWidget {
|
||||
const AppBarBackButton({
|
||||
@ -15,7 +14,7 @@ class AppBarBackButton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppBarButton(
|
||||
onTap: onTap ?? () => context.pop(),
|
||||
onTap: onTap ?? () => Navigator.pop(context),
|
||||
child: const Icon(Icons.arrow_back_ios_new),
|
||||
);
|
||||
}
|
||||
@ -42,7 +41,7 @@ class AppBarCloseButton extends StatelessWidget {
|
||||
Icons.close,
|
||||
),
|
||||
margin: margin,
|
||||
onTap: onTap ?? () => context.pop(),
|
||||
onTap: onTap ?? () => Navigator.pop(context),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,10 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_header.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:string_validator/string_validator.dart';
|
||||
|
||||
class MobileBottomSheetEditLinkWidget extends StatefulWidget {
|
||||
const MobileBottomSheetEditLinkWidget({
|
||||
@ -55,40 +53,24 @@ class _MobileBottomSheetEditLinkWidgetState
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildTextField(textController, null),
|
||||
const VSpace(12.0),
|
||||
_buildTextField(hrefController, LocaleKeys.editor_linkTextHint.tr()),
|
||||
const VSpace(12.0),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
text: LocaleKeys.button_cancel.tr(),
|
||||
onTap: () => context.pop(),
|
||||
),
|
||||
),
|
||||
const HSpace(8),
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
text: LocaleKeys.button_done.tr(),
|
||||
onTap: () {
|
||||
widget.onEdit(textController.text, hrefController.text);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (widget.href != null && isURL(widget.href)) ...[
|
||||
const HSpace(8),
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
text: LocaleKeys.editor_openLink.tr(),
|
||||
onTap: () {
|
||||
safeLaunchUrl(widget.href!);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
BottomSheetHeader(
|
||||
title: LocaleKeys.editor_editLink.tr(),
|
||||
onClose: () => context.pop(),
|
||||
onDone: () {
|
||||
widget.onEdit(textController.text, hrefController.text);
|
||||
},
|
||||
),
|
||||
const VSpace(20.0),
|
||||
_buildTextField(
|
||||
textController,
|
||||
LocaleKeys.document_inlineLink_title_placeholder.tr(),
|
||||
),
|
||||
const VSpace(12.0),
|
||||
_buildTextField(
|
||||
hrefController,
|
||||
LocaleKeys.document_inlineLink_url_placeholder.tr(),
|
||||
),
|
||||
const VSpace(12.0),
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -97,11 +79,14 @@ class _MobileBottomSheetEditLinkWidgetState
|
||||
TextEditingController controller,
|
||||
String? hintText,
|
||||
) {
|
||||
return SizedBox(
|
||||
height: 44.0,
|
||||
return Container(
|
||||
color: Colors.white,
|
||||
height: 48.0,
|
||||
child: FlowyTextField(
|
||||
controller: controller,
|
||||
hintText: hintText,
|
||||
textStyle: const TextStyle(fontSize: 16.0),
|
||||
hintStyle: const TextStyle(fontSize: 16.0),
|
||||
suffixIcon: Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: FlowyButton(
|
||||
|
@ -0,0 +1,64 @@
|
||||
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_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BottomSheetHeader extends StatelessWidget {
|
||||
const BottomSheetHeader({
|
||||
super.key,
|
||||
this.title,
|
||||
this.onClose,
|
||||
this.onDone,
|
||||
});
|
||||
|
||||
final String? title;
|
||||
final VoidCallback? onClose;
|
||||
final VoidCallback? onDone;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
if (onClose != null)
|
||||
Positioned(
|
||||
left: 0,
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: AppBarCloseButton(
|
||||
margin: EdgeInsets.zero,
|
||||
onTap: onClose,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (title != null)
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: FlowyText.medium(
|
||||
title!,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
if (onDone != null)
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: FlowyButton(
|
||||
useIntrinsicWidth: true,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5),
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||
color: Color(0xFF00BCF0),
|
||||
),
|
||||
text: FlowyText.medium(
|
||||
LocaleKeys.button_Done.tr(),
|
||||
color: Colors.white,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
onTap: onDone,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -18,6 +18,9 @@ Future<T?> showMobileBottomSheet<T>(
|
||||
Color? backgroundColor,
|
||||
bool isScrollControlled = true,
|
||||
BoxConstraints? constraints,
|
||||
bool showDivider = true,
|
||||
Color? barrierColor,
|
||||
double? elevation,
|
||||
}) async {
|
||||
assert(() {
|
||||
if (showCloseButton || title.isNotEmpty) assert(showHeader);
|
||||
@ -32,6 +35,8 @@ Future<T?> showMobileBottomSheet<T>(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
backgroundColor: backgroundColor,
|
||||
constraints: constraints,
|
||||
barrierColor: barrierColor,
|
||||
elevation: elevation,
|
||||
shape: shape ??
|
||||
const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
@ -43,7 +48,6 @@ Future<T?> showMobileBottomSheet<T>(
|
||||
|
||||
if (showDragHandle) {
|
||||
children.addAll([
|
||||
const VSpace(4),
|
||||
const DragHandler(),
|
||||
]);
|
||||
}
|
||||
@ -64,6 +68,8 @@ Future<T?> showMobileBottomSheet<T>(
|
||||
: const SizedBox.shrink(),
|
||||
FlowyText(
|
||||
title,
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
showCloseButton
|
||||
? HSpace(padding.right + 24)
|
||||
@ -71,7 +77,7 @@ Future<T?> showMobileBottomSheet<T>(
|
||||
],
|
||||
),
|
||||
const VSpace(4),
|
||||
const Divider(),
|
||||
if (showDivider) const Divider(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -81,7 +81,9 @@ class MobileGridFab extends StatelessWidget {
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
border: const Border.fromBorderSide(BorderSide( width: 0.5, color: Color(0xFFE4EDF0))),
|
||||
border: const Border.fromBorderSide(
|
||||
BorderSide(width: 0.5, color: Color(0xFFE4EDF0)),
|
||||
),
|
||||
borderRadius: radius,
|
||||
boxShadow: [boxShadow],
|
||||
),
|
||||
|
@ -10,19 +10,9 @@ import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
List<MobileToolbarItem> getMobileToolbarItems() {
|
||||
List<AppFlowyMobileToolbarItem> getMobileToolbarItems() {
|
||||
return [
|
||||
customTextDecorationMobileToolbarItem,
|
||||
buildTextAndBackgroundColorMobileToolbarItem(),
|
||||
mobileAddBlockToolbarItem,
|
||||
mobileConvertBlockToolbarItem,
|
||||
imageMobileToolbarItem,
|
||||
mobileAlignToolbarItem,
|
||||
mobileIndentToolbarItem,
|
||||
mobileOutdentToolbarItem,
|
||||
undoMobileToolbarItem,
|
||||
redoMobileToolbarItem,
|
||||
mobileBlockSettingsToolbarItem,
|
||||
aaToolbarItem,
|
||||
];
|
||||
}
|
||||
|
||||
@ -46,6 +36,7 @@ Map<String, BlockComponentBuilder> getEditorBuilderMap({
|
||||
final calloutBGColor = AFThemeExtension.of(context).calloutBGColor;
|
||||
|
||||
final configuration = BlockComponentConfiguration(
|
||||
// use EdgeInsets.zero to remove the default padding.
|
||||
padding: (_) => const EdgeInsets.symmetric(vertical: 5.0),
|
||||
indentPadding: (node, textDirection) => textDirection == TextDirection.ltr
|
||||
? const EdgeInsets.only(left: 26.0)
|
||||
|
@ -271,16 +271,21 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
_setInitialSelection(editorScrollController);
|
||||
|
||||
if (PlatformExtension.isMobile) {
|
||||
final theme = Theme.of(context);
|
||||
return MobileToolbarV2(
|
||||
toolbarHeight: 48.0,
|
||||
backgroundColor: theme.colorScheme.background,
|
||||
foregroundColor: theme.colorScheme.onSurface,
|
||||
iconColor: theme.iconTheme.color ?? theme.colorScheme.onSurface,
|
||||
tabBarSelectedBackgroundColor: theme.colorScheme.onSurfaceVariant,
|
||||
tabBarSelectedForegroundColor: theme.colorScheme.onPrimary,
|
||||
return AppFlowyMobileToolbar(
|
||||
toolbarHeight: 46.0,
|
||||
editorState: editorState,
|
||||
toolbarItems: getMobileToolbarItems(),
|
||||
toolbarItems: [
|
||||
undoToolbarItem,
|
||||
redoToolbarItem,
|
||||
addBlockToolbarItem,
|
||||
todoListToolbarItem,
|
||||
aaToolbarItem,
|
||||
boldToolbarItem,
|
||||
italicToolbarItem,
|
||||
underlineToolbarItem,
|
||||
colorToolbarItem,
|
||||
moreToolbarItem,
|
||||
],
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
|
@ -38,8 +38,8 @@ final _convertToBlockMenuItems = [
|
||||
icon: const FlowySvg(FlowySvgs.m_text_decoration_m),
|
||||
label: LocaleKeys.editor_text.tr(),
|
||||
onTap: (editorState, selection, _) => editorState.convertBlockType(
|
||||
selection,
|
||||
ParagraphBlockKeys.type,
|
||||
selection: selection,
|
||||
),
|
||||
),
|
||||
|
||||
@ -49,8 +49,8 @@ final _convertToBlockMenuItems = [
|
||||
icon: const FlowySvg(FlowySvgs.m_checkbox_m),
|
||||
label: LocaleKeys.editor_checkbox.tr(),
|
||||
onTap: (editorState, selection, _) => editorState.convertBlockType(
|
||||
selection,
|
||||
TodoListBlockKeys.type,
|
||||
selection: selection,
|
||||
extraAttributes: {
|
||||
TodoListBlockKeys.checked: false,
|
||||
},
|
||||
@ -74,8 +74,8 @@ final _convertToBlockMenuItems = [
|
||||
1,
|
||||
);
|
||||
editorState.convertBlockType(
|
||||
selection,
|
||||
HeadingBlockKeys.type,
|
||||
selection: selection,
|
||||
isSelected: isSelected,
|
||||
extraAttributes: {
|
||||
HeadingBlockKeys.level: 1,
|
||||
@ -99,8 +99,8 @@ final _convertToBlockMenuItems = [
|
||||
2,
|
||||
);
|
||||
editorState.convertBlockType(
|
||||
selection,
|
||||
HeadingBlockKeys.type,
|
||||
selection: selection,
|
||||
isSelected: isSelected,
|
||||
extraAttributes: {
|
||||
HeadingBlockKeys.level: 2,
|
||||
@ -124,8 +124,8 @@ final _convertToBlockMenuItems = [
|
||||
3,
|
||||
);
|
||||
editorState.convertBlockType(
|
||||
selection,
|
||||
HeadingBlockKeys.type,
|
||||
selection: selection,
|
||||
isSelected: isSelected,
|
||||
extraAttributes: {
|
||||
HeadingBlockKeys.level: 3,
|
||||
@ -140,8 +140,8 @@ final _convertToBlockMenuItems = [
|
||||
icon: const FlowySvg(FlowySvgs.m_bulleted_list_m),
|
||||
label: LocaleKeys.editor_bulletedList.tr(),
|
||||
onTap: (editorState, selection, _) => editorState.convertBlockType(
|
||||
selection,
|
||||
BulletedListBlockKeys.type,
|
||||
selection: selection,
|
||||
),
|
||||
),
|
||||
|
||||
@ -151,8 +151,8 @@ final _convertToBlockMenuItems = [
|
||||
icon: const FlowySvg(FlowySvgs.m_numbered_list_m),
|
||||
label: LocaleKeys.editor_numberedList.tr(),
|
||||
onTap: (editorState, selection, _) => editorState.convertBlockType(
|
||||
selection,
|
||||
NumberedListBlockKeys.type,
|
||||
selection: selection,
|
||||
),
|
||||
),
|
||||
|
||||
@ -162,7 +162,7 @@ final _convertToBlockMenuItems = [
|
||||
icon: const FlowySvg(FlowySvgs.m_toggle_list_m),
|
||||
label: LocaleKeys.document_plugins_toggleList.tr(),
|
||||
onTap: (editorState, selection, _) => editorState.convertBlockType(
|
||||
selection,
|
||||
selection: selection,
|
||||
ToggleListBlockKeys.type,
|
||||
),
|
||||
),
|
||||
@ -173,7 +173,7 @@ final _convertToBlockMenuItems = [
|
||||
icon: const FlowySvg(FlowySvgs.m_quote_m),
|
||||
label: LocaleKeys.editor_quote.tr(),
|
||||
onTap: (editorState, selection, _) => editorState.convertBlockType(
|
||||
selection,
|
||||
selection: selection,
|
||||
QuoteBlockKeys.type,
|
||||
),
|
||||
),
|
||||
@ -185,8 +185,8 @@ final _convertToBlockMenuItems = [
|
||||
icon: const Icon(Icons.note_rounded),
|
||||
label: LocaleKeys.document_plugins_callout.tr(),
|
||||
onTap: (editorState, selection, _) => editorState.convertBlockType(
|
||||
selection,
|
||||
CalloutBlockKeys.type,
|
||||
selection: selection,
|
||||
extraAttributes: {
|
||||
CalloutBlockKeys.icon: '📌',
|
||||
},
|
||||
@ -199,43 +199,12 @@ final _convertToBlockMenuItems = [
|
||||
icon: const FlowySvg(FlowySvgs.m_code_m),
|
||||
label: LocaleKeys.document_selectionMenu_codeBlock.tr(),
|
||||
onTap: (editorState, selection, _) => editorState.convertBlockType(
|
||||
selection,
|
||||
CodeBlockKeys.type,
|
||||
selection: selection,
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
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,
|
||||
|
@ -0,0 +1,217 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_item/utils.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
final customTextDecorationMobileToolbarItem = MobileToolbarItem.withMenu(
|
||||
itemIconBuilder: (_, __, ___) => const FlowySvg(
|
||||
FlowySvgs.text_s,
|
||||
size: Size.square(24),
|
||||
),
|
||||
itemMenuBuilder: (_, editorState, service) {
|
||||
final selection = editorState.selection;
|
||||
if (selection == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return _TextDecorationMenu(
|
||||
editorState,
|
||||
selection,
|
||||
service,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
class _TextDecorationMenu extends StatefulWidget {
|
||||
const _TextDecorationMenu(
|
||||
this.editorState,
|
||||
this.selection,
|
||||
this.service,
|
||||
);
|
||||
|
||||
final EditorState editorState;
|
||||
final Selection selection;
|
||||
final MobileToolbarWidgetService service;
|
||||
|
||||
@override
|
||||
State<_TextDecorationMenu> createState() => _TextDecorationMenuState();
|
||||
}
|
||||
|
||||
class _TextDecorationMenuState extends State<_TextDecorationMenu> {
|
||||
EditorState get editorState => widget.editorState;
|
||||
|
||||
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,
|
||||
),
|
||||
|
||||
// link
|
||||
TextDecorationUnit(
|
||||
icon: AFMobileIcons.link,
|
||||
label: AppFlowyEditorL10n.current.link,
|
||||
name: AppFlowyRichTextKeys.href,
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.editorState.selectionExtraInfo = null;
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final children = textDecorations
|
||||
.map((currentDecoration) {
|
||||
// Check current decoration is active or not
|
||||
final selection = widget.selection;
|
||||
|
||||
// only show edit link bottom sheet when selection is not collapsed
|
||||
if (selection.isCollapsed &&
|
||||
currentDecoration.name == AppFlowyRichTextKeys.href) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final nodes = editorState.getNodesInSelection(selection);
|
||||
final bool isSelected;
|
||||
if (selection.isCollapsed) {
|
||||
isSelected = 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,
|
||||
color: MobileToolbarTheme.of(context).iconColor,
|
||||
),
|
||||
label: FlowyText(currentDecoration.label),
|
||||
isSelected: isSelected,
|
||||
onPressed: () {
|
||||
if (currentDecoration.name == AppFlowyRichTextKeys.href) {
|
||||
if (selection.isCollapsed) {
|
||||
return;
|
||||
}
|
||||
|
||||
_closeKeyboard();
|
||||
|
||||
// show edit link bottom sheet
|
||||
final context = nodes.firstOrNull?.context;
|
||||
if (context != null) {
|
||||
final text = editorState
|
||||
.getTextInSelection(
|
||||
widget.selection,
|
||||
)
|
||||
.join('');
|
||||
final href =
|
||||
editorState.getDeltaAttributeValueInSelection<String>(
|
||||
AppFlowyRichTextKeys.href,
|
||||
widget.selection,
|
||||
);
|
||||
showEditLinkBottomSheet(
|
||||
context,
|
||||
text,
|
||||
href,
|
||||
(context, newText, newHref) {
|
||||
_updateTextAndHref(text, href, newText, newHref);
|
||||
context.pop();
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
setState(() {
|
||||
editorState.toggleAttribute(currentDecoration.name);
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
})
|
||||
.nonNulls
|
||||
.toList();
|
||||
|
||||
return GridView.count(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.zero,
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
childAspectRatio: 4,
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
|
||||
void _closeKeyboard() {
|
||||
editorState.updateSelectionWithReason(
|
||||
widget.selection,
|
||||
extraInfo: {
|
||||
disableMobileToolbarKey: true,
|
||||
},
|
||||
);
|
||||
editorState.service.keyboardService?.closeKeyboard();
|
||||
}
|
||||
|
||||
void _updateTextAndHref(
|
||||
String prevText,
|
||||
String? prevHref,
|
||||
String text,
|
||||
String href,
|
||||
) async {
|
||||
final selection = widget.selection;
|
||||
if (!selection.isSingle) {
|
||||
return;
|
||||
}
|
||||
final node = editorState.getNodeAtPath(selection.start.path);
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
final transaction = editorState.transaction;
|
||||
if (prevText != text) {
|
||||
transaction.replaceText(
|
||||
node,
|
||||
selection.startIndex,
|
||||
selection.length,
|
||||
text,
|
||||
);
|
||||
}
|
||||
// if the text is empty, it means the user wants to remove the text
|
||||
if (text.isNotEmpty && prevHref != href) {
|
||||
transaction.formatText(node, selection.startIndex, text.length, {
|
||||
AppFlowyRichTextKeys.href: href.isEmpty ? null : href,
|
||||
});
|
||||
}
|
||||
await editorState.apply(transaction);
|
||||
}
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_edit_link_widget.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void showEditLinkBottomSheet(
|
||||
@ -10,13 +8,9 @@ void showEditLinkBottomSheet(
|
||||
String? href,
|
||||
void Function(BuildContext context, String text, String href) onEdit,
|
||||
) {
|
||||
assert(text.isNotEmpty);
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
showCloseButton: true,
|
||||
showDragHandle: true,
|
||||
showHeader: true,
|
||||
title: LocaleKeys.editor_editLink.tr(),
|
||||
showHeader: false,
|
||||
builder: (context) {
|
||||
return MobileBottomSheetEditLinkWidget(
|
||||
text: text,
|
||||
|
@ -0,0 +1,30 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/util.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AlignItems extends StatelessWidget {
|
||||
const AlignItems({
|
||||
super.key,
|
||||
required this.editorState,
|
||||
});
|
||||
|
||||
final EditorState editorState;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MobileToolbarItemWrapper(
|
||||
size: const Size(82, 52),
|
||||
onTap: () async {
|
||||
await editorState.alignBlock('left');
|
||||
},
|
||||
icon: FlowySvgs.m_aa_align_left_s,
|
||||
isSelected: false,
|
||||
iconPadding: const EdgeInsets.symmetric(
|
||||
vertical: 14.0,
|
||||
),
|
||||
showDownArrow: true,
|
||||
backgroundColor: const Color(0xFFF2F2F7),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/util.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BIUSItems extends StatelessWidget {
|
||||
BIUSItems({
|
||||
super.key,
|
||||
required this.editorState,
|
||||
});
|
||||
|
||||
final EditorState editorState;
|
||||
|
||||
final List<(FlowySvgData, String)> _bius = [
|
||||
(FlowySvgs.m_aa_bold_s, AppFlowyRichTextKeys.bold),
|
||||
(FlowySvgs.m_aa_italic_s, AppFlowyRichTextKeys.italic),
|
||||
(FlowySvgs.m_aa_underline_s, AppFlowyRichTextKeys.underline),
|
||||
(FlowySvgs.m_aa_strike_s, AppFlowyRichTextKeys.strikethrough),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IntrinsicHeight(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: _bius
|
||||
.mapIndexed(
|
||||
(index, e) => [
|
||||
_buildBIUSItem(
|
||||
index,
|
||||
e.$1,
|
||||
e.$2,
|
||||
),
|
||||
if (index != 0 || index != _bius.length - 1)
|
||||
const ScaledVerticalDivider(),
|
||||
],
|
||||
)
|
||||
.flattened
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBIUSItem(
|
||||
int index,
|
||||
FlowySvgData icon,
|
||||
String richTextKey,
|
||||
) {
|
||||
return StatefulBuilder(
|
||||
builder: (_, setState) => MobileToolbarItemWrapper(
|
||||
size: const Size(62, 52),
|
||||
enableTopLeftRadius: index == 0,
|
||||
enableBottomLeftRadius: index == 0,
|
||||
enableTopRightRadius: index == _bius.length - 1,
|
||||
enableBottomRightRadius: index == _bius.length - 1,
|
||||
backgroundColor: const Color(0xFFF2F2F7),
|
||||
onTap: () async {
|
||||
await editorState.toggleAttribute(richTextKey);
|
||||
// refresh the status
|
||||
setState(() {});
|
||||
},
|
||||
icon: icon,
|
||||
isSelected: editorState.isTextDecorationSelected(richTextKey),
|
||||
iconPadding: const EdgeInsets.symmetric(
|
||||
vertical: 14.0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_item/utils.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class BlockItems extends StatelessWidget {
|
||||
BlockItems({
|
||||
super.key,
|
||||
required this.service,
|
||||
required this.editorState,
|
||||
});
|
||||
|
||||
final EditorState editorState;
|
||||
final AppFlowyMobileToolbarWidgetService service;
|
||||
|
||||
final List<(FlowySvgData, String)> _blockItems = [
|
||||
(FlowySvgs.m_aa_bulleted_list_s, BulletedListBlockKeys.type),
|
||||
(FlowySvgs.m_aa_numbered_list_s, NumberedListBlockKeys.type),
|
||||
(FlowySvgs.m_aa_quote_s, QuoteBlockKeys.type),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IntrinsicHeight(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
..._blockItems
|
||||
.mapIndexed(
|
||||
(index, e) => [
|
||||
_buildBlockItem(
|
||||
index,
|
||||
e.$1,
|
||||
e.$2,
|
||||
),
|
||||
if (index != 0) const ScaledVerticalDivider(),
|
||||
],
|
||||
)
|
||||
.flattened,
|
||||
// this item is a special case, use link item here instead of block item
|
||||
|
||||
_buildLinkItem(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBlockItem(
|
||||
int index,
|
||||
FlowySvgData icon,
|
||||
String blockType,
|
||||
) {
|
||||
return MobileToolbarItemWrapper(
|
||||
size: const Size(62, 54),
|
||||
enableTopLeftRadius: index == 0,
|
||||
enableBottomLeftRadius: index == 0,
|
||||
enableTopRightRadius: false,
|
||||
enableBottomRightRadius: false,
|
||||
onTap: () async {
|
||||
await editorState.convertBlockType(blockType);
|
||||
},
|
||||
backgroundColor: const Color(0xFFF2F2F7),
|
||||
icon: icon,
|
||||
isSelected: editorState.isBlockTypeSelected(blockType),
|
||||
iconPadding: const EdgeInsets.symmetric(
|
||||
vertical: 14.0,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLinkItem() {
|
||||
return MobileToolbarItemWrapper(
|
||||
size: const Size(62, 54),
|
||||
enableTopLeftRadius: false,
|
||||
enableBottomLeftRadius: false,
|
||||
enableTopRightRadius: true,
|
||||
enableBottomRightRadius: true,
|
||||
showDownArrow: true,
|
||||
onTap: _onLinkItemTap,
|
||||
backgroundColor: const Color(0xFFF2F2F7),
|
||||
icon: FlowySvgs.m_aa_link_s,
|
||||
isSelected: false,
|
||||
iconPadding: const EdgeInsets.symmetric(
|
||||
vertical: 14.0,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onLinkItemTap() {
|
||||
final selection = editorState.selection;
|
||||
if (selection == null) {
|
||||
return;
|
||||
}
|
||||
final nodes = editorState.getNodesInSelection(selection);
|
||||
// show edit link bottom sheet
|
||||
final context = nodes.firstOrNull?.context;
|
||||
if (context != null) {
|
||||
_closeKeyboard(selection);
|
||||
|
||||
final text = editorState
|
||||
.getTextInSelection(
|
||||
selection,
|
||||
)
|
||||
.join('');
|
||||
final href = editorState.getDeltaAttributeValueInSelection<String>(
|
||||
AppFlowyRichTextKeys.href,
|
||||
selection,
|
||||
);
|
||||
showEditLinkBottomSheet(
|
||||
context,
|
||||
text,
|
||||
href,
|
||||
(context, newText, newHref) {
|
||||
editorState.updateTextAndHref(
|
||||
text,
|
||||
href,
|
||||
newText,
|
||||
newHref,
|
||||
selection: selection,
|
||||
);
|
||||
context.pop();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _closeKeyboard(Selection selection) {
|
||||
editorState.updateSelectionWithReason(
|
||||
selection,
|
||||
extraInfo: {
|
||||
disableMobileToolbarKey: true,
|
||||
},
|
||||
);
|
||||
editorState.service.keyboardService?.closeKeyboard();
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/util.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ColorItem extends StatelessWidget {
|
||||
const ColorItem({
|
||||
super.key,
|
||||
required this.editorState,
|
||||
});
|
||||
|
||||
final EditorState editorState;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MobileToolbarItemWrapper(
|
||||
size: const Size(82, 52),
|
||||
onTap: () {
|
||||
keepEditorFocusNotifier.increase();
|
||||
|
||||
// showTextColorAndBackgroundColorPicker(context);
|
||||
},
|
||||
icon: FlowySvgs.m_aa_color_s,
|
||||
backgroundColor: const Color(0xFFF2F2F7),
|
||||
isSelected: false,
|
||||
showRightArrow: true,
|
||||
iconPadding: const EdgeInsets.only(
|
||||
top: 14.0,
|
||||
bottom: 14.0,
|
||||
right: 28.0,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,297 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Future<void> showTextColorAndBackgroundColorPicker(
|
||||
BuildContext context, {
|
||||
required EditorState editorState,
|
||||
required Selection selection,
|
||||
}) async {
|
||||
await showMobileBottomSheet(
|
||||
context,
|
||||
showHeader: true,
|
||||
showCloseButton: true,
|
||||
showDivider: false,
|
||||
showDragHandle: true,
|
||||
barrierColor: Colors.transparent,
|
||||
backgroundColor: Colors.white,
|
||||
elevation: 20,
|
||||
title: LocaleKeys.grid_selectOption_colorPanelTitle.tr(),
|
||||
padding: const EdgeInsets.fromLTRB(18, 4, 18, 0),
|
||||
builder: (context) {
|
||||
return _TextColorAndBackgroundColor(
|
||||
editorState: editorState,
|
||||
selection: selection,
|
||||
);
|
||||
},
|
||||
);
|
||||
await editorState.updateSelectionWithReason(
|
||||
null,
|
||||
extraInfo: null,
|
||||
);
|
||||
}
|
||||
|
||||
class _TextColorAndBackgroundColor extends StatefulWidget {
|
||||
const _TextColorAndBackgroundColor({
|
||||
required this.editorState,
|
||||
required this.selection,
|
||||
});
|
||||
|
||||
final EditorState editorState;
|
||||
final Selection selection;
|
||||
|
||||
@override
|
||||
State<_TextColorAndBackgroundColor> createState() =>
|
||||
_TextColorAndBackgroundColorState();
|
||||
}
|
||||
|
||||
class _TextColorAndBackgroundColorState
|
||||
extends State<_TextColorAndBackgroundColor> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final String? selectedTextColor =
|
||||
widget.editorState.getDeltaAttributeValueInSelection(
|
||||
AppFlowyRichTextKeys.textColor,
|
||||
widget.selection,
|
||||
);
|
||||
final String? selectedBackgroundColor =
|
||||
widget.editorState.getDeltaAttributeValueInSelection(
|
||||
AppFlowyRichTextKeys.highlightColor,
|
||||
widget.selection,
|
||||
);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 20,
|
||||
left: 6.0,
|
||||
),
|
||||
child: FlowyText(
|
||||
LocaleKeys.editor_textColor.tr(),
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
_TextColors(
|
||||
selectedColor: selectedTextColor?.tryToColor(),
|
||||
onSelectedColor: (textColor) async {
|
||||
final hex = textColor.alpha == 0 ? null : textColor.toHex();
|
||||
await widget.editorState.formatDelta(
|
||||
widget.selection,
|
||||
{
|
||||
AppFlowyRichTextKeys.textColor: hex,
|
||||
},
|
||||
selectionExtraInfo: {
|
||||
disableFloatingToolbar: true,
|
||||
disableMobileToolbarKey: true,
|
||||
},
|
||||
);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 18.0,
|
||||
left: 6.0,
|
||||
),
|
||||
child: FlowyText(
|
||||
LocaleKeys.editor_backgroundColor.tr(),
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
_BackgroundColors(
|
||||
selectedColor: selectedBackgroundColor?.tryToColor(),
|
||||
onSelectedColor: (backgroundColor) async {
|
||||
final hex =
|
||||
backgroundColor.alpha == 0 ? null : backgroundColor.toHex();
|
||||
await widget.editorState.formatDelta(
|
||||
widget.selection,
|
||||
{
|
||||
AppFlowyRichTextKeys.highlightColor: hex,
|
||||
},
|
||||
selectionExtraInfo: {
|
||||
disableFloatingToolbar: true,
|
||||
disableMobileToolbarKey: true,
|
||||
},
|
||||
);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _BackgroundColors extends StatelessWidget {
|
||||
_BackgroundColors({
|
||||
this.selectedColor,
|
||||
required this.onSelectedColor,
|
||||
});
|
||||
|
||||
final Color? selectedColor;
|
||||
final void Function(Color color) onSelectedColor;
|
||||
|
||||
final colors = [
|
||||
const Color(0x00FFFFFF),
|
||||
const Color(0xFFE8E0FF),
|
||||
const Color(0xFFFFE6FD),
|
||||
const Color(0xFFFFDAE6),
|
||||
const Color(0xFFFFEFE3),
|
||||
const Color(0xFFF5FFDC),
|
||||
const Color(0xFFDDFFD6),
|
||||
const Color(0xFFDEFFF1),
|
||||
const Color(0xFFE1FBFF),
|
||||
const Color(0xFFFFADAD),
|
||||
const Color(0xFFFFE088),
|
||||
const Color(0xFFA7DF4A),
|
||||
const Color(0xFFD4C0FF),
|
||||
const Color(0xFFFDB2FE),
|
||||
const Color(0xFFFFD18B),
|
||||
const Color(0xFFFFF176),
|
||||
const Color(0xFF71E6B4),
|
||||
const Color(0xFF80F1FF),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GridView.count(
|
||||
crossAxisCount: 6,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: colors.mapIndexed(
|
||||
(index, color) {
|
||||
return _BackgroundColorItem(
|
||||
color: color,
|
||||
isSelected:
|
||||
selectedColor == null ? index == 0 : selectedColor == color,
|
||||
onTap: () => onSelectedColor(color),
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _BackgroundColorItem extends StatelessWidget {
|
||||
const _BackgroundColorItem({
|
||||
required this.color,
|
||||
required this.isSelected,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final VoidCallback onTap;
|
||||
final Color color;
|
||||
final bool isSelected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.all(
|
||||
6.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: Corners.s12Border,
|
||||
border: Border.all(
|
||||
color: isSelected
|
||||
? const Color(0xff00C6F1)
|
||||
: Theme.of(context).dividerColor,
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: isSelected
|
||||
? const FlowySvg(
|
||||
FlowySvgs.blue_check_s,
|
||||
size: Size.square(28.0),
|
||||
blendMode: null,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TextColors extends StatelessWidget {
|
||||
_TextColors({
|
||||
this.selectedColor,
|
||||
required this.onSelectedColor,
|
||||
});
|
||||
|
||||
final Color? selectedColor;
|
||||
final void Function(Color color) onSelectedColor;
|
||||
|
||||
final colors = [
|
||||
const Color(0x00FFFFFF),
|
||||
const Color(0xFFDB3636),
|
||||
const Color(0xFFEA8F06),
|
||||
const Color(0xFF18A166),
|
||||
const Color(0xFF205EEE),
|
||||
const Color(0xFFC619C9),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GridView.count(
|
||||
crossAxisCount: 6,
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.zero,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: colors.mapIndexed(
|
||||
(index, color) {
|
||||
return _TextColorItem(
|
||||
color: color,
|
||||
isSelected:
|
||||
selectedColor == null ? index == 0 : selectedColor == color,
|
||||
onTap: () => onSelectedColor(color),
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TextColorItem extends StatelessWidget {
|
||||
const _TextColorItem({
|
||||
required this.color,
|
||||
required this.isSelected,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final VoidCallback onTap;
|
||||
final Color color;
|
||||
final bool isSelected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.all(
|
||||
6.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: Corners.s12Border,
|
||||
border: Border.all(
|
||||
color: isSelected
|
||||
? const Color(0xff00C6F1)
|
||||
: Theme.of(context).dividerColor,
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: FlowyText(
|
||||
'A',
|
||||
fontSize: 24,
|
||||
color: color.alpha == 0 ? null : color,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
import 'package:appflowy/mobile/presentation/setting/font/font_picker_screen.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class FontFamilyItem extends StatelessWidget {
|
||||
const FontFamilyItem({
|
||||
super.key,
|
||||
required this.editorState,
|
||||
});
|
||||
|
||||
final EditorState editorState;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final fontFamily = editorState.getDeltaAttributeValueInSelection<String>(
|
||||
AppFlowyRichTextKeys.fontFamily,
|
||||
);
|
||||
final systemFonFamily =
|
||||
context.read<DocumentAppearanceCubit>().state.fontFamily;
|
||||
return MobileToolbarItemWrapper(
|
||||
size: const Size(144, 52),
|
||||
onTap: () async {
|
||||
final selection = editorState.selection;
|
||||
final newFont = await context
|
||||
.read<GoRouter>()
|
||||
.push<String>(FontPickerScreen.routeName);
|
||||
if (newFont != null && newFont != fontFamily) {
|
||||
await editorState.formatDelta(selection, {
|
||||
AppFlowyRichTextKeys.fontFamily:
|
||||
GoogleFonts.getFont(newFont).fontFamily,
|
||||
});
|
||||
}
|
||||
},
|
||||
text: fontFamily ?? systemFonFamily,
|
||||
fontFamily: fontFamily ?? systemFonFamily,
|
||||
backgroundColor: const Color(0xFFF2F2F7),
|
||||
isSelected: false,
|
||||
enable: editorState.selection?.isCollapsed == false,
|
||||
showRightArrow: true,
|
||||
iconPadding: const EdgeInsets.only(
|
||||
top: 14.0,
|
||||
bottom: 14.0,
|
||||
left: 14.0,
|
||||
right: 12.0,
|
||||
),
|
||||
textPadding: const EdgeInsets.only(
|
||||
right: 16.0,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/util.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HeadingsAndTextItems extends StatelessWidget {
|
||||
const HeadingsAndTextItems({
|
||||
super.key,
|
||||
required this.editorState,
|
||||
});
|
||||
|
||||
final EditorState editorState;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
_HeadingOrTextItem(
|
||||
icon: FlowySvgs.m_aa_h1_s,
|
||||
blockType: HeadingBlockKeys.type,
|
||||
editorState: editorState,
|
||||
level: 1,
|
||||
),
|
||||
_HeadingOrTextItem(
|
||||
icon: FlowySvgs.m_aa_h2_s,
|
||||
blockType: HeadingBlockKeys.type,
|
||||
editorState: editorState,
|
||||
level: 2,
|
||||
),
|
||||
_HeadingOrTextItem(
|
||||
icon: FlowySvgs.m_aa_h3_s,
|
||||
blockType: HeadingBlockKeys.type,
|
||||
editorState: editorState,
|
||||
level: 3,
|
||||
),
|
||||
_HeadingOrTextItem(
|
||||
icon: FlowySvgs.m_aa_text_s,
|
||||
blockType: ParagraphBlockKeys.type,
|
||||
editorState: editorState,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _HeadingOrTextItem extends StatelessWidget {
|
||||
const _HeadingOrTextItem({
|
||||
required this.icon,
|
||||
required this.blockType,
|
||||
required this.editorState,
|
||||
this.level,
|
||||
});
|
||||
|
||||
final FlowySvgData icon;
|
||||
final String blockType;
|
||||
final EditorState editorState;
|
||||
final int? level;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isSelected = editorState.isBlockTypeSelected(
|
||||
blockType,
|
||||
level: level,
|
||||
);
|
||||
final padding = level != null
|
||||
? EdgeInsets.symmetric(
|
||||
vertical: 14.0 - (3 - level!) * 3.0,
|
||||
)
|
||||
: const EdgeInsets.symmetric(
|
||||
vertical: 16.0,
|
||||
);
|
||||
return MobileToolbarItemWrapper(
|
||||
size: const Size(76, 52),
|
||||
onTap: () async => await _convert(isSelected),
|
||||
icon: icon,
|
||||
isSelected: isSelected,
|
||||
iconPadding: padding,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _convert(bool isSelected) async {
|
||||
editorState.convertBlockType(
|
||||
blockType,
|
||||
isSelected: isSelected,
|
||||
extraAttributes: level != null
|
||||
? {
|
||||
HeadingBlockKeys.level: level!,
|
||||
}
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/util.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class IndentAndOutdentItems extends StatelessWidget {
|
||||
const IndentAndOutdentItems({
|
||||
super.key,
|
||||
required this.editorState,
|
||||
});
|
||||
|
||||
final EditorState editorState;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IntrinsicHeight(
|
||||
child: Row(
|
||||
children: [
|
||||
MobileToolbarItemWrapper(
|
||||
size: const Size(95, 52),
|
||||
icon: FlowySvgs.m_aa_outdent_s,
|
||||
enable: isOutdentable(editorState),
|
||||
isSelected: false,
|
||||
enableTopRightRadius: false,
|
||||
enableBottomRightRadius: false,
|
||||
iconPadding: const EdgeInsets.symmetric(vertical: 14.0),
|
||||
backgroundColor: const Color(0xFFF2F2F7),
|
||||
onTap: () {
|
||||
outdentCommand.execute(editorState);
|
||||
},
|
||||
),
|
||||
const ScaledVerticalDivider(),
|
||||
MobileToolbarItemWrapper(
|
||||
size: const Size(95, 52),
|
||||
icon: FlowySvgs.m_aa_indent_s,
|
||||
enable: isIndentable(editorState),
|
||||
isSelected: false,
|
||||
enableTopLeftRadius: false,
|
||||
enableBottomLeftRadius: false,
|
||||
iconPadding: const EdgeInsets.symmetric(vertical: 14.0),
|
||||
backgroundColor: const Color(0xFFF2F2F7),
|
||||
onTap: () {
|
||||
indentCommand.execute(editorState);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/_align_items.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/_bius_items.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/_block_items.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/_color_item.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/_font_item.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/_heading_and_text_items.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/_indent_items.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
final aaToolbarItem = AppFlowyMobileToolbarItem(
|
||||
itemBuilder: (context, editorState, service, onMenu, _) {
|
||||
return AppFlowyMobileToolbarIconItem(
|
||||
isSelected: () => service.showMenuNotifier.value,
|
||||
keepSelectedStatus: true,
|
||||
icon: FlowySvgs.m_toolbar_aa_s,
|
||||
onTap: () => onMenu?.call(),
|
||||
);
|
||||
},
|
||||
menuBuilder: (context, editorState, service) {
|
||||
final selection = editorState.selection;
|
||||
if (selection == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return _TextDecorationMenu(
|
||||
editorState,
|
||||
selection,
|
||||
service,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
class _TextDecorationMenu extends StatefulWidget {
|
||||
const _TextDecorationMenu(
|
||||
this.editorState,
|
||||
this.selection,
|
||||
this.service,
|
||||
);
|
||||
|
||||
final EditorState editorState;
|
||||
final Selection selection;
|
||||
final AppFlowyMobileToolbarWidgetService service;
|
||||
|
||||
@override
|
||||
State<_TextDecorationMenu> createState() => _TextDecorationMenuState();
|
||||
}
|
||||
|
||||
class _TextDecorationMenuState extends State<_TextDecorationMenu> {
|
||||
EditorState get editorState => widget.editorState;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 24,
|
||||
bottom: 20,
|
||||
left: 12,
|
||||
right: 12,
|
||||
) *
|
||||
context.scale,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
HeadingsAndTextItems(
|
||||
editorState: editorState,
|
||||
),
|
||||
const ScaledVSpace(),
|
||||
Row(
|
||||
children: [
|
||||
BIUSItems(
|
||||
editorState: editorState,
|
||||
),
|
||||
const Spacer(),
|
||||
ColorItem(
|
||||
editorState: editorState,
|
||||
),
|
||||
],
|
||||
),
|
||||
const ScaledVSpace(),
|
||||
Row(
|
||||
children: [
|
||||
BlockItems(
|
||||
service: widget.service,
|
||||
editorState: editorState,
|
||||
),
|
||||
const Spacer(),
|
||||
AlignItems(
|
||||
editorState: editorState,
|
||||
),
|
||||
],
|
||||
),
|
||||
const ScaledVSpace(),
|
||||
Row(
|
||||
children: [
|
||||
FontFamilyItem(
|
||||
editorState: editorState,
|
||||
),
|
||||
const Spacer(),
|
||||
IndentAndOutdentItems(
|
||||
editorState: editorState,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
|
||||
final addBlockToolbarItem = AppFlowyMobileToolbarItem(
|
||||
itemBuilder: (context, editorState, _, __, onAction) {
|
||||
return AppFlowyMobileToolbarIconItem(
|
||||
icon: FlowySvgs.m_toolbar_add_s,
|
||||
onTap: () {},
|
||||
);
|
||||
},
|
||||
);
|
@ -0,0 +1,539 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar_item.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/keyboard_height_observer.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
|
||||
abstract class AppFlowyMobileToolbarWidgetService {
|
||||
void closeItemMenu();
|
||||
void closeKeyboard();
|
||||
|
||||
PropertyValueNotifier<bool> get showMenuNotifier;
|
||||
}
|
||||
|
||||
class AppFlowyMobileToolbar extends StatefulWidget {
|
||||
const AppFlowyMobileToolbar({
|
||||
super.key,
|
||||
this.toolbarHeight = 50.0,
|
||||
required this.editorState,
|
||||
required this.toolbarItems,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
final EditorState editorState;
|
||||
final double toolbarHeight;
|
||||
final List<AppFlowyMobileToolbarItem> toolbarItems;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
State<AppFlowyMobileToolbar> createState() => _AppFlowyMobileToolbarState();
|
||||
}
|
||||
|
||||
class _AppFlowyMobileToolbarState extends State<AppFlowyMobileToolbar> {
|
||||
OverlayEntry? toolbarOverlay;
|
||||
|
||||
final isKeyboardShow = ValueNotifier(false);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_insertKeyboardToolbar();
|
||||
KeyboardHeightObserver.instance.addListener(_onKeyboardHeightChanged);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_removeKeyboardToolbar();
|
||||
KeyboardHeightObserver.instance.removeListener(_onKeyboardHeightChanged);
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: widget.child,
|
||||
),
|
||||
// add a bottom offset to make sure the toolbar is above the keyboard
|
||||
ValueListenableBuilder(
|
||||
valueListenable: isKeyboardShow,
|
||||
builder: (context, isKeyboardShow, __) {
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 110),
|
||||
height: isKeyboardShow ? widget.toolbarHeight : 0,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _onKeyboardHeightChanged(double height) {
|
||||
isKeyboardShow.value = height > 0;
|
||||
}
|
||||
|
||||
void _removeKeyboardToolbar() {
|
||||
toolbarOverlay?.remove();
|
||||
toolbarOverlay?.dispose();
|
||||
toolbarOverlay = null;
|
||||
}
|
||||
|
||||
void _insertKeyboardToolbar() {
|
||||
_removeKeyboardToolbar();
|
||||
|
||||
Widget child = ValueListenableBuilder<Selection?>(
|
||||
valueListenable: widget.editorState.selectionNotifier,
|
||||
builder: (_, Selection? selection, __) {
|
||||
// if the selection is null, hide the toolbar
|
||||
if (selection == null ||
|
||||
widget.editorState.selectionExtraInfo?[disableMobileToolbarKey] ==
|
||||
true) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return RepaintBoundary(
|
||||
child: _MobileToolbar(
|
||||
editorState: widget.editorState,
|
||||
toolbarItems: widget.toolbarItems,
|
||||
toolbarHeight: widget.toolbarHeight,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
child = Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: Material(
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
final router = GoRouter.of(context);
|
||||
|
||||
toolbarOverlay = OverlayEntry(
|
||||
builder: (context) {
|
||||
return Provider.value(
|
||||
value: router,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
Overlay.of(context, rootOverlay: true).insert(toolbarOverlay!);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _MobileToolbar extends StatefulWidget {
|
||||
const _MobileToolbar({
|
||||
required this.editorState,
|
||||
required this.toolbarItems,
|
||||
required this.toolbarHeight,
|
||||
});
|
||||
|
||||
final EditorState editorState;
|
||||
final List<AppFlowyMobileToolbarItem> toolbarItems;
|
||||
final double toolbarHeight;
|
||||
|
||||
@override
|
||||
State<_MobileToolbar> createState() => _MobileToolbarState();
|
||||
}
|
||||
|
||||
class _MobileToolbarState extends State<_MobileToolbar>
|
||||
implements AppFlowyMobileToolbarWidgetService {
|
||||
// used to control the toolbar menu items
|
||||
@override
|
||||
PropertyValueNotifier<bool> showMenuNotifier = PropertyValueNotifier(false);
|
||||
|
||||
// when the users click the menu item, the keyboard will be hidden,
|
||||
// but in this case, we don't want to update the cached keyboard height.
|
||||
// This is because we want to keep the same height when the menu is shown.
|
||||
bool canUpdateCachedKeyboardHeight = true;
|
||||
ValueNotifier<double> cachedKeyboardHeight = ValueNotifier(0.0);
|
||||
|
||||
// used to check if click the same item again
|
||||
int? selectedMenuIndex;
|
||||
|
||||
Selection? currentSelection;
|
||||
|
||||
bool closeKeyboardInitiative = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
currentSelection = widget.editorState.selection;
|
||||
KeyboardHeightObserver.instance.addListener(_onKeyboardHeightChanged);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant _MobileToolbar oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
if (currentSelection != widget.editorState.selection) {
|
||||
currentSelection = widget.editorState.selection;
|
||||
closeItemMenu();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
showMenuNotifier.dispose();
|
||||
cachedKeyboardHeight.dispose();
|
||||
KeyboardHeightObserver.instance.removeListener(_onKeyboardHeightChanged);
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void reassemble() {
|
||||
super.reassemble();
|
||||
|
||||
canUpdateCachedKeyboardHeight = true;
|
||||
closeItemMenu();
|
||||
_closeKeyboard();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// toolbar
|
||||
// - if the menu is shown, the toolbar will be pushed up by the height of the menu
|
||||
// - otherwise, add a spacer to push the toolbar up when the keyboard is shown
|
||||
return Column(
|
||||
children: [
|
||||
Divider(
|
||||
height: 0.5,
|
||||
color: Colors.grey.withOpacity(0.5),
|
||||
),
|
||||
_buildToolbar(context),
|
||||
Divider(
|
||||
height: 0.5,
|
||||
color: Colors.grey.withOpacity(0.5),
|
||||
),
|
||||
_buildMenuOrSpacer(context),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void closeItemMenu() {
|
||||
showMenuNotifier.value = false;
|
||||
}
|
||||
|
||||
@override
|
||||
void closeKeyboard() {
|
||||
_closeKeyboard();
|
||||
}
|
||||
|
||||
void showItemMenu() {
|
||||
showMenuNotifier.value = true;
|
||||
}
|
||||
|
||||
void _onKeyboardHeightChanged(double height) {
|
||||
if (canUpdateCachedKeyboardHeight) {
|
||||
cachedKeyboardHeight.value = height;
|
||||
}
|
||||
|
||||
// if the keyboard is not closed initiative, we need to close the menu at same time
|
||||
if (!closeKeyboardInitiative &&
|
||||
cachedKeyboardHeight.value != 0 &&
|
||||
height == 0) {
|
||||
widget.editorState.selection = null;
|
||||
}
|
||||
|
||||
if (height == 0) {
|
||||
closeKeyboardInitiative = false;
|
||||
}
|
||||
}
|
||||
|
||||
// toolbar list view and close keyboard/menu button
|
||||
Widget _buildToolbar(BuildContext context) {
|
||||
return Container(
|
||||
color: const Color(0xFFF3F3F8),
|
||||
height: widget.toolbarHeight,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// toolbar list view
|
||||
Expanded(
|
||||
child: _ToolbarItemListView(
|
||||
toolbarItems: widget.toolbarItems,
|
||||
editorState: widget.editorState,
|
||||
toolbarWidgetService: this,
|
||||
itemWithActionOnPressed: (_) {
|
||||
if (showMenuNotifier.value) {
|
||||
closeItemMenu();
|
||||
_showKeyboard();
|
||||
// update the cached keyboard height after the keyboard is shown
|
||||
Debounce.debounce('canUpdateCachedKeyboardHeight',
|
||||
const Duration(milliseconds: 500), () {
|
||||
canUpdateCachedKeyboardHeight = true;
|
||||
});
|
||||
}
|
||||
},
|
||||
itemWithMenuOnPressed: (index) {
|
||||
// click the same one
|
||||
if (selectedMenuIndex == index && showMenuNotifier.value) {
|
||||
// if the menu is shown, close it and show the keyboard
|
||||
closeItemMenu();
|
||||
_showKeyboard();
|
||||
// update the cached keyboard height after the keyboard is shown
|
||||
Debounce.debounce('canUpdateCachedKeyboardHeight',
|
||||
const Duration(milliseconds: 500), () {
|
||||
canUpdateCachedKeyboardHeight = true;
|
||||
});
|
||||
} else {
|
||||
canUpdateCachedKeyboardHeight = false;
|
||||
selectedMenuIndex = index;
|
||||
showItemMenu();
|
||||
closeKeyboardInitiative = true;
|
||||
_closeKeyboard();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
// divider
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
),
|
||||
child: VerticalDivider(
|
||||
width: 1,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
// close menu or close keyboard button
|
||||
ValueListenableBuilder(
|
||||
valueListenable: showMenuNotifier,
|
||||
builder: (_, showingMenu, __) {
|
||||
return _CloseKeyboardOrMenuButton(
|
||||
showingMenu: showingMenu,
|
||||
onPressed: () {
|
||||
if (showingMenu) {
|
||||
// close the menu and show the keyboard
|
||||
closeItemMenu();
|
||||
_showKeyboard();
|
||||
} else {
|
||||
closeKeyboardInitiative = true;
|
||||
// close the keyboard and clear the selection
|
||||
// if the selection is null, the keyboard and the toolbar will be hidden automatically
|
||||
widget.editorState.selection = null;
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// if there's no menu, we need to add a spacer to push the toolbar up when the keyboard is shown
|
||||
Widget _buildMenuOrSpacer(BuildContext context) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: cachedKeyboardHeight,
|
||||
builder: (_, height, ___) {
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(microseconds: 110),
|
||||
height: height,
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: showMenuNotifier,
|
||||
builder: (_, showingMenu, __) {
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(microseconds: 110),
|
||||
height: height,
|
||||
child: (showingMenu && selectedMenuIndex != null)
|
||||
? widget.toolbarItems[selectedMenuIndex!].menuBuilder?.call(
|
||||
context,
|
||||
widget.editorState,
|
||||
this,
|
||||
) ??
|
||||
const SizedBox.shrink()
|
||||
: const SizedBox.shrink(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _showKeyboard() {
|
||||
final selection = widget.editorState.selection;
|
||||
if (selection != null) {
|
||||
widget.editorState.service.keyboardService?.enableKeyBoard(selection);
|
||||
}
|
||||
}
|
||||
|
||||
void _closeKeyboard() {
|
||||
widget.editorState.service.keyboardService?.closeKeyboard();
|
||||
}
|
||||
}
|
||||
|
||||
class _ToolbarItemListView extends StatefulWidget {
|
||||
const _ToolbarItemListView({
|
||||
required this.toolbarItems,
|
||||
required this.editorState,
|
||||
required this.toolbarWidgetService,
|
||||
required this.itemWithMenuOnPressed,
|
||||
required this.itemWithActionOnPressed,
|
||||
});
|
||||
|
||||
final Function(int index) itemWithMenuOnPressed;
|
||||
final Function(int index) itemWithActionOnPressed;
|
||||
final List<AppFlowyMobileToolbarItem> toolbarItems;
|
||||
final EditorState editorState;
|
||||
final AppFlowyMobileToolbarWidgetService toolbarWidgetService;
|
||||
|
||||
@override
|
||||
State<_ToolbarItemListView> createState() => _ToolbarItemListViewState();
|
||||
}
|
||||
|
||||
class _ToolbarItemListViewState extends State<_ToolbarItemListView> {
|
||||
final scrollController = ItemScrollController();
|
||||
Selection? previousSelection;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
widget.editorState.selectionNotifier
|
||||
.addListener(_debounceUpdatePilotPosition);
|
||||
previousSelection = widget.editorState.selection;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.editorState.selectionNotifier
|
||||
.removeListener(_debounceUpdatePilotPosition);
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final children = [
|
||||
const HSpace(8),
|
||||
...widget.toolbarItems
|
||||
.mapIndexed(
|
||||
(index, element) => element.itemBuilder.call(
|
||||
context,
|
||||
widget.editorState,
|
||||
widget.toolbarWidgetService,
|
||||
element.menuBuilder != null
|
||||
? () {
|
||||
widget.itemWithMenuOnPressed(index);
|
||||
}
|
||||
: null,
|
||||
element.menuBuilder == null
|
||||
? () {
|
||||
widget.itemWithActionOnPressed(index);
|
||||
}
|
||||
: null,
|
||||
),
|
||||
)
|
||||
.map((e) => [e, const HSpace(10)])
|
||||
.flattened,
|
||||
];
|
||||
|
||||
return PageStorage(
|
||||
bucket: PageStorageBucket(),
|
||||
child: ScrollablePositionedList.builder(
|
||||
physics: const ClampingScrollPhysics(),
|
||||
itemScrollController: scrollController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemBuilder: (context, index) => children[index],
|
||||
itemCount: children.length,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _debounceUpdatePilotPosition() {
|
||||
Debounce.debounce(
|
||||
'updatePilotPosition',
|
||||
const Duration(milliseconds: 250),
|
||||
_updatePilotPosition,
|
||||
);
|
||||
}
|
||||
|
||||
void _updatePilotPosition() {
|
||||
final selection = widget.editorState.selection;
|
||||
if (selection == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (previousSelection != null &&
|
||||
previousSelection!.isCollapsed == selection.isCollapsed) {
|
||||
return;
|
||||
}
|
||||
|
||||
final toolbarItems = widget.toolbarItems;
|
||||
final alignment = selection.isCollapsed ? 0.0 : -1.0;
|
||||
final index = toolbarItems.indexWhere(
|
||||
(element) => selection.isCollapsed
|
||||
? element.pilotAtCollapsedSelection
|
||||
: element.pilotAtExpandedSelection,
|
||||
);
|
||||
if (index != -1) {
|
||||
scrollController.scrollTo(
|
||||
alignment: alignment,
|
||||
index: index,
|
||||
duration: const Duration(
|
||||
milliseconds: 250,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
previousSelection = selection;
|
||||
}
|
||||
}
|
||||
|
||||
class _CloseKeyboardOrMenuButton extends StatelessWidget {
|
||||
const _CloseKeyboardOrMenuButton({
|
||||
required this.showingMenu,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
final bool showingMenu;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: 64,
|
||||
height: 46,
|
||||
child: FlowyButton(
|
||||
text: showingMenu
|
||||
? const Padding(
|
||||
padding: EdgeInsets.only(right: 0.5),
|
||||
child: FlowySvg(
|
||||
FlowySvgs.m_toolbar_show_keyboard_s,
|
||||
),
|
||||
)
|
||||
: const FlowySvg(
|
||||
FlowySvgs.m_toolbar_hide_keyboard_s,
|
||||
),
|
||||
onTap: onPressed,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// build the toolbar item, like Aa, +, image ...
|
||||
typedef AppFlowyMobileToolbarItemBuilder = Widget Function(
|
||||
BuildContext context,
|
||||
EditorState editorState,
|
||||
AppFlowyMobileToolbarWidgetService service,
|
||||
VoidCallback? onMenuCallback,
|
||||
VoidCallback? onActionCallback,
|
||||
);
|
||||
|
||||
// build the menu after clicking the toolbar item
|
||||
typedef AppFlowyMobileToolbarItemMenuBuilder = Widget Function(
|
||||
BuildContext context,
|
||||
EditorState editorState,
|
||||
AppFlowyMobileToolbarWidgetService service,
|
||||
);
|
||||
|
||||
class AppFlowyMobileToolbarItem {
|
||||
/// Tool bar item that implements attribute directly(without opening menu)
|
||||
const AppFlowyMobileToolbarItem({
|
||||
required this.itemBuilder,
|
||||
this.menuBuilder,
|
||||
this.pilotAtCollapsedSelection = false,
|
||||
this.pilotAtExpandedSelection = false,
|
||||
});
|
||||
|
||||
final AppFlowyMobileToolbarItemBuilder itemBuilder;
|
||||
final AppFlowyMobileToolbarItemMenuBuilder? menuBuilder;
|
||||
final bool pilotAtCollapsedSelection;
|
||||
final bool pilotAtExpandedSelection;
|
||||
}
|
||||
|
||||
class AppFlowyMobileToolbarIconItem extends StatefulWidget {
|
||||
const AppFlowyMobileToolbarIconItem({
|
||||
super.key,
|
||||
this.icon,
|
||||
this.keepSelectedStatus = false,
|
||||
this.iconBuilder,
|
||||
this.isSelected,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final FlowySvgData? icon;
|
||||
final bool keepSelectedStatus;
|
||||
final VoidCallback onTap;
|
||||
final WidgetBuilder? iconBuilder;
|
||||
final bool Function()? isSelected;
|
||||
|
||||
@override
|
||||
State<AppFlowyMobileToolbarIconItem> createState() =>
|
||||
_AppFlowyMobileToolbarIconItemState();
|
||||
}
|
||||
|
||||
class _AppFlowyMobileToolbarIconItemState
|
||||
extends State<AppFlowyMobileToolbarIconItem> {
|
||||
bool isSelected = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
isSelected = widget.isSelected?.call() ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant AppFlowyMobileToolbarIconItem oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
if (widget.isSelected != null) {
|
||||
isSelected = widget.isSelected!.call();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
widget.onTap();
|
||||
if (widget.keepSelectedStatus && widget.isSelected == null) {
|
||||
setState(() {
|
||||
isSelected = !isSelected;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
isSelected = widget.isSelected?.call() ?? false;
|
||||
});
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
width: 48,
|
||||
padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: isSelected ? const Color(0x1f232914) : null,
|
||||
),
|
||||
child: widget.iconBuilder?.call(context) ??
|
||||
FlowySvg(
|
||||
widget.icon!,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/_color_list.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
final boldToolbarItem = AppFlowyMobileToolbarItem(
|
||||
pilotAtExpandedSelection: true,
|
||||
itemBuilder: (context, editorState, _, __, onAction) {
|
||||
return AppFlowyMobileToolbarIconItem(
|
||||
isSelected: () => editorState.isTextDecorationSelected(
|
||||
AppFlowyRichTextKeys.bold,
|
||||
),
|
||||
icon: FlowySvgs.m_toolbar_bold_s,
|
||||
onTap: () async => await editorState.toggleAttribute(
|
||||
AppFlowyRichTextKeys.bold,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
final italicToolbarItem = AppFlowyMobileToolbarItem(
|
||||
itemBuilder: (context, editorState, _, __, onAction) {
|
||||
return AppFlowyMobileToolbarIconItem(
|
||||
// keepSelectedStatus: true,
|
||||
isSelected: () => editorState.isTextDecorationSelected(
|
||||
AppFlowyRichTextKeys.italic,
|
||||
),
|
||||
icon: FlowySvgs.m_toolbar_italic_s,
|
||||
onTap: () async => await editorState.toggleAttribute(
|
||||
AppFlowyRichTextKeys.italic,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
final underlineToolbarItem = AppFlowyMobileToolbarItem(
|
||||
itemBuilder: (context, editorState, _, __, onAction) {
|
||||
return AppFlowyMobileToolbarIconItem(
|
||||
isSelected: () => editorState.isTextDecorationSelected(
|
||||
AppFlowyRichTextKeys.underline,
|
||||
),
|
||||
icon: FlowySvgs.m_toolbar_underline_s,
|
||||
onTap: () async => await editorState.toggleAttribute(
|
||||
AppFlowyRichTextKeys.underline,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
final colorToolbarItem = AppFlowyMobileToolbarItem(
|
||||
itemBuilder: (context, editorState, service, __, onAction) {
|
||||
return AppFlowyMobileToolbarIconItem(
|
||||
icon: FlowySvgs.m_toolbar_color_s,
|
||||
onTap: () {
|
||||
service.closeKeyboard();
|
||||
editorState.updateSelectionWithReason(
|
||||
editorState.selection,
|
||||
extraInfo: {
|
||||
disableMobileToolbarKey: true,
|
||||
},
|
||||
);
|
||||
keepEditorFocusNotifier.increase();
|
||||
showTextColorAndBackgroundColorPicker(
|
||||
context,
|
||||
editorState: editorState,
|
||||
selection: editorState.selection!,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
@ -0,0 +1,22 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
final todoListToolbarItem = AppFlowyMobileToolbarItem(
|
||||
itemBuilder: (context, editorState, _, __, onAction) {
|
||||
final isSelected = editorState.isBlockTypeSelected(TodoListBlockKeys.type);
|
||||
return AppFlowyMobileToolbarIconItem(
|
||||
keepSelectedStatus: true,
|
||||
isSelected: () => isSelected,
|
||||
icon: FlowySvgs.m_toolbar_checkbox_s,
|
||||
onTap: () async {
|
||||
await editorState.convertBlockType(
|
||||
TodoListBlockKeys.type,
|
||||
extraAttributes: {
|
||||
TodoListBlockKeys.checked: false,
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
@ -0,0 +1,39 @@
|
||||
import 'package:keyboard_height_plugin/keyboard_height_plugin.dart';
|
||||
|
||||
typedef KeyboardHeightCallback = void Function(double height);
|
||||
|
||||
// the KeyboardHeightPlugin only accepts one listener, so we need to create a
|
||||
// singleton class to manage the multiple listeners.
|
||||
class KeyboardHeightObserver {
|
||||
KeyboardHeightObserver._() {
|
||||
_keyboardHeightPlugin.onKeyboardHeightChanged((height) {
|
||||
currentKeyboardHeight = height;
|
||||
notify(height);
|
||||
});
|
||||
}
|
||||
|
||||
static final KeyboardHeightObserver instance = KeyboardHeightObserver._();
|
||||
static double currentKeyboardHeight = 0;
|
||||
|
||||
final List<KeyboardHeightCallback> _listeners = [];
|
||||
final KeyboardHeightPlugin _keyboardHeightPlugin = KeyboardHeightPlugin();
|
||||
|
||||
void addListener(KeyboardHeightCallback listener) {
|
||||
_listeners.add(listener);
|
||||
}
|
||||
|
||||
void removeListener(KeyboardHeightCallback listener) {
|
||||
_listeners.remove(listener);
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_listeners.clear();
|
||||
_keyboardHeightPlugin.dispose();
|
||||
}
|
||||
|
||||
void notify(double height) {
|
||||
for (final listener in _listeners) {
|
||||
listener(height);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
|
||||
final moreToolbarItem = AppFlowyMobileToolbarItem(
|
||||
itemBuilder: (context, editorState, _, __, onAction) {
|
||||
return AppFlowyMobileToolbarIconItem(
|
||||
icon: FlowySvgs.m_toolbar_more_s,
|
||||
onTap: () {},
|
||||
);
|
||||
},
|
||||
);
|
@ -0,0 +1,35 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
final undoToolbarItem = AppFlowyMobileToolbarItem(
|
||||
pilotAtCollapsedSelection: true,
|
||||
itemBuilder: (context, editorState, _, __, onAction) {
|
||||
return AppFlowyMobileToolbarIconItem(
|
||||
iconBuilder: (context) {
|
||||
final canUndo = editorState.undoManager.undoStack.isNonEmpty;
|
||||
return FlowySvg(
|
||||
FlowySvgs.m_toolbar_undo_s,
|
||||
color: canUndo ? null : const Color(0xFFC7C7CC),
|
||||
);
|
||||
},
|
||||
onTap: () => undoCommand.execute(editorState),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
final redoToolbarItem = AppFlowyMobileToolbarItem(
|
||||
itemBuilder: (context, editorState, _, __, onAction) {
|
||||
return AppFlowyMobileToolbarIconItem(
|
||||
iconBuilder: (context) {
|
||||
final canRedo = editorState.undoManager.redoStack.isNonEmpty;
|
||||
return FlowySvg(
|
||||
FlowySvgs.m_toolbar_redo_s,
|
||||
color: canRedo ? null : const Color(0xFFC7C7CC),
|
||||
);
|
||||
},
|
||||
onTap: () => redoCommand.execute(editorState),
|
||||
);
|
||||
},
|
||||
);
|
@ -0,0 +1,319 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MobileToolbarItemWrapper extends StatelessWidget {
|
||||
const MobileToolbarItemWrapper({
|
||||
super.key,
|
||||
required this.size,
|
||||
this.icon,
|
||||
this.text,
|
||||
this.backgroundColor,
|
||||
this.enable,
|
||||
this.fontFamily,
|
||||
required this.isSelected,
|
||||
required this.iconPadding,
|
||||
this.enableBottomLeftRadius = true,
|
||||
this.enableBottomRightRadius = true,
|
||||
this.enableTopLeftRadius = true,
|
||||
this.enableTopRightRadius = true,
|
||||
this.showDownArrow = false,
|
||||
this.showRightArrow = false,
|
||||
this.textPadding = EdgeInsets.zero,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final Size size;
|
||||
final VoidCallback onTap;
|
||||
final FlowySvgData? icon;
|
||||
final String? text;
|
||||
final bool? enable;
|
||||
final String? fontFamily;
|
||||
final bool isSelected;
|
||||
final EdgeInsets iconPadding;
|
||||
final bool enableTopLeftRadius;
|
||||
final bool enableTopRightRadius;
|
||||
final bool enableBottomRightRadius;
|
||||
final bool enableBottomLeftRadius;
|
||||
final bool showDownArrow;
|
||||
final bool showRightArrow;
|
||||
final Color? backgroundColor;
|
||||
final EdgeInsets textPadding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color? iconColor;
|
||||
if (enable != null) {
|
||||
iconColor = enable! ? null : const Color(0xFFC7C7CC);
|
||||
} else {
|
||||
iconColor = isSelected ? Colors.white : Colors.black;
|
||||
}
|
||||
final textColor = enable == false ? const Color(0xFFC7C7CC) : null;
|
||||
// the ui design is based on 375.0 width
|
||||
final scale = context.scale;
|
||||
final radius = Radius.circular(12 * scale);
|
||||
final Widget child;
|
||||
if (icon != null) {
|
||||
child = FlowySvg(
|
||||
icon!,
|
||||
color: iconColor,
|
||||
);
|
||||
} else if (text != null) {
|
||||
child = Padding(
|
||||
padding: textPadding * scale,
|
||||
child: FlowyText(
|
||||
text!,
|
||||
fontSize: 16.0,
|
||||
color: textColor,
|
||||
fontFamily: fontFamily,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
throw ArgumentError('icon and text cannot be null at the same time');
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: onTap,
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
height: size.height * scale,
|
||||
width: size.width * scale,
|
||||
alignment: text != null ? Alignment.centerLeft : Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? const Color(0xFF00BCF0) : backgroundColor,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: enableTopLeftRadius ? radius : Radius.zero,
|
||||
topRight: enableTopRightRadius ? radius : Radius.zero,
|
||||
bottomRight: enableBottomRightRadius ? radius : Radius.zero,
|
||||
bottomLeft: enableBottomLeftRadius ? radius : Radius.zero,
|
||||
),
|
||||
),
|
||||
padding: iconPadding * scale,
|
||||
child: child,
|
||||
),
|
||||
if (showDownArrow)
|
||||
Positioned(
|
||||
right: 9.0 * scale,
|
||||
bottom: 9.0 * scale,
|
||||
child: const FlowySvg(FlowySvgs.m_aa_down_arrow_s),
|
||||
),
|
||||
if (showRightArrow)
|
||||
Positioned.fill(
|
||||
right: 12.0 * scale,
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: FlowySvg(
|
||||
FlowySvgs.m_aa_arrow_right_s,
|
||||
color: iconColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ScaledVerticalDivider extends StatelessWidget {
|
||||
const ScaledVerticalDivider({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return HSpace(
|
||||
1.5 * context.scale,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ScaledVSpace extends StatelessWidget {
|
||||
const ScaledVSpace({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return VSpace(12.0 * context.scale);
|
||||
}
|
||||
}
|
||||
|
||||
extension MobileToolbarBuildContext on BuildContext {
|
||||
double get scale => MediaQuery.of(this).size.width / 375.0;
|
||||
}
|
||||
|
||||
extension MobileToolbarEditorState on EditorState {
|
||||
bool isBlockTypeSelected(
|
||||
String blockType, {
|
||||
int? level,
|
||||
}) {
|
||||
final selection = this.selection;
|
||||
if (selection == null) {
|
||||
return false;
|
||||
}
|
||||
final node = getNodeAtPath(selection.start.path);
|
||||
final type = node?.type;
|
||||
if (node == null || type == null) {
|
||||
return false;
|
||||
}
|
||||
if (level != null && blockType == HeadingBlockKeys.type) {
|
||||
return type == blockType &&
|
||||
node.attributes[HeadingBlockKeys.level] == level;
|
||||
}
|
||||
return type == blockType;
|
||||
}
|
||||
|
||||
bool isTextDecorationSelected(
|
||||
String richTextKey,
|
||||
) {
|
||||
final selection = this.selection;
|
||||
if (selection == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final nodes = getNodesInSelection(selection);
|
||||
bool isSelected = false;
|
||||
if (selection.isCollapsed) {
|
||||
if (toggledStyle.containsKey(richTextKey)) {
|
||||
isSelected = toggledStyle[richTextKey] as bool;
|
||||
} else {
|
||||
if (selection.startIndex != 0) {
|
||||
// get previous index text style
|
||||
isSelected = nodes.allSatisfyInSelection(
|
||||
selection.copyWith(
|
||||
start: selection.start.copyWith(
|
||||
offset: selection.startIndex - 1,
|
||||
),
|
||||
), (delta) {
|
||||
return delta.everyAttributes(
|
||||
(attributes) => attributes[richTextKey] == true,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
isSelected = nodes.allSatisfyInSelection(selection, (delta) {
|
||||
return delta.everyAttributes(
|
||||
(attributes) => attributes[richTextKey] == true,
|
||||
);
|
||||
});
|
||||
}
|
||||
return isSelected;
|
||||
}
|
||||
|
||||
Future<void> convertBlockType(
|
||||
String newBlockType, {
|
||||
Selection? selection,
|
||||
Attributes? extraAttributes,
|
||||
bool? isSelected,
|
||||
}) async {
|
||||
selection = selection ?? this.selection;
|
||||
if (selection == null) {
|
||||
return;
|
||||
}
|
||||
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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> alignBlock(
|
||||
String alignment, {
|
||||
Selection? selection,
|
||||
}) async {
|
||||
await updateNode(
|
||||
selection,
|
||||
(node) => node.copyWith(
|
||||
attributes: {
|
||||
...node.attributes,
|
||||
blockComponentAlign: alignment,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> updateTextAndHref(
|
||||
String? prevText,
|
||||
String? prevHref,
|
||||
String? text,
|
||||
String? href, {
|
||||
Selection? selection,
|
||||
}) async {
|
||||
if (prevText == null && text == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
selection ??= this.selection;
|
||||
// doesn't support multiple selection now
|
||||
if (selection == null || !selection.isSingle) {
|
||||
return;
|
||||
}
|
||||
|
||||
final node = getNodeAtPath(selection.start.path);
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final transaction = this.transaction;
|
||||
|
||||
// insert a new link
|
||||
if (prevText == null &&
|
||||
text != null &&
|
||||
text.isNotEmpty &&
|
||||
selection.isCollapsed) {
|
||||
final attributes = href != null && href.isNotEmpty
|
||||
? {
|
||||
AppFlowyRichTextKeys.href: href,
|
||||
}
|
||||
: null;
|
||||
transaction.insertText(
|
||||
node,
|
||||
selection.startIndex,
|
||||
text,
|
||||
attributes: attributes,
|
||||
);
|
||||
} else if (text != null && prevText != text) {
|
||||
// update text
|
||||
transaction.replaceText(
|
||||
node,
|
||||
selection.startIndex,
|
||||
selection.length,
|
||||
text,
|
||||
);
|
||||
}
|
||||
|
||||
// if the text is empty, it means the user wants to remove the text
|
||||
if (text != null && text.isNotEmpty && prevHref != href) {
|
||||
// update href
|
||||
transaction.formatText(
|
||||
node,
|
||||
selection.startIndex,
|
||||
text.length,
|
||||
{
|
||||
AppFlowyRichTextKeys.href: href?.isEmpty == true ? null : href,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
await apply(transaction);
|
||||
}
|
||||
}
|
@ -29,14 +29,15 @@ export 'link_preview/link_preview_cache.dart';
|
||||
export 'link_preview/link_preview_menu.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_block_settings_toolbar_item.dart';
|
||||
export 'mobile_toolbar_item/mobile_convert_block_toolbar_item.dart';
|
||||
export 'mobile_toolbar_item/mobile_indent_toolbar_item.dart';
|
||||
export 'mobile_toolbar_item/mobile_text_decoration_item.dart';
|
||||
export 'mobile_toolbar_item/undo_redo/redo_mobile_toolbar_item.dart';
|
||||
export 'mobile_toolbar_item/undo_redo/undo_mobile_toolbar_item.dart';
|
||||
export 'mobile_toolbar_v3/aa_toolbar_item.dart';
|
||||
export 'mobile_toolbar_v3/add_block_toolbar_item.dart';
|
||||
export 'mobile_toolbar_v3/appflowy_mobile_toolbar.dart';
|
||||
export 'mobile_toolbar_v3/appflowy_mobile_toolbar_item.dart';
|
||||
export 'mobile_toolbar_v3/biuc_toolbar_item.dart';
|
||||
export 'mobile_toolbar_v3/checkbox_toolbar_item.dart';
|
||||
export 'mobile_toolbar_v3/more_toolbar_item.dart';
|
||||
export 'mobile_toolbar_v3/undo_redo_toolbar_item.dart';
|
||||
export 'mobile_toolbar_v3/util.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';
|
||||
|
@ -1,8 +1,8 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/inline_math_equation/inline_math_equation.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_item/utils.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
||||
import 'package:appflowy/plugins/inline_actions/inline_actions_menu.dart';
|
||||
import 'package:appflowy/util/google_font_family_extension.dart';
|
||||
@ -288,19 +288,35 @@ class EditorStyleCustomizer {
|
||||
text: text.text,
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
final editorState = context.read<EditorState>();
|
||||
if (editorState.selection == null) {
|
||||
safeLaunchUrl(href);
|
||||
return;
|
||||
}
|
||||
|
||||
editorState.updateSelectionWithReason(
|
||||
editorState.selection,
|
||||
extraInfo: {
|
||||
disableMobileToolbarKey: true,
|
||||
},
|
||||
);
|
||||
|
||||
showEditLinkBottomSheet(
|
||||
context,
|
||||
text.text,
|
||||
href,
|
||||
(linkContext, newText, newHref) {
|
||||
_updateTextAndHref(
|
||||
context,
|
||||
node,
|
||||
index,
|
||||
final selection = Selection.single(
|
||||
path: node.path,
|
||||
startOffset: index,
|
||||
endOffset: index + text.text.length,
|
||||
);
|
||||
editorState.updateTextAndHref(
|
||||
text.text,
|
||||
href,
|
||||
newText,
|
||||
newHref,
|
||||
selection: selection,
|
||||
);
|
||||
linkContext.pop();
|
||||
},
|
||||
@ -318,37 +334,4 @@ class EditorStyleCustomizer {
|
||||
after,
|
||||
);
|
||||
}
|
||||
|
||||
void _updateTextAndHref(
|
||||
BuildContext context,
|
||||
Node node,
|
||||
int index,
|
||||
String prevText,
|
||||
String? prevHref,
|
||||
String text,
|
||||
String href,
|
||||
) async {
|
||||
final selection = Selection.single(
|
||||
path: node.path,
|
||||
startOffset: index,
|
||||
endOffset: index + prevText.length,
|
||||
);
|
||||
final editorState = context.read<EditorState>();
|
||||
final transaction = editorState.transaction;
|
||||
if (prevText != text) {
|
||||
transaction.replaceText(
|
||||
node,
|
||||
selection.startIndex,
|
||||
selection.length,
|
||||
text,
|
||||
);
|
||||
}
|
||||
// if the text is empty, it means the user wants to remove the text
|
||||
if (text.isNotEmpty && prevHref != href) {
|
||||
transaction.formatText(node, selection.startIndex, text.length, {
|
||||
AppFlowyRichTextKeys.href: href.isEmpty ? null : href,
|
||||
});
|
||||
}
|
||||
await editorState.apply(transaction);
|
||||
}
|
||||
}
|
||||
|
@ -37,10 +37,27 @@ class VSpace extends StatelessWidget {
|
||||
}
|
||||
|
||||
class HSpace extends StatelessWidget {
|
||||
final double size;
|
||||
const HSpace(
|
||||
this.size, {
|
||||
super.key,
|
||||
this.color,
|
||||
});
|
||||
|
||||
const HSpace(this.size, {Key? key}) : super(key: key);
|
||||
final double size;
|
||||
final Color? color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Space(size, 0);
|
||||
Widget build(BuildContext context) {
|
||||
if (color != null) {
|
||||
return SizedBox(
|
||||
height: double.infinity,
|
||||
width: size,
|
||||
child: ColoredBox(
|
||||
color: color!,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Space(size, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,16 +50,20 @@ class FlowySvg extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final iconColor = color ?? Theme.of(context).iconTheme.color;
|
||||
|
||||
return SvgPicture.asset(
|
||||
_normalized(),
|
||||
return SizedBox(
|
||||
width: size?.width,
|
||||
height: size?.height,
|
||||
colorFilter: iconColor != null && blendMode != null
|
||||
? ColorFilter.mode(
|
||||
iconColor,
|
||||
blendMode!,
|
||||
)
|
||||
: null,
|
||||
child: SvgPicture.asset(
|
||||
_normalized(),
|
||||
width: size?.width,
|
||||
height: size?.height,
|
||||
colorFilter: iconColor != null && blendMode != null
|
||||
? ColorFilter.mode(
|
||||
iconColor,
|
||||
blendMode!,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -53,8 +53,8 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "82f3baf"
|
||||
resolved-ref: "82f3baf3f121277c5e235faa04687a7fc383b37d"
|
||||
ref: "92e4260"
|
||||
resolved-ref: "92e4260c062189cf4d5626272af62d0024d69455"
|
||||
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
||||
source: git
|
||||
version: "2.1.0"
|
||||
@ -1003,7 +1003,7 @@ packages:
|
||||
source: hosted
|
||||
version: "0.3.1"
|
||||
keyboard_height_plugin:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: keyboard_height_plugin
|
||||
sha256: bbb32804bf93601249c17c33125cd2e654f5ef650fc6acf1b031d69b478b35ce
|
||||
@ -1506,6 +1506,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.9"
|
||||
scrollable_positioned_list:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: scrollable_positioned_list
|
||||
sha256: "1b54d5f1329a1e263269abc9e2543d90806131aa14fe7c6062a8054d57249287"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.8"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -126,6 +126,8 @@ dependencies:
|
||||
image_gallery_saver: ^2.0.3
|
||||
cached_network_image: ^3.3.0
|
||||
leak_tracker: ^9.0.6
|
||||
keyboard_height_plugin: ^0.0.5
|
||||
scrollable_positioned_list: ^0.3.8
|
||||
|
||||
dev_dependencies:
|
||||
flutter_lints: ^3.0.1
|
||||
@ -161,7 +163,7 @@ dependency_overrides:
|
||||
appflowy_editor:
|
||||
git:
|
||||
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
||||
ref: "82f3baf"
|
||||
ref: "92e4260"
|
||||
|
||||
logger: ^2.0.0
|
||||
|
||||
|
5
frontend/resources/flowy_icons/16x/m_aa_align_left.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.79102 5.57827C3.79102 5.07013 4.20329 4.6582 4.71185 4.6582H21.2868C21.7954 4.6582 22.2077 5.07013 22.2077 5.57827C22.2077 6.08641 21.7954 6.49834 21.2868 6.49834H4.71185C4.20329 6.49834 3.79102 6.08641 3.79102 5.57827Z" fill="#2B2F36"/>
|
||||
<path d="M3.79102 20.4215C3.79102 19.9133 4.20329 19.5014 4.71185 19.5014H21.2868C21.7954 19.5014 22.2077 19.9133 22.2077 20.4215C22.2077 20.9296 21.7954 21.3415 21.2868 21.3415H4.71185C4.20329 21.3415 3.79102 20.9296 3.79102 20.4215Z" fill="#2B2F36"/>
|
||||
<path d="M4.71185 12.0798C4.20329 12.0798 3.79102 12.4917 3.79102 12.9999C3.79102 13.508 4.20329 13.9199 4.71185 13.9199H12.9993C13.5079 13.9199 13.9202 13.508 13.9202 12.9999C13.9202 12.4917 13.5079 12.0798 12.9993 12.0798H4.71185Z" fill="#2B2F36"/>
|
||||
</svg>
|
After Width: | Height: | Size: 857 B |
5
frontend/resources/flowy_icons/16x/m_aa_arrow_right.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g opacity="0.9">
|
||||
<path d="M3.79294 10.0858C3.56421 10.3146 3.56421 10.6854 3.79294 10.9142C4.02168 11.1429 4.39253 11.1429 4.62127 10.9142L8.76772 6.76772C9.19171 6.34372 9.19171 5.65628 8.76772 5.23228L4.62127 1.08584C4.39253 0.857101 4.02168 0.857101 3.79294 1.08584C3.56421 1.31457 3.56421 1.68543 3.79294 1.91416L7.87878 6L3.79294 10.0858Z" fill="#2B2F36" stroke="black" stroke-width="0.171429" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 557 B |
3
frontend/resources/flowy_icons/16x/m_aa_bold.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.60938 4.75098C6.60938 4.40054 6.89346 4.11646 7.2439 4.11646H13.2719C15.9002 4.11646 18.0308 6.2471 18.0308 8.87538C18.0308 10.2227 17.4709 11.4392 16.5712 12.305C18.1929 13.1524 19.2999 14.8504 19.2999 16.8069C19.2999 19.6104 17.0272 21.8831 14.2237 21.8831H7.2439C6.89346 21.8831 6.60938 21.599 6.60938 21.2486V4.75098ZM8.39955 11.7307H13.2719C14.8488 11.7307 16.1272 10.4524 16.1272 8.87538C16.1272 7.29841 14.8488 5.90663 13.2719 5.90663H8.39955V11.7307ZM8.39955 13.6343V19.9796H14.2237C15.9759 19.9796 17.3963 18.5591 17.3963 16.8069C17.3963 15.0547 15.9759 13.6343 14.2237 13.6343H8.39955Z" fill="#2B2F36" stroke="#1F2329"/>
|
||||
</svg>
|
After Width: | Height: | Size: 746 B |
@ -0,0 +1,8 @@
|
||||
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.57208 7.6759C6.25586 7.6759 6.81017 7.12158 6.81017 6.4378C6.81017 5.75402 6.25586 5.19971 5.57208 5.19971C4.8883 5.19971 4.33398 5.75402 4.33398 6.4378C4.33398 7.12158 4.8883 7.6759 5.57208 7.6759Z" fill="#2B2F36"/>
|
||||
<path d="M10.1118 5.61241C9.65591 5.61241 9.28637 5.98195 9.28637 6.4378C9.28637 6.89366 9.65591 7.2632 10.1118 7.2632H20.8419C21.2978 7.2632 21.6673 6.89366 21.6673 6.4378C21.6673 5.98195 21.2978 5.61241 20.8419 5.61241H10.1118Z" fill="#2B2F36"/>
|
||||
<path d="M10.1118 12.2156C9.65591 12.2156 9.28637 12.5851 9.28637 13.041C9.28637 13.4968 9.65591 13.8664 10.1118 13.8664H20.8419C21.2978 13.8664 21.6673 13.4968 21.6673 13.041C21.6673 12.5851 21.2978 12.2156 20.8419 12.2156H10.1118Z" fill="#2B2F36"/>
|
||||
<path d="M9.28637 19.6442C9.28637 19.1883 9.65591 18.8188 10.1118 18.8188H20.8419C21.2978 18.8188 21.6673 19.1883 21.6673 19.6442C21.6673 20.1 21.2978 20.4695 20.8419 20.4695H10.1118C9.65591 20.4695 9.28637 20.1 9.28637 19.6442Z" fill="#2B2F36"/>
|
||||
<path d="M6.81017 13.041C6.81017 13.7248 6.25586 14.2791 5.57208 14.2791C4.8883 14.2791 4.33398 13.7248 4.33398 13.041C4.33398 12.3572 4.8883 11.8029 5.57208 11.8029C6.25586 11.8029 6.81017 12.3572 6.81017 13.041Z" fill="#2B2F36"/>
|
||||
<path d="M5.57208 20.8822C6.25586 20.8822 6.81017 20.3279 6.81017 19.6442C6.81017 18.9604 6.25586 18.4061 5.57208 18.4061C4.8883 18.4061 4.33398 18.9604 4.33398 19.6442C4.33398 20.3279 4.8883 20.8822 5.57208 20.8822Z" fill="#2B2F36"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
7
frontend/resources/flowy_icons/16x/m_aa_color.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<svg width="27" height="26" viewBox="0 0 27 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M22.0896 4.09781C21.7085 3.7892 21.1494 3.84796 20.8408 4.22907L12.7525 14.2173C12.134 14.981 11.7166 15.8874 11.5382 16.8538C11.5099 17.0068 11.6624 17.1303 11.8062 17.0708C12.7144 16.6954 13.5141 16.0986 14.1326 15.3348L22.2209 5.34664C22.5295 4.96554 22.4707 4.40642 22.0896 4.09781Z" fill="#1F2329"/>
|
||||
<path d="M17.6948 5.46158C16.2968 4.89135 14.765 4.58858 13.2029 4.58725L13.1813 4.59154L12.7035 4.60096L12.2499 4.62717C11.193 4.70864 10.1466 4.93208 9.13416 5.29326C7.88985 5.74259 6.76968 6.3818 5.8004 7.19571C4.82717 8.01371 4.06016 8.96254 3.51996 10.0158C2.95409 11.118 2.66797 12.2774 2.66797 13.4651C2.66797 14.6505 2.95549 15.8 3.5222 16.8817C4.06185 17.9092 4.83083 18.8309 5.80696 19.6197C6.77525 20.4002 7.89584 21.0118 9.14073 21.4382C10.4229 21.8771 11.7882 22.0999 13.1926 22.0999L13.6767 22.0913L14.1428 22.0655C14.6086 22.0311 15.0733 21.9709 15.5364 21.885L16.1777 21.7537L16.3517 21.7001C16.8081 21.5352 17.2035 21.1977 17.4647 20.7391C17.7924 20.1638 17.877 19.4548 17.6952 18.8051L17.6156 18.4951L17.5892 18.3017C17.5492 17.8487 17.6591 17.3886 17.9055 17.011C18.2227 16.5276 18.7306 16.2395 19.2713 16.237H20.8412L21.0219 16.2307C22.4557 16.1305 23.6057 14.8466 23.6922 13.2427L23.6974 13.0484L23.6927 12.8354L23.6483 12.4511L23.5886 12.0922C23.4058 11.139 23.0369 10.2186 22.4907 9.35794C22.2769 9.01993 22.0392 8.69503 21.7784 8.38427L20.7143 9.69897C20.8509 9.87658 20.9784 10.059 21.0964 10.246C21.5247 10.9212 21.8144 11.6341 21.9608 12.3716L22.0147 12.6892L22.0425 12.9344L22.0447 13.0106L22.0386 13.2053L22.0202 13.3558C21.9146 13.9988 21.4839 14.5114 20.9629 14.5813L20.8412 14.5895H19.2688L19.0506 14.597C18.0373 14.6669 17.1038 15.2205 16.5226 16.1071C15.9836 16.928 15.8028 17.9343 16.0126 18.9086L16.0732 19.1514L16.1058 19.267L16.1303 19.4035C16.1491 19.5869 16.113 19.773 16.0258 19.9261C15.9664 20.0296 15.8878 20.108 15.806 20.1446L15.7442 20.1639L15.1481 20.2824L14.6615 20.3577C14.1739 20.4223 13.6832 20.4549 13.1901 20.4549C11.9664 20.4549 10.7825 20.2622 9.67277 19.8815C8.60931 19.5179 7.65582 18.9993 6.84091 18.3419C6.04115 17.6946 5.41398 16.9472 4.97901 16.1222C4.53751 15.2794 4.31297 14.3875 4.31297 13.4725C4.31297 12.5519 4.53908 11.643 4.98375 10.7786C5.4228 9.923 6.05419 9.14556 6.85751 8.46987C7.67772 7.78139 8.62982 7.23877 9.68667 6.8556C10.6669 6.49993 11.6827 6.29892 12.7147 6.25423L13.1926 6.24465L13.6328 6.25384C14.6569 6.29666 15.6584 6.48842 16.5973 6.81615L17.6948 5.46158Z" fill="#1F2329"/>
|
||||
<path d="M8.69801 15.0514C8.69572 14.3785 8.15097 13.8314 7.47804 13.8314C6.80511 13.8314 6.25806 14.3785 6.25806 15.0514C6.25806 15.7243 6.80282 16.2714 7.47804 16.2714C8.15097 16.2714 8.69801 15.7243 8.69801 15.0514Z" fill="#1F2329"/>
|
||||
<path d="M9.80708 10.6151C9.80708 9.94221 9.26004 9.39516 8.5871 9.39516C7.91417 9.39516 7.36713 9.94221 7.36713 10.6151C7.36713 11.2881 7.91188 11.8351 8.5871 11.8351C9.26004 11.8351 9.80708 11.2881 9.80708 10.6151Z" fill="#1F2329"/>
|
||||
<path d="M14.4652 8.95154C14.4652 8.27861 13.9204 7.73156 13.2452 7.73156C12.5723 7.73156 12.0252 8.27861 12.0252 8.95154C12.0252 9.62447 12.57 10.1715 13.2452 10.1715C13.9181 10.1715 14.4652 9.62447 14.4652 8.95154Z" fill="#1F2329"/>
|
||||
</svg>
|
After Width: | Height: | Size: 3.2 KiB |
5
frontend/resources/flowy_icons/16x/m_aa_down_arrow.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="6" height="6" viewBox="0 0 6 6" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g opacity="0.8">
|
||||
<path d="M0.573223 1.92678C0.670854 1.82915 0.829146 1.82915 0.926777 1.92678L3 4L5.07322 1.92678C5.17085 1.82915 5.32915 1.82915 5.42678 1.92678C5.52441 2.02441 5.52441 2.1827 5.42678 2.28033L3.35355 4.35355C3.15829 4.54882 2.84171 4.54882 2.64645 4.35355L0.573223 2.28033C0.475592 2.1827 0.475592 2.02441 0.573223 1.92678Z" fill="#2B2F36" stroke="black" stroke-width="0.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 546 B |
4
frontend/resources/flowy_icons/16x/m_aa_h1.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="38" height="38" viewBox="0 0 38 38" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19.6009 8.5498H22.8784V28.4998H19.6009V19.8643H11.0509V28.4998H7.77344V8.5498H11.0509V16.7293H19.6009V8.5498Z" fill="#1F2329"/>
|
||||
<path d="M28.5762 15.1998H30.8562V28.4998H28.2342V17.9738L25.7262 18.6768L25.0802 16.4348L28.5762 15.1998Z" fill="#1F2329"/>
|
||||
</svg>
|
After Width: | Height: | Size: 366 B |
4
frontend/resources/flowy_icons/16x/m_aa_h2.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15.3506 7.2002H18.1106V24.0002H15.3506V16.7282H8.15062V24.0002H5.39062V7.2002H8.15062V14.0882H15.3506V7.2002Z" fill="#1F2329"/>
|
||||
<path d="M20.2207 24.0002V22.3522L24.0448 18.4162C24.8981 17.5202 25.3248 16.7735 25.3248 16.1762C25.3248 15.7389 25.1861 15.3869 24.9088 15.1202C24.6421 14.8535 24.2954 14.7202 23.8687 14.7202C23.0261 14.7202 22.4021 15.1575 21.9967 16.0322L20.1407 14.9442C20.4928 14.1762 20.9994 13.5895 21.6607 13.1842C22.3221 12.7789 23.0474 12.5762 23.8368 12.5762C24.8501 12.5762 25.7194 12.8962 26.4447 13.5362C27.1701 14.1655 27.5328 15.0189 27.5328 16.0962C27.5328 17.2589 26.9194 18.4535 25.6928 19.6802L23.5007 21.8722H27.7087V24.0002H20.2207Z" fill="#1F2329"/>
|
||||
</svg>
|
After Width: | Height: | Size: 798 B |
4
frontend/resources/flowy_icons/16x/m_aa_h3.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.3835 5.84985H14.626V19.4999H12.3835V13.5914H6.53352V19.4999H4.29102V5.84985H6.53352V11.4464H12.3835V5.84985Z" fill="#1F2329"/>
|
||||
<path d="M20.5785 14.0919C21.1938 14.2739 21.6922 14.5945 22.0735 15.0539C22.4635 15.5045 22.6585 16.0505 22.6585 16.6919C22.6585 17.6279 22.3422 18.3602 21.7095 18.8889C21.0855 19.4175 20.3185 19.6819 19.4085 19.6819C18.6978 19.6819 18.0608 19.5215 17.4975 19.2009C16.9428 18.8715 16.5398 18.3905 16.2885 17.7579L17.8225 16.8739C18.0478 17.5759 18.5765 17.9269 19.4085 17.9269C19.8678 17.9269 20.2232 17.8185 20.4745 17.6019C20.7345 17.3765 20.8645 17.0732 20.8645 16.6919C20.8645 16.3192 20.7345 16.0202 20.4745 15.7949C20.2232 15.5695 19.8678 15.4569 19.4085 15.4569H19.0185L18.3295 14.4169L20.1235 12.0769H16.5615V10.3999H22.2815V11.8819L20.5785 14.0919Z" fill="#1F2329"/>
|
||||
</svg>
|
After Width: | Height: | Size: 920 B |
6
frontend/resources/flowy_icons/16x/m_aa_indent.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.20422 4C3.71824 4 3.32422 4.39402 3.32422 4.88C3.32422 5.36598 3.71824 5.76 4.20422 5.76H20.0442C20.5302 5.76 20.9242 5.36598 20.9242 4.88C20.9242 4.39402 20.5302 4 20.0442 4H4.20422Z" fill="#2B2F36"/>
|
||||
<path d="M4.20422 18.08C3.71824 18.08 3.32422 18.474 3.32422 18.96C3.32422 19.446 3.71824 19.84 4.20422 19.84H20.0442C20.5302 19.84 20.9242 19.446 20.9242 18.96C20.9242 18.474 20.5302 18.08 20.0442 18.08H4.20422Z" fill="#2B2F36"/>
|
||||
<path d="M12.1242 11.92C12.1242 11.434 12.5182 11.04 13.0042 11.04H20.0442C20.5302 11.04 20.9242 11.434 20.9242 11.92C20.9242 12.406 20.5302 12.8 20.0442 12.8H13.0042C12.5182 12.8 12.1242 12.406 12.1242 11.92Z" fill="#2B2F36"/>
|
||||
<path d="M7.85323 12.4832C8.22878 12.2016 8.22878 11.6384 7.85323 11.3568L4.45064 8.80477C3.98647 8.45672 3.32422 8.7879 3.32422 9.36798V14.472C3.32422 15.0521 3.98647 15.3833 4.45064 15.0352L7.85323 12.4832Z" fill="#2B2F36"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1003 B |
3
frontend/resources/flowy_icons/16x/m_aa_italic.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="27" height="26" viewBox="0 0 27 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.3356 6.30774L12.7901 19.6924H16.7413C17.3005 19.6924 17.7538 20.1418 17.7538 20.6962C17.7538 21.2506 17.3005 21.7 16.7413 21.7H6.71172C6.15253 21.7 5.69922 21.2506 5.69922 20.6962C5.69922 20.1418 6.15253 19.6924 6.71172 19.6924H10.6629L14.2083 6.30774H10.2572C9.69798 6.30774 9.24467 5.8583 9.24467 5.30389C9.24467 4.74949 9.69799 4.30005 10.2572 4.30005H20.2867C20.8459 4.30005 21.2992 4.74949 21.2992 5.30389C21.2992 5.8583 20.8459 6.30774 20.2867 6.30774H16.3356Z" fill="#2B2F36"/>
|
||||
</svg>
|
After Width: | Height: | Size: 601 B |
3
frontend/resources/flowy_icons/16x/m_aa_link.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.0864 4.0627C15.7831 4.05051 14.5402 4.59074 13.622 5.54583L13.6219 5.54577L13.6139 5.55434L12.2098 7.05769C11.9035 7.38563 11.9211 7.89978 12.249 8.20607C12.5769 8.51236 13.0911 8.49481 13.3974 8.16687L14.7973 6.66804C15.4182 6.02427 16.2357 5.67981 17.0712 5.68763C17.9084 5.69547 18.7226 6.05675 19.3341 6.71534C19.9474 7.37588 20.3046 8.28125 20.3124 9.2391C20.3201 10.1946 19.9792 11.105 19.3788 11.7771L16.9348 14.4091L16.9347 14.4092C16.6 14.7698 16.2011 15.0445 15.7672 15.2188C15.3335 15.3931 14.8725 15.464 14.4144 15.4287C13.9562 15.3933 13.5075 15.2522 13.0985 15.0115C12.6893 14.7708 12.3281 14.4351 12.0423 14.0237C11.7863 13.6552 11.2801 13.5639 10.9115 13.8199C10.5429 14.0759 10.4517 14.5822 10.7077 14.9507C11.1231 15.5488 11.6564 16.0484 12.2745 16.4121C12.8928 16.7759 13.58 16.9941 14.2894 17.0489C14.9989 17.1036 15.7098 16.9931 16.3729 16.7267C17.0357 16.4604 17.6328 16.0457 18.1256 15.5148L18.1257 15.5147L20.5746 12.8774L20.5747 12.8775L20.5841 12.867C21.4679 11.8815 21.9482 10.5732 21.9373 9.22599C21.9264 7.87874 21.4251 6.57909 20.5249 5.60962C19.6229 4.63821 18.3895 4.0749 17.0864 4.0627ZM11.7106 8.95114C11.0011 8.89642 10.2902 9.00689 9.62707 9.27328C8.96426 9.53955 8.36723 9.95427 7.8744 10.4852L7.8743 10.4853L5.42538 13.1226L5.42529 13.1225L5.4159 13.133C4.53207 14.1185 4.05181 15.4268 4.06268 16.774C4.07355 18.1213 4.57489 19.4209 5.4751 20.3904C6.37712 21.3618 7.61048 21.9251 8.91363 21.9373C10.2169 21.9495 11.4598 21.4093 12.378 20.4542L12.3781 20.4543L12.3877 20.4439L13.7836 18.9406C14.089 18.6118 14.0699 18.0977 13.7411 17.7923C13.4123 17.487 12.8982 17.506 12.5928 17.8349L11.202 19.3327C10.5811 19.976 9.76395 20.3202 8.92883 20.3124C8.09157 20.3045 7.27745 19.9433 6.6659 19.2847C6.05255 18.6241 5.69536 17.7188 5.68763 16.7609C5.67992 15.8054 6.02078 14.895 6.62122 14.2229L9.0652 11.5909L9.0653 11.5908C9.40003 11.2302 9.79892 10.9555 10.2328 10.7812C10.6665 10.6069 11.1275 10.536 11.5856 10.5713C12.0438 10.6067 12.4925 10.7478 12.9015 10.9885C13.3107 11.2292 13.6719 11.5649 13.9577 11.9763C14.2137 12.3448 14.7199 12.4361 15.0885 12.1801C15.4571 11.9241 15.5483 11.4178 15.2923 11.0493C14.8769 10.4512 14.3436 9.95155 13.7255 9.58791C13.1072 9.22411 12.42 9.00585 11.7106 8.95114Z" fill="#333333"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
@ -0,0 +1,8 @@
|
||||
<svg width="27" height="26" viewBox="0 0 27 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.28674 4.88434C7.28674 4.54264 6.91808 4.3303 6.62567 4.50359L6.21977 4.74408L6.21664 4.74603C6.17398 4.77264 6.08284 4.82815 5.96719 4.89858C5.69069 5.06697 5.27405 5.3207 5.04464 5.4691C4.90885 5.55693 4.83398 5.70725 4.83398 5.86314C4.83398 6.25554 5.27364 6.49281 5.59983 6.28828C5.78315 6.17334 5.97523 6.05424 6.11808 5.96684V8.79613C6.11808 9.12116 6.3797 9.38465 6.70241 9.38465C7.02513 9.38465 7.28674 9.12116 7.28674 8.79613V4.88434Z" fill="#2B2F36"/>
|
||||
<path d="M6.56048 10.683C5.56511 10.683 4.90026 11.348 4.90026 12.1803C4.90026 12.293 4.99095 12.3844 5.10282 12.3844H5.82219C5.93754 12.3844 6.03106 12.2902 6.03106 12.174C6.03106 11.9132 6.2153 11.6948 6.52262 11.6948C6.67091 11.6948 6.78927 11.7427 6.86893 11.8168C6.94731 11.8898 6.99841 11.9972 6.99841 12.139C6.99841 12.3949 6.84609 12.6095 6.56306 12.9106L5.09759 14.5115C5.0052 14.6125 4.9539 14.7447 4.9539 14.882C4.9539 15.1839 5.19688 15.4286 5.49661 15.4286H7.92159C8.20248 15.4286 8.43019 15.1993 8.43019 14.9164C8.43019 14.6335 8.20248 14.4041 7.92159 14.4041H6.61413L7.36767 13.5515C7.85971 13.0212 8.16707 12.6353 8.16707 12.0596C8.16707 11.6575 8.00187 11.3085 7.71257 11.0626C7.42521 10.8183 7.02472 10.683 6.56048 10.683Z" fill="#2B2F36"/>
|
||||
<path d="M6.5731 16.7271C5.76056 16.7271 5.20644 17.1157 5.00147 17.6853C4.94032 17.8552 4.98677 18.0195 5.08791 18.1348C5.18624 18.2469 5.33561 18.3141 5.49397 18.3141C5.64866 18.3141 5.7779 18.2476 5.87977 18.1673C5.98074 18.0878 6.06497 17.9867 6.13223 17.8991C6.20964 17.7983 6.33955 17.7167 6.54471 17.7167C6.71461 17.7167 6.8429 17.7679 6.9262 17.8408C7.00809 17.9125 7.05521 18.0121 7.05521 18.1323L7.05521 18.1338C7.05639 18.2682 7.00616 18.3762 6.92168 18.4524C6.83578 18.5298 6.70432 18.5829 6.53209 18.5829H6.4185C6.14806 18.5829 5.92883 18.8037 5.92883 19.0761C5.92883 19.3485 6.14806 19.5693 6.4185 19.5693H6.55417C6.77678 19.5693 6.93274 19.6298 7.03067 19.7139C7.12652 19.7962 7.18021 19.9123 7.18142 20.0562C7.18263 20.2026 7.12869 20.3234 7.03178 20.4095C6.93332 20.497 6.77781 20.5595 6.56048 20.5595C6.30207 20.5595 6.13402 20.4666 6.04077 20.3449C5.97442 20.2583 5.89156 20.1596 5.79197 20.0821C5.69134 20.0037 5.56426 19.9398 5.41242 19.9398C5.25391 19.9398 5.10446 20.0068 5.00541 20.1185C4.90373 20.2332 4.85566 20.3967 4.91327 20.5671C5.11014 21.1498 5.66129 21.5586 6.5384 21.5586C7.0242 21.5586 7.47637 21.4221 7.81008 21.1706C8.1466 20.917 8.3618 20.5451 8.35636 20.0939C8.34874 19.5333 8.05255 19.181 7.71568 19.0099C7.9902 18.8222 8.2248 18.4866 8.21752 18.0148C8.20251 17.2321 7.47506 16.7271 6.5731 16.7271Z" fill="#2B2F36"/>
|
||||
<path d="M10.0517 6.66707C10.0517 6.21778 10.4133 5.85357 10.8594 5.85357H21.3596C21.8057 5.85357 22.1673 6.21778 22.1673 6.66707C22.1673 7.11635 21.8057 7.48057 21.3596 7.48057H10.8594C10.4133 7.48057 10.0517 7.11635 10.0517 6.66707Z" fill="#2B2F36"/>
|
||||
<path d="M10.0517 13.1751C10.0517 12.7258 10.4133 12.3616 10.8594 12.3616H21.3596C21.8057 12.3616 22.1673 12.7258 22.1673 13.1751C22.1673 13.6244 21.8057 13.9886 21.3596 13.9886H10.8594C10.4133 13.9886 10.0517 13.6244 10.0517 13.1751Z" fill="#2B2F36"/>
|
||||
<path d="M10.0517 19.6831C10.0517 19.2338 10.4133 18.8696 10.8594 18.8696H21.3596C21.8057 18.8696 22.1673 19.2338 22.1673 19.6831C22.1673 20.1323 21.8057 20.4966 21.3596 20.4966H10.8594C10.4133 20.4966 10.0517 20.1323 10.0517 19.6831Z" fill="#2B2F36"/>
|
||||
</svg>
|
After Width: | Height: | Size: 3.4 KiB |
6
frontend/resources/flowy_icons/16x/m_aa_outdent.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg width="27" height="26" viewBox="0 0 27 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.29513 4.33325C4.76862 4.33325 4.3418 4.76007 4.3418 5.28659C4.3418 5.8131 4.76862 6.23992 5.29513 6.23992H22.4551C22.9816 6.23992 23.4085 5.8131 23.4085 5.28659C23.4085 4.76007 22.9816 4.33325 22.4551 4.33325H5.29513Z" fill="#2B2F36"/>
|
||||
<path d="M5.29513 19.5866C4.76862 19.5866 4.3418 20.0134 4.3418 20.5399C4.3418 21.0664 4.76862 21.4933 5.29513 21.4933H22.4551C22.9816 21.4933 23.4085 21.0664 23.4085 20.5399C23.4085 20.0134 22.9816 19.5866 22.4551 19.5866H5.29513Z" fill="#2B2F36"/>
|
||||
<path d="M4.84961 12.9131C4.84961 12.3865 5.27643 11.9597 5.80294 11.9597H13.4296C13.9561 11.9597 14.3829 12.3865 14.3829 12.9131C14.3829 13.4396 13.9561 13.8664 13.4296 13.8664H5.80294C5.27643 13.8664 4.84961 13.4396 4.84961 12.9131Z" fill="#2B2F36"/>
|
||||
<path d="M18.5121 12.3029C18.1053 12.6079 18.1053 13.2181 18.5121 13.5231L22.1983 16.2878C22.7011 16.6649 23.4186 16.3061 23.4186 15.6777V10.1483C23.4186 9.51986 22.7011 9.16111 22.1983 9.5382L18.5121 12.3029Z" fill="#2B2F36"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
4
frontend/resources/flowy_icons/16x/m_aa_quote.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.2724 15.4109C20.8019 14.8327 21.125 14.0624 21.125 13.2166C21.125 11.4216 19.6699 9.96655 17.875 9.96655C16.0801 9.96655 14.625 11.4216 14.625 13.2166C14.625 14.9134 15.9254 16.3065 17.584 16.4537L16.2705 18.3651L17.6097 19.2854L20.2724 15.4109Z" fill="#333333"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.5224 12.1609C11.0519 11.5827 11.375 10.8124 11.375 9.96655C11.375 8.17163 9.91993 6.71655 8.125 6.71655C6.33007 6.71655 4.875 8.17163 4.875 9.96655C4.875 11.6634 6.17541 13.0565 7.834 13.2037L6.52046 15.1151L7.8597 16.0354L10.5224 12.1609Z" fill="#333333"/>
|
||||
</svg>
|
After Width: | Height: | Size: 731 B |
3
frontend/resources/flowy_icons/16x/m_aa_strike.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="27" height="26" viewBox="0 0 27 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.85996 8.64213C7.88564 7.52223 8.32036 6.46638 9.10406 5.62693C10.0886 4.56472 11.5893 4 13.4433 4C15.3336 4 16.7787 4.50555 17.7386 5.50034C18.0937 5.85032 18.5097 6.35646 18.8552 6.92534C19.2436 7.5649 18.683 8.27607 17.9348 8.27607C17.3989 8.27607 17.0165 7.88098 16.7646 7.40802C16.6788 7.24707 16.5749 7.09961 16.4434 6.94585C15.7947 6.11929 14.759 5.68547 13.4547 5.68547C11.3339 5.68547 9.78895 6.92465 9.74824 8.69953C9.73102 9.4505 10.0462 10.103 10.6266 10.5144H8.19899C7.96052 9.96233 7.84416 9.33068 7.85996 8.64213ZM15.7185 13.9937H6.58288C6.12291 13.9937 5.75005 13.657 5.75002 13.197C5.74999 12.7371 6.12284 12.3281 6.58277 12.3281H20.8027C21.2626 12.3281 21.6355 12.7159 21.6355 13.1758C21.6355 13.6358 21.2626 13.9937 20.8027 13.9937H18.6418C19.1202 14.7102 19.4888 15.7297 19.4663 16.7081C19.3947 19.8298 17.0192 21.7667 13.2265 21.7667C10.6894 21.7667 8.78898 20.91 7.8174 19.328C7.647 19.046 7.48021 18.6721 7.33264 18.261C7.10437 17.6252 7.616 16.9978 8.29154 16.9978C8.79843 16.9978 9.20505 17.366 9.38498 17.8399C9.53581 18.2371 9.73848 18.5485 10.0334 18.858C10.7439 19.6207 11.8901 20.0171 13.3572 20.0171C15.8675 20.0171 17.501 18.783 17.5463 16.8109C17.5681 15.8583 17.3206 15.3805 16.755 14.6608C16.3377 14.1297 15.7185 13.9937 15.7185 13.9937Z" fill="#1F2329"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
3
frontend/resources/flowy_icons/16x/m_aa_text.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.69922 3.06956C2.69922 2.6443 3.04691 2.29956 3.47581 2.29956H16.5226C16.9515 2.29956 17.2992 2.6443 17.2992 3.06956V4.96888C17.2992 5.39414 16.9515 5.73888 16.5226 5.73888C16.0937 5.73888 15.746 5.39414 15.746 4.96888V3.83956H10.7759V16.1596H13.1056C13.5345 16.1596 13.8822 16.5043 13.8822 16.9296C13.8822 17.3548 13.5345 17.6996 13.1056 17.6996H6.8928C6.4639 17.6996 6.1162 17.3548 6.1162 16.9296C6.1162 16.5043 6.4639 16.1596 6.8928 16.1596H9.22268V3.83956H4.25241V4.96888C4.25241 5.39414 3.90472 5.73888 3.47581 5.73888C3.04691 5.73888 2.69922 5.39414 2.69922 4.96888V3.06956Z" fill="#1F2329"/>
|
||||
</svg>
|
After Width: | Height: | Size: 713 B |
3
frontend/resources/flowy_icons/16x/m_aa_underline.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.07552 5.018C9.07552 4.52009 8.68179 4.11646 8.1908 4.11646C7.69982 4.11646 7.29751 4.52009 7.29751 5.018V10.2469C7.29751 14.06 9.62366 16.8582 13.2242 16.8582C16.8248 16.8582 19.1509 14.06 19.1509 10.2469V5.0181C19.1509 4.52016 18.7511 4.11658 18.2601 4.1165C17.7691 4.11642 17.3729 4.52004 17.3729 5.0179V10.2469C17.3729 12.9705 15.796 15.0552 13.2242 15.0552C10.6524 15.0552 9.07552 12.9705 9.07552 10.2469V5.018ZM5.22299 19.8634C4.73201 19.8634 4.33398 20.261 4.33398 20.7589C4.33398 21.2568 4.732 21.6665 5.22299 21.6665H21.2251C21.7161 21.6665 22.1141 21.2553 22.1141 20.7574C22.1141 20.2595 21.7161 19.8634 21.2251 19.8634H5.22299Z" fill="#1F2329"/>
|
||||
</svg>
|
After Width: | Height: | Size: 771 B |
3
frontend/resources/flowy_icons/16x/m_toolbar_aa.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.947 8.07235L16.5972 23.5028H14.5885L13.3601 20.0576L13.343 20.0095H13.292H6.40881H6.35779L6.34066 20.0576L5.11226 23.5028H3.10354L8.75375 8.07235H10.947ZM7.03988 18.1218L7.00551 18.2185H7.10805H12.5927H12.6955L12.6608 18.1217L9.90757 10.4453L9.83919 10.2547L9.7713 10.4455L7.03988 18.1218ZM22.969 23.7921C21.4597 23.7921 20.1748 23.2369 19.1102 22.1236C18.0454 20.9955 17.513 19.6266 17.513 18.0126C17.513 16.3986 18.0455 15.0373 19.1099 13.9241L19.1103 13.9237C20.175 12.7956 21.46 12.2331 22.969 12.2331C24.7518 12.2331 26.1051 12.9557 27.0381 14.4028L27.1713 14.6093V14.3636V12.5224H28.9276V23.5028H27.1713V21.6616V21.4159L27.0381 21.6224C26.1051 23.0695 24.7518 23.7921 22.969 23.7921ZM26.0361 20.9113L26.0366 20.9108C26.7933 20.1252 27.1713 19.1576 27.1713 18.0126C27.1713 16.8677 26.7933 15.9072 26.0363 15.1363C25.2793 14.3505 24.3353 13.9575 23.2094 13.9575C22.0977 13.9575 21.1611 14.3507 20.4043 15.1364C19.6473 15.9073 19.2694 16.8677 19.2694 18.0126C19.2694 19.1576 19.6474 20.1252 20.404 20.9108L20.4045 20.9113C21.1615 21.6821 22.0981 22.0677 23.2094 22.0677C24.335 22.0677 25.2789 21.6824 26.0361 20.9113Z" fill="#333333" stroke="#F3F3F8" stroke-width="0.144706"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
3
frontend/resources/flowy_icons/16x/m_toolbar_add.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15 15.1186H15.1V15.0186V12.0186C15.1 11.5215 15.5029 11.1186 16 11.1186C16.4971 11.1186 16.9 11.5215 16.9 12.0186V15.0186V15.1186H17H20C20.4971 15.1186 20.9 15.5215 20.9 16.0186C20.9 16.5156 20.4971 16.9186 20 16.9186H17H16.9V17.0186V20.0186C16.9 20.5156 16.4971 20.9186 16 20.9186C15.5029 20.9186 15.1 20.5156 15.1 20.0186V17.0186V16.9186H15H12C11.5029 16.9186 11.1 16.5156 11.1 16.0186C11.1 15.5215 11.5029 15.1186 12 15.1186H15ZM16 25.1C21.0258 25.1 25.1 21.0258 25.1 16C25.1 10.9742 21.0258 6.9 16 6.9C10.9742 6.9 6.9 10.9742 6.9 16C6.9 21.0258 10.9742 25.1 16 25.1ZM26.9 16C26.9 22.0199 22.0199 26.9 16 26.9C9.9801 26.9 5.1 22.0199 5.1 16C5.1 9.9801 9.9801 5.1 16 5.1C22.0199 5.1 26.9 9.9801 26.9 16Z" fill="#2B2F36" stroke="white" stroke-width="0.2" stroke-linecap="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 894 B |
3
frontend/resources/flowy_icons/16x/m_toolbar_bold.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.60938 7.75073C9.60938 7.4003 9.89346 7.11621 10.2439 7.11621H16.2719C18.9002 7.11621 21.0308 9.24686 21.0308 11.8751C21.0308 13.2224 20.4709 14.4389 19.5712 15.3047C21.1929 16.1521 22.2999 17.8501 22.2999 19.8067C22.2999 22.6102 20.0272 24.8829 17.2237 24.8829H10.2439C9.89346 24.8829 9.60938 24.5988 9.60938 24.2484V7.75073ZM11.3995 14.7305H16.2719C17.8488 14.7305 19.1272 13.4521 19.1272 11.8751C19.1272 10.2982 17.8488 8.90639 16.2719 8.90639H11.3995V14.7305ZM11.3995 16.6341V22.9793H17.2237C18.9758 22.9793 20.3963 21.5589 20.3963 19.8067C20.3963 18.0545 18.9758 16.6341 17.2237 16.6341H11.3995Z" fill="#2B2F36" stroke="#1F2329"/>
|
||||
</svg>
|
After Width: | Height: | Size: 750 B |
@ -0,0 +1,6 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.06835 8.37911C8.2694 8.17699 8.5421 8.06344 8.82643 8.06344H19.5474C20.0318 8.06344 20.4246 7.66864 20.4246 7.18162C20.4246 6.69461 20.0318 6.2998 19.5474 6.2998H8.82643C8.07682 6.2998 7.3579 6.59917 6.82784 7.13204C6.29778 7.6649 6 8.38763 6 9.14122V22.8584C6 23.612 6.29778 24.3347 6.82784 24.8676C7.3579 25.4004 8.07682 25.6998 8.82643 25.6998H22.4713C23.2209 25.6998 23.9398 25.4004 24.4699 24.8676C24.9999 24.3347 25.2977 23.612 25.2977 22.8584V16.8816C25.2977 16.3946 24.905 15.9998 24.4205 15.9998C23.9361 15.9998 23.5434 16.3946 23.5434 16.8816V22.8584C23.5434 23.1442 23.4304 23.4184 23.2294 23.6205C23.0283 23.8226 22.7556 23.9362 22.4713 23.9362H8.82643C8.5421 23.9362 8.2694 23.8226 8.06835 23.6205C7.86729 23.4184 7.75434 23.1442 7.75434 22.8584V9.14122C7.75434 8.85537 7.86729 8.58124 8.06835 8.37911Z" fill="#1F2329"/>
|
||||
<path d="M25.8957 10.4745C26.2507 10.1431 26.2713 9.58511 25.9416 9.22823C25.612 8.87135 25.057 8.85068 24.702 9.18207L15.8524 17.4431L13.6153 15.3548C13.2603 15.0234 12.7053 15.0441 12.3757 15.401C12.046 15.7578 12.0666 16.3158 12.4216 16.6472L15.2555 19.2926C15.5921 19.6068 16.1127 19.6068 16.4493 19.2926L25.8957 10.4745Z" fill="#1F2329"/>
|
||||
<path d="M8.06835 8.37911C8.2694 8.17699 8.5421 8.06344 8.82643 8.06344H19.5474C20.0318 8.06344 20.4246 7.66864 20.4246 7.18162C20.4246 6.69461 20.0318 6.2998 19.5474 6.2998H8.82643C8.07682 6.2998 7.3579 6.59917 6.82784 7.13204C6.29778 7.6649 6 8.38763 6 9.14122V22.8584C6 23.612 6.29778 24.3347 6.82784 24.8676C7.3579 25.4004 8.07682 25.6998 8.82643 25.6998H22.4713C23.2209 25.6998 23.9398 25.4004 24.4699 24.8676C24.9999 24.3347 25.2977 23.612 25.2977 22.8584V16.8816C25.2977 16.3946 24.905 15.9998 24.4205 15.9998C23.9361 15.9998 23.5434 16.3946 23.5434 16.8816V22.8584C23.5434 23.1442 23.4304 23.4184 23.2294 23.6205C23.0283 23.8226 22.7556 23.9362 22.4713 23.9362H8.82643C8.5421 23.9362 8.2694 23.8226 8.06835 23.6205C7.86729 23.4184 7.75434 23.1442 7.75434 22.8584V9.14122C7.75434 8.85537 7.86729 8.58124 8.06835 8.37911Z" stroke="#F3F3F8" stroke-width="0.194" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M25.8957 10.4745C26.2507 10.1431 26.2713 9.58511 25.9416 9.22823C25.612 8.87135 25.057 8.85068 24.702 9.18207L15.8524 17.4431L13.6153 15.3548C13.2603 15.0234 12.7053 15.0441 12.3757 15.401C12.046 15.7578 12.0666 16.3158 12.4216 16.6472L15.2555 19.2926C15.5921 19.6068 16.1127 19.6068 16.4493 19.2926L25.8957 10.4745Z" stroke="#F3F3F8" stroke-width="0.194" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
7
frontend/resources/flowy_icons/16x/m_toolbar_color.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M24.5896 7.09781C24.2085 6.7892 23.6494 6.84796 23.3408 7.22907L15.2525 17.2173C14.634 17.981 14.2166 18.8874 14.0382 19.8538C14.0099 20.0068 14.1624 20.1303 14.3062 20.0708C15.2144 19.6954 16.0141 19.0986 16.6326 18.3348L24.7209 8.34664C25.0295 7.96554 24.9707 7.40642 24.5896 7.09781Z" fill="#1F2329"/>
|
||||
<path d="M20.1948 8.46158C18.7968 7.89135 17.265 7.58858 15.7029 7.58725L15.6813 7.59154L15.2035 7.60096L14.7499 7.62717C13.693 7.70864 12.6466 7.93208 11.6342 8.29326C10.3898 8.74259 9.26968 9.3818 8.3004 10.1957C7.32717 11.0137 6.56016 11.9625 6.01996 13.0158C5.45409 14.118 5.16797 15.2774 5.16797 16.4651C5.16797 17.6505 5.45549 18.8 6.0222 19.8817C6.56185 20.9092 7.33083 21.8309 8.30696 22.6197C9.27525 23.4002 10.3958 24.0118 11.6407 24.4382C12.9229 24.8771 14.2882 25.0999 15.6926 25.0999L16.1767 25.0913L16.6428 25.0655C17.1086 25.0311 17.5733 24.9709 18.0364 24.885L18.6777 24.7537L18.8517 24.7001C19.3081 24.5352 19.7035 24.1977 19.9647 23.7391C20.2924 23.1638 20.377 22.4548 20.1952 21.8051L20.1156 21.4951L20.0892 21.3017C20.0492 20.8487 20.1591 20.3886 20.4055 20.011C20.7227 19.5276 21.2306 19.2395 21.7713 19.237H23.3412L23.5219 19.2307C24.9557 19.1305 26.1057 17.8466 26.1922 16.2427L26.1974 16.0484L26.1927 15.8354L26.1483 15.4511L26.0886 15.0922C25.9058 14.139 25.5369 13.2186 24.9907 12.3579C24.7769 12.0199 24.5392 11.695 24.2784 11.3843L23.2143 12.699C23.3509 12.8766 23.4784 13.059 23.5964 13.246C24.0247 13.9212 24.3144 14.6341 24.4608 15.3716L24.5147 15.6892L24.5425 15.9344L24.5447 16.0106L24.5386 16.2053L24.5202 16.3558C24.4146 16.9988 23.9839 17.5114 23.4629 17.5813L23.3412 17.5895H21.7688L21.5506 17.597C20.5373 17.6669 19.6038 18.2205 19.0226 19.1071C18.4836 19.928 18.3028 20.9343 18.5126 21.9086L18.5732 22.1514L18.6058 22.267L18.6303 22.4035C18.6491 22.5869 18.613 22.773 18.5258 22.9261C18.4664 23.0296 18.3878 23.108 18.306 23.1446L18.2442 23.1639L17.6481 23.2824L17.1615 23.3577C16.6739 23.4224 16.1832 23.4549 15.6901 23.4549C14.4664 23.4549 13.2825 23.2622 12.1728 22.8815C11.1093 22.5179 10.1558 21.9993 9.34091 21.3419C8.54115 20.6946 7.91398 19.9472 7.47901 19.1222C7.03751 18.2794 6.81297 17.3875 6.81297 16.4725C6.81297 15.5519 7.03908 14.643 7.48375 13.7786C7.9228 12.923 8.55419 12.1456 9.35751 11.4699C10.1777 10.7814 11.1298 10.2388 12.1867 9.8556C13.1669 9.49993 14.1827 9.29892 15.2147 9.25423L15.6926 9.24465L16.1328 9.25384C17.1569 9.29666 18.1584 9.48842 19.0973 9.81615L20.1948 8.46158Z" fill="#1F2329"/>
|
||||
<path d="M11.198 18.0514C11.1957 17.3785 10.651 16.8314 9.97804 16.8314C9.30511 16.8314 8.75806 17.3785 8.75806 18.0514C8.75806 18.7243 9.30282 19.2714 9.97804 19.2714C10.651 19.2714 11.198 18.7243 11.198 18.0514Z" fill="#1F2329"/>
|
||||
<path d="M12.3071 13.6151C12.3071 12.9422 11.76 12.3952 11.0871 12.3952C10.4142 12.3952 9.86713 12.9422 9.86713 13.6151C9.86713 14.2881 10.4119 14.8351 11.0871 14.8351C11.76 14.8351 12.3071 14.2881 12.3071 13.6151Z" fill="#1F2329"/>
|
||||
<path d="M16.9652 11.9515C16.9652 11.2786 16.4204 10.7316 15.7452 10.7316C15.0723 10.7316 14.5252 11.2786 14.5252 11.9515C14.5252 12.6245 15.07 13.1715 15.7452 13.1715C16.4181 13.1715 16.9652 12.6245 16.9652 11.9515Z" fill="#1F2329"/>
|
||||
</svg>
|
After Width: | Height: | Size: 3.2 KiB |
@ -0,0 +1,8 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.80078 10.4225C3.80078 8.47705 5.381 6.8999 7.33029 6.8999H24.0367C25.986 6.8999 27.5662 8.47705 27.5662 10.4225V16.0586C27.186 15.9816 26.7926 15.9412 26.3897 15.9412C26.2313 15.9412 26.0743 15.9474 25.9191 15.9597V10.4225C25.9191 9.38495 25.0763 8.5438 24.0367 8.5438H7.33029C6.29067 8.5438 5.44789 9.38495 5.44789 10.4225V21.4602C5.44789 22.4978 6.29067 23.3389 7.33029 23.3389H20.708C20.8667 23.9287 21.1151 24.4819 21.4378 24.9828H7.33029C5.381 24.9828 3.80078 23.4057 3.80078 21.4602V10.4225Z" fill="#333333"/>
|
||||
<path d="M21.9042 17.688C21.9945 17.688 22.0813 17.7025 22.1626 17.7293C21.7103 18.1956 21.3348 18.7366 21.0563 19.3319H9.46266C9.00782 19.3319 8.6391 18.9639 8.6391 18.51C8.6391 18.056 9.00782 17.688 9.46266 17.688H21.9042Z" fill="#333333"/>
|
||||
<path d="M8.6391 13.8865C8.6391 13.4325 9.00782 13.0645 9.46266 13.0645H10.7896C11.2444 13.0645 11.6131 13.4325 11.6131 13.8865C11.6131 14.3404 11.2444 14.7084 10.7896 14.7084H9.46266C9.00782 14.7084 8.6391 14.3404 8.6391 13.8865Z" fill="#333333"/>
|
||||
<path d="M13.1121 13.8865C13.1121 13.4325 13.4809 13.0645 13.9357 13.0645H15.334C15.7888 13.0645 16.1575 13.4325 16.1575 13.8865C16.1575 14.3404 15.7888 14.7084 15.334 14.7084H13.9357C13.4809 14.7084 13.1121 14.3404 13.1121 13.8865Z" fill="#333333"/>
|
||||
<path d="M17.4519 13.8865C17.4519 13.4325 17.8206 13.0645 18.2754 13.0645H21.9042C22.3591 13.0645 22.7278 13.4325 22.7278 13.8865C22.7278 14.3404 22.3591 14.7084 21.9042 14.7084H18.2754C17.8206 14.7084 17.4519 14.3404 17.4519 13.8865Z" fill="#333333"/>
|
||||
<path d="M27.2127 18.8766C27.2127 18.5523 26.9493 18.2895 26.6244 18.2895C26.2996 18.2894 26.0362 18.5523 26.0362 18.8766L26.0361 23.0954L24.2167 21.2796C23.987 21.0503 23.6146 21.0503 23.3848 21.2796C23.1551 21.5089 23.1551 21.8806 23.3848 22.1099L26.2084 24.9279C26.3187 25.038 26.4683 25.0999 26.6243 25.0999C26.7804 25.0999 26.93 25.038 27.0403 24.9279L29.8639 22.1099C30.0936 21.8806 30.0936 21.5089 29.8639 21.2796C29.6341 21.0503 29.2617 21.0503 29.032 21.2796L27.2126 23.0954L27.2127 18.8766Z" fill="#333333"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
3
frontend/resources/flowy_icons/16x/m_toolbar_italic.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18.8356 9.3075L15.2901 22.6921H19.2413C19.8005 22.6921 20.2538 23.1415 20.2538 23.696C20.2538 24.2504 19.8005 24.6998 19.2413 24.6998H9.21172C8.65253 24.6998 8.19922 24.2504 8.19922 23.696C8.19922 23.1415 8.65253 22.6921 9.21172 22.6921H13.1629L16.7083 9.3075H12.7572C12.198 9.3075 11.7447 8.85806 11.7447 8.30365C11.7447 7.74924 12.198 7.2998 12.7572 7.2998H22.7867C23.3459 7.2998 23.7992 7.74924 23.7992 8.30365C23.7992 8.85806 23.3459 9.3075 22.7867 9.3075H18.8356Z" fill="#2B2F36"/>
|
||||
</svg>
|
After Width: | Height: | Size: 600 B |
5
frontend/resources/flowy_icons/16x/m_toolbar_more.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="9.6" cy="16.0001" r="1.6" transform="rotate(-90 9.6 16.0001)" fill="#333333"/>
|
||||
<circle cx="16.1996" cy="16.0001" r="1.6" transform="rotate(-90 16.1996 16.0001)" fill="#333333"/>
|
||||
<circle cx="22.7992" cy="16.0001" r="1.6" transform="rotate(-90 22.7992 16.0001)" fill="#333333"/>
|
||||
</svg>
|
After Width: | Height: | Size: 392 B |
3
frontend/resources/flowy_icons/16x/m_toolbar_redo.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M22.6211 10.9646L22.7065 11.05H22.5858H13.5C9.38548 11.05 6.05 14.3855 6.05 18.5C6.05 22.6145 9.38548 25.95 13.5 25.95H17C17.5247 25.95 17.95 25.5247 17.95 25C17.95 24.4753 17.5247 24.05 17 24.05H13.5C10.4348 24.05 7.95 21.5652 7.95 18.5C7.95 15.4348 10.4348 12.95 13.5 12.95H22.5858H22.7065L22.6211 13.0354L19.3282 16.3282C18.9573 16.6992 18.9573 17.3008 19.3282 17.6718C19.6992 18.0428 20.3008 18.0428 20.6718 17.6718L25.6718 12.6718C26.0427 12.3008 26.0427 11.6992 25.6718 11.3282L20.6718 6.32825C20.3008 5.95725 19.6992 5.95725 19.3282 6.32825C18.9573 6.69925 18.9573 7.30075 19.3282 7.67175L22.6211 10.9646Z" fill="#2B2F36" stroke="#F3F3F8" stroke-width="0.1"/>
|
||||
</svg>
|
After Width: | Height: | Size: 779 B |
@ -0,0 +1,3 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.81245 13.9498H11.1323M14.2616 13.9498H15.6524M18.5781 13.9498H22.1875M9.81245 18.5623H22.1875M27 21.4938V10.5058C27 9.0113 25.7885 7.7998 24.294 7.7998H7.70596C6.2115 7.7998 5 9.0113 5 10.5058V21.4938C5 22.9883 6.2115 24.1998 7.70596 24.1998H24.294C25.7885 24.1998 27 22.9883 27 21.4938Z" stroke="#333333" stroke-width="1.64711" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 493 B |
@ -0,0 +1,3 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.0755 8.01775C12.0755 7.51984 11.6818 7.11621 11.1908 7.11621C10.6998 7.11621 10.2975 7.51985 10.2975 8.01775V13.2467C10.2975 17.0597 12.6237 19.858 16.2242 19.858C19.8248 19.858 22.1509 17.0597 22.1509 13.2467V8.01785C22.1509 7.51992 21.7511 7.11634 21.2601 7.11626C20.7691 7.11618 20.3729 7.51979 20.3729 8.01765V13.2467C20.3729 15.9703 18.796 18.0549 16.2242 18.0549C13.6524 18.0549 12.0755 15.9703 12.0755 13.2467V8.01775ZM8.22299 22.8631C7.73201 22.8631 7.33398 23.2608 7.33398 23.7587C7.33398 24.2566 7.732 24.6662 8.22299 24.6662H24.2251C24.7161 24.6662 25.1141 24.255 25.1141 23.7571C25.1141 23.2592 24.7161 22.8631 24.2251 22.8631H8.22299Z" fill="#1F2329"/>
|
||||
</svg>
|
After Width: | Height: | Size: 782 B |
3
frontend/resources/flowy_icons/16x/m_toolbar_undo.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.7071 6.29289C13.0976 6.68342 13.0976 7.31658 12.7071 7.70711L9.41421 11H18.5C22.6421 11 26 14.3579 26 18.5C26 22.6421 22.6421 26 18.5 26H15C14.4477 26 14 25.5523 14 25C14 24.4477 14.4477 24 15 24H18.5C21.5376 24 24 21.5376 24 18.5C24 15.4624 21.5376 13 18.5 13H9.41421L12.7071 16.2929C13.0976 16.6834 13.0976 17.3166 12.7071 17.7071C12.3166 18.0976 11.6834 18.0976 11.2929 17.7071L6.29289 12.7071C5.90237 12.3166 5.90237 11.6834 6.29289 11.2929L11.2929 6.29289C11.6834 5.90237 12.3166 5.90237 12.7071 6.29289Z" fill="#2B2F36" stroke="#F3F3F8" stroke-width="0.1"/>
|
||||
</svg>
|
After Width: | Height: | Size: 680 B |