mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: support table plugin (#3280)
This commit is contained in:
parent
abb6eff23d
commit
df8642d446
@ -193,6 +193,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
||||
if (lastNode == null || lastNode.delta == null) {
|
||||
final transaction = editorState.transaction;
|
||||
transaction.insertNode([document.root.children.length], paragraphNode());
|
||||
transaction.afterSelection = transaction.beforeSelection;
|
||||
await editorState.apply(transaction);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/plugins/document/application/doc_service.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-document2/protobuf.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart'
|
||||
@ -12,10 +14,9 @@ import 'package:appflowy_editor/appflowy_editor.dart'
|
||||
DeleteOperation,
|
||||
PathExtensions,
|
||||
Node,
|
||||
Path,
|
||||
composeAttributes;
|
||||
import 'package:collection/collection.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:nanoid/nanoid.dart';
|
||||
|
||||
/// Uses to adjust the data structure between the editor and the backend.
|
||||
@ -46,7 +47,7 @@ class TransactionAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
extension on Operation {
|
||||
extension BlockAction on Operation {
|
||||
List<BlockActionPB> toBlockAction(EditorState editorState) {
|
||||
final op = this;
|
||||
if (op is InsertOperation) {
|
||||
@ -61,19 +62,22 @@ extension on Operation {
|
||||
}
|
||||
|
||||
extension on InsertOperation {
|
||||
List<BlockActionPB> toBlockAction(EditorState editorState) {
|
||||
List<BlockActionPB> toBlockAction(
|
||||
EditorState editorState, {
|
||||
Node? previousNode,
|
||||
}) {
|
||||
Path currentPath = path;
|
||||
final List<BlockActionPB> actions = [];
|
||||
// store the previous node for continuous insertion.
|
||||
// because the backend needs to know the previous node's id.
|
||||
Node? previousNode;
|
||||
for (final node in nodes) {
|
||||
final parentId =
|
||||
node.parent?.id ?? editorState.getNodeAtPath(path.parent)?.id ?? '';
|
||||
final parentId = node.parent?.id ??
|
||||
editorState.getNodeAtPath(currentPath.parent)?.id ??
|
||||
'';
|
||||
var prevId = previousNode?.id ??
|
||||
editorState.getNodeAtPath(path.previous)?.id ??
|
||||
editorState.getNodeAtPath(currentPath.previous)?.id ??
|
||||
'';
|
||||
assert(parentId.isNotEmpty);
|
||||
if (path.equals(path.previous) && !path.equals([0])) {
|
||||
if (currentPath.equals(currentPath.previous) &&
|
||||
!currentPath.equals([0])) {
|
||||
prevId = '';
|
||||
} else {
|
||||
assert(prevId.isNotEmpty && prevId != node.id);
|
||||
@ -89,12 +93,17 @@ extension on InsertOperation {
|
||||
..payload = payload,
|
||||
);
|
||||
if (node.children.isNotEmpty) {
|
||||
final childrenActions = node.children
|
||||
.map((e) => InsertOperation(e.path, [e]).toBlockAction(editorState))
|
||||
.expand((element) => element);
|
||||
actions.addAll(childrenActions);
|
||||
Node? prevChild;
|
||||
for (final child in node.children) {
|
||||
actions.addAll(
|
||||
InsertOperation(currentPath + child.path, [child])
|
||||
.toBlockAction(editorState, previousNode: prevChild),
|
||||
);
|
||||
prevChild = child;
|
||||
}
|
||||
}
|
||||
previousNode = node;
|
||||
currentPath = currentPath.next;
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
@ -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!,
|
||||
|
@ -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!,
|
||||
|
@ -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!,
|
||||
|
@ -144,7 +144,7 @@ class _MathEquationBlockComponentWidgetState
|
||||
),
|
||||
);
|
||||
|
||||
if (widget.actionBuilder != null) {
|
||||
if (widget.showActions && widget.actionBuilder != null) {
|
||||
child = BlockComponentActionWrapper(
|
||||
node: node,
|
||||
actionBuilder: widget.actionBuilder!,
|
||||
|
@ -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,
|
||||
),
|
||||
|
@ -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';
|
||||
|
@ -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:
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
@ -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!,
|
||||
|
@ -362,6 +362,7 @@ class AppearanceSettingsState with _$AppearanceSettingsState {
|
||||
fontColor: theme.shader3,
|
||||
),
|
||||
calloutBGColor: theme.hoverBG3,
|
||||
tableCellBGColor: theme.surface,
|
||||
caption: _getFontStyle(
|
||||
fontFamily: fontFamily,
|
||||
fontSize: FontSizes.s11,
|
||||
|
@ -23,6 +23,7 @@ class AFThemeExtension extends ThemeExtension<AFThemeExtension> {
|
||||
final Color progressBarBGColor;
|
||||
final Color toggleButtonBGColor;
|
||||
final Color calloutBGColor;
|
||||
final Color tableCellBGColor;
|
||||
|
||||
final TextStyle code;
|
||||
final TextStyle callout;
|
||||
@ -46,6 +47,7 @@ class AFThemeExtension extends ThemeExtension<AFThemeExtension> {
|
||||
required this.toggleOffFill,
|
||||
required this.textColor,
|
||||
required this.calloutBGColor,
|
||||
required this.tableCellBGColor,
|
||||
required this.code,
|
||||
required this.callout,
|
||||
required this.caption,
|
||||
@ -72,6 +74,7 @@ class AFThemeExtension extends ThemeExtension<AFThemeExtension> {
|
||||
Color? tint9,
|
||||
Color? textColor,
|
||||
Color? calloutBGColor,
|
||||
Color? tableCellBGColor,
|
||||
Color? greyHover,
|
||||
Color? greySelect,
|
||||
Color? lightGreyHover,
|
||||
@ -96,6 +99,7 @@ class AFThemeExtension extends ThemeExtension<AFThemeExtension> {
|
||||
tint9: tint9 ?? this.tint9,
|
||||
textColor: textColor ?? this.textColor,
|
||||
calloutBGColor: calloutBGColor ?? this.calloutBGColor,
|
||||
tableCellBGColor: tableCellBGColor ?? this.tableCellBGColor,
|
||||
greyHover: greyHover ?? this.greyHover,
|
||||
greySelect: greySelect ?? this.greySelect,
|
||||
lightGreyHover: lightGreyHover ?? this.lightGreyHover,
|
||||
@ -128,6 +132,8 @@ class AFThemeExtension extends ThemeExtension<AFThemeExtension> {
|
||||
tint9: Color.lerp(tint9, other.tint9, t)!,
|
||||
textColor: Color.lerp(textColor, other.textColor, t)!,
|
||||
calloutBGColor: Color.lerp(calloutBGColor, other.calloutBGColor, t)!,
|
||||
tableCellBGColor:
|
||||
Color.lerp(tableCellBGColor, other.tableCellBGColor, t)!,
|
||||
greyHover: Color.lerp(greyHover, other.greyHover, t)!,
|
||||
greySelect: Color.lerp(greySelect, other.greySelect, t)!,
|
||||
lightGreyHover: Color.lerp(lightGreyHover, other.lightGreyHover, t)!,
|
||||
|
@ -54,11 +54,11 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: a912c1c
|
||||
resolved-ref: a912c1c96532ec561ea68d5138aee415fdecede2
|
||||
ref: "0e55cce"
|
||||
resolved-ref: "0e55cce14f2ead916a8942a123d08b818934e2fd"
|
||||
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
||||
source: git
|
||||
version: "1.2.4"
|
||||
version: "1.3.0"
|
||||
appflowy_popover:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -48,7 +48,7 @@ dependencies:
|
||||
appflowy_editor:
|
||||
git:
|
||||
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
||||
ref: a912c1c
|
||||
ref: 0e55cce
|
||||
appflowy_popover:
|
||||
path: packages/appflowy_popover
|
||||
|
||||
|
@ -0,0 +1,152 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-document2/protobuf.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:appflowy/plugins/document/application/editor_transaction_adapter.dart';
|
||||
|
||||
void main() {
|
||||
group('TransactionAdapter', () {
|
||||
test('toBlockAction insert node with children operation', () {
|
||||
final editorState = EditorState.blank();
|
||||
|
||||
final transaction = editorState.transaction;
|
||||
transaction.insertNode(
|
||||
[0],
|
||||
paragraphNode(
|
||||
children: [
|
||||
paragraphNode(text: '1', children: [paragraphNode(text: '1.1')]),
|
||||
paragraphNode(text: '2'),
|
||||
paragraphNode(text: '3', children: [paragraphNode(text: '3.1')]),
|
||||
paragraphNode(text: '4'),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
expect(transaction.operations.length, 1);
|
||||
expect(transaction.operations[0] is InsertOperation, true);
|
||||
|
||||
final actions = transaction.operations[0].toBlockAction(editorState);
|
||||
|
||||
expect(actions.length, 7);
|
||||
for (final action in actions) {
|
||||
expect(action.action, BlockActionTypePB.Insert);
|
||||
}
|
||||
|
||||
expect(
|
||||
actions[0].payload.parentId,
|
||||
editorState.document.root.id,
|
||||
reason: '0 - parent id',
|
||||
);
|
||||
expect(
|
||||
actions[0].payload.prevId,
|
||||
editorState.document.root.children.first.id,
|
||||
reason: '0 - prev id',
|
||||
);
|
||||
expect(
|
||||
actions[1].payload.parentId,
|
||||
actions[0].payload.block.id,
|
||||
reason: '1 - parent id',
|
||||
);
|
||||
expect(
|
||||
actions[1].payload.prevId,
|
||||
'',
|
||||
reason: '1 - prev id',
|
||||
);
|
||||
expect(
|
||||
actions[2].payload.parentId,
|
||||
actions[1].payload.block.id,
|
||||
reason: '2 - parent id',
|
||||
);
|
||||
expect(
|
||||
actions[2].payload.prevId,
|
||||
'',
|
||||
reason: '2 - prev id',
|
||||
);
|
||||
expect(
|
||||
actions[3].payload.parentId,
|
||||
actions[0].payload.block.id,
|
||||
reason: '3 - parent id',
|
||||
);
|
||||
expect(
|
||||
actions[3].payload.prevId,
|
||||
actions[1].payload.block.id,
|
||||
reason: '3 - prev id',
|
||||
);
|
||||
expect(
|
||||
actions[4].payload.parentId,
|
||||
actions[0].payload.block.id,
|
||||
reason: '4 - parent id',
|
||||
);
|
||||
expect(
|
||||
actions[4].payload.prevId,
|
||||
actions[3].payload.block.id,
|
||||
reason: '4 - prev id',
|
||||
);
|
||||
expect(
|
||||
actions[5].payload.parentId,
|
||||
actions[4].payload.block.id,
|
||||
reason: '5 - parent id',
|
||||
);
|
||||
expect(
|
||||
actions[5].payload.prevId,
|
||||
'',
|
||||
reason: '5 - prev id',
|
||||
);
|
||||
expect(
|
||||
actions[6].payload.parentId,
|
||||
actions[0].payload.block.id,
|
||||
reason: '6 - parent id',
|
||||
);
|
||||
expect(
|
||||
actions[6].payload.prevId,
|
||||
actions[4].payload.block.id,
|
||||
reason: '6 - prev id',
|
||||
);
|
||||
});
|
||||
|
||||
test('toBlockAction insert node before all children nodes', () {
|
||||
final document = Document(
|
||||
root: Node(
|
||||
type: 'page',
|
||||
children: [
|
||||
paragraphNode(children: [paragraphNode(text: '1')])
|
||||
],
|
||||
),
|
||||
);
|
||||
final editorState = EditorState(document: document);
|
||||
|
||||
final transaction = editorState.transaction;
|
||||
transaction.insertNodes([0, 0], [paragraphNode(), paragraphNode()]);
|
||||
|
||||
expect(transaction.operations.length, 1);
|
||||
expect(transaction.operations[0] is InsertOperation, true);
|
||||
|
||||
final actions = transaction.operations[0].toBlockAction(editorState);
|
||||
|
||||
expect(actions.length, 2);
|
||||
for (final action in actions) {
|
||||
expect(action.action, BlockActionTypePB.Insert);
|
||||
}
|
||||
|
||||
expect(
|
||||
actions[0].payload.parentId,
|
||||
editorState.document.root.children.first.id,
|
||||
reason: '0 - parent id',
|
||||
);
|
||||
expect(
|
||||
actions[0].payload.prevId,
|
||||
'',
|
||||
reason: '0 - prev id',
|
||||
);
|
||||
expect(
|
||||
actions[1].payload.parentId,
|
||||
editorState.document.root.children.first.id,
|
||||
reason: '1 - parent id',
|
||||
);
|
||||
expect(
|
||||
actions[1].payload.prevId,
|
||||
actions[0].payload.block.id,
|
||||
reason: '1 - prev id',
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
@ -557,6 +557,14 @@
|
||||
"outline": {
|
||||
"addHeadingToCreateOutline": "Add headings to create a table of contents."
|
||||
},
|
||||
"table": {
|
||||
"addAfter": "Add after",
|
||||
"addBefore": "Add before",
|
||||
"delete": "Delete",
|
||||
"clear": "Clear content",
|
||||
"duplicate": "Duplicate",
|
||||
"bgColor": "Background color"
|
||||
},
|
||||
"contextMenu": {
|
||||
"copy": "Copy",
|
||||
"cut": "Cut",
|
||||
|
Loading…
Reference in New Issue
Block a user