Skip to content

Commit dc3dccb

Browse files
authored
Merge pull request #272 from beNative/tisi/add-table-item-to-context-menu
Add table submenu to rich text context menu
2 parents 99e3318 + 977fbfd commit dc3dccb

1 file changed

Lines changed: 129 additions & 3 deletions

File tree

components/RichTextEditor.tsx

Lines changed: 129 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ interface ToolbarButtonConfig {
131131
id: string;
132132
label: string;
133133
icon: React.FC<{ className?: string }>;
134-
group: 'history' | 'inline-format' | 'structure' | 'insert' | 'alignment' | 'utility';
134+
group: 'history' | 'inline-format' | 'structure' | 'insert' | 'alignment' | 'utility' | 'table';
135135
isActive?: boolean;
136136
disabled?: boolean;
137137
onClick: () => void;
@@ -986,6 +986,35 @@ const ToolbarPlugin: React.FC<{
986986
setIsTableModalOpen(false);
987987
}, [runWithActiveTable]);
988988

989+
const selectTable = useCallback(
990+
() =>
991+
runWithActiveTable(selection => {
992+
const tableCell = $getTableCellNodeFromLexicalNode(selection.anchor.getNode());
993+
if (!tableCell) {
994+
return;
995+
}
996+
const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCell);
997+
const firstRow = tableNode.getFirstChild();
998+
const lastRow = tableNode.getLastChild();
999+
1000+
if (!$isTableRowNode(firstRow) || !$isTableRowNode(lastRow)) {
1001+
return;
1002+
}
1003+
1004+
const firstCell = firstRow.getFirstChild();
1005+
const lastCell = lastRow.getLastChild();
1006+
1007+
if (!$isTableCellNode(firstCell) || !$isTableCellNode(lastCell)) {
1008+
return;
1009+
}
1010+
1011+
const tableSelection = $createTableSelection();
1012+
tableSelection.set(tableNode.getKey(), firstCell.getKey(), lastCell.getKey());
1013+
$setSelection(tableSelection);
1014+
}),
1015+
[runWithActiveTable],
1016+
);
1017+
9891018
const toggleHeaderRow = useCallback(
9901019
() =>
9911020
runWithActiveTable(selection => {
@@ -1279,9 +1308,93 @@ const ToolbarPlugin: React.FC<{
12791308
],
12801309
);
12811310

1311+
const tableContextActions = useMemo<ToolbarButtonConfig[]>(
1312+
() => [
1313+
{
1314+
id: 'insert-column-before',
1315+
label: 'Insert column before',
1316+
icon: TableIcon,
1317+
group: 'table',
1318+
disabled: readOnly || !isInTable,
1319+
onClick: () => insertTableColumn(false),
1320+
},
1321+
{
1322+
id: 'insert-column-after',
1323+
label: 'Insert column after',
1324+
icon: TableIcon,
1325+
group: 'table',
1326+
disabled: readOnly || !isInTable,
1327+
onClick: () => insertTableColumn(true),
1328+
},
1329+
{
1330+
id: 'insert-row-before',
1331+
label: 'Insert row before',
1332+
icon: TableIcon,
1333+
group: 'table',
1334+
disabled: readOnly || !isInTable,
1335+
onClick: () => insertTableRow(false),
1336+
},
1337+
{
1338+
id: 'insert-row-after',
1339+
label: 'Insert row after',
1340+
icon: TableIcon,
1341+
group: 'table',
1342+
disabled: readOnly || !isInTable,
1343+
onClick: () => insertTableRow(true),
1344+
},
1345+
{
1346+
id: 'delete-row',
1347+
label: 'Delete row',
1348+
icon: TableIcon,
1349+
group: 'table',
1350+
disabled: readOnly || !isInTable,
1351+
onClick: deleteTableRow,
1352+
},
1353+
{
1354+
id: 'delete-column',
1355+
label: 'Delete column',
1356+
icon: TableIcon,
1357+
group: 'table',
1358+
disabled: readOnly || !isInTable,
1359+
onClick: deleteTableColumn,
1360+
},
1361+
{
1362+
id: 'delete-table',
1363+
label: 'Delete table',
1364+
icon: TableIcon,
1365+
group: 'table',
1366+
disabled: readOnly || !isInTable,
1367+
onClick: deleteTable,
1368+
},
1369+
{
1370+
id: 'select-table',
1371+
label: 'Select table',
1372+
icon: TableIcon,
1373+
group: 'table',
1374+
disabled: readOnly || !isInTable,
1375+
onClick: selectTable,
1376+
},
1377+
],
1378+
[
1379+
deleteTable,
1380+
deleteTableColumn,
1381+
deleteTableRow,
1382+
insertTableColumn,
1383+
insertTableRow,
1384+
isInTable,
1385+
readOnly,
1386+
selectTable,
1387+
],
1388+
);
1389+
1390+
const contextMenuActions = useMemo(
1391+
() => [...toolbarButtons, ...tableContextActions],
1392+
[tableContextActions, toolbarButtons],
1393+
);
1394+
12821395
useEffect(() => {
1283-
onActionsChange(toolbarButtons);
1284-
}, [toolbarButtons, onActionsChange]);
1396+
onActionsChange(contextMenuActions);
1397+
}, [contextMenuActions, onActionsChange]);
12851398

12861399
const renderedToolbarElements = useMemo(
12871400
() => {
@@ -1606,6 +1719,7 @@ const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorProps>(
16061719
const selectionActions = contextActions.filter(action =>
16071720
['inline-format', 'alignment', 'structure', 'utility'].includes(action.group),
16081721
);
1722+
const tableActions = contextActions.filter(action => action.group === 'table');
16091723
const insertActions = contextActions.filter(action => action.group === 'insert');
16101724

16111725
const items: ContextMenuItem[] = [];
@@ -1623,6 +1737,18 @@ const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorProps>(
16231737
});
16241738
}
16251739

1740+
if (items.length > 0 && tableActions.length > 0 && items[items.length - 1]?.type !== 'separator') {
1741+
items.push({ type: 'separator' });
1742+
}
1743+
1744+
if (tableActions.length > 0) {
1745+
items.push({
1746+
label: 'Table',
1747+
submenu: mapActionsToMenuItems(tableActions),
1748+
disabled: tableActions.every(action => action.disabled),
1749+
});
1750+
}
1751+
16261752
if (items.length > 0 && insertActions.length > 0) {
16271753
items.push({ type: 'separator' });
16281754
}

0 commit comments

Comments
 (0)