mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-12-15 17:24:08 +00:00
fix: simple table issues (#6985)
* fix: list padding in table cell is too wide * feat: improve tab in table cell * feat: improve shift+tab in table cell * fix: unable to edit cell after deleting an image * fix: inline attribute issue * fix: disable dragging a block into table * feat: add distribute column evenly in column action menu * fix: numbered list icon align in table cell * feat: add setToPageWidth and distributeColumnEvenly in table menu * feat: support highlight color * chore: update editor version * test: add setToPageWidth and distributeColumnEvenly in table menu * test: inline attribute issues * test: add distribute column evenly in column action menu * test: select all in table * test: improve tab(+shift) shortcut in table cell * test: improve enter shortcut in table cell * feat: keep the same column width after using distribute column widths evenly * test: keep the same column width after using distribute column widths evenly * test: drag block to other block's child
This commit is contained in:
parent
d74e7b63ca
commit
f307300b96
@ -138,13 +138,6 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('insert a bmp image from network', (tester) async {
|
||||
await testEmbedImage(
|
||||
tester,
|
||||
'https://people.math.sc.edu/Burkardt/data/bmp/snail.bmp',
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('insert a jpg image from network', (tester) async {
|
||||
await testEmbedImage(
|
||||
tester,
|
||||
|
||||
@ -113,5 +113,41 @@ void main() {
|
||||
|
||||
tester.expectToSeeText(formula);
|
||||
});
|
||||
|
||||
testWidgets('insert a inline math equation and type something after it',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'math equation',
|
||||
);
|
||||
|
||||
// tap the first line of the document
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
// insert a inline page
|
||||
const formula = 'E = MC ^ 2';
|
||||
await tester.ime.insertText(formula);
|
||||
await tester.editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0, endOffset: formula.length),
|
||||
);
|
||||
|
||||
// tap the inline math equation button
|
||||
final inlineMathEquationButton = find.findFlowyTooltip(
|
||||
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
|
||||
);
|
||||
await tester.tapButton(inlineMathEquationButton);
|
||||
|
||||
// expect to see the math equation block
|
||||
final inlineMathEquation = find.byType(InlineMathEquation);
|
||||
expect(inlineMathEquation, findsOneWidget);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
const text = 'Hello World';
|
||||
await tester.ime.insertText(text);
|
||||
|
||||
expect(find.textContaining(text, findRichText: true), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -94,6 +94,20 @@ void main() {
|
||||
await tester.tapButton(finder);
|
||||
expect(find.byType(GridPage), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('insert a inline page and type something after the page',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await insertInlinePage(tester, ViewLayoutPB.Grid);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
const text = 'Hello World';
|
||||
await tester.ime.insertText(text);
|
||||
|
||||
expect(find.textContaining(text, findRichText: true), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -406,6 +406,160 @@ void main() {
|
||||
|
||||
final afterWidth = tableNode.width;
|
||||
expect(afterWidth, equals(beforeWidth));
|
||||
|
||||
final distributeColumnWidthsEvenly =
|
||||
tableNode.attributes[SimpleTableBlockKeys.distributeColumnWidthsEvenly];
|
||||
expect(distributeColumnWidthsEvenly, isTrue);
|
||||
});
|
||||
|
||||
testWidgets('distribute columns evenly (2)', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'simple_table_test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.insertTableInDocument();
|
||||
|
||||
final tableNode = tester.editor.getNodeAtPath([0]);
|
||||
final beforeWidth = tableNode.width;
|
||||
|
||||
// set the column width to page width
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.column,
|
||||
index: 0,
|
||||
action: SimpleTableMoreAction.distributeColumnsEvenly,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final afterWidth = tableNode.width;
|
||||
expect(afterWidth, equals(beforeWidth));
|
||||
|
||||
final distributeColumnWidthsEvenly =
|
||||
tableNode.attributes[SimpleTableBlockKeys.distributeColumnWidthsEvenly];
|
||||
expect(distributeColumnWidthsEvenly, isTrue);
|
||||
});
|
||||
|
||||
testWidgets('using option menu to set column width', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'simple_table_test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.insertTableInDocument();
|
||||
await tester.editor.hoverAndClickOptionMenuButton([0]);
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
final beforeWidth = editorState.document.nodeAtPath([0])!.width;
|
||||
|
||||
await tester.tapButton(
|
||||
find.text(
|
||||
LocaleKeys.document_plugins_simpleTable_moreActions_setToPageWidth.tr(),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final afterWidth = editorState.document.nodeAtPath([0])!.width;
|
||||
expect(afterWidth, greaterThan(beforeWidth));
|
||||
|
||||
await tester.editor.hoverAndClickOptionMenuButton([0]);
|
||||
await tester.tapButton(
|
||||
find.text(
|
||||
LocaleKeys
|
||||
.document_plugins_simpleTable_moreActions_distributeColumnsWidth
|
||||
.tr(),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final afterWidth2 = editorState.document.nodeAtPath([0])!.width;
|
||||
expect(afterWidth2, equals(afterWidth));
|
||||
});
|
||||
|
||||
testWidgets('insert a table and use select all the delete it',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'simple_table_test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.insertTableInDocument();
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(1);
|
||||
await tester.ime.insertText('Hello World');
|
||||
|
||||
// select all
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyA,
|
||||
isMetaPressed: UniversalPlatform.isMacOS,
|
||||
isControlPressed: !UniversalPlatform.isMacOS,
|
||||
);
|
||||
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.backspace);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
// only one paragraph left
|
||||
expect(editorState.document.root.children.length, 1);
|
||||
final paragraphNode = editorState.document.nodeAtPath([0])!;
|
||||
expect(paragraphNode.delta, isNull);
|
||||
});
|
||||
|
||||
testWidgets('use tab or shift+tab to navigate in table', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'simple_table_test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.insertTableInDocument();
|
||||
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.tab);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
final selection = editorState.selection;
|
||||
expect(selection, isNotNull);
|
||||
expect(selection!.start.path, [0, 0, 1, 0]);
|
||||
expect(selection.end.path, [0, 0, 1, 0]);
|
||||
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.tab,
|
||||
isShiftPressed: true,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final selection2 = editorState.selection;
|
||||
expect(selection2, isNotNull);
|
||||
expect(selection2!.start.path, [0, 0, 0, 0]);
|
||||
expect(selection2.end.path, [0, 0, 0, 0]);
|
||||
});
|
||||
|
||||
testWidgets('shift+enter to insert a new line in table', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'simple_table_test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.insertTableInDocument();
|
||||
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.enter,
|
||||
isShiftPressed: true,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
final node = editorState.document.nodeAtPath([0, 0, 0])!;
|
||||
expect(node.children.length, 1);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -125,6 +125,14 @@ List<OptionAction> _buildOptionActions(BuildContext context, String type) {
|
||||
standardActions.addAll([OptionAction.divider, OptionAction.depth]);
|
||||
}
|
||||
|
||||
if (SimpleTableBlockKeys.type == type) {
|
||||
standardActions.addAll([
|
||||
OptionAction.divider,
|
||||
OptionAction.setToPageWidth,
|
||||
OptionAction.distributeColumnsEvenly,
|
||||
]);
|
||||
}
|
||||
|
||||
return standardActions;
|
||||
}
|
||||
|
||||
@ -150,6 +158,7 @@ void _customBlockOptionActions(
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
builder.configuration = builder.configuration.copyWith(
|
||||
blockSelectionAreaMargin: (_) => const EdgeInsets.symmetric(
|
||||
vertical: 1,
|
||||
@ -163,7 +172,7 @@ void _customBlockOptionActions(
|
||||
if ((type == HeadingBlockKeys.type ||
|
||||
type == ToggleListBlockKeys.type) &&
|
||||
level > 0) {
|
||||
final offset = [14.0, 11.0, 8.0, 6.0, 4.0, 2.0];
|
||||
final offset = [13.0, 11.0, 8.0, 6.0, 4.0, 2.0];
|
||||
top += offset[level - 1];
|
||||
} else if (type == SimpleTableBlockKeys.type) {
|
||||
top += 8.0;
|
||||
|
||||
@ -144,6 +144,11 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage>
|
||||
_initEditorL10n();
|
||||
_initializeShortcuts();
|
||||
|
||||
AppFlowyRichTextKeys.partialSliced.addAll([
|
||||
MentionBlockKeys.mention,
|
||||
InlineMathEquationKeys.formula,
|
||||
]);
|
||||
|
||||
indentableBlockTypes.add(ToggleListBlockKeys.type);
|
||||
convertibleBlockTypes.add(ToggleListBlockKeys.type);
|
||||
slashMenuItems = _customSlashMenuItems();
|
||||
|
||||
@ -43,6 +43,12 @@ class BlockActionOptionCubit extends Cubit<BlockActionOptionState> {
|
||||
case OptionAction.copyLinkToBlock:
|
||||
await _copyLinkToBlock(node);
|
||||
break;
|
||||
case OptionAction.setToPageWidth:
|
||||
await _setToPageWidth(node);
|
||||
break;
|
||||
case OptionAction.distributeColumnsEvenly:
|
||||
await _distributeColumnsEvenly(node);
|
||||
break;
|
||||
case OptionAction.align:
|
||||
case OptionAction.color:
|
||||
case OptionAction.divider:
|
||||
@ -657,4 +663,20 @@ class BlockActionOptionCubit extends Cubit<BlockActionOptionState> {
|
||||
// then updating the selection with the beforeSelection that may contains multiple blocks
|
||||
return beforeSelection;
|
||||
}
|
||||
|
||||
Future<void> _setToPageWidth(Node node) async {
|
||||
if (node.type != SimpleTableBlockKeys.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
await editorState.setColumnWidthToPageWidth(tableNode: node);
|
||||
}
|
||||
|
||||
Future<void> _distributeColumnsEvenly(Node node) async {
|
||||
if (node.type != SimpleTableBlockKeys.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
await editorState.distributeColumnWidthToPageWidth(tableNode: node);
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,6 +86,7 @@ class _DraggableOptionButtonState extends State<DraggableOptionButton> {
|
||||
details.globalPosition,
|
||||
builder: (context, data) {
|
||||
return VisualDragArea(
|
||||
editorState: widget.editorState,
|
||||
data: data,
|
||||
dragNode: widget.blockComponentContext.node,
|
||||
);
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -37,13 +38,24 @@ Future<void> dragToMoveNode(
|
||||
// Determine the new path based on drop position
|
||||
// For VerticalPosition.top, we keep the target node's path
|
||||
if (verticalPosition == VerticalPosition.bottom) {
|
||||
newPath = horizontalPosition == HorizontalPosition.left
|
||||
? newPath.next // Insert after target node
|
||||
: newPath.child(0); // Insert as first child of target node
|
||||
if (horizontalPosition == HorizontalPosition.left) {
|
||||
newPath = newPath.next;
|
||||
final node = editorState.document.nodeAtPath(newPath);
|
||||
if (node == null) {
|
||||
// if node is null, it means the node is the last one of the document.
|
||||
newPath = targetNode.path;
|
||||
}
|
||||
} else {
|
||||
newPath = newPath.child(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the drop should be ignored
|
||||
if (shouldIgnoreDragTarget(node, newPath)) {
|
||||
if (shouldIgnoreDragTarget(
|
||||
editorState: editorState,
|
||||
dragNode: node,
|
||||
targetPath: newPath,
|
||||
)) {
|
||||
Log.info(
|
||||
'Drop ignored: node($node, ${node.path}), path($acceptedPath)',
|
||||
);
|
||||
@ -110,7 +122,11 @@ Future<void> dragToMoveNode(
|
||||
return (verticalPosition, horizontalPosition, globalBlockRect);
|
||||
}
|
||||
|
||||
bool shouldIgnoreDragTarget(Node dragNode, Path? targetPath) {
|
||||
bool shouldIgnoreDragTarget({
|
||||
required EditorState editorState,
|
||||
required Node dragNode,
|
||||
required Path? targetPath,
|
||||
}) {
|
||||
if (targetPath == null) {
|
||||
return true;
|
||||
}
|
||||
@ -123,5 +139,10 @@ bool shouldIgnoreDragTarget(Node dragNode, Path? targetPath) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final targetNode = editorState.getNodeAtPath(targetPath);
|
||||
if (targetNode != null && targetNode.isInTable) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -10,16 +10,21 @@ class VisualDragArea extends StatelessWidget {
|
||||
super.key,
|
||||
required this.data,
|
||||
required this.dragNode,
|
||||
required this.editorState,
|
||||
});
|
||||
|
||||
final DragAreaBuilderData data;
|
||||
final Node dragNode;
|
||||
|
||||
final EditorState editorState;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final targetNode = data.targetNode;
|
||||
|
||||
final ignore = shouldIgnoreDragTarget(dragNode, targetNode.path);
|
||||
final ignore = shouldIgnoreDragTarget(
|
||||
editorState: editorState,
|
||||
dragNode: dragNode,
|
||||
targetPath: targetNode.path,
|
||||
);
|
||||
if (ignore) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
@ -67,7 +67,13 @@ enum OptionAction {
|
||||
color,
|
||||
divider,
|
||||
align,
|
||||
depth;
|
||||
|
||||
// Outline block
|
||||
depth,
|
||||
|
||||
// Simple table
|
||||
setToPageWidth,
|
||||
distributeColumnsEvenly;
|
||||
|
||||
FlowySvgData get svg {
|
||||
switch (this) {
|
||||
@ -91,6 +97,10 @@ enum OptionAction {
|
||||
return FlowySvgs.tag_s;
|
||||
case OptionAction.copyLinkToBlock:
|
||||
return FlowySvgs.share_tab_copy_s;
|
||||
case OptionAction.setToPageWidth:
|
||||
return FlowySvgs.table_set_to_page_width_s;
|
||||
case OptionAction.distributeColumnsEvenly:
|
||||
return FlowySvgs.table_distribute_columns_evenly_s;
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,6 +126,14 @@ enum OptionAction {
|
||||
return LocaleKeys.document_plugins_optionAction_copyLinkToBlock.tr();
|
||||
case OptionAction.divider:
|
||||
throw UnsupportedError('Divider does not have description');
|
||||
case OptionAction.setToPageWidth:
|
||||
return LocaleKeys
|
||||
.document_plugins_simpleTable_moreActions_setToPageWidth
|
||||
.tr();
|
||||
case OptionAction.distributeColumnsEvenly:
|
||||
return LocaleKeys
|
||||
.document_plugins_simpleTable_moreActions_distributeColumnsWidth
|
||||
.tr();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ class NumberedListIcon extends StatelessWidget {
|
||||
minWidth: size,
|
||||
minHeight: size,
|
||||
),
|
||||
margin: const EdgeInsets.only(right: 8.0),
|
||||
margin: const EdgeInsets.only(top: 0.5, right: 8.0),
|
||||
alignment: Alignment.center,
|
||||
child: Center(
|
||||
child: Text(
|
||||
|
||||
@ -47,6 +47,7 @@ List<CommandShortcutEvent> commandShortcutEvents = [
|
||||
undoCommand,
|
||||
redoCommand,
|
||||
exitEditingCommand,
|
||||
...tableCommands,
|
||||
].contains(shortcut),
|
||||
),
|
||||
|
||||
|
||||
@ -48,6 +48,13 @@ class SimpleTableBlockKeys {
|
||||
// column widths
|
||||
// it's a `SimpleTableColumnWidthMap` value, {column_index: width, ...}
|
||||
static const String columnWidths = 'column_widths';
|
||||
|
||||
// distribute column widths evenly
|
||||
// if the user distributed the column widths evenly before, the value should be true,
|
||||
// and for the newly added column, using the width of the previous column.
|
||||
// it's a bool value, default is false
|
||||
static const String distributeColumnWidthsEvenly =
|
||||
'distribute_column_widths_evenly';
|
||||
}
|
||||
|
||||
Node simpleTableBlockNode({
|
||||
|
||||
@ -187,8 +187,17 @@ class SimpleTableCellBlockWidgetState extends State<SimpleTableCellBlockWidget>
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Column(
|
||||
children: node.children.map(_buildCellContent).toList(),
|
||||
child: Container(
|
||||
padding: SimpleTableConstants.cellEdgePadding,
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: SimpleTableConstants.minimumColumnWidth,
|
||||
),
|
||||
width: node.columnWidth,
|
||||
child: node.children.isEmpty
|
||||
? _buildEmptyCellContent()
|
||||
: Column(
|
||||
children: node.children.map(_buildCellContent).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -196,21 +205,33 @@ class SimpleTableCellBlockWidgetState extends State<SimpleTableCellBlockWidget>
|
||||
|
||||
Widget _buildCellContent(Node childNode) {
|
||||
final alignment = _buildAlignment();
|
||||
return Container(
|
||||
padding: SimpleTableConstants.cellEdgePadding,
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: SimpleTableConstants.minimumColumnWidth,
|
||||
),
|
||||
width: node.columnWidth,
|
||||
|
||||
return Align(
|
||||
alignment: alignment,
|
||||
child: IntrinsicWidth(
|
||||
child: IntrinsicHeight(
|
||||
child: editorState.renderer.build(context, childNode),
|
||||
),
|
||||
child: editorState.renderer.build(context, childNode),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyCellContent() {
|
||||
// if the table cell is empty, we should allow the user to tap on it to create a new paragraph.
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
final transaction = editorState.transaction;
|
||||
final path = node.path.child(0);
|
||||
transaction
|
||||
..insertNode(
|
||||
path,
|
||||
paragraphNode(),
|
||||
)
|
||||
..afterSelection = Selection.collapsed(Position(path: path));
|
||||
editorState.apply(transaction);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRowMoreActionButton() {
|
||||
final rowIndex = node.rowIndex;
|
||||
|
||||
@ -252,7 +273,12 @@ class SimpleTableCellBlockWidgetState extends State<SimpleTableCellBlockWidget>
|
||||
}
|
||||
|
||||
Color? _buildBackgroundColor() {
|
||||
// Priority: column color > row color > header color > default color
|
||||
// Priority: highlight color > column color > row color > header color > default color
|
||||
final isSelectingTable =
|
||||
simpleTableContext?.isSelectingTable.value ?? false;
|
||||
if (isSelectingTable) {
|
||||
return Theme.of(context).colorScheme.primary.withOpacity(0.1);
|
||||
}
|
||||
|
||||
final columnColor = node.buildColumnColor(context);
|
||||
if (columnColor != null && columnColor != Colors.transparent) {
|
||||
|
||||
@ -47,6 +47,7 @@ enum SimpleTableMoreActionType {
|
||||
SimpleTableMoreAction.align,
|
||||
SimpleTableMoreAction.divider,
|
||||
SimpleTableMoreAction.setToPageWidth,
|
||||
SimpleTableMoreAction.distributeColumnsEvenly,
|
||||
SimpleTableMoreAction.divider,
|
||||
SimpleTableMoreAction.duplicate,
|
||||
SimpleTableMoreAction.clearContents,
|
||||
|
||||
@ -167,6 +167,16 @@ extension TableMapOperation on Node {
|
||||
comparator: (iKey, index) => iKey >= index,
|
||||
);
|
||||
|
||||
final bool distributeColumnWidthsEvenly =
|
||||
attributes[SimpleTableBlockKeys.distributeColumnWidthsEvenly] ??
|
||||
false;
|
||||
|
||||
if (distributeColumnWidthsEvenly) {
|
||||
// if the distribute column widths evenly flag is true,
|
||||
// we should distribute the column widths evenly
|
||||
columnWidths[index.toString()] = columnWidths.values.firstOrNull;
|
||||
}
|
||||
|
||||
return attributes
|
||||
.mergeValues(
|
||||
SimpleTableBlockKeys.columnColors,
|
||||
|
||||
@ -210,6 +210,11 @@ extension TableNodeExtension on Node {
|
||||
return tableCellNode;
|
||||
}
|
||||
|
||||
/// Whether the current node is in a table.
|
||||
bool get isInTable {
|
||||
return parentTableNode != null;
|
||||
}
|
||||
|
||||
double get columnWidth {
|
||||
final parentTableNode = this.parentTableNode;
|
||||
|
||||
|
||||
@ -95,6 +95,8 @@ extension TableOptionOperation on EditorState {
|
||||
double.infinity,
|
||||
),
|
||||
},
|
||||
// reset the distribute column widths evenly flag
|
||||
SimpleTableBlockKeys.distributeColumnWidthsEvenly: false,
|
||||
});
|
||||
await apply(transaction);
|
||||
}
|
||||
@ -276,6 +278,7 @@ extension TableOptionOperation on EditorState {
|
||||
}
|
||||
transaction.updateNode(tableNode, {
|
||||
SimpleTableBlockKeys.columnWidths: columnWidths,
|
||||
SimpleTableBlockKeys.distributeColumnWidthsEvenly: false,
|
||||
});
|
||||
await apply(transaction);
|
||||
}
|
||||
@ -315,6 +318,7 @@ extension TableOptionOperation on EditorState {
|
||||
}
|
||||
transaction.updateNode(tableNode, {
|
||||
SimpleTableBlockKeys.columnWidths: columnWidths,
|
||||
SimpleTableBlockKeys.distributeColumnWidthsEvenly: true,
|
||||
});
|
||||
await apply(transaction);
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ class SimpleTableRowBlockComponentBuilder extends BlockComponentBuilder {
|
||||
}
|
||||
|
||||
@override
|
||||
BlockComponentValidate get validate => (node) => node.children.isNotEmpty;
|
||||
BlockComponentValidate get validate => (_) => true;
|
||||
}
|
||||
|
||||
class SimpleTableRowBlockWidget extends BlockComponentStatefulWidget {
|
||||
@ -72,6 +72,10 @@ class _SimpleTableRowBlockWidgetState extends State<SimpleTableRowBlockWidget>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (node.children.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return IntrinsicHeight(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
|
||||
@ -21,13 +21,13 @@ KeyEventResult _backspaceInTableCellHandler(EditorState editorState) {
|
||||
}
|
||||
|
||||
final onlyContainsOneChild = tableCellNode.children.length == 1;
|
||||
final isParagraphNode =
|
||||
tableCellNode.children.first.type == ParagraphBlockKeys.type;
|
||||
final isCodeBlock = tableCellNode.children.first.type == CodeBlockKeys.type;
|
||||
final firstChild = tableCellNode.children.first;
|
||||
final isParagraphNode = firstChild.type == ParagraphBlockKeys.type;
|
||||
final isCodeBlock = firstChild.type == CodeBlockKeys.type;
|
||||
if (onlyContainsOneChild &&
|
||||
selection.isCollapsed &&
|
||||
selection.end.offset == 0) {
|
||||
if (isParagraphNode) {
|
||||
if (isParagraphNode && firstChild.children.isEmpty) {
|
||||
return KeyEventResult.skipRemainingHandlers;
|
||||
} else if (isCodeBlock) {
|
||||
// replace the codeblock with a paragraph
|
||||
|
||||
@ -65,6 +65,10 @@ extension TableCommandExtension on EditorState {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
if (isOutdentable(editorState)) {
|
||||
return outdentCommand.execute(editorState);
|
||||
}
|
||||
|
||||
Selection? newSelection;
|
||||
|
||||
final previousCell = tableCellNode.getPreviousCellInSameRow();
|
||||
@ -126,6 +130,10 @@ extension TableCommandExtension on EditorState {
|
||||
|
||||
Selection? newSelection;
|
||||
|
||||
if (isIndentable(editorState)) {
|
||||
return indentCommand.execute(editorState);
|
||||
}
|
||||
|
||||
final nextCell = tableCellNode.getNextCellInSameRow();
|
||||
if (nextCell != null && !nextCell.path.equals(tableCellNode.path)) {
|
||||
// get the first children of the next cell
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_cell_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_shortcuts/simple_table_command_extension.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
final CommandShortcutEvent enterInTableCell = CommandShortcutEvent(
|
||||
key: 'Press enter in table cell',
|
||||
@ -15,10 +17,31 @@ KeyEventResult _enterInTableCellHandler(EditorState editorState) {
|
||||
if (!isInTableCell ||
|
||||
selection == null ||
|
||||
tableCellNode == null ||
|
||||
node == null) {
|
||||
node == null ||
|
||||
!selection.isCollapsed) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
// forward the enter command to the insertNewLine character command to support multi-line text in table cell
|
||||
return KeyEventResult.skipRemainingHandlers;
|
||||
// check if the shift key is pressed, if so, we should return false to let the system handle it.
|
||||
final isShiftPressed = HardwareKeyboard.instance.isShiftPressed;
|
||||
if (isShiftPressed) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
final delta = node.delta;
|
||||
if (!indentableBlockTypes.contains(node.type) || delta == null) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
if (selection.startIndex == 0 && delta.isEmpty) {
|
||||
// clear the style
|
||||
if (node.parent?.type != SimpleTableCellBlockKeys.type) {
|
||||
if (outdentCommand.execute(editorState) == KeyEventResult.handled) {
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
}
|
||||
return convertToParagraphCommand.execute(editorState);
|
||||
}
|
||||
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
@ -61,8 +61,8 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "954ffa5"
|
||||
resolved-ref: "954ffa538e0788a5d25dc16555ff80a3afae5ea3"
|
||||
ref: ca04a67
|
||||
resolved-ref: ca04a675c06071678ef5901d6000cc4d6ac745e8
|
||||
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
||||
source: git
|
||||
version: "4.0.0"
|
||||
|
||||
@ -173,7 +173,7 @@ dependency_overrides:
|
||||
appflowy_editor:
|
||||
git:
|
||||
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
||||
ref: "954ffa5"
|
||||
ref: "ca04a67"
|
||||
|
||||
appflowy_editor_plugins:
|
||||
git:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user