mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-12-26 22:55:31 +00:00
feat: simple table issues (#6871)
* fix: disable cut command in table cell * feat: only keep the table cell content when coping text from table * fix: focus on the first cell after inserting table * test: focus on the first cell after inserting table * feat: highlight the cell when editing * test: highlight the cell when editing * fix: creating a new row makes a cursor appear for a fraction of a second * fix: add 4px between scroll bar and add row button * chore: rename simple table components * fix: select all in table cell block * test: select all in table cell block * feat: disable two-fingers resize in table cell * feat: includ table when exporting markdown * test: include table when exporting markdown * feat: optimize add row button render logic * chore: optimize hover button render logic * fix: column button is not clickable * fix: theme assertion * feat: improve hovering logic * fix: selection issue in table * fix(flutter_desktop): popover conflicts on simple table * feat: support table markdown import * test: table cell isEditing test * test: select all in table test * fix: popover conflict in table action menu * test: insert row, column, row/column in table * test: delete row, column, row/column in table * test: enable header column and header row in table * test: duplicate/insert left/right/above/below in table * chore: duplicate table in optin menu * fix: integraion test --------- Co-authored-by: Richard Shiue <71320345+richardshiue@users.noreply.github.com>
This commit is contained in:
parent
550b8835c6
commit
e7491e5182
@ -1,8 +1,12 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/_shared_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
import '../../shared/util.dart';
|
||||
|
||||
@ -17,26 +21,350 @@ void main() {
|
||||
testWidgets('insert a simple table block', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'simple_table_test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await insertTableInDocument(tester);
|
||||
await tester.insertTableInDocument();
|
||||
|
||||
// validate the table is inserted
|
||||
expect(find.byType(SimpleTableBlockWidget), findsOneWidget);
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
expect(
|
||||
editorState.selection,
|
||||
// table -> row -> cell -> paragraph
|
||||
Selection.collapsed(Position(path: [0, 0, 0, 0])),
|
||||
);
|
||||
|
||||
final firstCell = find.byType(SimpleTableCellBlockWidget).first;
|
||||
expect(
|
||||
tester
|
||||
.state<SimpleTableCellBlockWidgetState>(firstCell)
|
||||
.isEditingCellNotifier
|
||||
.value,
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('select all in table cell', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'simple_table_test',
|
||||
);
|
||||
|
||||
const cell1Content = 'Cell 1';
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.ime.insertText('New Table');
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.editor.tapLineOfEditorAt(1);
|
||||
await tester.insertTableInDocument();
|
||||
await tester.ime.insertText(cell1Content);
|
||||
await tester.pumpAndSettle();
|
||||
// Select all in the cell
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyA,
|
||||
isControlPressed: !UniversalPlatform.isMacOS,
|
||||
isMetaPressed: UniversalPlatform.isMacOS,
|
||||
);
|
||||
|
||||
expect(
|
||||
tester.editor.getCurrentEditorState().selection,
|
||||
Selection(
|
||||
start: Position(path: [1, 0, 0, 0]),
|
||||
end: Position(path: [1, 0, 0, 0], offset: cell1Content.length),
|
||||
),
|
||||
);
|
||||
|
||||
// Press select all again, the selection should be the entire document
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyA,
|
||||
isControlPressed: !UniversalPlatform.isMacOS,
|
||||
isMetaPressed: UniversalPlatform.isMacOS,
|
||||
);
|
||||
|
||||
expect(
|
||||
tester.editor.getCurrentEditorState().selection,
|
||||
Selection(
|
||||
start: Position(path: [0]),
|
||||
end: Position(path: [1, 1, 1, 0]),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('''
|
||||
1. hover on the table
|
||||
1.1 click the add row button
|
||||
1.2 click the add column button
|
||||
1.3 click the add row and column button
|
||||
2. validate the table is updated
|
||||
3. delete the last column
|
||||
4. delete the last row
|
||||
5. validate the table is updated
|
||||
''', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'simple_table_test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.insertTableInDocument();
|
||||
|
||||
// hover on the table
|
||||
final tableBlock = find.byType(SimpleTableBlockWidget).first;
|
||||
await tester.hoverOnWidget(
|
||||
tableBlock,
|
||||
onHover: () async {
|
||||
// click the add row button
|
||||
final addRowButton = find.byType(SimpleTableAddRowButton).first;
|
||||
await tester.tap(addRowButton);
|
||||
|
||||
// click the add column button
|
||||
final addColumnButton = find.byType(SimpleTableAddColumnButton).first;
|
||||
await tester.tap(addColumnButton);
|
||||
|
||||
// click the add row and column button
|
||||
final addRowAndColumnButton =
|
||||
find.byType(SimpleTableAddColumnAndRowButton).first;
|
||||
await tester.tap(addRowAndColumnButton);
|
||||
},
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final tableNode =
|
||||
tester.editor.getCurrentEditorState().document.nodeAtPath([0])!;
|
||||
expect(tableNode.columnLength, 4);
|
||||
expect(tableNode.rowLength, 4);
|
||||
|
||||
// delete the last row
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.row,
|
||||
index: tableNode.rowLength - 1,
|
||||
action: SimpleTableMoreAction.delete,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(tableNode.rowLength, 3);
|
||||
expect(tableNode.columnLength, 4);
|
||||
|
||||
// delete the last column
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.column,
|
||||
index: tableNode.columnLength - 1,
|
||||
action: SimpleTableMoreAction.delete,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(tableNode.columnLength, 3);
|
||||
expect(tableNode.rowLength, 3);
|
||||
});
|
||||
|
||||
testWidgets('enable header column and header row', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'simple_table_test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.insertTableInDocument();
|
||||
|
||||
// enable the header row
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.row,
|
||||
index: 0,
|
||||
action: SimpleTableMoreAction.enableHeaderRow,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
// enable the header column
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.column,
|
||||
index: 0,
|
||||
action: SimpleTableMoreAction.enableHeaderColumn,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final tableNode =
|
||||
tester.editor.getCurrentEditorState().document.nodeAtPath([0])!;
|
||||
|
||||
expect(tableNode.isHeaderColumnEnabled, isTrue);
|
||||
expect(tableNode.isHeaderRowEnabled, isTrue);
|
||||
|
||||
// disable the header row
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.row,
|
||||
index: 0,
|
||||
action: SimpleTableMoreAction.enableHeaderRow,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(tableNode.isHeaderColumnEnabled, isTrue);
|
||||
expect(tableNode.isHeaderRowEnabled, isFalse);
|
||||
|
||||
// disable the header column
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.column,
|
||||
index: 0,
|
||||
action: SimpleTableMoreAction.enableHeaderColumn,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(tableNode.isHeaderColumnEnabled, isFalse);
|
||||
expect(tableNode.isHeaderRowEnabled, isFalse);
|
||||
});
|
||||
|
||||
testWidgets('duplicate a column / row', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'simple_table_test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.insertTableInDocument();
|
||||
|
||||
// duplicate the row
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.row,
|
||||
index: 0,
|
||||
action: SimpleTableMoreAction.duplicate,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// duplicate the column
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.column,
|
||||
index: 0,
|
||||
action: SimpleTableMoreAction.duplicate,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final tableNode =
|
||||
tester.editor.getCurrentEditorState().document.nodeAtPath([0])!;
|
||||
expect(tableNode.columnLength, 3);
|
||||
expect(tableNode.rowLength, 3);
|
||||
});
|
||||
|
||||
testWidgets('insert left / insert right', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'simple_table_test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.insertTableInDocument();
|
||||
|
||||
// insert left
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.column,
|
||||
index: 0,
|
||||
action: SimpleTableMoreAction.insertLeft,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// insert right
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.column,
|
||||
index: 0,
|
||||
action: SimpleTableMoreAction.insertRight,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final tableNode =
|
||||
tester.editor.getCurrentEditorState().document.nodeAtPath([0])!;
|
||||
expect(tableNode.columnLength, 4);
|
||||
expect(tableNode.rowLength, 2);
|
||||
});
|
||||
|
||||
testWidgets('insert above / insert below', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'simple_table_test',
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.insertTableInDocument();
|
||||
|
||||
// insert above
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.row,
|
||||
index: 0,
|
||||
action: SimpleTableMoreAction.insertAbove,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// insert below
|
||||
await tester.clickMoreActionItemInTableMenu(
|
||||
type: SimpleTableMoreActionType.row,
|
||||
index: 0,
|
||||
action: SimpleTableMoreAction.insertBelow,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final tableNode =
|
||||
tester.editor.getCurrentEditorState().document.nodeAtPath([0])!;
|
||||
expect(tableNode.rowLength, 4);
|
||||
expect(tableNode.columnLength, 2);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Insert a table in the document
|
||||
Future<void> insertTableInDocument(WidgetTester tester) async {
|
||||
// open the actions menu and insert the outline block
|
||||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_name_table.tr(),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
extension on WidgetTester {
|
||||
/// Insert a table in the document
|
||||
Future<void> insertTableInDocument() async {
|
||||
// open the actions menu and insert the outline block
|
||||
await editor.showSlashMenu();
|
||||
await editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_name_table.tr(),
|
||||
);
|
||||
await pumpAndSettle();
|
||||
}
|
||||
|
||||
Future<void> clickMoreActionItemInTableMenu({
|
||||
required SimpleTableMoreActionType type,
|
||||
required int index,
|
||||
required SimpleTableMoreAction action,
|
||||
}) async {
|
||||
if (type == SimpleTableMoreActionType.row) {
|
||||
final row = find.byWidgetPredicate((w) {
|
||||
return w is SimpleTableRowBlockWidget && w.node.rowIndex == index;
|
||||
});
|
||||
await hoverOnWidget(
|
||||
row,
|
||||
onHover: () async {
|
||||
final moreActionButton = find.byWidgetPredicate((w) {
|
||||
return w is SimpleTableMoreActionMenu &&
|
||||
w.type == SimpleTableMoreActionType.row &&
|
||||
w.index == index;
|
||||
});
|
||||
await tapButton(moreActionButton);
|
||||
await tapButton(find.text(action.name));
|
||||
},
|
||||
);
|
||||
await pumpAndSettle();
|
||||
} else if (type == SimpleTableMoreActionType.column) {
|
||||
final column = find.byWidgetPredicate((w) {
|
||||
return w is SimpleTableCellBlockWidget && w.node.columnIndex == index;
|
||||
}).first;
|
||||
await hoverOnWidget(
|
||||
column,
|
||||
onHover: () async {
|
||||
final moreActionButton = find.byWidgetPredicate((w) {
|
||||
return w is SimpleTableMoreActionMenu &&
|
||||
w.type == SimpleTableMoreActionType.column &&
|
||||
w.index == index;
|
||||
});
|
||||
await tapButton(moreActionButton);
|
||||
await tapButton(find.text(action.name));
|
||||
},
|
||||
);
|
||||
await pumpAndSettle();
|
||||
}
|
||||
|
||||
await tapAt(Offset.zero);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
@ -87,7 +88,7 @@ void main() {
|
||||
);
|
||||
expect(
|
||||
importedPageEditorState.getNodeAtPath([2])!.type,
|
||||
TableBlockKeys.type,
|
||||
SimpleTableBlockKeys.type,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -5,10 +5,6 @@ import 'package:appflowy/plugins/document/presentation/editor_page.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/mobile_block_action_buttons.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/code_block/code_block_copy_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_cell_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_row_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/table_operations/table_operations.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
|
||||
@ -147,6 +143,8 @@ void _customBlockOptionActions(
|
||||
level > 0) {
|
||||
final offset = [14.0, 11.0, 8.0, 6.0, 4.0, 2.0];
|
||||
top += offset[level - 1];
|
||||
} else if (type == SimpleTableBlockKeys.type) {
|
||||
top += 8.0;
|
||||
} else {
|
||||
top += 2.0;
|
||||
}
|
||||
|
||||
@ -34,7 +34,6 @@ class BlockActionList extends StatelessWidget {
|
||||
editorState: editorState,
|
||||
showSlashMenu: showSlashMenu,
|
||||
),
|
||||
const HSpace(4.0),
|
||||
BlockOptionButton(
|
||||
blockComponentContext: blockComponentContext,
|
||||
blockComponentState: blockComponentState,
|
||||
@ -42,7 +41,7 @@ class BlockActionList extends StatelessWidget {
|
||||
editorState: editorState,
|
||||
blockComponentBuilder: blockComponentBuilder,
|
||||
),
|
||||
const HSpace(4.0),
|
||||
const HSpace(8.0),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@ -86,6 +86,11 @@ class BlockActionOptionCubit extends Cubit<BlockActionOptionState> {
|
||||
Log.error('Block type $type is not valid');
|
||||
if (node.type == TableBlockKeys.type) {
|
||||
copiedNode = _fixTableBlock(node);
|
||||
copiedNode = _convertTableToSimpleTable(copiedNode);
|
||||
}
|
||||
} else {
|
||||
if (node.type == TableBlockKeys.type) {
|
||||
copiedNode = _convertTableToSimpleTable(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -138,6 +143,44 @@ class BlockActionOptionCubit extends Cubit<BlockActionOptionState> {
|
||||
);
|
||||
}
|
||||
|
||||
Node _convertTableToSimpleTable(Node node) {
|
||||
if (node.type != TableBlockKeys.type) {
|
||||
return node;
|
||||
}
|
||||
|
||||
// the table node should contains colsLen and rowsLen
|
||||
final colsLen = node.attributes[TableBlockKeys.colsLen];
|
||||
final rowsLen = node.attributes[TableBlockKeys.rowsLen];
|
||||
if (colsLen == null || rowsLen == null) {
|
||||
return node;
|
||||
}
|
||||
|
||||
final rows = <List<Node>>[];
|
||||
final children = node.children;
|
||||
for (var i = 0; i < rowsLen; i++) {
|
||||
final row = <Node>[];
|
||||
for (var j = 0; j < colsLen; j++) {
|
||||
final cell = children
|
||||
.where(
|
||||
(n) =>
|
||||
n.attributes[TableCellBlockKeys.rowPosition] == i &&
|
||||
n.attributes[TableCellBlockKeys.colPosition] == j,
|
||||
)
|
||||
.firstOrNull;
|
||||
row.add(
|
||||
simpleTableCellBlockNode(
|
||||
children: [cell?.children.first.copyWith() ?? paragraphNode()],
|
||||
),
|
||||
);
|
||||
}
|
||||
rows.add(row);
|
||||
}
|
||||
|
||||
return simpleTableBlockNode(
|
||||
children: rows.map((e) => simpleTableRowBlockNode(children: e)).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _copyLinkToBlock(Node node) async {
|
||||
final context = editorState.document.root.context;
|
||||
final viewId = context?.read<DocumentBloc>().documentId;
|
||||
@ -447,7 +490,7 @@ class BlockActionOptionCubit extends Cubit<BlockActionOptionState> {
|
||||
// We move views after applying transaction to avoid performing side-effects on the views
|
||||
final viewIdsToMove = _extractChildViewIds(selectedNodes);
|
||||
for (final viewId in viewIdsToMove) {
|
||||
// Attempt to put back from trash if neccessary
|
||||
// Attempt to put back from trash if necessary
|
||||
await TrashService.putback(viewId);
|
||||
|
||||
await ViewBackendService.moveViewV2(
|
||||
|
||||
@ -2,7 +2,6 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_option_cubit.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
@ -111,9 +110,7 @@ class _OptionButtonState extends State<OptionButton> {
|
||||
widget.blockComponentContext.node,
|
||||
beforeSelection,
|
||||
);
|
||||
Log.info(
|
||||
'update block selection, beforeSelection: $beforeSelection, afterSelection: $selection',
|
||||
);
|
||||
|
||||
widget.editorState.updateSelectionWithReason(
|
||||
selection,
|
||||
customSelectionType: SelectionType.block,
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/sub_page/sub_page_block_component.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table.dart';
|
||||
|
||||
/// Copy.
|
||||
///
|
||||
@ -59,11 +59,12 @@ KeyEventResult handleCopyCommand(
|
||||
// plain text.
|
||||
text = editorState.getTextInSelection(selection).join('\n');
|
||||
|
||||
final selectedNodes = editorState.getSelectedNodes(selection: selection);
|
||||
final nodes = _handleSubPageNodes(selectedNodes, isCut);
|
||||
final document = Document.blank()..insert([0], nodes);
|
||||
final document = _buildCopiedDocument(
|
||||
editorState,
|
||||
selection,
|
||||
isCut: isCut,
|
||||
);
|
||||
|
||||
// in app json
|
||||
inAppJson = jsonEncode(document.toJson());
|
||||
|
||||
// html
|
||||
@ -83,6 +84,34 @@ KeyEventResult handleCopyCommand(
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
|
||||
Document _buildCopiedDocument(
|
||||
EditorState editorState,
|
||||
Selection selection, {
|
||||
bool isCut = false,
|
||||
}) {
|
||||
// filter the table nodes
|
||||
final filteredNodes = <Node>[];
|
||||
final selectedNodes = editorState.getSelectedNodes(selection: selection);
|
||||
final nodes = _handleSubPageNodes(selectedNodes, isCut);
|
||||
for (final node in nodes) {
|
||||
if (node.type == SimpleTableCellBlockKeys.type) {
|
||||
// if the node is a table cell, we will fetch its children instead.
|
||||
filteredNodes.addAll(node.children);
|
||||
} else if (node.type == SimpleTableRowBlockKeys.type) {
|
||||
// if the node is a table row, we will fetch its children instead.
|
||||
filteredNodes.addAll(node.children.expand((e) => e.children));
|
||||
} else {
|
||||
filteredNodes.add(node);
|
||||
}
|
||||
}
|
||||
final document = Document.blank()
|
||||
..insert(
|
||||
[0],
|
||||
filteredNodes.map((e) => e.copyWith()),
|
||||
);
|
||||
return document;
|
||||
}
|
||||
|
||||
List<Node> _handleSubPageNodes(List<Node> nodes, [bool isCut = false]) {
|
||||
final handled = <Node>[];
|
||||
for (final node in nodes) {
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy/shared/clipboard_state.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
/// cut.
|
||||
@ -42,6 +41,11 @@ CommandShortcutEventHandler _cutCommandHandler = (editorState) {
|
||||
if (node == null) {
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
// prevent to cut the node that is selecting the table.
|
||||
if (node.parentTableNode != null) {
|
||||
return KeyEventResult.skipRemainingHandlers;
|
||||
}
|
||||
|
||||
final transaction = editorState.transaction;
|
||||
transaction.deleteNode(node);
|
||||
final nextNode = node.next;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/shortcuts/table_command_extension.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:appflowy_editor_plugins/appflowy_editor_plugins.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import 'package:appflowy/shared/markdown_to_document.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
extension AskAINodeExtension on EditorState {
|
||||
@ -32,7 +33,7 @@ extension AskAINodeExtension on EditorState {
|
||||
slicedNodes.add(copiedNode);
|
||||
}
|
||||
|
||||
final markdown = documentToMarkdown(
|
||||
final markdown = customDocumentToMarkdown(
|
||||
Document.blank()..insert([0], slicedNodes),
|
||||
);
|
||||
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
export 'callout_node_parser.dart';
|
||||
export 'custom_image_node_parser.dart';
|
||||
export 'markdown_code_parser.dart';
|
||||
export 'math_equation_node_parser.dart';
|
||||
export 'toggle_list_node_parser.dart';
|
||||
export 'simple_table_parser.dart';
|
||||
|
||||
@ -0,0 +1,103 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:markdown/markdown.dart' as md;
|
||||
|
||||
class MarkdownSimpleTableParser extends CustomMarkdownParser {
|
||||
const MarkdownSimpleTableParser();
|
||||
|
||||
@override
|
||||
List<Node> transform(
|
||||
md.Node element,
|
||||
List<CustomMarkdownParser> parsers, {
|
||||
MarkdownListType listType = MarkdownListType.unknown,
|
||||
int? startNumber,
|
||||
}) {
|
||||
if (element is! md.Element) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (element.tag != 'table') {
|
||||
return [];
|
||||
}
|
||||
|
||||
final ec = element.children;
|
||||
if (ec == null || ec.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final th = ec
|
||||
.whereType<md.Element>()
|
||||
.where((e) => e.tag == 'thead')
|
||||
.firstOrNull
|
||||
?.children
|
||||
?.whereType<md.Element>()
|
||||
.where((e) => e.tag == 'tr')
|
||||
.expand((e) => e.children?.whereType<md.Element>().toList() ?? [])
|
||||
.where((e) => e.tag == 'th')
|
||||
.toList();
|
||||
|
||||
final tr = ec
|
||||
.whereType<md.Element>()
|
||||
.where((e) => e.tag == 'tbody')
|
||||
.firstOrNull
|
||||
?.children
|
||||
?.whereType<md.Element>()
|
||||
.where((e) => e.tag == 'tr')
|
||||
.toList();
|
||||
|
||||
if (th == null || tr == null || th.isEmpty || tr.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final rows = <Node>[];
|
||||
|
||||
// Add header cells
|
||||
|
||||
rows.add(
|
||||
simpleTableRowBlockNode(
|
||||
children: th
|
||||
.map(
|
||||
(e) => simpleTableCellBlockNode(
|
||||
children: [
|
||||
paragraphNode(
|
||||
delta: DeltaMarkdownDecoder().convertNodes(e.children),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
|
||||
// Add body cells
|
||||
for (var i = 0; i < tr.length; i++) {
|
||||
final td = tr[i]
|
||||
.children
|
||||
?.whereType<md.Element>()
|
||||
.where((e) => e.tag == 'td')
|
||||
.toList();
|
||||
|
||||
if (td == null || td.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
rows.add(
|
||||
simpleTableRowBlockNode(
|
||||
children: td
|
||||
.map(
|
||||
(e) => simpleTableCellBlockNode(
|
||||
children: [
|
||||
paragraphNode(
|
||||
delta: DeltaMarkdownDecoder().convertNodes(e.children),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return [simpleTableBlockNode(children: rows)];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,86 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
/// Parser for converting SimpleTable nodes to markdown format
|
||||
class SimpleTableNodeParser extends NodeParser {
|
||||
const SimpleTableNodeParser();
|
||||
|
||||
@override
|
||||
String get id => SimpleTableBlockKeys.type;
|
||||
|
||||
@override
|
||||
String transform(Node node, DocumentMarkdownEncoder? encoder) {
|
||||
try {
|
||||
final tableData = _extractTableData(node);
|
||||
if (tableData.isEmpty) {
|
||||
return '';
|
||||
}
|
||||
return _buildMarkdownTable(tableData);
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracts table data from the node structure into a 2D list of strings
|
||||
/// Each inner list represents a row, and each string represents a cell's content
|
||||
List<List<String>> _extractTableData(Node node) {
|
||||
final tableData = <List<String>>[];
|
||||
final rows = node.children;
|
||||
|
||||
for (final row in rows) {
|
||||
final rowData = _extractRowData(row);
|
||||
tableData.add(rowData);
|
||||
}
|
||||
|
||||
return tableData;
|
||||
}
|
||||
|
||||
/// Extracts data from a single table row
|
||||
List<String> _extractRowData(Node row) {
|
||||
final rowData = <String>[];
|
||||
final cells = row.children;
|
||||
|
||||
for (final cell in cells) {
|
||||
final content = _extractCellContent(cell);
|
||||
rowData.add(content);
|
||||
}
|
||||
|
||||
return rowData;
|
||||
}
|
||||
|
||||
/// Extracts and formats content from a single table cell
|
||||
String _extractCellContent(Node cell) {
|
||||
final contentBuffer = StringBuffer();
|
||||
|
||||
for (final child in cell.children) {
|
||||
final delta = child.delta;
|
||||
if (delta == null) continue;
|
||||
|
||||
final content = DeltaMarkdownEncoder().convert(delta);
|
||||
// Escape pipe characters to prevent breaking markdown table structure
|
||||
contentBuffer.write(content.replaceAll('|', '\\|'));
|
||||
}
|
||||
|
||||
return contentBuffer.toString();
|
||||
}
|
||||
|
||||
/// Builds a markdown table string from the extracted table data
|
||||
/// First row is treated as header, followed by separator row and data rows
|
||||
String _buildMarkdownTable(List<List<String>> tableData) {
|
||||
final markdown = StringBuffer();
|
||||
final columnCount = tableData[0].length;
|
||||
|
||||
// Add header row
|
||||
markdown.writeln('|${tableData[0].join('|')}|');
|
||||
|
||||
// Add separator row
|
||||
markdown.writeln('|${List.filled(columnCount, '---').join('|')}|');
|
||||
|
||||
// Add data rows (skip header row)
|
||||
for (int i = 1; i < tableData.length; i++) {
|
||||
markdown.writeln('|${tableData[i].join('|')}|');
|
||||
}
|
||||
|
||||
return markdown.toString();
|
||||
}
|
||||
}
|
||||
@ -58,9 +58,11 @@ export 'openai/widgets/ask_ai_block_component.dart';
|
||||
export 'openai/widgets/ask_ai_toolbar_item.dart';
|
||||
export 'outline/outline_block_component.dart';
|
||||
export 'parsers/markdown_parsers.dart';
|
||||
export 'parsers/markdown_simple_table_parser.dart';
|
||||
export 'quote/quote_block_shortcuts.dart';
|
||||
export 'shortcuts/character_shortcuts.dart';
|
||||
export 'shortcuts/command_shortcuts.dart';
|
||||
export 'simple_table/simple_table.dart';
|
||||
export 'slash_menu/slash_menu_items.dart';
|
||||
export 'sub_page/sub_page_block_component.dart';
|
||||
export 'table/table_menu.dart';
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/align_toolbar_item/custom_text_align_command.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/shortcuts/simple_table_commands.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/undo_redo/custom_undo_redo_commands.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/emoji_picker/emoji_picker.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_constants.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_more_action.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/table_operations/table_operations.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
@ -12,6 +10,10 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
/// Only displaying the add row / add column / add column and row button
|
||||
/// when hovering on the last row / last column / last cell.
|
||||
bool _enableHoveringLogicV2 = true;
|
||||
|
||||
class SimpleTableReorderButton extends StatelessWidget {
|
||||
const SimpleTableReorderButton({
|
||||
super.key,
|
||||
@ -53,45 +55,99 @@ class SimpleTableReorderButton extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleTableAddRowHoverButton extends StatelessWidget {
|
||||
class SimpleTableAddRowHoverButton extends StatefulWidget {
|
||||
const SimpleTableAddRowHoverButton({
|
||||
super.key,
|
||||
required this.editorState,
|
||||
required this.node,
|
||||
required this.tableNode,
|
||||
});
|
||||
|
||||
final EditorState editorState;
|
||||
final Node node;
|
||||
final Node tableNode;
|
||||
|
||||
@override
|
||||
State<SimpleTableAddRowHoverButton> createState() =>
|
||||
_SimpleTableAddRowHoverButtonState();
|
||||
}
|
||||
|
||||
class _SimpleTableAddRowHoverButtonState
|
||||
extends State<SimpleTableAddRowHoverButton> {
|
||||
late final interceptorKey =
|
||||
'simple_table_add_row_hover_button_${widget.tableNode.id}';
|
||||
|
||||
SelectionGestureInterceptor? interceptor;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
interceptor = SelectionGestureInterceptor(
|
||||
key: interceptorKey,
|
||||
canTap: (details) => !_isTapInBounds(details.globalPosition),
|
||||
);
|
||||
widget.editorState.service.selectionService
|
||||
.registerGestureInterceptor(interceptor!);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.editorState.service.selectionService.unregisterGestureInterceptor(
|
||||
interceptorKey,
|
||||
);
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(node.type == SimpleTableBlockKeys.type);
|
||||
assert(widget.tableNode.type == SimpleTableBlockKeys.type);
|
||||
|
||||
if (node.type != SimpleTableBlockKeys.type) {
|
||||
if (widget.tableNode.type != SimpleTableBlockKeys.type) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final simpleTableContext = context.read<SimpleTableContext>();
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: context.read<SimpleTableContext>().hoveringTableCell,
|
||||
builder: (context, tableCell, child) {
|
||||
if (tableCell == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final showRowButton = tableCell.rowIndex + 1 == tableCell.rowLength;
|
||||
return showRowButton
|
||||
? Positioned(
|
||||
bottom: 0,
|
||||
left: SimpleTableConstants.tableLeftPadding -
|
||||
SimpleTableConstants.cellBorderWidth,
|
||||
right: SimpleTableConstants.addRowButtonRightPadding,
|
||||
child: SimpleTableAddRowButton(
|
||||
onTap: () => editorState.addRowInTable(node),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
valueListenable: simpleTableContext.isHoveringOnTableBlock,
|
||||
builder: (context, isHoveringOnTableBlock, child) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: simpleTableContext.hoveringTableCell,
|
||||
builder: (context, hoveringTableCell, child) {
|
||||
bool shouldShow = isHoveringOnTableBlock;
|
||||
if (hoveringTableCell != null && _enableHoveringLogicV2) {
|
||||
shouldShow =
|
||||
hoveringTableCell.rowIndex + 1 == hoveringTableCell.rowLength;
|
||||
}
|
||||
return shouldShow
|
||||
? Positioned(
|
||||
bottom: 2 * SimpleTableConstants.addRowButtonPadding,
|
||||
left: SimpleTableConstants.tableLeftPadding -
|
||||
SimpleTableConstants.cellBorderWidth,
|
||||
right: SimpleTableConstants.addRowButtonRightPadding,
|
||||
child: SimpleTableAddRowButton(
|
||||
onTap: () => widget.editorState.addRowInTable(
|
||||
widget.tableNode,
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
bool _isTapInBounds(Offset offset) {
|
||||
final renderBox = context.findRenderObject() as RenderBox?;
|
||||
if (renderBox == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final localPosition = renderBox.globalToLocal(offset);
|
||||
final result = renderBox.paintBounds.contains(localPosition);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleTableAddRowButton extends StatelessWidget {
|
||||
@ -107,14 +163,14 @@ class SimpleTableAddRowButton extends StatelessWidget {
|
||||
return FlowyTooltip(
|
||||
message: LocaleKeys.document_plugins_simpleTable_clickToAddNewRow.tr(),
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: onTap,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: Container(
|
||||
height: SimpleTableConstants.addRowButtonHeight,
|
||||
margin: const EdgeInsets.symmetric(
|
||||
vertical: SimpleTableConstants.addRowButtonPadding,
|
||||
vertical: SimpleTableConstants.addColumnButtonPadding,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(
|
||||
@ -132,7 +188,7 @@ class SimpleTableAddRowButton extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleTableAddColumnHoverButton extends StatelessWidget {
|
||||
class SimpleTableAddColumnHoverButton extends StatefulWidget {
|
||||
const SimpleTableAddColumnHoverButton({
|
||||
super.key,
|
||||
required this.editorState,
|
||||
@ -143,37 +199,88 @@ class SimpleTableAddColumnHoverButton extends StatelessWidget {
|
||||
final Node node;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(node.type == SimpleTableBlockKeys.type);
|
||||
State<SimpleTableAddColumnHoverButton> createState() =>
|
||||
_SimpleTableAddColumnHoverButtonState();
|
||||
}
|
||||
|
||||
if (node.type != SimpleTableBlockKeys.type) {
|
||||
class _SimpleTableAddColumnHoverButtonState
|
||||
extends State<SimpleTableAddColumnHoverButton> {
|
||||
late final interceptorKey =
|
||||
'simple_table_add_column_hover_button_${widget.node.id}';
|
||||
|
||||
SelectionGestureInterceptor? interceptor;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
interceptor = SelectionGestureInterceptor(
|
||||
key: interceptorKey,
|
||||
canTap: (details) => !_isTapInBounds(details.globalPosition),
|
||||
);
|
||||
widget.editorState.service.selectionService
|
||||
.registerGestureInterceptor(interceptor!);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.editorState.service.selectionService.unregisterGestureInterceptor(
|
||||
interceptorKey,
|
||||
);
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(widget.node.type == SimpleTableBlockKeys.type);
|
||||
|
||||
if (widget.node.type != SimpleTableBlockKeys.type) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: context.read<SimpleTableContext>().hoveringTableCell,
|
||||
builder: (context, tableCell, child) {
|
||||
if (tableCell == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final showColumnButton =
|
||||
tableCell.columnIndex + 1 == tableCell.columnLength;
|
||||
return showColumnButton
|
||||
? Positioned(
|
||||
top: SimpleTableConstants.tableTopPadding -
|
||||
SimpleTableConstants.cellBorderWidth,
|
||||
bottom: SimpleTableConstants.addColumnButtonBottomPadding,
|
||||
right: 0,
|
||||
child: SimpleTableAddColumnButton(
|
||||
onTap: () {
|
||||
editorState.addColumnInTable(node);
|
||||
},
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
valueListenable:
|
||||
context.read<SimpleTableContext>().isHoveringOnTableBlock,
|
||||
builder: (context, isHoveringOnTableBlock, _) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: context.read<SimpleTableContext>().hoveringTableCell,
|
||||
builder: (context, hoveringTableCell, _) {
|
||||
bool shouldShow = isHoveringOnTableBlock;
|
||||
if (hoveringTableCell != null && _enableHoveringLogicV2) {
|
||||
shouldShow = hoveringTableCell.columnIndex + 1 ==
|
||||
hoveringTableCell.columnLength;
|
||||
}
|
||||
return shouldShow
|
||||
? Positioned(
|
||||
top: SimpleTableConstants.tableTopPadding -
|
||||
SimpleTableConstants.cellBorderWidth,
|
||||
bottom: SimpleTableConstants.addColumnButtonBottomPadding,
|
||||
right: 0,
|
||||
child: SimpleTableAddColumnButton(
|
||||
onTap: () {
|
||||
widget.editorState.addColumnInTable(widget.node);
|
||||
},
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
bool _isTapInBounds(Offset offset) {
|
||||
final renderBox = context.findRenderObject() as RenderBox?;
|
||||
if (renderBox == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final localPosition = renderBox.globalToLocal(offset);
|
||||
final result = renderBox.paintBounds.contains(localPosition);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleTableAddColumnButton extends StatelessWidget {
|
||||
@ -233,23 +340,28 @@ class SimpleTableAddColumnAndRowHoverButton extends StatelessWidget {
|
||||
}
|
||||
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: context.read<SimpleTableContext>().hoveringTableCell,
|
||||
builder: (context, tableCell, child) {
|
||||
if (tableCell == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final showAddColumnAndRowButton =
|
||||
tableCell.rowIndex + 1 == tableCell.rowLength ||
|
||||
tableCell.columnIndex + 1 == tableCell.columnLength;
|
||||
return showAddColumnAndRowButton
|
||||
? Positioned(
|
||||
bottom: SimpleTableConstants.addRowButtonPadding,
|
||||
right: SimpleTableConstants.addColumnButtonPadding,
|
||||
child: SimpleTableAddColumnAndRowButton(
|
||||
onTap: () => editorState.addColumnAndRowInTable(node),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
valueListenable:
|
||||
context.read<SimpleTableContext>().isHoveringOnTableBlock,
|
||||
builder: (context, isHoveringOnTableBlock, child) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: context.read<SimpleTableContext>().hoveringTableCell,
|
||||
builder: (context, hoveringTableCell, child) {
|
||||
bool shouldShow = isHoveringOnTableBlock;
|
||||
if (hoveringTableCell != null && _enableHoveringLogicV2) {
|
||||
shouldShow = hoveringTableCell.isLastCellInTable;
|
||||
}
|
||||
return shouldShow
|
||||
? Positioned(
|
||||
bottom:
|
||||
SimpleTableConstants.addColumnAndRowButtonBottomPadding,
|
||||
right: SimpleTableConstants.addColumnButtonPadding,
|
||||
child: SimpleTableAddColumnAndRowButton(
|
||||
onTap: () => editorState.addColumnAndRowInTable(node),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -335,9 +447,6 @@ class SimpleTableAlignMenu extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _SimpleTableAlignMenuState extends State<SimpleTableAlignMenu> {
|
||||
final PopoverController controller = PopoverController();
|
||||
bool isOpen = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final align = switch (widget.type) {
|
||||
@ -345,34 +454,31 @@ class _SimpleTableAlignMenuState extends State<SimpleTableAlignMenu> {
|
||||
SimpleTableMoreActionType.row => widget.tableCellNode.rowAlign,
|
||||
};
|
||||
return AppFlowyPopover(
|
||||
controller: controller,
|
||||
asBarrier: true,
|
||||
mutex: widget.mutex,
|
||||
child: SimpleTableBasicButton(
|
||||
leftIconSvg: align.leftIconSvg,
|
||||
text: LocaleKeys.document_plugins_simpleTable_moreActions_align.tr(),
|
||||
onTap: () {
|
||||
if (!isOpen) {
|
||||
controller.show();
|
||||
}
|
||||
},
|
||||
onTap: () {},
|
||||
),
|
||||
onClose: () => isOpen = false,
|
||||
popupBuilder: (_) {
|
||||
isOpen = true;
|
||||
popupBuilder: (popoverContext) {
|
||||
void onClose() => PopoverContainer.of(popoverContext).closeAll();
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildAlignButton(context, TableAlign.left),
|
||||
_buildAlignButton(context, TableAlign.center),
|
||||
_buildAlignButton(context, TableAlign.right),
|
||||
_buildAlignButton(context, TableAlign.left, onClose),
|
||||
_buildAlignButton(context, TableAlign.center, onClose),
|
||||
_buildAlignButton(context, TableAlign.right, onClose),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAlignButton(BuildContext context, TableAlign align) {
|
||||
Widget _buildAlignButton(
|
||||
BuildContext context,
|
||||
TableAlign align,
|
||||
VoidCallback onClose,
|
||||
) {
|
||||
return SimpleTableBasicButton(
|
||||
leftIconSvg: align.leftIconSvg,
|
||||
text: align.name,
|
||||
@ -392,7 +498,7 @@ class _SimpleTableAlignMenuState extends State<SimpleTableAlignMenu> {
|
||||
break;
|
||||
}
|
||||
|
||||
PopoverContainer.of(context).close();
|
||||
onClose();
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -481,15 +587,25 @@ class _SimpleTableColumnResizeHandleState
|
||||
},
|
||||
child: GestureDetector(
|
||||
onHorizontalDragStart: (details) {
|
||||
// disable the two-finger drag on trackpad
|
||||
if (details.kind == PointerDeviceKind.trackpad) {
|
||||
return;
|
||||
}
|
||||
isStartDragging = true;
|
||||
},
|
||||
onHorizontalDragUpdate: (details) {
|
||||
if (!isStartDragging) {
|
||||
return;
|
||||
}
|
||||
context.read<EditorState>().updateColumnWidthInMemory(
|
||||
tableCellNode: widget.node,
|
||||
deltaX: details.delta.dx,
|
||||
);
|
||||
},
|
||||
onHorizontalDragEnd: (details) {
|
||||
if (!isStartDragging) {
|
||||
return;
|
||||
}
|
||||
context.read<SimpleTableContext>().hoveringOnResizeHandle.value =
|
||||
null;
|
||||
isStartDragging = false;
|
||||
@ -543,11 +659,9 @@ class SimpleTableBackgroundColorMenu extends StatefulWidget {
|
||||
|
||||
class _SimpleTableBackgroundColorMenuState
|
||||
extends State<SimpleTableBackgroundColorMenu> {
|
||||
final PopoverController controller = PopoverController();
|
||||
bool isOpen = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = AFThemeExtension.of(context);
|
||||
final backgroundColor = switch (widget.type) {
|
||||
SimpleTableMoreActionType.row =>
|
||||
widget.tableCellNode.buildRowColor(context),
|
||||
@ -555,39 +669,30 @@ class _SimpleTableBackgroundColorMenuState
|
||||
widget.tableCellNode.buildColumnColor(context),
|
||||
};
|
||||
return AppFlowyPopover(
|
||||
controller: controller,
|
||||
mutex: widget.mutex,
|
||||
asBarrier: true,
|
||||
popupBuilder: (_) {
|
||||
isOpen = true;
|
||||
popupBuilder: (popoverContext) {
|
||||
return _buildColorOptionMenu(
|
||||
context,
|
||||
controller,
|
||||
theme: theme,
|
||||
onClose: () => PopoverContainer.of(popoverContext).closeAll(),
|
||||
);
|
||||
},
|
||||
onClose: () => isOpen = false,
|
||||
direction: PopoverDirection.rightWithCenterAligned,
|
||||
animationDuration: Durations.short3,
|
||||
beginScaleFactor: 1.0,
|
||||
beginOpacity: 0.8,
|
||||
child: SimpleTableBasicButton(
|
||||
leftIconBuilder: (onHover) => ColorOptionIcon(
|
||||
color: backgroundColor ?? Colors.transparent,
|
||||
),
|
||||
text: LocaleKeys.document_plugins_simpleTable_moreActions_color.tr(),
|
||||
onTap: () {
|
||||
if (!isOpen) {
|
||||
controller.show();
|
||||
}
|
||||
},
|
||||
onTap: () {},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildColorOptionMenu(
|
||||
BuildContext context,
|
||||
PopoverController controller,
|
||||
) {
|
||||
BuildContext context, {
|
||||
required AFThemeExtension theme,
|
||||
required VoidCallback onClose,
|
||||
}) {
|
||||
final colors = [
|
||||
// reset to default background color
|
||||
FlowyColorOption(
|
||||
@ -597,7 +702,7 @@ class _SimpleTableBackgroundColorMenuState
|
||||
),
|
||||
...FlowyTint.values.map(
|
||||
(e) => FlowyColorOption(
|
||||
color: e.color(context),
|
||||
color: e.color(context, theme: theme),
|
||||
i18n: e.tintName(AppFlowyEditorL10n.current),
|
||||
id: e.id,
|
||||
),
|
||||
@ -607,7 +712,7 @@ class _SimpleTableBackgroundColorMenuState
|
||||
return FlowyColorPicker(
|
||||
colors: colors,
|
||||
border: Border.all(
|
||||
color: AFThemeExtension.of(context).onBackground,
|
||||
color: theme.onBackground,
|
||||
),
|
||||
onTap: (option, index) {
|
||||
switch (widget.type) {
|
||||
@ -625,8 +730,7 @@ class _SimpleTableBackgroundColorMenuState
|
||||
break;
|
||||
}
|
||||
|
||||
controller.close();
|
||||
PopoverContainer.of(context).close();
|
||||
onClose();
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
export 'simple_table_block_component.dart';
|
||||
export 'simple_table_cell_block_component.dart';
|
||||
export 'simple_table_constants.dart';
|
||||
export 'simple_table_more_action.dart';
|
||||
export 'simple_table_operations/simple_table_operations.dart';
|
||||
export 'simple_table_row_block_component.dart';
|
||||
export 'simple_table_shortcuts/simple_table_commands.dart';
|
||||
@ -1,7 +1,7 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/shared_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_cell_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_constants.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_row_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/_shared_widget.dart';
|
||||
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_constants.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_row_block_component.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@ -92,12 +92,17 @@ Node createSimpleTableBlockNode({
|
||||
required int columnCount,
|
||||
required int rowCount,
|
||||
String? defaultContent,
|
||||
String Function(int rowIndex, int columnIndex)? contentBuilder,
|
||||
}) {
|
||||
final rows = List.generate(rowCount, (_) {
|
||||
final rows = List.generate(rowCount, (rowIndex) {
|
||||
final cells = List.generate(
|
||||
columnCount,
|
||||
(_) => simpleTableCellBlockNode(
|
||||
children: [paragraphNode(text: defaultContent)],
|
||||
(columnIndex) => simpleTableCellBlockNode(
|
||||
children: [
|
||||
paragraphNode(
|
||||
text: defaultContent ?? contentBuilder?.call(rowIndex, columnIndex),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
return simpleTableRowBlockNode(children: cells);
|
||||
@ -203,37 +208,39 @@ class _SimpleTableBlockWidgetState extends State<SimpleTableBlockWidget>
|
||||
);
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
Widget _buildTable() {
|
||||
const bottomPadding = SimpleTableConstants.addRowButtonHeight +
|
||||
2 * SimpleTableConstants.addRowButtonPadding;
|
||||
const rightPadding = SimpleTableConstants.addColumnButtonWidth +
|
||||
2 * SimpleTableConstants.addColumnButtonPadding;
|
||||
// IntrinsicWidth and IntrinsicHeight are used to make the table size fit the content.
|
||||
return Provider.value(
|
||||
value: simpleTableContext,
|
||||
child: MouseRegion(
|
||||
onEnter: (event) => simpleTableContext.isHoveringOnTable.value = true,
|
||||
onEnter: (event) =>
|
||||
simpleTableContext.isHoveringOnTableBlock.value = true,
|
||||
onExit: (event) {
|
||||
simpleTableContext.isHoveringOnTable.value = false;
|
||||
simpleTableContext.hoveringTableCell.value = null;
|
||||
simpleTableContext.isHoveringOnTableBlock.value = false;
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
Scrollbar(
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTable() {
|
||||
// IntrinsicWidth and IntrinsicHeight are used to make the table size fit the content.
|
||||
return Provider.value(
|
||||
value: simpleTableContext,
|
||||
child: Stack(
|
||||
children: [
|
||||
MouseRegion(
|
||||
onEnter: (event) =>
|
||||
simpleTableContext.isHoveringOnTable.value = true,
|
||||
onExit: (event) {
|
||||
simpleTableContext.isHoveringOnTable.value = false;
|
||||
simpleTableContext.hoveringTableCell.value = null;
|
||||
},
|
||||
child: Scrollbar(
|
||||
controller: scrollController,
|
||||
child: SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: SimpleTableConstants.tableTopPadding,
|
||||
left: SimpleTableConstants.tableLeftPadding,
|
||||
bottom: bottomPadding,
|
||||
right: rightPadding,
|
||||
),
|
||||
padding: SimpleTableConstants.tablePadding,
|
||||
child: IntrinsicWidth(
|
||||
child: IntrinsicHeight(
|
||||
child: Column(
|
||||
@ -246,20 +253,20 @@ class _SimpleTableBlockWidgetState extends State<SimpleTableBlockWidget>
|
||||
),
|
||||
),
|
||||
),
|
||||
SimpleTableAddColumnHoverButton(
|
||||
editorState: editorState,
|
||||
node: node,
|
||||
),
|
||||
SimpleTableAddRowHoverButton(
|
||||
editorState: editorState,
|
||||
node: node,
|
||||
),
|
||||
SimpleTableAddColumnAndRowHoverButton(
|
||||
editorState: editorState,
|
||||
node: node,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SimpleTableAddColumnHoverButton(
|
||||
editorState: editorState,
|
||||
node: node,
|
||||
),
|
||||
SimpleTableAddRowHoverButton(
|
||||
editorState: editorState,
|
||||
tableNode: node,
|
||||
),
|
||||
SimpleTableAddColumnAndRowHoverButton(
|
||||
editorState: editorState,
|
||||
node: node,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -1,7 +1,5 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/shared_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_constants.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_more_action.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/table_operations/table_node_extension.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/_shared_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@ -61,10 +59,11 @@ class SimpleTableCellBlockWidget extends BlockComponentStatefulWidget {
|
||||
|
||||
@override
|
||||
State<SimpleTableCellBlockWidget> createState() =>
|
||||
_SimpleTableCellBlockWidgetState();
|
||||
SimpleTableCellBlockWidgetState();
|
||||
}
|
||||
|
||||
class _SimpleTableCellBlockWidgetState extends State<SimpleTableCellBlockWidget>
|
||||
@visibleForTesting
|
||||
class SimpleTableCellBlockWidgetState extends State<SimpleTableCellBlockWidget>
|
||||
with
|
||||
BlockComponentConfigurable,
|
||||
BlockComponentTextDirectionMixin,
|
||||
@ -78,32 +77,45 @@ class _SimpleTableCellBlockWidgetState extends State<SimpleTableCellBlockWidget>
|
||||
@override
|
||||
late EditorState editorState = context.read<EditorState>();
|
||||
|
||||
late SimpleTableContext simpleTableContext =
|
||||
context.read<SimpleTableContext>();
|
||||
late SimpleTableContext? simpleTableContext =
|
||||
context.read<SimpleTableContext?>();
|
||||
|
||||
ValueNotifier<bool> isEditingCellNotifier = ValueNotifier(false);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
simpleTableContext.isSelectingTable.addListener(_onSelectingTableChanged);
|
||||
simpleTableContext?.isSelectingTable.addListener(_onSelectingTableChanged);
|
||||
node.parentTableNode?.addListener(_onSelectingTableChanged);
|
||||
editorState.selectionNotifier.addListener(_onSelectionChanged);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
_onSelectionChanged();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
simpleTableContext.isSelectingTable.removeListener(
|
||||
simpleTableContext?.isSelectingTable.removeListener(
|
||||
_onSelectingTableChanged,
|
||||
);
|
||||
node.parentTableNode?.removeListener(_onSelectingTableChanged);
|
||||
editorState.selectionNotifier.removeListener(_onSelectionChanged);
|
||||
isEditingCellNotifier.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (simpleTableContext == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return MouseRegion(
|
||||
hitTestBehavior: HitTestBehavior.opaque,
|
||||
onEnter: (event) => simpleTableContext.hoveringTableCell.value = node,
|
||||
onEnter: (event) => simpleTableContext!.hoveringTableCell.value = node,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
@ -117,12 +129,11 @@ class _SimpleTableCellBlockWidgetState extends State<SimpleTableCellBlockWidget>
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: -SimpleTableConstants.tableTopPadding,
|
||||
child: _buildColumnMoreActionButton(),
|
||||
),
|
||||
Positioned(
|
||||
right: 0,
|
||||
top: 0,
|
||||
top: node.rowIndex == 0 ? SimpleTableConstants.tableTopPadding : 0,
|
||||
bottom: 0,
|
||||
child: SimpleTableColumnResizeHandle(
|
||||
node: node,
|
||||
@ -134,21 +145,38 @@ class _SimpleTableCellBlockWidgetState extends State<SimpleTableCellBlockWidget>
|
||||
}
|
||||
|
||||
Widget _buildCell() {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: simpleTableContext.selectingColumn,
|
||||
builder: (context, selectingColumn, child) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: simpleTableContext.selectingRow,
|
||||
builder: (context, selectingRow, _) {
|
||||
return DecoratedBox(
|
||||
decoration: _buildDecoration(),
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Column(
|
||||
children: node.children.map(_buildCellContent).toList(),
|
||||
if (simpleTableContext == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Padding(
|
||||
// add padding to the top of the cell if it is the first row, otherwise the
|
||||
// column action button is not clickable.
|
||||
// issue: https://github.com/flutter/flutter/issues/75747
|
||||
padding: EdgeInsets.only(
|
||||
top: node.rowIndex == 0 ? SimpleTableConstants.tableTopPadding : 0,
|
||||
),
|
||||
child: ValueListenableBuilder<bool>(
|
||||
valueListenable: isEditingCellNotifier,
|
||||
builder: (context, isEditingCell, child) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: simpleTableContext!.selectingColumn,
|
||||
builder: (context, selectingColumn, child) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: simpleTableContext!.selectingRow,
|
||||
builder: (context, selectingRow, _) {
|
||||
return DecoratedBox(
|
||||
decoration: _buildDecoration(),
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Column(
|
||||
children: node.children.map(_buildCellContent).toList(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -257,6 +285,8 @@ class _SimpleTableCellBlockWidgetState extends State<SimpleTableCellBlockWidget>
|
||||
return _buildColumnBorder();
|
||||
} else if (isCellInSelectedRow) {
|
||||
return _buildRowBorder();
|
||||
} else if (isEditingCellNotifier.value) {
|
||||
return _buildEditingBorder();
|
||||
} else {
|
||||
return _buildCellBorder();
|
||||
}
|
||||
@ -283,28 +313,14 @@ class _SimpleTableCellBlockWidgetState extends State<SimpleTableCellBlockWidget>
|
||||
/// the border wrapping the cell 2 and cell 4 is the column border
|
||||
Border _buildColumnBorder() {
|
||||
return Border(
|
||||
left: BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
width: 2,
|
||||
),
|
||||
right: BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
width: 2.5,
|
||||
),
|
||||
left: _buildHighlightBorderSide(),
|
||||
right: _buildHighlightBorderSide(),
|
||||
top: node.rowIndex == 0
|
||||
? BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
width: 2,
|
||||
)
|
||||
: BorderSide(
|
||||
color: context.simpleTableBorderColor,
|
||||
),
|
||||
? _buildHighlightBorderSide()
|
||||
: _buildDefaultBorderSide(),
|
||||
bottom: node.rowIndex + 1 == node.parentTableNode?.rowLength
|
||||
? BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
width: 2,
|
||||
)
|
||||
: BorderSide.none,
|
||||
? _buildHighlightBorderSide()
|
||||
: _buildDefaultBorderSide(),
|
||||
);
|
||||
}
|
||||
|
||||
@ -318,35 +334,38 @@ class _SimpleTableCellBlockWidgetState extends State<SimpleTableCellBlockWidget>
|
||||
/// the border wrapping the cell 1 and cell 2 is the row border
|
||||
Border _buildRowBorder() {
|
||||
return Border(
|
||||
top: BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
width: 2,
|
||||
),
|
||||
bottom: BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
width: 2.5,
|
||||
),
|
||||
top: _buildHighlightBorderSide(),
|
||||
bottom: _buildHighlightBorderSide(),
|
||||
left: node.columnIndex == 0
|
||||
? BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
width: 2,
|
||||
)
|
||||
: BorderSide(
|
||||
color: context.simpleTableBorderColor,
|
||||
),
|
||||
? _buildHighlightBorderSide()
|
||||
: _buildDefaultBorderSide(),
|
||||
right: node.columnIndex + 1 == node.parentTableNode?.columnLength
|
||||
? BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
width: 2,
|
||||
)
|
||||
: BorderSide.none,
|
||||
? _buildHighlightBorderSide()
|
||||
: _buildDefaultBorderSide(),
|
||||
);
|
||||
}
|
||||
|
||||
Border _buildCellBorder() {
|
||||
return Border(
|
||||
top: node.rowIndex == 0
|
||||
? _buildDefaultBorderSide()
|
||||
: _buildLightBorderSide(),
|
||||
bottom: node.rowIndex + 1 == node.parentTableNode?.rowLength
|
||||
? _buildDefaultBorderSide()
|
||||
: _buildLightBorderSide(),
|
||||
left: node.columnIndex == 0
|
||||
? _buildDefaultBorderSide()
|
||||
: _buildLightBorderSide(),
|
||||
right: node.columnIndex + 1 == node.parentTableNode?.columnLength
|
||||
? _buildDefaultBorderSide()
|
||||
: _buildLightBorderSide(),
|
||||
);
|
||||
}
|
||||
|
||||
Border _buildEditingBorder() {
|
||||
return Border.all(
|
||||
color: context.simpleTableBorderColor,
|
||||
strokeAlign: BorderSide.strokeAlignCenter,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
width: 2,
|
||||
);
|
||||
}
|
||||
|
||||
@ -355,37 +374,37 @@ class _SimpleTableCellBlockWidgetState extends State<SimpleTableCellBlockWidget>
|
||||
final columnIndex = node.columnIndex;
|
||||
|
||||
return Border(
|
||||
top: rowIndex == 0
|
||||
? _buildDefaultBorderSide()
|
||||
: BorderSide(
|
||||
color: context.simpleTableBorderColor,
|
||||
width: 0.5,
|
||||
),
|
||||
top:
|
||||
rowIndex == 0 ? _buildHighlightBorderSide() : _buildLightBorderSide(),
|
||||
bottom: rowIndex + 1 == node.parentTableNode?.rowLength
|
||||
? _buildDefaultBorderSide()
|
||||
: BorderSide(
|
||||
color: context.simpleTableBorderColor,
|
||||
width: 0.5,
|
||||
),
|
||||
? _buildHighlightBorderSide()
|
||||
: _buildLightBorderSide(),
|
||||
left: columnIndex == 0
|
||||
? _buildDefaultBorderSide()
|
||||
: BorderSide(
|
||||
color: context.simpleTableBorderColor,
|
||||
width: 0.5,
|
||||
),
|
||||
? _buildHighlightBorderSide()
|
||||
: _buildLightBorderSide(),
|
||||
right: columnIndex + 1 == node.parentTableNode?.columnLength
|
||||
? _buildDefaultBorderSide()
|
||||
: BorderSide(
|
||||
color: context.simpleTableBorderColor,
|
||||
width: 0.5,
|
||||
),
|
||||
? _buildHighlightBorderSide()
|
||||
: _buildLightBorderSide(),
|
||||
);
|
||||
}
|
||||
|
||||
BorderSide _buildHighlightBorderSide() {
|
||||
return BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
width: 2,
|
||||
);
|
||||
}
|
||||
|
||||
BorderSide _buildLightBorderSide() {
|
||||
return BorderSide(
|
||||
color: context.simpleTableBorderColor,
|
||||
width: 0.5,
|
||||
);
|
||||
}
|
||||
|
||||
BorderSide _buildDefaultBorderSide() {
|
||||
return BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
width: 2,
|
||||
color: context.simpleTableBorderColor,
|
||||
);
|
||||
}
|
||||
|
||||
@ -394,4 +413,17 @@ class _SimpleTableCellBlockWidgetState extends State<SimpleTableCellBlockWidget>
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
void _onSelectionChanged() {
|
||||
final selection = editorState.selection;
|
||||
|
||||
// check if the selection is in the cell
|
||||
if (selection != null &&
|
||||
node.path.isAncestorOf(selection.start.path) &&
|
||||
node.path.isAncestorOf(selection.end.path)) {
|
||||
isEditingCellNotifier.value = true;
|
||||
} else {
|
||||
isEditingCellNotifier.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,10 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/table_operations/table_node_extension.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table.dart';
|
||||
import 'package:appflowy/util/theme_extension.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
const enableTableDebugLog = false;
|
||||
const enableTableDebugLog = true;
|
||||
|
||||
class SimpleTableContext {
|
||||
SimpleTableContext() {
|
||||
@ -14,6 +14,7 @@ class SimpleTableContext {
|
||||
selectingColumn.addListener(_onSelectingColumnChanged);
|
||||
selectingRow.addListener(_onSelectingRowChanged);
|
||||
isSelectingTable.addListener(_onSelectingTableChanged);
|
||||
isHoveringOnTableBlock.addListener(_onHoveringOnTableBlockChanged);
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,6 +24,7 @@ class SimpleTableContext {
|
||||
final ValueNotifier<int?> selectingColumn = ValueNotifier(null);
|
||||
final ValueNotifier<int?> selectingRow = ValueNotifier(null);
|
||||
final ValueNotifier<bool> isSelectingTable = ValueNotifier(false);
|
||||
final ValueNotifier<bool> isHoveringOnTableBlock = ValueNotifier(false);
|
||||
|
||||
void _onHoveringOnTableChanged() {
|
||||
if (!enableTableDebugLog) {
|
||||
@ -69,6 +71,14 @@ class SimpleTableContext {
|
||||
Log.debug('isSelectingTable: ${isSelectingTable.value}');
|
||||
}
|
||||
|
||||
void _onHoveringOnTableBlockChanged() {
|
||||
if (!enableTableDebugLog) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.debug('isHoveringOnTableBlock: ${isHoveringOnTableBlock.value}');
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
isHoveringOnTable.dispose();
|
||||
hoveringTableCell.dispose();
|
||||
@ -76,6 +86,7 @@ class SimpleTableContext {
|
||||
selectingColumn.dispose();
|
||||
selectingRow.dispose();
|
||||
isSelectingTable.dispose();
|
||||
isHoveringOnTableBlock.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,9 +98,22 @@ class SimpleTableConstants {
|
||||
static const tableTopPadding = 8.0;
|
||||
static const tableLeftPadding = 8.0;
|
||||
|
||||
static const tableBottomPadding =
|
||||
addRowButtonHeight + 3 * addRowButtonPadding;
|
||||
static const tableRightPadding =
|
||||
addColumnButtonWidth + 2 * SimpleTableConstants.addColumnButtonPadding;
|
||||
|
||||
static const tablePadding = EdgeInsets.only(
|
||||
// don't add padding to the top of the table, the first row will have padding
|
||||
// to make the column action button clickable.
|
||||
bottom: tableBottomPadding,
|
||||
left: tableLeftPadding,
|
||||
right: tableRightPadding,
|
||||
);
|
||||
|
||||
// Add row button
|
||||
static const addRowButtonHeight = 16.0;
|
||||
static const addRowButtonPadding = 2.0;
|
||||
static const addRowButtonPadding = 4.0;
|
||||
static const addRowButtonRadius = 4.0;
|
||||
static const addRowButtonRightPadding =
|
||||
addColumnButtonWidth + addColumnButtonPadding * 2;
|
||||
@ -99,12 +123,13 @@ class SimpleTableConstants {
|
||||
static const addColumnButtonPadding = 2.0;
|
||||
static const addColumnButtonRadius = 4.0;
|
||||
static const addColumnButtonBottomPadding =
|
||||
addRowButtonHeight + addRowButtonPadding * 2;
|
||||
addRowButtonHeight + 3 * addRowButtonPadding;
|
||||
|
||||
// Add column and row button
|
||||
static const addColumnAndRowButtonWidth = addColumnButtonWidth;
|
||||
static const addColumnAndRowButtonHeight = addRowButtonHeight;
|
||||
static const addColumnAndRowButtonCornerRadius = addColumnButtonWidth / 2.0;
|
||||
static const addColumnAndRowButtonBottomPadding = 2.5 * addRowButtonPadding;
|
||||
|
||||
// Table cell
|
||||
static const cellEdgePadding = EdgeInsets.symmetric(
|
||||
@ -1,9 +1,9 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/shared_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_constants.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/table_operations/table_operations.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/_shared_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_constants.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_operations.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@ -166,6 +166,7 @@ class _SimpleTableMoreActionMenuState extends State<SimpleTableMoreActionMenu> {
|
||||
return child!;
|
||||
},
|
||||
child: SimpleTableMoreActionPopup(
|
||||
key: ValueKey(widget.type.name + widget.index.toString()),
|
||||
index: widget.index,
|
||||
isShowingMenu: this.isShowingMenu,
|
||||
type: widget.type,
|
||||
@ -242,6 +243,13 @@ class _SimpleTableMoreActionPopupState
|
||||
context.read<SimpleTableContext>().selectingRow.value =
|
||||
tableCellNode?.rowIndex;
|
||||
}
|
||||
|
||||
// Workaround to clear the selection after the menu is opened.
|
||||
Future.delayed(Durations.short3, () {
|
||||
if (!editorState.isDisposed) {
|
||||
editorState.selection = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
onClose: () {
|
||||
widget.isShowingMenu.value = false;
|
||||
@ -297,17 +305,28 @@ class _SimpleTableMoreActionPopupState
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleTableMoreActionList extends StatelessWidget {
|
||||
class SimpleTableMoreActionList extends StatefulWidget {
|
||||
const SimpleTableMoreActionList({
|
||||
super.key,
|
||||
required this.type,
|
||||
required this.index,
|
||||
required this.tableCellNode,
|
||||
this.mutex,
|
||||
});
|
||||
|
||||
final SimpleTableMoreActionType type;
|
||||
final int index;
|
||||
final Node tableCellNode;
|
||||
final PopoverMutex? mutex;
|
||||
|
||||
@override
|
||||
State<SimpleTableMoreActionList> createState() =>
|
||||
_SimpleTableMoreActionListState();
|
||||
}
|
||||
|
||||
class _SimpleTableMoreActionListState extends State<SimpleTableMoreActionList> {
|
||||
// ensure the background color menu and align menu exclusive
|
||||
final mutex = PopoverMutex();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -316,9 +335,10 @@ class SimpleTableMoreActionList extends StatelessWidget {
|
||||
children: _buildActions()
|
||||
.map(
|
||||
(action) => SimpleTableMoreActionItem(
|
||||
type: type,
|
||||
type: widget.type,
|
||||
action: action,
|
||||
tableCellNode: tableCellNode,
|
||||
tableCellNode: widget.tableCellNode,
|
||||
popoverMutex: mutex,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
@ -326,26 +346,27 @@ class SimpleTableMoreActionList extends StatelessWidget {
|
||||
}
|
||||
|
||||
List<SimpleTableMoreAction> _buildActions() {
|
||||
final actions = type.actions;
|
||||
final actions = widget.type.actions;
|
||||
|
||||
// if the index is 0, add the divider and enable header action
|
||||
if (index == 0) {
|
||||
if (widget.index == 0) {
|
||||
actions.addAll([
|
||||
SimpleTableMoreAction.divider,
|
||||
if (type == SimpleTableMoreActionType.column)
|
||||
if (widget.type == SimpleTableMoreActionType.column)
|
||||
SimpleTableMoreAction.enableHeaderColumn,
|
||||
if (type == SimpleTableMoreActionType.row)
|
||||
if (widget.type == SimpleTableMoreActionType.row)
|
||||
SimpleTableMoreAction.enableHeaderRow,
|
||||
]);
|
||||
}
|
||||
|
||||
// if the table only contains one row or one column, remove the delete action
|
||||
if (tableCellNode.rowLength == 1 && type == SimpleTableMoreActionType.row) {
|
||||
if (widget.tableCellNode.rowLength == 1 &&
|
||||
widget.type == SimpleTableMoreActionType.row) {
|
||||
actions.remove(SimpleTableMoreAction.delete);
|
||||
}
|
||||
|
||||
if (tableCellNode.columnLength == 1 &&
|
||||
type == SimpleTableMoreActionType.column) {
|
||||
if (widget.tableCellNode.columnLength == 1 &&
|
||||
widget.type == SimpleTableMoreActionType.column) {
|
||||
actions.remove(SimpleTableMoreAction.delete);
|
||||
}
|
||||
|
||||
@ -359,11 +380,13 @@ class SimpleTableMoreActionItem extends StatefulWidget {
|
||||
required this.type,
|
||||
required this.action,
|
||||
required this.tableCellNode,
|
||||
required this.popoverMutex,
|
||||
});
|
||||
|
||||
final SimpleTableMoreActionType type;
|
||||
final SimpleTableMoreAction action;
|
||||
final Node tableCellNode;
|
||||
final PopoverMutex popoverMutex;
|
||||
|
||||
@override
|
||||
State<SimpleTableMoreActionItem> createState() =>
|
||||
@ -371,10 +394,7 @@ class SimpleTableMoreActionItem extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _SimpleTableMoreActionItemState extends State<SimpleTableMoreActionItem> {
|
||||
// ensure the background color menu and align menu exclusive
|
||||
final mutex = PopoverMutex();
|
||||
|
||||
ValueNotifier<bool> isEnableHeader = ValueNotifier(false);
|
||||
final isEnableHeader = ValueNotifier(false);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -419,7 +439,7 @@ class _SimpleTableMoreActionItemState extends State<SimpleTableMoreActionItem> {
|
||||
return SimpleTableAlignMenu(
|
||||
type: widget.type,
|
||||
tableCellNode: widget.tableCellNode,
|
||||
mutex: mutex,
|
||||
mutex: widget.popoverMutex,
|
||||
);
|
||||
}
|
||||
|
||||
@ -427,7 +447,7 @@ class _SimpleTableMoreActionItemState extends State<SimpleTableMoreActionItem> {
|
||||
return SimpleTableBackgroundColorMenu(
|
||||
type: widget.type,
|
||||
tableCellNode: widget.tableCellNode,
|
||||
mutex: mutex,
|
||||
mutex: widget.popoverMutex,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_cell_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/table_operations/table_node_extension.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_block_component.dart';
|
||||
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_operations/simple_table_node_extension.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/table_operations/table_map_operation.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/table_operations/table_node_extension.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_map_operation.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_node_extension.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/table_operations/table_map_operation.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/table_operations/table_node_extension.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_map_operation.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_node_extension.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_block_component.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_cell_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_row_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/table_operations/table_map_operation.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/table_operations/table_node_extension.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_block_component.dart';
|
||||
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_row_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_map_operation.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_node_extension.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
@ -20,15 +20,15 @@ extension TableInsertionOperations on EditorState {
|
||||
/// Row 2: | | | |
|
||||
/// Row 3: | | | | ← New row
|
||||
///
|
||||
Future<void> addRowInTable(Node node) async {
|
||||
assert(node.type == SimpleTableBlockKeys.type);
|
||||
Future<void> addRowInTable(Node tableNode) async {
|
||||
assert(tableNode.type == SimpleTableBlockKeys.type);
|
||||
|
||||
if (node.type != SimpleTableBlockKeys.type) {
|
||||
Log.warn('node is not a table node: ${node.type}');
|
||||
if (tableNode.type != SimpleTableBlockKeys.type) {
|
||||
Log.warn('node is not a table node: ${tableNode.type}');
|
||||
return;
|
||||
}
|
||||
|
||||
await insertRowInTable(node, node.rowLength);
|
||||
await insertRowInTable(tableNode, tableNode.rowLength);
|
||||
}
|
||||
|
||||
/// Add a column at the end of the table.
|
||||
@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/table_operations/table_node_extension.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_node_extension.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_cell_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_constants.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_row_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_block_component.dart';
|
||||
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_constants.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_row_block_component.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -83,8 +83,12 @@ extension TableNodeExtension on Node {
|
||||
}
|
||||
|
||||
int get rowIndex {
|
||||
assert(type == SimpleTableCellBlockKeys.type);
|
||||
return path.parent.last;
|
||||
if (type == SimpleTableCellBlockKeys.type) {
|
||||
return path.parent.last;
|
||||
} else if (type == SimpleTableRowBlockKeys.type) {
|
||||
return path.last;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int get columnIndex {
|
||||
@ -165,6 +169,8 @@ extension TableNodeExtension on Node {
|
||||
tableNode = parent;
|
||||
} else if (type == SimpleTableCellBlockKeys.type) {
|
||||
tableNode = parent?.parent;
|
||||
} else {
|
||||
return parent?.parentTableNode;
|
||||
}
|
||||
|
||||
if (tableNode == null || tableNode.type != SimpleTableBlockKeys.type) {
|
||||
@ -0,0 +1,8 @@
|
||||
export 'simple_table_content_operation.dart';
|
||||
export 'simple_table_delete_operation.dart';
|
||||
export 'simple_table_duplicate_operation.dart';
|
||||
export 'simple_table_header_operation.dart';
|
||||
export 'simple_table_insert_operation.dart';
|
||||
export 'simple_table_map_operation.dart';
|
||||
export 'simple_table_node_extension.dart';
|
||||
export 'simple_table_style_operation.dart';
|
||||
@ -1,8 +1,8 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_cell_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_constants.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/table_operations/table_map_operation.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/table_operations/table_node_extension.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_block_component.dart';
|
||||
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_constants.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_map_operation.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_node_extension.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/shared_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_constants.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/_shared_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_constants.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/shortcuts/table_command_extension.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/table_operations/table_operations.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_shortcuts/simple_table_command_extension.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_operations.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/shortcuts/table_command_extension.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';
|
||||
|
||||
final CommandShortcutEvent arrowLeftInTableCell = CommandShortcutEvent(
|
||||
@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/shortcuts/table_command_extension.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';
|
||||
|
||||
final CommandShortcutEvent arrowRightInTableCell = CommandShortcutEvent(
|
||||
@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/shortcuts/table_command_extension.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/table_operations/table_operations.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_shortcuts/simple_table_command_extension.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_operations.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/shortcuts/table_command_extension.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';
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_cell_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/table_operations/table_operations.dart';
|
||||
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_operations/simple_table_operations.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -18,16 +18,33 @@ extension TableCommandExtension on EditorState {
|
||||
/// The third element is the node that is the current selection.
|
||||
IsInTableCellResult isCurrentSelectionInTableCell() {
|
||||
final selection = this.selection;
|
||||
if (selection == null || !selection.isCollapsed) {
|
||||
if (selection == null) {
|
||||
return (false, null, null, null);
|
||||
}
|
||||
|
||||
final node = document.nodeAtPath(selection.end.path);
|
||||
final tableCellParent = node?.findParent(
|
||||
(node) => node.type == SimpleTableCellBlockKeys.type,
|
||||
);
|
||||
final isInTableCell = tableCellParent != null;
|
||||
return (isInTableCell, selection, tableCellParent, node);
|
||||
if (selection.isCollapsed) {
|
||||
// if the selection is collapsed, check if the node is in a table cell
|
||||
final node = document.nodeAtPath(selection.end.path);
|
||||
final tableCellParent = node?.findParent(
|
||||
(node) => node.type == SimpleTableCellBlockKeys.type,
|
||||
);
|
||||
final isInTableCell = tableCellParent != null;
|
||||
return (isInTableCell, selection, tableCellParent, node);
|
||||
} else {
|
||||
// if the selection is not collapsed, check if the start and end nodes are in a table cell
|
||||
final startNode = document.nodeAtPath(selection.start.path);
|
||||
final endNode = document.nodeAtPath(selection.end.path);
|
||||
final startNodeInTableCell = startNode?.findParent(
|
||||
(node) => node.type == SimpleTableCellBlockKeys.type,
|
||||
);
|
||||
final endNodeInTableCell = endNode?.findParent(
|
||||
(node) => node.type == SimpleTableCellBlockKeys.type,
|
||||
);
|
||||
final isInSameTableCell = startNodeInTableCell != null &&
|
||||
endNodeInTableCell != null &&
|
||||
startNodeInTableCell.path.equals(endNodeInTableCell.path);
|
||||
return (isInSameTableCell, selection, startNodeInTableCell, endNode);
|
||||
}
|
||||
}
|
||||
|
||||
/// Move the selection to the previous cell
|
||||
@ -0,0 +1,18 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_shortcuts/simple_table_arrow_down_command.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_shortcuts/simple_table_arrow_left_command.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_shortcuts/simple_table_arrow_right_command.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_shortcuts/simple_table_arrow_up_command.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_shortcuts/simple_table_backspace_command.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_shortcuts/simple_table_select_all_command.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_shortcuts/simple_table_tab_command.dart';
|
||||
|
||||
final simpleTableCommands = [
|
||||
arrowUpInTableCell,
|
||||
arrowDownInTableCell,
|
||||
arrowLeftInTableCell,
|
||||
arrowRightInTableCell,
|
||||
tabInTableCell,
|
||||
shiftTabInTableCell,
|
||||
backspaceInTableCell,
|
||||
selectAllInTableCellCommand,
|
||||
];
|
||||
@ -0,0 +1,46 @@
|
||||
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:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
final CommandShortcutEvent selectAllInTableCellCommand = CommandShortcutEvent(
|
||||
key: 'Select all contents in table cell',
|
||||
getDescription: () => 'Select all contents in table cell',
|
||||
command: 'ctrl+a',
|
||||
macOSCommand: 'cmd+a',
|
||||
handler: _selectAllInTableCellHandler,
|
||||
);
|
||||
|
||||
KeyEventResult _selectAllInTableCellHandler(EditorState editorState) {
|
||||
final (isInTableCell, selection, tableCellNode, _) =
|
||||
editorState.isCurrentSelectionInTableCell();
|
||||
if (!isInTableCell || selection == null || tableCellNode == null) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
final firstFocusableChild = tableCellNode.children.firstWhereOrNull(
|
||||
(e) => e.delta != null,
|
||||
);
|
||||
final lastFocusableChild = tableCellNode.lastChildWhere(
|
||||
(e) => e.delta != null,
|
||||
);
|
||||
if (firstFocusableChild == null || lastFocusableChild == null) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
final afterSelection = Selection(
|
||||
start: Position(path: firstFocusableChild.path),
|
||||
end: Position(
|
||||
path: lastFocusableChild.path,
|
||||
offset: lastFocusableChild.delta?.length ?? 0,
|
||||
),
|
||||
);
|
||||
|
||||
if (afterSelection == editorState.selection) {
|
||||
// Focus on the cell already
|
||||
return KeyEventResult.ignored;
|
||||
} else {
|
||||
editorState.selection = afterSelection;
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/shortcuts/table_command_extension.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/table_operations/table_operations.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_shortcuts/simple_table_command_extension.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_operations.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
final CommandShortcutEvent tabInTableCell = CommandShortcutEvent(
|
||||
@ -8,9 +8,6 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/image/imag
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/slash_menu_items.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/shared_context/shared_context.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_cell_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_row_block_component.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_service.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/emoji_picker/emoji_menu_item.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
@ -599,55 +596,32 @@ SelectionMenuItem tableSlashMenuItem = SelectionMenuItem(
|
||||
return;
|
||||
}
|
||||
|
||||
final tableNode = simpleTableBlockNode(
|
||||
children: [
|
||||
simpleTableRowBlockNode(
|
||||
children: [
|
||||
simpleTableCellBlockNode(
|
||||
children: [
|
||||
paragraphNode(),
|
||||
],
|
||||
),
|
||||
simpleTableCellBlockNode(
|
||||
children: [
|
||||
paragraphNode(),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
simpleTableRowBlockNode(
|
||||
children: [
|
||||
simpleTableCellBlockNode(
|
||||
children: [
|
||||
paragraphNode(),
|
||||
],
|
||||
),
|
||||
simpleTableCellBlockNode(
|
||||
children: [
|
||||
paragraphNode(),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
// create a simple table with 2 columns and 2 rows
|
||||
final tableNode = createSimpleTableBlockNode(
|
||||
columnCount: 2,
|
||||
rowCount: 2,
|
||||
);
|
||||
|
||||
final transaction = editorState.transaction;
|
||||
final delta = currentNode.delta;
|
||||
if (delta != null && delta.isEmpty) {
|
||||
final path = selection.end.path;
|
||||
transaction
|
||||
..insertNode(selection.end.path, tableNode)
|
||||
..insertNode(path, tableNode)
|
||||
..deleteNode(currentNode);
|
||||
transaction.afterSelection = Selection.collapsed(
|
||||
Position(
|
||||
path: selection.end.path + [0, 0],
|
||||
// table -> row -> cell -> paragraph
|
||||
path: path + [0, 0, 0],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
transaction.insertNode(selection.end.path.next, tableNode);
|
||||
final path = selection.end.path.next;
|
||||
transaction.insertNode(path, tableNode);
|
||||
transaction.afterSelection = Selection.collapsed(
|
||||
Position(
|
||||
path: selection.end.path.next + [0, 0],
|
||||
// table -> row -> cell -> paragraph
|
||||
path: path + [0, 0, 0],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/shortcuts/simple_table_arrow_down_command.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/shortcuts/simple_table_arrow_left_command.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/shortcuts/simple_table_arrow_right_command.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/shortcuts/simple_table_arrow_up_command.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/shortcuts/simple_table_backspace_command.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/shortcuts/simple_table_tab_command.dart';
|
||||
|
||||
final simpleTableCommands = [
|
||||
arrowUpInTableCell,
|
||||
arrowDownInTableCell,
|
||||
arrowLeftInTableCell,
|
||||
arrowRightInTableCell,
|
||||
tabInTableCell,
|
||||
shiftTabInTableCell,
|
||||
backspaceInTableCell,
|
||||
];
|
||||
@ -1,10 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
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;
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
const tableActions = <TableOptionAction>[
|
||||
TableOptionAction.addAfter,
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
export 'table_content_operation.dart';
|
||||
export 'table_delete_operation.dart';
|
||||
export 'table_duplicate_operation.dart';
|
||||
export 'table_header_operation.dart';
|
||||
export 'table_insert_operation.dart';
|
||||
export 'table_map_operation.dart';
|
||||
export 'table_node_extension.dart';
|
||||
export 'table_style_operation.dart';
|
||||
@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/parsers/markdown_code_parser.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
Document customMarkdownToDocument(String markdown) {
|
||||
@ -6,6 +6,20 @@ Document customMarkdownToDocument(String markdown) {
|
||||
markdown,
|
||||
markdownParsers: [
|
||||
const MarkdownCodeBlockParser(),
|
||||
const MarkdownSimpleTableParser(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
String customDocumentToMarkdown(Document document) {
|
||||
return documentToMarkdown(
|
||||
document,
|
||||
customParsers: [
|
||||
const MathEquationNodeParser(),
|
||||
const CalloutNodeParser(),
|
||||
const ToggleListNodeParser(),
|
||||
const CustomImageNodeParser(),
|
||||
const SimpleTableNodeParser(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@ -3,20 +3,13 @@ import 'dart:convert';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
|
||||
import 'package:appflowy/plugins/document/application/prelude.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/parsers/document_markdown_parsers.dart';
|
||||
import 'package:appflowy/shared/markdown_to_document.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
const List<NodeParser> _customParsers = [
|
||||
MathEquationNodeParser(),
|
||||
CalloutNodeParser(),
|
||||
ToggleListNodeParser(),
|
||||
CustomImageNodeParser(),
|
||||
];
|
||||
|
||||
enum DocumentExportType {
|
||||
json,
|
||||
markdown,
|
||||
@ -50,10 +43,7 @@ class DocumentExporter {
|
||||
case DocumentExportType.json:
|
||||
return FlowyResult.success(jsonEncode(document));
|
||||
case DocumentExportType.markdown:
|
||||
final markdown = documentToMarkdown(
|
||||
document,
|
||||
customParsers: _customParsers,
|
||||
);
|
||||
final markdown = customDocumentToMarkdown(document);
|
||||
return FlowyResult.success(markdown);
|
||||
case DocumentExportType.text:
|
||||
throw UnimplementedError();
|
||||
|
||||
@ -248,16 +248,17 @@ enum FlowyTint {
|
||||
return null;
|
||||
}
|
||||
|
||||
Color color(BuildContext context) => switch (this) {
|
||||
FlowyTint.tint1 => AFThemeExtension.of(context).tint1,
|
||||
FlowyTint.tint2 => AFThemeExtension.of(context).tint2,
|
||||
FlowyTint.tint3 => AFThemeExtension.of(context).tint3,
|
||||
FlowyTint.tint4 => AFThemeExtension.of(context).tint4,
|
||||
FlowyTint.tint5 => AFThemeExtension.of(context).tint5,
|
||||
FlowyTint.tint6 => AFThemeExtension.of(context).tint6,
|
||||
FlowyTint.tint7 => AFThemeExtension.of(context).tint7,
|
||||
FlowyTint.tint8 => AFThemeExtension.of(context).tint8,
|
||||
FlowyTint.tint9 => AFThemeExtension.of(context).tint9,
|
||||
Color color(BuildContext context, {AFThemeExtension? theme}) =>
|
||||
switch (this) {
|
||||
FlowyTint.tint1 => theme?.tint1 ?? AFThemeExtension.of(context).tint1,
|
||||
FlowyTint.tint2 => theme?.tint2 ?? AFThemeExtension.of(context).tint2,
|
||||
FlowyTint.tint3 => theme?.tint3 ?? AFThemeExtension.of(context).tint3,
|
||||
FlowyTint.tint4 => theme?.tint4 ?? AFThemeExtension.of(context).tint4,
|
||||
FlowyTint.tint5 => theme?.tint5 ?? AFThemeExtension.of(context).tint5,
|
||||
FlowyTint.tint6 => theme?.tint6 ?? AFThemeExtension.of(context).tint6,
|
||||
FlowyTint.tint7 => theme?.tint7 ?? AFThemeExtension.of(context).tint7,
|
||||
FlowyTint.tint8 => theme?.tint8 ?? AFThemeExtension.of(context).tint8,
|
||||
FlowyTint.tint9 => theme?.tint9 ?? AFThemeExtension.of(context).tint9,
|
||||
};
|
||||
|
||||
String get id => switch (this) {
|
||||
|
||||
@ -61,8 +61,8 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "5b3878d"
|
||||
resolved-ref: "5b3878dcc5876ae7a329b308ff82763f02cf8c5f"
|
||||
ref: "817c965"
|
||||
resolved-ref: "817c965f26804efc09ecf97b1fb9d7d4e139ef4b"
|
||||
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
||||
source: git
|
||||
version: "4.0.0"
|
||||
@ -434,6 +434,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.10"
|
||||
defer_pointer:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: defer_pointer
|
||||
sha256: d69e6f8c1d0f052d2616cc1db3782e0ea73f42e4c6f6122fd1a548dfe79faf02
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.2"
|
||||
desktop_drop:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@ -43,6 +43,7 @@ dependencies:
|
||||
cross_file: ^0.3.4+1
|
||||
|
||||
# Desktop Drop uses Cross File (XFile) data type
|
||||
defer_pointer: ^0.0.2
|
||||
desktop_drop: ^0.4.4
|
||||
device_info_plus:
|
||||
diffutil_dart: ^4.0.1
|
||||
@ -172,7 +173,7 @@ dependency_overrides:
|
||||
appflowy_editor:
|
||||
git:
|
||||
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
||||
ref: "5b3878d"
|
||||
ref: "817c965"
|
||||
|
||||
appflowy_editor_plugins:
|
||||
git:
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/table_operations/table_operations.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/table_operations/table_operations.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/table_operations/table_operations.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/table_operations/table_operations.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/table_operations/table_operations.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
|
||||
@ -0,0 +1,162 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy/shared/markdown_to_document.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
group('Simple table markdown:', () {
|
||||
setUpAll(() {
|
||||
Log.shared.disableLog = true;
|
||||
});
|
||||
|
||||
tearDownAll(() {
|
||||
Log.shared.disableLog = false;
|
||||
});
|
||||
|
||||
test('convert simple table to markdown (1)', () async {
|
||||
final tableNode = createSimpleTableBlockNode(
|
||||
columnCount: 7,
|
||||
rowCount: 11,
|
||||
contentBuilder: (rowIndex, columnIndex) =>
|
||||
_sampleContents[rowIndex][columnIndex],
|
||||
);
|
||||
final markdown = const SimpleTableNodeParser().transform(
|
||||
tableNode,
|
||||
null,
|
||||
);
|
||||
expect(markdown,
|
||||
'''|Index|Customer Id|First Name|Last Name|Company|City|Country|
|
||||
|---|---|---|---|---|---|---|
|
||||
|1|DD37Cf93aecA6Dc|Sheryl|Baxter|Rasmussen Group|East Leonard|Chile|
|
||||
|2|1Ef7b82A4CAAD10|Preston|Lozano|Vega-Gentry|East Jimmychester|Djibouti|
|
||||
|3|6F94879bDAfE5a6|Roy|Berry|Murillo-Perry|Isabelborough|Antigua and Barbuda|
|
||||
|4|5Cef8BFA16c5e3c|Linda|Olsen|Dominguez, Mcmillan and Donovan|Bensonview|Dominican Republic|
|
||||
|5|053d585Ab6b3159|Joanna|Bender|Martin, Lang and Andrade|West Priscilla|Slovakia (Slovak Republic)|
|
||||
|6|2d08FB17EE273F4|Aimee|Downs|Steele Group|Chavezborough|Bosnia and Herzegovina|
|
||||
|7|EAd384DfDbBf77|Darren|Peck|Lester, Woodard and Mitchell|Lake Ana|Pitcairn Islands|
|
||||
|8|0e04AFde9f225dE|Brett|Mullen|Sanford, Davenport and Giles|Kimport|Bulgaria|
|
||||
|9|C2dE4dEEc489ae0|Sheryl|Meyers|Browning-Simon|Robersonstad|Cyprus|
|
||||
|10|8C2811a503C7c5a|Michelle|Gallagher|Beck-Hendrix|Elaineberg|Timor-Leste|
|
||||
''');
|
||||
});
|
||||
|
||||
test('convert markdown to simple table (1)', () async {
|
||||
final document = customMarkdownToDocument(_sampleMarkdown1);
|
||||
expect(document, isNotNull);
|
||||
final tableNode = document.nodeAtPath([0])!;
|
||||
expect(tableNode, isNotNull);
|
||||
expect(tableNode.type, equals(SimpleTableBlockKeys.type));
|
||||
expect(tableNode.rowLength, equals(4));
|
||||
expect(tableNode.columnLength, equals(4));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const _sampleContents = <List<String>>[
|
||||
[
|
||||
"Index",
|
||||
"Customer Id",
|
||||
"First Name",
|
||||
"Last Name",
|
||||
"Company",
|
||||
"City",
|
||||
"Country",
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"DD37Cf93aecA6Dc",
|
||||
"Sheryl",
|
||||
"Baxter",
|
||||
"Rasmussen Group",
|
||||
"East Leonard",
|
||||
"Chile",
|
||||
],
|
||||
[
|
||||
"2",
|
||||
"1Ef7b82A4CAAD10",
|
||||
"Preston",
|
||||
"Lozano",
|
||||
"Vega-Gentry",
|
||||
"East Jimmychester",
|
||||
"Djibouti",
|
||||
],
|
||||
[
|
||||
"3",
|
||||
"6F94879bDAfE5a6",
|
||||
"Roy",
|
||||
"Berry",
|
||||
"Murillo-Perry",
|
||||
"Isabelborough",
|
||||
"Antigua and Barbuda",
|
||||
],
|
||||
[
|
||||
"4",
|
||||
"5Cef8BFA16c5e3c",
|
||||
"Linda",
|
||||
"Olsen",
|
||||
"Dominguez, Mcmillan and Donovan",
|
||||
"Bensonview",
|
||||
"Dominican Republic",
|
||||
],
|
||||
[
|
||||
"5",
|
||||
"053d585Ab6b3159",
|
||||
"Joanna",
|
||||
"Bender",
|
||||
"Martin, Lang and Andrade",
|
||||
"West Priscilla",
|
||||
"Slovakia (Slovak Republic)",
|
||||
],
|
||||
[
|
||||
"6",
|
||||
"2d08FB17EE273F4",
|
||||
"Aimee",
|
||||
"Downs",
|
||||
"Steele Group",
|
||||
"Chavezborough",
|
||||
"Bosnia and Herzegovina",
|
||||
],
|
||||
[
|
||||
"7",
|
||||
"EAd384DfDbBf77",
|
||||
"Darren",
|
||||
"Peck",
|
||||
"Lester, Woodard and Mitchell",
|
||||
"Lake Ana",
|
||||
"Pitcairn Islands",
|
||||
],
|
||||
[
|
||||
"8",
|
||||
"0e04AFde9f225dE",
|
||||
"Brett",
|
||||
"Mullen",
|
||||
"Sanford, Davenport and Giles",
|
||||
"Kimport",
|
||||
"Bulgaria",
|
||||
],
|
||||
[
|
||||
"9",
|
||||
"C2dE4dEEc489ae0",
|
||||
"Sheryl",
|
||||
"Meyers",
|
||||
"Browning-Simon",
|
||||
"Robersonstad",
|
||||
"Cyprus",
|
||||
],
|
||||
[
|
||||
"10",
|
||||
"8C2811a503C7c5a",
|
||||
"Michelle",
|
||||
"Gallagher",
|
||||
"Beck-Hendrix",
|
||||
"Elaineberg",
|
||||
"Timor-Leste",
|
||||
],
|
||||
];
|
||||
|
||||
const _sampleMarkdown1 = '''|A|B|C||
|
||||
|---|---|---|---|
|
||||
|D|E|F||
|
||||
|1|2|3||
|
||||
|||||
|
||||
''';
|
||||
@ -1,5 +1,4 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_constants.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/table_operations/table_operations.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/table/simple_table_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_block_component.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
(EditorState editorState, Node tableNode) createEditorStateAndTable({
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user