feat: move all the heading toolbar items into a popup menu (#5890)

* chore: udpate translations

* feat: move all the heading items into a popup menu

* chore: add arrow down icon after heading toolbar items

* fix: compile

* chore: adjust heading toolbar style
This commit is contained in:
Lucas.Xu 2024-08-07 12:42:52 +08:00 committed by GitHub
parent e279ad1cc7
commit 98b7882d43
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 216 additions and 8 deletions

View File

@ -130,8 +130,8 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
final List<ToolbarItem> toolbarItems = [ final List<ToolbarItem> toolbarItems = [
smartEditItem..isActive = onlyShowInSingleTextTypeSelectionAndExcludeTable, smartEditItem..isActive = onlyShowInSingleTextTypeSelectionAndExcludeTable,
paragraphItem..isActive = onlyShowInSingleTextTypeSelectionAndExcludeTable, paragraphItem..isActive = onlyShowInSingleTextTypeSelectionAndExcludeTable,
...headingItems headingsToolbarItem
..forEach((e) => e.isActive = onlyShowInSingleSelectionAndTextType), ..isActive = onlyShowInSingleTextTypeSelectionAndExcludeTable,
...markdownFormatItems..forEach((e) => e.isActive = showInAnyTextType), ...markdownFormatItems..forEach((e) => e.isActive = showInAnyTextType),
quoteItem..isActive = onlyShowInSingleTextTypeSelectionAndExcludeTable, quoteItem..isActive = onlyShowInSingleTextTypeSelectionAndExcludeTable,
bulletedListItem bulletedListItem

View File

@ -94,9 +94,9 @@ class _AlignmentButtonsState extends State<_AlignmentButtons> {
windowPadding: const EdgeInsets.all(0), windowPadding: const EdgeInsets.all(0),
margin: const EdgeInsets.symmetric(vertical: 2.0), margin: const EdgeInsets.symmetric(vertical: 2.0),
direction: PopoverDirection.bottomWithCenterAligned, direction: PopoverDirection.bottomWithCenterAligned,
offset: const Offset(0, 12), offset: const Offset(0, 10),
decorationColor: Theme.of(context).colorScheme.onTertiary, decorationColor: Theme.of(context).colorScheme.onTertiary,
borderRadius: const BorderRadius.all(Radius.circular(4)), borderRadius: BorderRadius.circular(6.0),
popupBuilder: (_) { popupBuilder: (_) {
keepEditorFocusNotifier.increase(); keepEditorFocusNotifier.increase();
return _AlignButtons(onAlignChanged: widget.onAlignChanged); return _AlignButtons(onAlignChanged: widget.onAlignChanged);

View File

@ -0,0 +1,207 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
final _headingData = [
(FlowySvgs.h1_s, LocaleKeys.editor_heading1.tr()),
(FlowySvgs.h2_s, LocaleKeys.editor_heading2.tr()),
(FlowySvgs.h3_s, LocaleKeys.editor_heading3.tr()),
];
final headingsToolbarItem = ToolbarItem(
id: 'editor.headings',
group: 1,
isActive: onlyShowInTextType,
builder: (context, editorState, highlightColor, _, __) {
final selection = editorState.selection!;
final node = editorState.getNodeAtPath(selection.start.path)!;
final delta = (node.delta ?? Delta()).toJson();
int level = node.attributes[HeadingBlockKeys.level] ?? 1;
final isHighlight =
node.type == HeadingBlockKeys.type && (level >= 1 && level <= 3);
// only supports the level 1 - 3 in the toolbar, ignore the other levels
level = level.clamp(1, 3);
final svg = _headingData[level - 1].$1;
final message = _headingData[level - 1].$2;
final child = FlowyTooltip(
message: message,
preferBelow: false,
child: Row(
children: [
FlowySvg(
svg,
size: const Size.square(18),
color: isHighlight ? highlightColor : Colors.white,
),
const HSpace(2.0),
const FlowySvg(
FlowySvgs.arrow_down_s,
size: Size.square(12),
color: Colors.grey,
),
],
),
);
return _HeadingPopup(
currentLevel: isHighlight ? level : -1,
highlightColor: highlightColor,
child: child,
onLevelChanged: (level) async {
await editorState.formatNode(
selection,
(node) => node.copyWith(
type: isHighlight ? ParagraphBlockKeys.type : HeadingBlockKeys.type,
attributes: {
HeadingBlockKeys.level: level,
blockComponentBackgroundColor:
node.attributes[blockComponentBackgroundColor],
blockComponentTextDirection:
node.attributes[blockComponentTextDirection],
blockComponentDelta: delta,
},
),
);
},
);
},
);
class _HeadingPopup extends StatelessWidget {
const _HeadingPopup({
required this.currentLevel,
required this.highlightColor,
required this.onLevelChanged,
required this.child,
});
final int currentLevel;
final Color highlightColor;
final Function(int level) onLevelChanged;
final Widget child;
@override
Widget build(BuildContext context) {
return AppFlowyPopover(
windowPadding: const EdgeInsets.all(0),
margin: const EdgeInsets.symmetric(vertical: 2.0),
direction: PopoverDirection.bottomWithCenterAligned,
offset: const Offset(0, 10),
decorationColor: Theme.of(context).colorScheme.onTertiary,
borderRadius: BorderRadius.circular(6.0),
popupBuilder: (_) {
keepEditorFocusNotifier.increase();
return _HeadingButtons(
currentLevel: currentLevel,
highlightColor: highlightColor,
onLevelChanged: onLevelChanged,
);
},
onClose: () {
keepEditorFocusNotifier.decrease();
},
child: FlowyButton(
useIntrinsicWidth: true,
hoverColor: Colors.grey.withOpacity(0.3),
text: child,
),
);
}
}
class _HeadingButtons extends StatelessWidget {
const _HeadingButtons({
required this.highlightColor,
required this.currentLevel,
required this.onLevelChanged,
});
final int currentLevel;
final Color highlightColor;
final Function(int level) onLevelChanged;
@override
Widget build(BuildContext context) {
return SizedBox(
height: 28,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const HSpace(4),
..._headingData.mapIndexed((index, data) {
final svg = data.$1;
final message = data.$2;
return [
_HeadingButton(
icon: svg,
tooltip: message,
onTap: () => onLevelChanged(index + 1),
isHighlight: index + 1 == currentLevel,
highlightColor: highlightColor,
),
index != _headingData.length - 1
? const _Divider()
: const SizedBox.shrink(),
];
}).flattened,
const HSpace(4),
],
),
);
}
}
class _HeadingButton extends StatelessWidget {
const _HeadingButton({
required this.icon,
required this.tooltip,
required this.onTap,
required this.highlightColor,
required this.isHighlight,
});
final Color highlightColor;
final FlowySvgData icon;
final String tooltip;
final VoidCallback onTap;
final bool isHighlight;
@override
Widget build(BuildContext context) {
return FlowyButton(
useIntrinsicWidth: true,
hoverColor: Colors.grey.withOpacity(0.3),
onTap: onTap,
text: FlowyTooltip(
message: tooltip,
preferBelow: true,
child: FlowySvg(
icon,
size: const Size.square(18),
color: isHighlight ? highlightColor : Colors.white,
),
),
);
}
}
class _Divider extends StatelessWidget {
const _Divider();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(4),
child: Container(
width: 1,
color: Colors.grey,
),
);
}
}

View File

@ -14,15 +14,17 @@ export 'database/inline_database_menu_item.dart';
export 'database/referenced_database_menu_item.dart'; export 'database/referenced_database_menu_item.dart';
export 'error/error_block_component_builder.dart'; export 'error/error_block_component_builder.dart';
export 'extensions/flowy_tint_extension.dart'; export 'extensions/flowy_tint_extension.dart';
export 'file/file_block.dart';
export 'find_and_replace/find_and_replace_menu.dart'; export 'find_and_replace/find_and_replace_menu.dart';
export 'font/customize_font_toolbar_item.dart'; export 'font/customize_font_toolbar_item.dart';
export 'header/cover_editor_bloc.dart'; export 'header/cover_editor_bloc.dart';
export 'header/custom_cover_picker.dart'; export 'header/custom_cover_picker.dart';
export 'header/document_header_node_widget.dart'; export 'header/document_header_node_widget.dart';
export 'heading/heading_toolbar_item.dart';
export 'image/custom_image_block_component/image_menu.dart'; export 'image/custom_image_block_component/image_menu.dart';
export 'image/multi_image_block_component/multi_image_menu.dart';
export 'image/image_selection_menu.dart'; export 'image/image_selection_menu.dart';
export 'image/mobile_image_toolbar_item.dart'; export 'image/mobile_image_toolbar_item.dart';
export 'image/multi_image_block_component/multi_image_menu.dart';
export 'inline_math_equation/inline_math_equation.dart'; export 'inline_math_equation/inline_math_equation.dart';
export 'inline_math_equation/inline_math_equation_toolbar_item.dart'; export 'inline_math_equation/inline_math_equation_toolbar_item.dart';
export 'link_preview/custom_link_preview.dart'; export 'link_preview/custom_link_preview.dart';
@ -53,4 +55,3 @@ export 'table/table_option_action.dart';
export 'todo_list/todo_list_icon.dart'; export 'todo_list/todo_list_icon.dart';
export 'toggle/toggle_block_component.dart'; export 'toggle/toggle_block_component.dart';
export 'toggle/toggle_block_shortcut_event.dart'; export 'toggle/toggle_block_shortcut_event.dart';
export 'file/file_block.dart';

View File

@ -1479,7 +1479,7 @@
"autoGeneratorRewrite": "Rewrite", "autoGeneratorRewrite": "Rewrite",
"smartEdit": "AI Assistants", "smartEdit": "AI Assistants",
"aI": "AI", "aI": "AI",
"smartEditFixSpelling": "Fix spelling", "smartEditFixSpelling": "Fix spelling & grammar",
"warning": "⚠️ AI responses can be inaccurate or misleading.", "warning": "⚠️ AI responses can be inaccurate or misleading.",
"smartEditSummarize": "Summarize", "smartEditSummarize": "Summarize",
"smartEditImproveWriting": "Improve writing", "smartEditImproveWriting": "Improve writing",
@ -2376,4 +2376,4 @@
"commentAddedSuccessfully": "Comment added successfully.", "commentAddedSuccessfully": "Comment added successfully.",
"commentAddedSuccessTip": "You've just added or replied to a comment. Would you like to jump to the top to see the latest comments?" "commentAddedSuccessTip": "You've just added or replied to a comment. Would you like to jump to the top to see the latest comments?"
} }
} }