Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import 'package:appflowy/workspace/presentation/home/af_focus_manager.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide QuoteBlockKeys;
import 'package:collection/collection.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra/theme_extension_v2.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
Expand Down Expand Up @@ -350,6 +351,8 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage>
final isViewDeleted = context.read<DocumentBloc>().state.isDeleted;
final isLocked =
context.read<ViewLockStatusBloc?>()?.state.isLocked ?? false;

final themeV2 = AFThemeExtensionV2.of(context);
final editor = Directionality(
textDirection: textDirection,
child: AppFlowyEditor(
Expand Down Expand Up @@ -428,20 +431,25 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage>
),
);
}

return Center(
child: FloatingToolbar(
floatingToolbarHeight: 40,
padding: EdgeInsets.symmetric(horizontal: 6),
style: FloatingToolbarStyle(
backgroundColor: Theme.of(context).cardColor,
toolbarActiveColor: Color(0xffe0f8fd),
toolbarElevation: 10,
),
items: toolbarItems,
decoration: ShapeDecoration(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Theme.of(context).cardColor,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)),
boxShadow: [
BoxShadow(
offset: Offset(0, 4),
blurRadius: 24,
color: themeV2.shadow_medium,
),
],
),
toolbarBuilder: (context, child, onDismiss, isMetricsChanged) =>
DesktopFloatingToolbar(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_option_cubit.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/toolbar_item/text_suggestions_toolbar_item.dart';
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:appflowy_editor/appflowy_editor.dart'
hide QuoteBlockKeys, quoteNode;
Expand Down Expand Up @@ -149,214 +148,134 @@ class TurnIntoOptionMenu extends StatelessWidget {

@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: _buildTurnIntoOptions(context, node),
);
}

List<Widget> _buildTurnIntoOptions(BuildContext context, Node node) {
final children = <Widget>[];

if (hasNonSupportedTypes) {
return children
..add(
_TurnInfoButton(
type: SubPageBlockKeys.type,
node: node,
),
);
return buildItem(
pateItem,
textSuggestionItem,
context.read<BlockActionOptionCubit>().editorState,
);
}

for (final type in EditorOptionActionType.turnInto.supportTypes) {
if (type == ToggleListBlockKeys.type) {
// toggle list block and toggle heading block are the same type,
// but they have different attributes.

// toggle list block
children.add(
_TurnInfoButton(
type: type,
node: node,
),
);
return _buildTurnIntoOptions(context, node);
}

// toggle heading block
for (final i in [1, 2, 3]) {
children.add(
_TurnInfoButton(
type: type,
node: node,
level: i,
),
);
Widget _buildTurnIntoOptions(BuildContext context, Node node) {
final editorState = context.read<BlockActionOptionCubit>().editorState;
SuggestionItem currentSuggestionItem = textSuggestionItem;
final List<SuggestionItem> suggestionItems = suggestions.sublist(0, 4);
final List<SuggestionItem> turnIntoItems =
suggestions.sublist(4, suggestions.length);
final textColor = Color(0xff99A1A8);

void refreshSuggestions() {
final selection = editorState.selection;
if (selection == null || !selection.isSingle) return;
final node = editorState.getNodeAtPath(selection.start.path);
if (node == null || node.delta == null) return;
final nodeType = node.type;
SuggestionType? suggestionType;
if (nodeType == HeadingBlockKeys.type) {
final level = node.attributes[HeadingBlockKeys.level] ?? 1;
if (level == 1) {
suggestionType = SuggestionType.h1;
} else if (level == 2) {
suggestionType = SuggestionType.h2;
} else if (level == 3) {
suggestionType = SuggestionType.h3;
}
} else if (nodeType == ToggleListBlockKeys.type) {
final level = node.attributes[ToggleListBlockKeys.level];
if (level == null) {
suggestionType = SuggestionType.toggle;
} else if (level == 1) {
suggestionType = SuggestionType.toggleH1;
} else if (level == 2) {
suggestionType = SuggestionType.toggleH2;
} else if (level == 3) {
suggestionType = SuggestionType.toggleH3;
}
} else if (type != HeadingBlockKeys.type) {
children.add(
_TurnInfoButton(
type: type,
node: node,
),
);
} else {
for (final i in [1, 2, 3]) {
children.add(
_TurnInfoButton(
type: type,
node: node,
level: i,
),
);
suggestionType = nodeType2SuggestionType[nodeType];
}
if (suggestionType == null) return;
suggestionItems.clear();
turnIntoItems.clear();
for (final item in suggestions) {
if (item.type.group == suggestionType.group &&
item.type != suggestionType) {
suggestionItems.add(item);
} else {
turnIntoItems.add(item);
}
}
currentSuggestionItem =
suggestions.where((item) => item.type == suggestionType).first;
}

return children;
}
}

class _TurnInfoButton extends StatelessWidget {
const _TurnInfoButton({
required this.type,
required this.node,
this.level,
});

final String type;
final Node node;
final int? level;

@override
Widget build(BuildContext context) {
final name = _buildLocalization(type, level: level);
final leftIcon = _buildLeftIcon(type, level: level);
final rightIcon = _buildRightIcon(type, node, level: level);
refreshSuggestions();

return HoverButton(
name: name,
leftIcon: FlowySvg(leftIcon),
rightIcon: rightIcon,
itemHeight: ActionListSizes.itemHeight,
onTap: () => BlockActionOptionCubit.turnIntoBlock(
type,
node,
context.read<BlockActionOptionCubit>().editorState,
level: level,
currentViewId: getIt<MenuSharedState>().latestOpenView?.id,
),
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildSubTitle(
LocaleKeys.document_toolbar_suggestions.tr(),
textColor,
),
...List.generate(suggestionItems.length, (index) {
return buildItem(
suggestionItems[index],
currentSuggestionItem,
editorState,
);
}),
buildSubTitle(LocaleKeys.document_toolbar_turnInto.tr(), textColor),
...List.generate(turnIntoItems.length, (index) {
return buildItem(
turnIntoItems[index],
currentSuggestionItem,
editorState,
);
}),
],
);
}

Widget? _buildRightIcon(String type, Node node, {int? level}) {
if (type != node.type) {
return null;
}

if (node.type == HeadingBlockKeys.type) {
final nodeLevel = node.attributes[HeadingBlockKeys.level] ?? 1;
if (level != nodeLevel) {
return null;
}
}

if (node.type == ToggleListBlockKeys.type) {
final nodeLevel = node.attributes[ToggleListBlockKeys.level];
if (level != nodeLevel) {
return null;
}
}

return const FlowySvg(
FlowySvgs.workspace_selected_s,
blendMode: null,
Widget buildSubTitle(String text, Color color) {
return Container(
height: 32,
margin: EdgeInsets.symmetric(horizontal: 8),
child: Align(
alignment: Alignment.centerLeft,
child: FlowyText.semibold(
text,
color: color,
figmaLineHeight: 16,
),
),
);
}

FlowySvgData _buildLeftIcon(String type, {int? level}) {
if (type == ParagraphBlockKeys.type) {
return FlowySvgs.type_text_m;
} else if (type == HeadingBlockKeys.type) {
switch (level) {
case 1:
return FlowySvgs.type_h1_m;
case 2:
return FlowySvgs.type_h2_m;
case 3:
return FlowySvgs.type_h3_m;
default:
return FlowySvgs.type_text_m;
}
} else if (type == QuoteBlockKeys.type) {
return FlowySvgs.type_quote_m;
} else if (type == BulletedListBlockKeys.type) {
return FlowySvgs.type_bulleted_list_m;
} else if (type == NumberedListBlockKeys.type) {
return FlowySvgs.type_numbered_list_m;
} else if (type == TodoListBlockKeys.type) {
return FlowySvgs.type_todo_m;
} else if (type == CalloutBlockKeys.type) {
return FlowySvgs.type_callout_m;
} else if (type == SubPageBlockKeys.type) {
return FlowySvgs.icon_document_s;
} else if (type == ToggleListBlockKeys.type) {
switch (level) {
case 1:
return FlowySvgs.type_toggle_h1_m;
case 2:
return FlowySvgs.type_toggle_h2_m;
case 3:
return FlowySvgs.type_toggle_h3_m;
default:
return FlowySvgs.type_toggle_list_m;
}
}

throw UnimplementedError('Unsupported block type: $type');
}

String _buildLocalization(
String type, {
int? level,
}) {
switch (type) {
case ParagraphBlockKeys.type:
return LocaleKeys.document_slashMenu_name_text.tr();
case HeadingBlockKeys.type:
switch (level) {
case 1:
return LocaleKeys.document_slashMenu_name_heading1.tr();
case 2:
return LocaleKeys.document_slashMenu_name_heading2.tr();
case 3:
return LocaleKeys.document_slashMenu_name_heading3.tr();
default:
return LocaleKeys.document_slashMenu_name_text.tr();
}
case QuoteBlockKeys.type:
return LocaleKeys.document_slashMenu_name_quote.tr();
case BulletedListBlockKeys.type:
return LocaleKeys.editor_bulletedListShortForm.tr();
case NumberedListBlockKeys.type:
return LocaleKeys.editor_numberedListShortForm.tr();
case TodoListBlockKeys.type:
return LocaleKeys.editor_checkbox.tr();
case CalloutBlockKeys.type:
return LocaleKeys.document_slashMenu_name_callout.tr();
case SubPageBlockKeys.type:
return LocaleKeys.editor_page.tr();
case ToggleListBlockKeys.type:
switch (level) {
case 1:
return LocaleKeys.editor_toggleHeading1ShortForm.tr();
case 2:
return LocaleKeys.editor_toggleHeading2ShortForm.tr();
case 3:
return LocaleKeys.editor_toggleHeading3ShortForm.tr();
default:
return LocaleKeys.editor_toggleListShortForm.tr();
}
}

throw UnimplementedError('Unsupported block type: $type');
Widget buildItem(
SuggestionItem item,
SuggestionItem currentSuggestionItem,
EditorState state,
) {
final isSelected = item.type == currentSuggestionItem.type;
return SizedBox(
height: 36,
child: FlowyButton(
leftIconSize: const Size.square(20),
leftIcon: FlowySvg(item.svg),
iconPadding: 12,
text: FlowyText(
item.title,
fontWeight: FontWeight.w400,
figmaLineHeight: 20,
),
rightIcon: isSelected ? FlowySvg(FlowySvgs.toolbar_check_m) : null,
onTap: () => item.onTap.call(state, false),
),
);
}
}
Loading
Loading