feat: support table plugin (#3280)

This commit is contained in:
Mohammad Zolfaghari
2023-09-01 10:15:21 +03:30
committed by GitHub
parent abb6eff23d
commit df8642d446
19 changed files with 538 additions and 43 deletions

View File

@ -57,13 +57,18 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
];
final List<ToolbarItem> toolbarItems = [
smartEditItem,
paragraphItem,
...headingItems,
smartEditItem..isActive = onlyShowInSingleTextTypeSelectionAndExcludeTable,
paragraphItem..isActive = onlyShowInSingleTextTypeSelectionAndExcludeTable,
...(headingItems
..forEach(
(e) => e.isActive = onlyShowInSingleTextTypeSelectionAndExcludeTable,
)),
...markdownFormatItems,
quoteItem,
bulletedListItem,
numberedListItem,
quoteItem..isActive = onlyShowInSingleTextTypeSelectionAndExcludeTable,
bulletedListItem
..isActive = onlyShowInSingleTextTypeSelectionAndExcludeTable,
numberedListItem
..isActive = onlyShowInSingleTextTypeSelectionAndExcludeTable,
inlineMathEquationItem,
linkItem,
alignToolbarItem,
@ -241,6 +246,28 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
),
),
),
TableBlockKeys.type: TableBlockComponentBuilder(
menuBuilder: (node, editorState, position, dir, onBuild, onClose) =>
TableMenu(
node: node,
editorState: editorState,
position: position,
dir: dir,
onBuild: onBuild,
onClose: onClose,
),
),
TableCellBlockKeys.type: TableCellBlockComponentBuilder(
menuBuilder: (node, editorState, position, dir, onBuild, onClose) =>
TableMenu(
node: node,
editorState: editorState,
position: position,
dir: dir,
onBuild: onBuild,
onClose: onClose,
),
),
DatabaseBlockKeys.gridType: DatabaseViewBlockComponentBuilder(
configuration: configuration.copyWith(
padding: (_) => const EdgeInsets.symmetric(vertical: 10),
@ -338,7 +365,8 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
if (supportAlignBuilderType.contains(entry.key)) ...alignAction,
];
builder.showActions = (_) => true;
builder.showActions =
(node) => node.parent?.type != TableCellBlockKeys.type;
builder.actionBuilder = (context, state) {
final top = builder.configuration.padding(context.node).top;
final padding = context.node.type == HeadingBlockKeys.type

View File

@ -0,0 +1,29 @@
import 'package:appflowy_editor/appflowy_editor.dart';
bool notShowInTable(EditorState editorState) {
final selection = editorState.selection;
if (selection == null) {
return false;
}
final nodes = editorState.getNodesInSelection(selection);
return nodes.every((element) {
if (element.type == TableBlockKeys.type) {
return false;
}
var parent = element.parent;
while (parent != null) {
if (parent.type == TableBlockKeys.type) {
return false;
}
parent = parent.parent;
}
return true;
});
}
bool onlyShowInSingleTextTypeSelectionAndExcludeTable(
EditorState editorState,
) {
return onlyShowInSingleSelectionAndTextType(editorState) &&
notShowInTable(editorState);
}

View File

@ -195,7 +195,7 @@ class _CalloutBlockComponentWidgetState
child: child,
);
if (widget.actionBuilder != null) {
if (widget.showActions && widget.actionBuilder != null) {
child = BlockComponentActionWrapper(
node: widget.node,
actionBuilder: widget.actionBuilder!,

View File

@ -203,7 +203,7 @@ class _CodeBlockComponentWidgetState extends State<CodeBlockComponentWidget>
child: child,
);
if (widget.actionBuilder != null) {
if (widget.showActions && widget.actionBuilder != null) {
child = BlockComponentActionWrapper(
node: widget.node,
actionBuilder: widget.actionBuilder!,

View File

@ -88,7 +88,7 @@ class _DatabaseBlockComponentWidgetState
child: child,
);
if (widget.actionBuilder != null) {
if (widget.showActions && widget.actionBuilder != null) {
child = BlockComponentActionWrapper(
node: widget.node,
actionBuilder: widget.actionBuilder!,

View File

@ -144,7 +144,7 @@ class _MathEquationBlockComponentWidgetState
),
);
if (widget.actionBuilder != null) {
if (widget.showActions && widget.actionBuilder != null) {
child = BlockComponentActionWrapper(
node: node,
actionBuilder: widget.actionBuilder!,

View File

@ -1,26 +1,19 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/smart_edit_action.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/user/application/user_service.dart';
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:easy_localization/easy_localization.dart';
final ToolbarItem smartEditItem = ToolbarItem(
id: 'appflowy.editor.smart_edit',
group: 0,
isActive: (editorState) {
final selection = editorState.selection;
if (selection == null) {
return false;
}
final nodes = editorState.getNodesInSelection(selection);
return nodes.every((element) => element.delta != null);
},
isActive: onlyShowInSingleSelectionAndTextType,
builder: (context, editorState, _) => SmartEditActionList(
editorState: editorState,
),

View File

@ -1,5 +1,7 @@
export 'actions/block_action_list.dart';
export 'actions/option_action.dart';
export 'align_toolbar_item/align_toolbar_item.dart';
export 'base/toolbar_extension.dart';
export 'callout/callout_block_component.dart';
export 'code_block/code_block_component.dart';
export 'code_block/code_block_shortcut_event.dart';
@ -19,11 +21,12 @@ export 'image/image_menu.dart';
export 'image/image_selection_menu.dart';
export 'inline_math_equation/inline_math_equation.dart';
export 'inline_math_equation/inline_math_equation_toolbar_item.dart';
export 'align_toolbar_item/align_toolbar_item.dart';
export 'math_equation/math_equation_block_component.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';
export 'outline/outline_block_component.dart';
export 'table/table_menu.dart';
export 'table/table_option_action.dart';
export 'toggle/toggle_block_component.dart';
export 'toggle/toggle_block_shortcut_event.dart';

View File

@ -0,0 +1,111 @@
import 'package:flutter/material.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/table_option_action.dart';
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
import 'dart:math' as math;
const tableActions = <TableOptionAction>[
TableOptionAction.addAfter,
TableOptionAction.addBefore,
TableOptionAction.delete,
TableOptionAction.duplicate,
TableOptionAction.clear,
TableOptionAction.bgColor,
];
class TableMenu extends StatelessWidget {
const TableMenu({
super.key,
required this.node,
required this.editorState,
required this.position,
required this.dir,
this.onBuild,
this.onClose,
});
final Node node;
final EditorState editorState;
final int position;
final TableDirection dir;
final VoidCallback? onBuild;
final VoidCallback? onClose;
@override
Widget build(BuildContext context) {
final actions = tableActions.map((action) {
switch (action) {
case TableOptionAction.bgColor:
return TableColorOptionAction(
node: node,
editorState: editorState,
position: position,
dir: dir,
);
default:
return TableOptionActionWrapper(action);
}
}).toList();
return PopoverActionList<PopoverAction>(
direction: dir == TableDirection.col
? PopoverDirection.bottomWithCenterAligned
: PopoverDirection.rightWithTopAligned,
actions: actions,
onPopupBuilder: onBuild,
onClosed: onClose,
onSelected: (action, controller) {
if (action is TableOptionActionWrapper) {
_onSelectAction(action.inner);
controller.close();
}
},
buildChild: (controller) => _buildOptionButton(controller, context),
);
}
Widget _buildOptionButton(
PopoverController controller,
BuildContext context,
) {
return Card(
elevation: 1.0,
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () => controller.show(),
child: Transform.rotate(
angle: dir == TableDirection.col ? math.pi / 2 : 0,
child: const FlowySvg(
FlowySvgs.drag_element_s,
size: Size.square(18.0),
),
),
),
),
);
}
void _onSelectAction(TableOptionAction action) {
switch (action) {
case TableOptionAction.addAfter:
TableActions.add(node, position + 1, editorState, dir);
break;
case TableOptionAction.addBefore:
TableActions.add(node, position, editorState, dir);
break;
case TableOptionAction.delete:
TableActions.delete(node, position, editorState, dir);
break;
case TableOptionAction.clear:
TableActions.clear(node, position, editorState, dir);
break;
case TableOptionAction.duplicate:
TableActions.duplicate(node, position, editorState, dir);
break;
default:
}
}
}

View File

@ -0,0 +1,154 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/extensions/flowy_tint_extension.dart';
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.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/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/extension.dart';
import 'package:flutter/material.dart';
enum TableOptionAction {
addAfter,
addBefore,
delete,
duplicate,
clear,
/// row|cell background color
bgColor;
Widget icon(Color? color) {
switch (this) {
case TableOptionAction.addAfter:
return const FlowySvg(FlowySvgs.add_s);
case TableOptionAction.addBefore:
return const FlowySvg(FlowySvgs.add_s);
case TableOptionAction.delete:
return const FlowySvg(FlowySvgs.delete_s);
case TableOptionAction.duplicate:
return const FlowySvg(FlowySvgs.copy_s);
case TableOptionAction.clear:
return const FlowySvg(FlowySvgs.close_s);
case TableOptionAction.bgColor:
return const FlowySvg(
FlowySvgs.color_format_m,
size: Size.square(12),
).padding(all: 2.0);
}
}
String get description {
switch (this) {
case TableOptionAction.addAfter:
return LocaleKeys.document_plugins_table_addAfter.tr();
case TableOptionAction.addBefore:
return LocaleKeys.document_plugins_table_addBefore.tr();
case TableOptionAction.delete:
return LocaleKeys.document_plugins_table_delete.tr();
case TableOptionAction.duplicate:
return LocaleKeys.document_plugins_table_duplicate.tr();
case TableOptionAction.clear:
return LocaleKeys.document_plugins_table_clear.tr();
case TableOptionAction.bgColor:
return LocaleKeys.document_plugins_table_bgColor.tr();
}
}
}
class TableOptionActionWrapper extends ActionCell {
TableOptionActionWrapper(this.inner);
final TableOptionAction inner;
@override
Widget? leftIcon(Color iconColor) => inner.icon(iconColor);
@override
String get name => inner.description;
}
class TableColorOptionAction extends PopoverActionCell {
TableColorOptionAction({
required this.node,
required this.editorState,
required this.position,
required this.dir,
});
final Node node;
final EditorState editorState;
final int position;
final TableDirection dir;
@override
Widget? leftIcon(Color iconColor) =>
TableOptionAction.bgColor.icon(iconColor);
@override
String get name => TableOptionAction.bgColor.description;
@override
Widget Function(
BuildContext context,
PopoverController parentController,
PopoverController controller,
) get builder => (context, parentController, controller) {
int row = 0, col = position;
if (dir == TableDirection.row) {
col = 0;
row = position;
}
final cell = node.children.firstWhereOrNull(
(n) =>
n.attributes[TableCellBlockKeys.colPosition] == col &&
n.attributes[TableCellBlockKeys.rowPosition] == row,
);
final key = dir == TableDirection.col
? TableCellBlockKeys.colBackgroundColor
: TableCellBlockKeys.rowBackgroundColor;
final bgColor = cell?.attributes[key] as String?;
final selectedColor = bgColor?.toColor();
// get default background color from themeExtension
final defaultColor = AFThemeExtension.of(context).tableCellBGColor;
final colors = [
// reset to default background color
FlowyColorOption(
color: defaultColor,
name: LocaleKeys.document_plugins_optionAction_defaultColor.tr(),
),
...FlowyTint.values.map(
(e) => FlowyColorOption(
color: e.color(context),
name: e.tintName(AppFlowyEditorLocalizations.current),
),
),
];
return FlowyColorPicker(
colors: colors,
selected: selectedColor,
border: Border.all(
color: Theme.of(context).colorScheme.onBackground,
width: 1,
),
onTap: (color, index) async {
final backgroundColor = selectedColor != color ? color.toHex() : "";
TableActions.setBgColor(
node,
position,
editorState,
backgroundColor,
dir,
);
controller.close();
parentController.close();
},
);
};
}

View File

@ -195,7 +195,7 @@ class _ToggleListBlockComponentWidgetState
child: child,
);
if (widget.actionBuilder != null) {
if (widget.showActions && widget.actionBuilder != null) {
child = BlockComponentActionWrapper(
node: node,
actionBuilder: widget.actionBuilder!,