diff --git a/lib/src/editor/block_component/table_block_component/table_action.dart b/lib/src/editor/block_component/table_block_component/table_action.dart index 4430024d4..25213ad17 100644 --- a/lib/src/editor/block_component/table_block_component/table_action.dart +++ b/lib/src/editor/block_component/table_block_component/table_action.dart @@ -7,73 +7,75 @@ class TableActions { static void add( Node node, int position, - Transaction transaction, + EditorState editorState, TableDirection dir, ) { if (dir == TableDirection.col) { - _addCol(node, position, transaction); + _addCol(node, position, editorState); } else { - _addRow(node, position, transaction); + _addRow(node, position, editorState); } } static void delete( Node node, int position, - Transaction transaction, + EditorState editorState, TableDirection dir, ) { if (dir == TableDirection.col) { - _deleteCol(node, position, transaction); + _deleteCol(node, position, editorState); } else { - _deleteRow(node, position, transaction); + _deleteRow(node, position, editorState); } } static void duplicate( Node node, int position, - Transaction transaction, + EditorState editorState, TableDirection dir, ) { if (dir == TableDirection.col) { - _duplicateCol(node, position, transaction); + _duplicateCol(node, position, editorState); } else { - _duplicateRow(node, position, transaction); + _duplicateRow(node, position, editorState); } } static void clear( Node node, int position, - Transaction transaction, + EditorState editorState, TableDirection dir, ) { if (dir == TableDirection.col) { - _clearCol(node, position, transaction); + _clearCol(node, position, editorState); } else { - _clearRow(node, position, transaction); + _clearRow(node, position, editorState); } } static void setBgColor( Node node, int position, - Transaction transaction, + EditorState editorState, String? color, TableDirection dir, ) { if (dir == TableDirection.col) { - _setColBgColor(node, position, transaction, color); + _setColBgColor(node, position, editorState, color); } else { - _setRowBgColor(node, position, transaction, color); + _setRowBgColor(node, position, editorState, color); } } } -void _addCol(Node tableNode, int position, Transaction transaction) { +void _addCol(Node tableNode, int position, EditorState editorState) { assert(position >= 0); + final transaction = editorState.transaction; + List cellNodes = []; final int rowsLen = tableNode.attributes[TableBlockKeys.rowsLen], colsLen = tableNode.attributes[TableBlockKeys.colsLen]; @@ -82,7 +84,7 @@ void _addCol(Node tableNode, int position, Transaction transaction) { for (var i = position; i < colsLen; i++) { for (var j = 0; j < rowsLen; j++) { final node = getCellNode(tableNode, i, j)!; - transaction.updateNode(node, {TableBlockKeys.colPosition: i + 1}); + transaction.updateNode(node, {TableCellBlockKeys.colPosition: i + 1}); } } } @@ -91,11 +93,20 @@ void _addCol(Node tableNode, int position, Transaction transaction) { final node = Node( type: TableCellBlockKeys.type, attributes: { - TableBlockKeys.colPosition: position, - TableBlockKeys.rowPosition: i, + TableCellBlockKeys.colPosition: position, + TableCellBlockKeys.rowPosition: i, }, ); node.insert(paragraphNode()); + final firstCellInRow = getCellNode(tableNode, 0, i); + if (firstCellInRow?.attributes + .containsKey(TableCellBlockKeys.rowBackgroundColor) ?? + false) { + node.updateAttributes({ + TableCellBlockKeys.rowBackgroundColor: + firstCellInRow!.attributes[TableCellBlockKeys.rowBackgroundColor] + }); + } cellNodes.add(newCellNode(tableNode, node)); } @@ -110,32 +121,34 @@ void _addCol(Node tableNode, int position, Transaction transaction) { // way? transaction.insertNodes(insertPath, cellNodes); transaction.updateNode(tableNode, {TableBlockKeys.colsLen: colsLen + 1}); + + editorState.apply(transaction, withUpdateSelection: false); } -void _addRow(Node tableNode, int position, Transaction transaction) { +void _addRow(Node tableNode, int position, EditorState editorState) async { assert(position >= 0); final int rowsLen = tableNode.attributes[TableBlockKeys.rowsLen], colsLen = tableNode.attributes[TableBlockKeys.colsLen]; - if (position != rowsLen) { - for (var i = position; i < rowsLen; i++) { - for (var j = 0; j < colsLen; j++) { - final node = getCellNode(tableNode, j, i)!; - transaction.updateNode(node, {TableBlockKeys.rowPosition: i + 1}); - } - } - } - for (var i = 0; i < colsLen; i++) { final node = Node( type: TableCellBlockKeys.type, attributes: { - TableBlockKeys.colPosition: i, - TableBlockKeys.rowPosition: position, + TableCellBlockKeys.colPosition: i, + TableCellBlockKeys.rowPosition: position, }, ); node.insert(paragraphNode()); + final firstCellInCol = getCellNode(tableNode, i, 0); + if (firstCellInCol?.attributes + .containsKey(TableCellBlockKeys.colBackgroundColor) ?? + false) { + node.updateAttributes({ + TableCellBlockKeys.colBackgroundColor: + firstCellInCol!.attributes[TableCellBlockKeys.colBackgroundColor] + }); + } late Path insertPath; if (position == 0) { @@ -143,15 +156,29 @@ void _addRow(Node tableNode, int position, Transaction transaction) { } else { insertPath = getCellNode(tableNode, i, position - 1)!.path.next; } + + final transaction = editorState.transaction; + if (position != rowsLen) { + for (var j = position; j < rowsLen; j++) { + final node = getCellNode(tableNode, i, j)!; + transaction.updateNode(node, {TableCellBlockKeys.rowPosition: j + 1}); + } + } transaction.insertNode( insertPath, newCellNode(tableNode, node), ); + await editorState.apply(transaction, withUpdateSelection: false); } + + final transaction = editorState.transaction; transaction.updateNode(tableNode, {TableBlockKeys.rowsLen: rowsLen + 1}); + await editorState.apply(transaction, withUpdateSelection: false); } -void _deleteCol(Node tableNode, int col, Transaction transaction) { +void _deleteCol(Node tableNode, int col, EditorState editorState) { + final transaction = editorState.transaction; + final int rowsLen = tableNode.attributes[TableBlockKeys.rowsLen], colsLen = tableNode.attributes[TableBlockKeys.colsLen]; List nodes = []; @@ -160,12 +187,16 @@ void _deleteCol(Node tableNode, int col, Transaction transaction) { } transaction.deleteNodes(nodes); - _updateCellPositions(tableNode, transaction, col + 1, 0, -1, 0); + _updateCellPositions(tableNode, editorState, col + 1, 0, -1, 0); transaction.updateNode(tableNode, {TableBlockKeys.colsLen: colsLen - 1}); + + editorState.apply(transaction, withUpdateSelection: false); } -void _deleteRow(Node tableNode, int row, Transaction transaction) { +void _deleteRow(Node tableNode, int row, EditorState editorState) { + final transaction = editorState.transaction; + final int rowsLen = tableNode.attributes[TableBlockKeys.rowsLen], colsLen = tableNode.attributes[TableBlockKeys.colsLen]; List nodes = []; @@ -174,12 +205,16 @@ void _deleteRow(Node tableNode, int row, Transaction transaction) { } transaction.deleteNodes(nodes); - _updateCellPositions(tableNode, transaction, 0, row + 1, 0, -1); + _updateCellPositions(tableNode, editorState, 0, row + 1, 0, -1); transaction.updateNode(tableNode, {TableBlockKeys.rowsLen: rowsLen - 1}); + + editorState.apply(transaction, withUpdateSelection: false); } -void _duplicateCol(Node tableNode, int col, Transaction transaction) { +void _duplicateCol(Node tableNode, int col, EditorState editorState) { + final transaction = editorState.transaction; + final int rowsLen = tableNode.attributes[TableBlockKeys.rowsLen], colsLen = tableNode.attributes[TableBlockKeys.colsLen]; List nodes = []; @@ -189,8 +224,8 @@ void _duplicateCol(Node tableNode, int col, Transaction transaction) { node.copyWith( attributes: { ...node.attributes, - TableBlockKeys.colPosition: col + 1, - TableBlockKeys.rowPosition: i, + TableCellBlockKeys.colPosition: col + 1, + TableCellBlockKeys.rowPosition: i, }, ), ); @@ -200,70 +235,88 @@ void _duplicateCol(Node tableNode, int col, Transaction transaction) { nodes, ); - _updateCellPositions(tableNode, transaction, col + 1, 0, 1, 0); + _updateCellPositions(tableNode, editorState, col + 1, 0, 1, 0); transaction.updateNode(tableNode, {TableBlockKeys.colsLen: colsLen + 1}); + + editorState.apply(transaction, withUpdateSelection: false); } -void _duplicateRow(Node tableNode, int row, Transaction transaction) { +void _duplicateRow(Node tableNode, int row, EditorState editorState) async { + Transaction transaction = editorState.transaction; + _updateCellPositions(tableNode, editorState, 0, row + 1, 0, 1); + await editorState.apply(transaction, withUpdateSelection: false); + final int rowsLen = tableNode.attributes[TableBlockKeys.rowsLen], colsLen = tableNode.attributes[TableBlockKeys.colsLen]; for (var i = 0; i < colsLen; i++) { final node = getCellNode(tableNode, i, row)!; + transaction = editorState.transaction; transaction.insertNode( node.path.next, node.copyWith( attributes: { ...node.attributes, - TableBlockKeys.rowPosition: row + 1, - TableBlockKeys.colPosition: i, + TableCellBlockKeys.rowPosition: row + 1, + TableCellBlockKeys.colPosition: i, }, ), ); + await editorState.apply(transaction, withUpdateSelection: false); } - _updateCellPositions(tableNode, transaction, 0, row + 1, 0, 1); - + transaction = editorState.transaction; transaction.updateNode(tableNode, {TableBlockKeys.rowsLen: rowsLen + 1}); + editorState.apply(transaction, withUpdateSelection: false); } void _setColBgColor( Node tableNode, int col, - Transaction transaction, + EditorState editorState, String? color, ) { + final transaction = editorState.transaction; + final rowslen = tableNode.attributes[TableBlockKeys.rowsLen]; for (var i = 0; i < rowslen; i++) { final node = getCellNode(tableNode, col, i)!; transaction.updateNode( node, - {TableBlockKeys.backgroundColor: color}, + {TableCellBlockKeys.colBackgroundColor: color}, ); } + + editorState.apply(transaction, withUpdateSelection: false); } void _setRowBgColor( Node tableNode, int row, - Transaction transaction, + EditorState editorState, String? color, ) { + final transaction = editorState.transaction; + final colsLen = tableNode.attributes[TableBlockKeys.colsLen]; for (var i = 0; i < colsLen; i++) { final node = getCellNode(tableNode, i, row)!; transaction.updateNode( node, - {TableBlockKeys.backgroundColor: color}, + {TableCellBlockKeys.rowBackgroundColor: color}, ); } + + editorState.apply(transaction, withUpdateSelection: false); } void _clearCol( Node tableNode, int col, - Transaction transaction, + EditorState editorState, ) { + final transaction = editorState.transaction; + final rowsLen = tableNode.attributes[TableBlockKeys.rowsLen]; for (var i = 0; i < rowsLen; i++) { final node = getCellNode(tableNode, col, i)!; @@ -272,13 +325,17 @@ void _clearCol( paragraphNode(text: ''), ); } + + editorState.apply(transaction, withUpdateSelection: false); } void _clearRow( Node tableNode, int row, - Transaction transaction, + EditorState editorState, ) { + final transaction = editorState.transaction; + final colsLen = tableNode.attributes[TableBlockKeys.colsLen]; for (var i = 0; i < colsLen; i++) { final node = getCellNode(tableNode, i, row)!; @@ -287,42 +344,44 @@ void _clearRow( paragraphNode(text: ''), ); } + + editorState.apply(transaction, withUpdateSelection: false); } dynamic newCellNode(Node tableNode, n) { - final row = n.attributes[TableBlockKeys.rowPosition] as int; - final col = n.attributes[TableBlockKeys.colPosition] as int; + final row = n.attributes[TableCellBlockKeys.rowPosition] as int; + final col = n.attributes[TableCellBlockKeys.colPosition] as int; final int rowsLen = tableNode.attributes[TableBlockKeys.rowsLen]; final int colsLen = tableNode.attributes[TableBlockKeys.colsLen]; - if (!n.attributes.containsKey(TableBlockKeys.height)) { + if (!n.attributes.containsKey(TableCellBlockKeys.height)) { double nodeHeight = double.tryParse( tableNode.attributes[TableBlockKeys.rowDefaultHeight].toString(), )!; if (row < rowsLen) { nodeHeight = double.tryParse( getCellNode(tableNode, 0, row)! - .attributes[TableBlockKeys.height] + .attributes[TableCellBlockKeys.height] .toString(), ) ?? nodeHeight; } - n.updateAttributes({TableBlockKeys.height: nodeHeight}); + n.updateAttributes({TableCellBlockKeys.height: nodeHeight}); } - if (!n.attributes.containsKey(TableBlockKeys.width)) { + if (!n.attributes.containsKey(TableCellBlockKeys.width)) { double nodeWidth = double.tryParse( tableNode.attributes[TableBlockKeys.colDefaultWidth].toString(), )!; if (col < colsLen) { nodeWidth = double.tryParse( getCellNode(tableNode, col, 0)! - .attributes[TableBlockKeys.width] + .attributes[TableCellBlockKeys.width] .toString(), ) ?? nodeWidth; } - n.updateAttributes({TableBlockKeys.width: nodeWidth}); + n.updateAttributes({TableCellBlockKeys.width: nodeWidth}); } return n; @@ -330,21 +389,25 @@ dynamic newCellNode(Node tableNode, n) { void _updateCellPositions( Node tableNode, - Transaction transaction, + EditorState editorState, int fromCol, int fromRow, int addToCol, int addToRow, ) { + final transaction = editorState.transaction; + final int rowsLen = tableNode.attributes[TableBlockKeys.rowsLen], colsLen = tableNode.attributes[TableBlockKeys.colsLen]; for (var i = fromCol; i < colsLen; i++) { for (var j = fromRow; j < rowsLen; j++) { transaction.updateNode(getCellNode(tableNode, i, j)!, { - TableBlockKeys.colPosition: i + addToCol, - TableBlockKeys.rowPosition: j + addToRow, + TableCellBlockKeys.colPosition: i + addToCol, + TableCellBlockKeys.rowPosition: j + addToRow, }); } } + + editorState.apply(transaction, withUpdateSelection: false); } diff --git a/lib/src/editor/block_component/table_block_component/table_action_menu.dart b/lib/src/editor/block_component/table_block_component/table_action_menu.dart index f910ac65c..1d42c608f 100644 --- a/lib/src/editor/block_component/table_block_component/table_action_menu.dart +++ b/lib/src/editor/block_component/table_block_component/table_action_menu.dart @@ -39,27 +39,19 @@ void showActionMenu( height: 230, children: [ _menuItem(context, 'Add after', Icons.last_page, () { - final transaction = editorState.transaction; - TableActions.add(node, position + 1, transaction, dir); - editorState.apply(transaction); + TableActions.add(node, position + 1, editorState, dir); dismissOverlay(); }), _menuItem(context, 'Add before', Icons.first_page, () { - final transaction = editorState.transaction; - TableActions.add(node, position, transaction, dir); - editorState.apply(transaction); + TableActions.add(node, position, editorState, dir); dismissOverlay(); }), _menuItem(context, 'Remove', Icons.delete, () { - final transaction = editorState.transaction; - TableActions.delete(node, position, transaction, dir); - editorState.apply(transaction); + TableActions.delete(node, position, editorState, dir); dismissOverlay(); }), _menuItem(context, 'Duplicate', Icons.content_copy, () { - final transaction = editorState.transaction; - TableActions.duplicate(node, position, transaction, dir); - editorState.apply(transaction); + TableActions.duplicate(node, position, editorState, dir); dismissOverlay(); }), _menuItem( @@ -70,33 +62,31 @@ void showActionMenu( final cell = dir == TableDirection.col ? getCellNode(node, position, 0) : getCellNode(node, 0, position); + final key = dir == TableDirection.col + ? TableCellBlockKeys.colBackgroundColor + : TableCellBlockKeys.rowBackgroundColor; _showColorMenu( context, (color) { - final transaction = editorState.transaction; TableActions.setBgColor( node, position, - transaction, + editorState, color, dir, ); - editorState.apply(transaction); }, top: top, bottom: bottom, left: left, - selectedColorHex: - cell?.attributes[TableBlockKeys.backgroundColor], + selectedColorHex: cell?.attributes[key], ); dismissOverlay(); }, ), _menuItem(context, 'Clear Content', Icons.clear, () { - final transaction = editorState.transaction; - TableActions.clear(node, position, transaction, dir); - editorState.apply(transaction); + TableActions.clear(node, position, editorState, dir); dismissOverlay(); }), ], diff --git a/lib/src/editor/block_component/table_block_component/table_block_component.dart b/lib/src/editor/block_component/table_block_component/table_block_component.dart index edcdb4dd2..7b9480d36 100644 --- a/lib/src/editor/block_component/table_block_component/table_block_component.dart +++ b/lib/src/editor/block_component/table_block_component/table_block_component.dart @@ -2,6 +2,7 @@ import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/src/editor/block_component/table_block_component/table_node.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; + import 'table_view.dart'; class TableBlockKeys { @@ -21,17 +22,7 @@ class TableBlockKeys { static const String rowsLen = 'rowsLen'; - static const String rowPosition = 'rowPosition'; - - static const String colPosition = 'colPosition'; - - static const String height = 'height'; - - static const String width = 'width'; - static const String colsHeight = 'colsHeight'; - - static const String backgroundColor = 'backgroundColor'; } class TableDefaults { @@ -265,8 +256,20 @@ SelectionMenuItem tableMenuItem = SelectionMenuItem( transaction ..insertNode(selection.end.path, tableNode.node) ..deleteNode(currentNode); + transaction.afterSelection = Selection.collapsed( + Position( + path: selection.end.path + [0, 0], + offset: 0, + ), + ); } else { transaction.insertNode(selection.end.path.next, tableNode.node); + transaction.afterSelection = Selection.collapsed( + Position( + path: selection.end.path.next + [0, 0], + offset: 0, + ), + ); } editorState.apply(transaction); diff --git a/lib/src/editor/block_component/table_block_component/table_cell_block_component.dart b/lib/src/editor/block_component/table_block_component/table_cell_block_component.dart index f8e9cdda7..4de9a0721 100644 --- a/lib/src/editor/block_component/table_block_component/table_cell_block_component.dart +++ b/lib/src/editor/block_component/table_block_component/table_cell_block_component.dart @@ -8,6 +8,18 @@ class TableCellBlockKeys { const TableCellBlockKeys._(); static const String type = 'table/cell'; + + static const String rowPosition = 'rowPosition'; + + static const String colPosition = 'colPosition'; + + static const String height = 'height'; + + static const String width = 'width'; + + static const String rowBackgroundColor = 'rowBackgroundColor'; + + static const String colBackgroundColor = 'colBackgroundColor'; } class TableCellBlockComponentBuilder extends BlockComponentBuilder { @@ -40,8 +52,8 @@ class TableCellBlockComponentBuilder extends BlockComponentBuilder { @override bool validate(Node node) => node.attributes.isNotEmpty && - node.attributes.containsKey(TableBlockKeys.rowPosition) && - node.attributes.containsKey(TableBlockKeys.colPosition); + node.attributes.containsKey(TableCellBlockKeys.rowPosition) && + node.attributes.containsKey(TableCellBlockKeys.colPosition); } class TableCelBlockWidget extends BlockComponentStatefulWidget { @@ -74,12 +86,17 @@ class _TableCeBlockWidgetState extends State { child: Container( constraints: BoxConstraints( minHeight: context - .select((Node n) => n.attributes[TableBlockKeys.height]), + .select((Node n) => n.attributes[TableCellBlockKeys.height]), + ), + color: context.select( + (Node n) => + (n.attributes[TableCellBlockKeys.colBackgroundColor] + as String?) + ?.toColor() ?? + (n.attributes[TableCellBlockKeys.rowBackgroundColor] + as String?) + ?.toColor(), ), - color: context.select((Node n) { - return (n.attributes[TableBlockKeys.backgroundColor] as String?) - ?.toColor(); - }), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ @@ -98,13 +115,13 @@ class _TableCeBlockWidgetState extends State { visible: _rowActionVisibility, node: widget.node.parent!, editorState: editorState, - position: widget.node.attributes[TableBlockKeys.rowPosition], + position: widget.node.attributes[TableCellBlockKeys.rowPosition], transform: context.select((Node n) { - final int col = n.attributes[TableBlockKeys.colPosition]; + final int col = n.attributes[TableCellBlockKeys.colPosition]; double left = -15.0; for (var i = 0; i < col; i++) { left -= getCellNode(n.parent!, i, 0) - ?.attributes[TableBlockKeys.width] as double; + ?.attributes[TableCellBlockKeys.width] as double; left -= n.parent!.attributes['borderWidth'] ?? TableDefaults.borderWidth; } @@ -112,8 +129,8 @@ class _TableCeBlockWidgetState extends State { return Matrix4.translationValues(left, 0.0, 0.0); }), alignment: Alignment.centerLeft, - height: - context.select((Node n) => n.attributes[TableBlockKeys.height]), + height: context + .select((Node n) => n.attributes[TableCellBlockKeys.height]), menuBuilder: widget.menuBuilder, dir: TableDirection.row, ), diff --git a/lib/src/editor/block_component/table_block_component/table_col.dart b/lib/src/editor/block_component/table_block_component/table_col.dart index 2084439df..9122a501c 100644 --- a/lib/src/editor/block_component/table_block_component/table_col.dart +++ b/lib/src/editor/block_component/table_block_component/table_col.dart @@ -41,6 +41,7 @@ class _TableColState extends State { TableColBorder( resizable: false, tableNode: widget.tableNode, + editorState: widget.editorState, colIdx: widget.colIdx, borderColor: widget.borderColor, borderHoverColor: widget.borderHoverColor, @@ -52,7 +53,7 @@ class _TableColState extends State { SizedBox( width: context.select( (Node n) => getCellNode(n, widget.colIdx, 0) - ?.attributes[TableBlockKeys.width], + ?.attributes[TableCellBlockKeys.width], ), child: Stack( children: [ @@ -77,6 +78,7 @@ class _TableColState extends State { TableColBorder( resizable: true, tableNode: widget.tableNode, + editorState: widget.editorState, colIdx: widget.colIdx, borderColor: widget.borderColor, borderHoverColor: widget.borderHoverColor, @@ -117,9 +119,16 @@ class _TableColState extends State { } void updateRowHeightCallback(int row) => - WidgetsBinding.instance.addPostFrameCallback( - (_) => row < widget.tableNode.rowsLen - ? widget.tableNode.updateRowHeight(row) - : null, - ); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (row >= widget.tableNode.rowsLen) { + return; + } + + final transaction = widget.editorState.transaction; + widget.tableNode.updateRowHeight(row, transaction); + if (transaction.operations.isNotEmpty) { + transaction.afterSelection = transaction.beforeSelection; + widget.editorState.apply(transaction); + } + }); } diff --git a/lib/src/editor/block_component/table_block_component/table_col_border.dart b/lib/src/editor/block_component/table_block_component/table_col_border.dart index 9e97c863d..d755002b3 100644 --- a/lib/src/editor/block_component/table_block_component/table_col_border.dart +++ b/lib/src/editor/block_component/table_block_component/table_col_border.dart @@ -1,12 +1,13 @@ -import 'package:flutter/material.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/src/editor/block_component/table_block_component/table_node.dart'; +import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; class TableColBorder extends StatefulWidget { const TableColBorder({ Key? key, required this.tableNode, + required this.editorState, required this.colIdx, required this.resizable, required this.borderColor, @@ -16,6 +17,7 @@ class TableColBorder extends StatefulWidget { final bool resizable; final int colIdx; final TableNode tableNode; + final EditorState editorState; final Color borderColor; final Color borderHoverColor; @@ -44,7 +46,7 @@ class _TableColBorderState extends State { child: GestureDetector( onHorizontalDragStart: (_) => setState(() => _borderDragging = true), onHorizontalDragEnd: (_) => setState(() => _borderDragging = false), - onHorizontalDragUpdate: (DragUpdateDetails details) { + onHorizontalDragUpdate: (DragUpdateDetails details) async { final RenderBox box = _borderKey.currentContext?.findRenderObject() as RenderBox; final Offset pos = box.localToGlobal(Offset.zero); @@ -58,8 +60,15 @@ class _TableColBorderState extends State { } final colWidth = widget.tableNode.getColWidth(widget.colIdx); - widget.tableNode - .setColWidth(widget.colIdx, colWidth + details.delta.dx); + + final transaction = widget.editorState.transaction; + widget.tableNode.setColWidth( + widget.colIdx, + colWidth + details.delta.dx, + transaction, + ); + transaction.afterSelection = transaction.beforeSelection; + await widget.editorState.apply(transaction); }, child: Container( key: _borderKey, diff --git a/lib/src/editor/block_component/table_block_component/table_commands.dart b/lib/src/editor/block_component/table_block_component/table_commands.dart index 276d99197..358037a98 100644 --- a/lib/src/editor/block_component/table_block_component/table_commands.dart +++ b/lib/src/editor/block_component/table_block_component/table_commands.dart @@ -189,8 +189,8 @@ bool _hasSelectionAndTableCell( Node? _getNextNode(Iterable nodes, int colDiff, rowDiff) { final cell = nodes.first.parent!; - final col = cell.attributes[TableBlockKeys.colPosition]; - final row = cell.attributes[TableBlockKeys.rowPosition]; + final col = cell.attributes[TableCellBlockKeys.colPosition]; + final row = cell.attributes[TableCellBlockKeys.rowPosition]; return cell.parent != null ? getCellNode(cell.parent!, col + colDiff, row + rowDiff) : null; diff --git a/lib/src/editor/block_component/table_block_component/table_node.dart b/lib/src/editor/block_component/table_block_component/table_node.dart index e677227f8..a280c0338 100644 --- a/lib/src/editor/block_component/table_block_component/table_node.dart +++ b/lib/src/editor/block_component/table_block_component/table_node.dart @@ -1,4 +1,5 @@ import 'dart:math'; + import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/src/editor/block_component/table_block_component/table_config.dart'; @@ -27,15 +28,15 @@ class TableNode { assert( node.children.every( (n) => - n.attributes.containsKey(TableBlockKeys.rowPosition) && - n.attributes.containsKey(TableBlockKeys.colPosition), + n.attributes.containsKey(TableCellBlockKeys.rowPosition) && + n.attributes.containsKey(TableCellBlockKeys.colPosition), ), ); assert( node.children.every( (n) => - n.attributes.containsKey(TableBlockKeys.rowPosition) && - n.attributes.containsKey(TableBlockKeys.colPosition), + n.attributes.containsKey(TableCellBlockKeys.rowPosition) && + n.attributes.containsKey(TableCellBlockKeys.colPosition), ), ); @@ -44,8 +45,8 @@ class TableNode { for (var j = 0; j < rowsCount; j++) { final cell = node.children.where( (n) => - n.attributes[TableBlockKeys.colPosition] == i && - n.attributes[TableBlockKeys.rowPosition] == j, + n.attributes[TableCellBlockKeys.colPosition] == i && + n.attributes[TableCellBlockKeys.rowPosition] == j, ); assert(cell.length == 1); _cells[i].add(newCellNode(node, cell.first)); @@ -85,8 +86,8 @@ class TableNode { final cell = Node( type: TableCellBlockKeys.type, attributes: { - TableBlockKeys.colPosition: i, - TableBlockKeys.rowPosition: j + TableCellBlockKeys.colPosition: i, + TableCellBlockKeys.rowPosition: j }, ); @@ -117,7 +118,7 @@ class TableNode { double getRowHeight(int row) => double.tryParse( - _cells[0][row].attributes[TableBlockKeys.height].toString(), + _cells[0][row].attributes[TableCellBlockKeys.height].toString(), ) ?? _config.rowDefaultHeight; @@ -130,7 +131,7 @@ class TableNode { double getColWidth(int col) => double.tryParse( - _cells[col][0].attributes[TableBlockKeys.width].toString(), + _cells[col][0].attributes[TableCellBlockKeys.width].toString(), ) ?? _config.colDefaultWidth; @@ -141,33 +142,34 @@ class TableNode { ) + _config.borderWidth; - void setColWidth(int col, double w) { + void setColWidth(int col, double w, Transaction transaction) { w = w < _config.colMinimumWidth ? _config.colMinimumWidth : w; if (getColWidth(col) != w) { for (var i = 0; i < rowsLen; i++) { - _cells[col][i].updateAttributes({TableBlockKeys.width: w}); - } - for (var i = 0; i < rowsLen; i++) { - updateRowHeight(i); + transaction.updateNode(_cells[col][i], {TableCellBlockKeys.width: w}); + updateRowHeight(i, transaction); } - node.updateAttributes({}); + transaction.updateNode(node, node.attributes); } } - void updateRowHeight(int row) { + void updateRowHeight(int row, Transaction transaction) { // The extra 8 is because of paragraph padding double maxHeight = _cells .map((c) => c[row].children.first.rect.height + 8) .reduce(max); - if (_cells[0][row].attributes[TableBlockKeys.height] != maxHeight) { + if (_cells[0][row].attributes[TableCellBlockKeys.height] != maxHeight) { for (var i = 0; i < colsLen; i++) { - _cells[i][row].updateAttributes({TableBlockKeys.height: maxHeight}); + transaction.updateNode( + _cells[i][row], + {TableCellBlockKeys.height: maxHeight}, + ); } } if (node.attributes[TableBlockKeys.colsHeight] != colsHeight) { - node.updateAttributes({TableBlockKeys.colsHeight: colsHeight}); + transaction.updateNode(node, {TableBlockKeys.colsHeight: colsHeight}); } } } diff --git a/lib/src/editor/block_component/table_block_component/table_view.dart b/lib/src/editor/block_component/table_block_component/table_view.dart index 6852618fb..258a387ef 100644 --- a/lib/src/editor/block_component/table_block_component/table_view.dart +++ b/lib/src/editor/block_component/table_block_component/table_view.dart @@ -46,14 +46,12 @@ class _TableViewState extends State { width: 28, height: widget.tableNode.colsHeight, onPressed: () { - final transaction = widget.editorState.transaction; TableActions.add( widget.tableNode.node, widget.tableNode.colsLen, - transaction, + widget.editorState, TableDirection.col, ); - widget.editorState.apply(transaction); }, ), ], @@ -64,14 +62,12 @@ class _TableViewState extends State { height: 28, width: widget.tableNode.tableWidth, onPressed: () { - final transaction = widget.editorState.transaction; TableActions.add( widget.tableNode.node, widget.tableNode.rowsLen, - transaction, + widget.editorState, TableDirection.row, ); - widget.editorState.apply(transaction); }, ), ], diff --git a/lib/src/editor/block_component/table_block_component/util.dart b/lib/src/editor/block_component/table_block_component/util.dart index 7a9d4c1ca..7ade0b580 100644 --- a/lib/src/editor/block_component/table_block_component/util.dart +++ b/lib/src/editor/block_component/table_block_component/util.dart @@ -4,7 +4,7 @@ import 'package:collection/collection.dart'; Node? getCellNode(Node tableNode, int col, int row) { return tableNode.children.firstWhereOrNull( (n) => - n.attributes[TableBlockKeys.colPosition] == col && - n.attributes[TableBlockKeys.rowPosition] == row, + n.attributes[TableCellBlockKeys.colPosition] == col && + n.attributes[TableCellBlockKeys.rowPosition] == row, ); } diff --git a/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/slash_command.dart b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/slash_command.dart index 0081bb7ed..5d2b41924 100644 --- a/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/slash_command.dart +++ b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/slash_command.dart @@ -34,6 +34,15 @@ CharacterShortcutEvent customSlashCommand( ); } +final supportSlashMenuNodeWhiteList = [ + ParagraphBlockKeys.type, + HeadingBlockKeys.type, + TodoListBlockKeys.type, + BulletedListBlockKeys.type, + NumberedListBlockKeys.type, + QuoteBlockKeys.type, +]; + SelectionMenuService? _selectionMenuService; Future _showSlashMenu( EditorState editorState, @@ -61,6 +70,13 @@ Future _showSlashMenu( return true; } + final node = editorState.getNodeAtPath(selection.start.path); + + // only enable in white-list nodes + if (node == null || !_isSupportSlashMenuNode(node)) { + return false; + } + // insert the slash character if (shouldInsertSlash) { if (kIsWeb) { @@ -94,3 +110,11 @@ Future _showSlashMenu( return true; } + +bool _isSupportSlashMenuNode(Node node) { + var result = supportSlashMenuNodeWhiteList.contains(node.type); + if (node.level > 1 && node.parent != null) { + return result && _isSupportSlashMenuNode(node.parent!); + } + return result; +} diff --git a/test/new/block_component/table_block_component/table_action_test.dart b/test/new/block_component/table_block_component/table_action_test.dart index 3a47ede43..91a2220df 100644 --- a/test/new/block_component/table_block_component/table_action_test.dart +++ b/test/new/block_component/table_block_component/table_action_test.dart @@ -1,7 +1,9 @@ +import 'package:flutter/material.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; -import 'package:flutter_test/flutter_test.dart'; import 'package:appflowy_editor/src/editor/block_component/table_block_component/table_node.dart'; import 'package:appflowy_editor/src/editor/block_component/table_block_component/util.dart'; +import 'package:flutter_test/flutter_test.dart'; + import '../../infra/testable_editor.dart'; void main() async { @@ -20,9 +22,12 @@ void main() async { await editor.startTesting(); await tester.pumpAndSettle(); - final transaction = editor.editorState.transaction; - TableActions.delete(tableNode.node, 0, transaction, TableDirection.col); - editor.editorState.apply(transaction); + TableActions.delete( + tableNode.node, + 0, + editor.editorState, + TableDirection.col, + ); await tester.pump(const Duration(milliseconds: 100)); tableNode = TableNode(node: tableNode.node); @@ -51,10 +56,13 @@ void main() async { await editor.startTesting(); await tester.pumpAndSettle(); - final transaction = editor.editorState.transaction; - TableActions.delete(tableNode.node, 0, transaction, TableDirection.row); - editor.editorState.apply(transaction); - await tester.pump(const Duration(milliseconds: 100)); + TableActions.delete( + tableNode.node, + 0, + editor.editorState, + TableDirection.row, + ); + await tester.pumpAndSettle(const Duration(milliseconds: 200)); tableNode = TableNode(node: tableNode.node); expect(tableNode.rowsLen, 1); @@ -69,6 +77,7 @@ void main() async { } }, ); + await editor.dispose(); }); @@ -82,15 +91,13 @@ void main() async { await editor.startTesting(); await tester.pumpAndSettle(); - final transaction = editor.editorState.transaction; TableActions.duplicate( tableNode.node, 0, - transaction, + editor.editorState, TableDirection.col, ); - editor.editorState.apply(transaction); - await tester.pump(const Duration(milliseconds: 100)); + await tester.pumpAndSettle(const Duration(milliseconds: 100)); tableNode = TableNode(node: tableNode.node); expect(tableNode.colsLen, 3); @@ -113,15 +120,13 @@ void main() async { await editor.startTesting(); await tester.pumpAndSettle(); - final transaction = editor.editorState.transaction; TableActions.duplicate( tableNode.node, 0, - transaction, + editor.editorState, TableDirection.row, ); - editor.editorState.apply(transaction); - await tester.pump(const Duration(milliseconds: 100)); + await tester.pumpAndSettle(const Duration(milliseconds: 100)); tableNode = TableNode(node: tableNode.node); expect(tableNode.rowsLen, 3); @@ -133,5 +138,177 @@ void main() async { } await editor.dispose(); }); + + testWidgets('add column', (tester) async { + var tableNode = TableNode.fromList([ + ['', ''], + ['', ''] + ]); + final editor = tester.editor..addNode(tableNode.node); + + await editor.startTesting(); + await tester.pumpAndSettle(); + + TableActions.add( + tableNode.node, + 2, + editor.editorState, + TableDirection.col, + ); + await tester.pumpAndSettle(const Duration(milliseconds: 100)); + tableNode = TableNode(node: tableNode.node); + + expect(tableNode.colsLen, 3); + expect( + tableNode.getCell(2, 1).children.first.toJson(), + { + "type": "paragraph", + "data": {"delta": []} + }, + ); + expect(tableNode.getColWidth(2), tableNode.config.colDefaultWidth); + await editor.dispose(); + }); + + testWidgets('add row', (tester) async { + var tableNode = TableNode.fromList([ + ['', ''], + ['', ''] + ]); + final editor = tester.editor..addNode(tableNode.node); + + await editor.startTesting(); + await tester.pumpAndSettle(); + + TableActions.add( + tableNode.node, + 2, + editor.editorState, + TableDirection.row, + ); + await tester.pumpAndSettle(const Duration(milliseconds: 100)); + tableNode = TableNode(node: tableNode.node); + + expect(tableNode.rowsLen, 3); + expect( + tableNode.getCell(0, 2).children.first.toJson(), + { + "type": "paragraph", + "data": {"delta": []} + }, + ); + + var cell12 = getCellNode(tableNode.node, 1, 2)!; + expect(tableNode.getRowHeight(2), cell12.children.first.rect.height + 8); + await editor.dispose(); + }); + + testWidgets('set row bg color', (tester) async { + var tableNode = TableNode.fromList([ + ['', ''], + ['', ''] + ]); + final editor = tester.editor..addNode(tableNode.node); + + await editor.startTesting(); + await tester.pumpAndSettle(); + + final color = Colors.green.toHex(); + TableActions.setBgColor( + tableNode.node, + 0, + editor.editorState, + color, + TableDirection.row, + ); + await tester.pumpAndSettle(const Duration(milliseconds: 100)); + + for (var i = 0; i < 2; i++) { + expect( + tableNode + .getCell(i, 0) + .attributes[TableCellBlockKeys.rowBackgroundColor], + color, + ); + } + await editor.dispose(); + }); + + testWidgets('add column respect row bg color', (tester) async { + var tableNode = TableNode.fromList([ + ['', ''], + ['', ''] + ]); + final editor = tester.editor..addNode(tableNode.node); + + await editor.startTesting(); + await tester.pumpAndSettle(); + + final color = Colors.green.toHex(); + TableActions.setBgColor( + tableNode.node, + 0, + editor.editorState, + color, + TableDirection.row, + ); + await tester.pumpAndSettle(const Duration(milliseconds: 100)); + + TableActions.add( + tableNode.node, + 2, + editor.editorState, + TableDirection.col, + ); + await tester.pumpAndSettle(const Duration(milliseconds: 100)); + tableNode = TableNode(node: tableNode.node); + + expect(tableNode.colsLen, 3); + expect( + tableNode + .getCell(2, 0) + .attributes[TableCellBlockKeys.rowBackgroundColor], + color, + ); + await editor.dispose(); + }); + + testWidgets('add row respect column bg color', (tester) async { + var tableNode = TableNode.fromList([ + ['', ''], + ['', ''] + ]); + final editor = tester.editor..addNode(tableNode.node); + + await editor.startTesting(); + await tester.pumpAndSettle(); + + final color = Colors.green.toHex(); + TableActions.setBgColor( + tableNode.node, + 0, + editor.editorState, + color, + TableDirection.col, + ); + await tester.pumpAndSettle(const Duration(milliseconds: 100)); + + TableActions.add( + tableNode.node, + 2, + editor.editorState, + TableDirection.row, + ); + await tester.pumpAndSettle(const Duration(milliseconds: 1000)); + tableNode = TableNode(node: tableNode.node); + + expect(tableNode.rowsLen, 3); + expect( + tableNode + .getCell(0, 2) + .attributes[TableCellBlockKeys.colBackgroundColor], + color, + ); + }); }); } diff --git a/test/new/block_component/table_block_component/table_node_test.dart b/test/new/block_component/table_block_component/table_node_test.dart index 56ddefeaf..69132cee3 100644 --- a/test/new/block_component/table_block_component/table_node_test.dart +++ b/test/new/block_component/table_block_component/table_node_test.dart @@ -20,9 +20,9 @@ void main() { { 'type': TableCellBlockKeys.type, 'data': { - TableBlockKeys.colPosition: 0, - TableBlockKeys.rowPosition: 0, - TableBlockKeys.width: 35, + TableCellBlockKeys.colPosition: 0, + TableCellBlockKeys.rowPosition: 0, + TableCellBlockKeys.width: 35, }, 'children': [ { @@ -39,8 +39,8 @@ void main() { { 'type': TableCellBlockKeys.type, 'data': { - TableBlockKeys.colPosition: 0, - TableBlockKeys.rowPosition: 1, + TableCellBlockKeys.colPosition: 0, + TableCellBlockKeys.rowPosition: 1, }, 'children': [ { @@ -59,8 +59,8 @@ void main() { { 'type': TableCellBlockKeys.type, 'data': { - TableBlockKeys.colPosition: 1, - TableBlockKeys.rowPosition: 0, + TableCellBlockKeys.colPosition: 1, + TableCellBlockKeys.rowPosition: 0, }, 'children': [ { @@ -79,8 +79,8 @@ void main() { { 'type': TableCellBlockKeys.type, 'data': { - TableBlockKeys.colPosition: 1, - TableBlockKeys.rowPosition: 1, + TableCellBlockKeys.colPosition: 1, + TableCellBlockKeys.rowPosition: 1, }, 'children': [ { @@ -160,9 +160,9 @@ void main() { { 'type': TableCellBlockKeys.type, 'data': { - TableBlockKeys.colPosition: 0, - TableBlockKeys.rowPosition: 0, - TableBlockKeys.width: 35, + TableCellBlockKeys.colPosition: 0, + TableCellBlockKeys.rowPosition: 0, + TableCellBlockKeys.width: 35, }, 'children': [ { @@ -179,8 +179,8 @@ void main() { { 'type': TableCellBlockKeys.type, 'data': { - TableBlockKeys.colPosition: 1, - TableBlockKeys.rowPosition: 0, + TableCellBlockKeys.colPosition: 1, + TableCellBlockKeys.rowPosition: 0, }, 'children': [ { @@ -199,8 +199,8 @@ void main() { { 'type': TableCellBlockKeys.type, 'data': { - TableBlockKeys.colPosition: 1, - TableBlockKeys.rowPosition: 1, + TableCellBlockKeys.colPosition: 1, + TableCellBlockKeys.rowPosition: 1, }, 'children': [ { diff --git a/test/new/block_component/table_block_component/table_view_test.dart b/test/new/block_component/table_block_component/table_view_test.dart index cdf7d814d..da549185b 100644 --- a/test/new/block_component/table_block_component/table_view_test.dart +++ b/test/new/block_component/table_block_component/table_view_test.dart @@ -1,7 +1,8 @@ -import 'package:flutter_test/flutter_test.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; -import 'package:appflowy_editor/src/editor/block_component/table_block_component/util.dart'; import 'package:appflowy_editor/src/editor/block_component/table_block_component/table_node.dart'; +import 'package:appflowy_editor/src/editor/block_component/table_block_component/util.dart'; +import 'package:flutter_test/flutter_test.dart'; + import '../../infra/testable_editor.dart'; void main() async { @@ -35,7 +36,10 @@ void main() async { ), ); await editor.ime.insertText('aaaaaaaaa'); - tableNode.updateRowHeight(0); + + final transaction = editor.editorState.transaction; + tableNode.updateRowHeight(0, transaction); + await editor.editorState.apply(transaction); expect(tableNode.getRowHeight(0) != row0beforeHeight, true); expect(tableNode.getRowHeight(0), cell10.children.first.rect.height + 8); @@ -66,73 +70,21 @@ void main() async { ), ); await editor.ime.insertText('aaaaaaaaa'); - tableNode.updateRowHeight(0); + + Transaction transaction = editor.editorState.transaction; + tableNode.updateRowHeight(0, transaction); + await editor.editorState.apply(transaction); expect(tableNode.getRowHeight(0) != row0beforeHeight, true); expect(tableNode.getRowHeight(0), cell10.children.first.rect.height + 8); - tableNode.setColWidth(1, 302.5); - await tester.pump(const Duration(milliseconds: 300)); - - expect(tableNode.getRowHeight(0), row0beforeHeight); - await editor.dispose(); - }); - - testWidgets('add column', (tester) async { - var tableNode = TableNode.fromList([ - ['', ''], - ['', ''] - ]); - final editor = tester.editor..addNode(tableNode.node); - - await editor.startTesting(); - await tester.pumpAndSettle(); - - final transaction = editor.editorState.transaction; - TableActions.add(tableNode.node, 2, transaction, TableDirection.col); - editor.editorState.apply(transaction); - await tester.pump(const Duration(milliseconds: 100)); - tableNode = TableNode(node: tableNode.node); - - expect(tableNode.colsLen, 3); - expect( - tableNode.getCell(2, 1).children.first.toJson(), - { - "type": "paragraph", - "data": {"delta": []} - }, - ); - expect(tableNode.getColWidth(2), tableNode.config.colDefaultWidth); - await editor.dispose(); - }); - - testWidgets('add row', (tester) async { - var tableNode = TableNode.fromList([ - ['', ''], - ['', ''] - ]); - final editor = tester.editor..addNode(tableNode.node); + transaction = editor.editorState.transaction; + tableNode.setColWidth(1, 302.5, transaction); + await editor.editorState.apply(transaction); - await editor.startTesting(); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(const Duration(milliseconds: 300)); - final transaction = editor.editorState.transaction; - TableActions.add(tableNode.node, 2, transaction, TableDirection.row); - editor.editorState.apply(transaction); - await tester.pump(const Duration(milliseconds: 100)); - tableNode = TableNode(node: tableNode.node); - - expect(tableNode.rowsLen, 3); - expect( - tableNode.getCell(0, 2).children.first.toJson(), - { - "type": "paragraph", - "data": {"delta": []} - }, - ); - - var cell12 = getCellNode(tableNode.node, 1, 2)!; - expect(tableNode.getRowHeight(2), cell12.children.first.rect.height + 8); + expect(tableNode.getRowHeight(0), row0beforeHeight); await editor.dispose(); }); });