From 8b44676b0dbcc57212b36a116a90465867f53c35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Jablo=C3=B1ski?= <43938777+GermanJablo@users.noreply.github.com> Date: Sat, 28 Sep 2024 14:10:44 -0300 Subject: [PATCH] feat(richtext-lexical)!: upgrade lexical from 0.17.0 to 0.18.0, make tables more reliable (#8444) This PR - Introduces multiline markdown transformers / mdx support - Introduce `shouldMergeAdjacentLines` option in `$convertFromMarkdownString`. If true, merges adjacent lines as per commonmark spec. This would allow to close: https://github.com/payloadcms/payload/issues/8049 - Many new features and bug fixes! - Ports over changes from the lexical playground. Most notably: - add support for enabling table row stripping - make table resizing & table cell selection more reliable **BREAKING**: This upgrades lexical from 0.17.0 to 0.18.0. If you have any lexical packages installed in your project, please update them accordingly. Additionally, if you depend on the lexical APIs, please consult their changelog, as lexical may introduce breaking changes: https://github.com/facebook/lexical/releases/tag/v0.18.0 --------- Co-authored-by: Alessio Gravili --- package.json | 2 +- packages/richtext-lexical/package.json | 44 +-- .../blocks/client/componentInline/index.tsx | 17 +- .../plugins/TableActionMenuPlugin/index.tsx | 80 ++-- .../plugins/TableCellResizerPlugin/index.tsx | 75 ++-- .../client/plugins/TablePlugin/index.scss | 7 +- .../client/plugins/TablePlugin/index.tsx | 2 +- .../horizontalRule/client/component/index.tsx | 18 +- .../components/RelationshipComponent.tsx | 17 +- .../upload/client/component/index.tsx | 17 +- packages/richtext-lexical/src/index.ts | 2 +- .../src/lexical/theme/EditorTheme.tsx | 1 + .../ui/src/providers/ScrollInfo/index.tsx | 11 +- pnpm-lock.yaml | 354 +++++++++--------- templates/website/package.json | 2 +- test/package.json | 6 +- 16 files changed, 317 insertions(+), 338 deletions(-) diff --git a/package.json b/package.json index e17231bdfe..31596948d1 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "prettier --write", "eslint --cache --fix" ], - "templates/website/**/*": "sh -c \"cd templates/website; pnpm install --ignore-workspace; pnpm run lint --fix\"", + "templates/website/**/*": "sh -c \"cd templates/website; pnpm install --no-frozen-lockfile --ignore-workspace; pnpm run lint --fix\"", "tsconfig.json": "node scripts/reset-tsconfig.js" }, "devDependencies": { diff --git a/packages/richtext-lexical/package.json b/packages/richtext-lexical/package.json index 6a02e932a3..51cccbb506 100644 --- a/packages/richtext-lexical/package.json +++ b/packages/richtext-lexical/package.json @@ -52,22 +52,22 @@ "translateNewKeys": "node --no-deprecation --import @swc-node/register/esm-register scripts/translateNewKeys.ts" }, "dependencies": { - "@lexical/headless": "0.17.0", - "@lexical/link": "0.17.0", - "@lexical/list": "0.17.0", - "@lexical/mark": "0.17.0", - "@lexical/markdown": "0.17.0", - "@lexical/react": "0.17.0", - "@lexical/rich-text": "0.17.0", - "@lexical/selection": "0.17.0", - "@lexical/utils": "0.17.0", + "@lexical/headless": "0.18.0", + "@lexical/link": "0.18.0", + "@lexical/list": "0.18.0", + "@lexical/mark": "0.18.0", + "@lexical/markdown": "0.18.0", + "@lexical/react": "0.18.0", + "@lexical/rich-text": "0.18.0", + "@lexical/selection": "0.18.0", + "@lexical/utils": "0.18.0", "@payloadcms/translations": "workspace:*", "@payloadcms/ui": "workspace:*", "@types/uuid": "10.0.0", "bson-objectid": "2.0.4", "dequal": "2.0.3", "escape-html": "1.0.3", - "lexical": "0.17.0", + "lexical": "0.18.0", "react-error-boundary": "4.0.13", "uuid": "10.0.0" }, @@ -77,7 +77,7 @@ "@babel/preset-env": "^7.24.5", "@babel/preset-react": "^7.24.1", "@babel/preset-typescript": "^7.24.1", - "@lexical/eslint-plugin": "0.17.0", + "@lexical/eslint-plugin": "0.18.0", "@payloadcms/eslint-config": "workspace:*", "@types/escape-html": "1.0.4", "@types/json-schema": "7.0.15", @@ -95,18 +95,18 @@ "peerDependencies": { "@faceless-ui/modal": "3.0.0-beta.2", "@faceless-ui/scroll-info": "2.0.0-beta.0", - "@lexical/headless": "0.17.0", - "@lexical/link": "0.17.0", - "@lexical/list": "0.17.0", - "@lexical/mark": "0.17.0", - "@lexical/markdown": "0.17.0", - "@lexical/react": "0.17.0", - "@lexical/rich-text": "0.17.0", - "@lexical/selection": "0.17.0", - "@lexical/table": "0.17.0", - "@lexical/utils": "0.17.0", + "@lexical/headless": "0.18.0", + "@lexical/link": "0.18.0", + "@lexical/list": "0.18.0", + "@lexical/mark": "0.18.0", + "@lexical/markdown": "0.18.0", + "@lexical/react": "0.18.0", + "@lexical/rich-text": "0.18.0", + "@lexical/selection": "0.18.0", + "@lexical/table": "0.18.0", + "@lexical/utils": "0.18.0", "@payloadcms/next": "workspace:*", - "lexical": "0.17.0", + "lexical": "0.18.0", "payload": "workspace:*", "react": "^19.0.0 || ^19.0.0-rc-5dcb0097-20240918", "react-dom": "^19.0.0 || ^19.0.0-rc-5dcb0097-20240918" diff --git a/packages/richtext-lexical/src/features/blocks/client/componentInline/index.tsx b/packages/richtext-lexical/src/features/blocks/client/componentInline/index.tsx index 1a30c6a99b..3817b15736 100644 --- a/packages/richtext-lexical/src/features/blocks/client/componentInline/index.tsx +++ b/packages/richtext-lexical/src/features/blocks/client/componentInline/index.tsx @@ -56,17 +56,20 @@ export const InlineBlockComponent: React.FC = (props) => { const $onDelete = useCallback( (event: KeyboardEvent) => { - if (isSelected && $isNodeSelection($getSelection())) { + const deleteSelection = $getSelection() + if (isSelected && $isNodeSelection(deleteSelection)) { event.preventDefault() - const node = $getNodeByKey(nodeKey) - if ($isInlineBlockNode(node)) { - node.remove() - return true - } + editor.update(() => { + deleteSelection.getNodes().forEach((node) => { + if ($isInlineBlockNode(node)) { + node.remove() + } + }) + }) } return false }, - [isSelected, nodeKey], + [editor, isSelected], ) const onClick = useCallback( (payload: MouseEvent) => { diff --git a/packages/richtext-lexical/src/features/experimental_table/client/plugins/TableActionMenuPlugin/index.tsx b/packages/richtext-lexical/src/features/experimental_table/client/plugins/TableActionMenuPlugin/index.tsx index af8263ad3e..6d1ab2c35b 100644 --- a/packages/richtext-lexical/src/features/experimental_table/client/plugins/TableActionMenuPlugin/index.tsx +++ b/packages/richtext-lexical/src/features/experimental_table/client/plugins/TableActionMenuPlugin/index.tsx @@ -58,47 +58,6 @@ function computeSelectionCount(selection: TableSelection): { } } -// This is important when merging cells as there is no good way to re-merge weird shapes (a result -// of selecting merged cells and non-merged) -function isTableSelectionRectangular(selection: TableSelection): boolean { - const nodes = selection.getNodes() - const currentRows: Array = [] - let currentRow: null | TableRowNode = null - let expectedColumns: null | number = null - let currentColumns = 0 - for (let i = 0; i < nodes.length; i++) { - const node = nodes[i] - if ($isTableCellNode(node)) { - const row = node.getParentOrThrow() - if (!$isTableRowNode(row)) { - throw new Error('Expected CellNode to have a RowNode parent') - } - if (currentRow !== row) { - if (expectedColumns !== null && currentColumns !== expectedColumns) { - return false - } - if (currentRow !== null) { - expectedColumns = currentColumns - } - currentRow = row - currentColumns = 0 - } - const colSpan = node.__colSpan - for (let j = 0; j < colSpan; j++) { - if (currentRows[currentColumns + j] === undefined) { - currentRows[currentColumns + j] = 0 - } - currentRows[currentColumns + j] += node.__rowSpan - } - currentColumns += colSpan - } - } - return ( - (expectedColumns === null || currentColumns === expectedColumns) && - currentRows.every((v) => v === currentRows[0]) - ) -} - function $canUnmerge(): boolean { const selection = $getSelection() if ( @@ -183,10 +142,8 @@ function TableActionMenu({ if ($isTableSelection(selection)) { const currentSelectionCounts = computeSelectionCount(selection) updateSelectionCounts(computeSelectionCount(selection)) - setCanMergeCells( - isTableSelectionRectangular(selection) && - (currentSelectionCounts.columns > 1 || currentSelectionCounts.rows > 1), - ) + + setCanMergeCells(currentSelectionCounts.columns > 1 || currentSelectionCounts.rows > 1) } // Unmerge cell setCanUnmergeCell($canUnmerge()) @@ -252,9 +209,9 @@ function TableActionMenu({ throw new Error('Expected to find tableElement in DOM') } - const tableSelection = getTableObserverFromTableElement(tableElement) - if (tableSelection !== null) { - tableSelection.clearHighlight() + const tableObserver = getTableObserverFromTableElement(tableElement) + if (tableObserver !== null) { + tableObserver.clearHighlight() } tableNode.markDirty() @@ -374,12 +331,13 @@ function TableActionMenu({ throw new Error('Expected table row') } + const newStyle = tableCellNode.getHeaderStyles() ^ TableCellHeaderStates.ROW tableRow.getChildren().forEach((tableCell) => { if (!$isTableCellNode(tableCell)) { throw new Error('Expected table cell') } - tableCell.toggleHeaderStyle(TableCellHeaderStates.ROW) + tableCell.setHeaderStyles(newStyle, TableCellHeaderStates.ROW) }) clearTableSelection() @@ -400,6 +358,7 @@ function TableActionMenu({ throw new Error('Expected table cell to be inside of table row.') } + const newStyle = tableCellNode.getHeaderStyles() ^ TableCellHeaderStates.COLUMN for (let r = 0; r < tableRows.length; r++) { const tableRow = tableRows[r] @@ -419,7 +378,20 @@ function TableActionMenu({ throw new Error('Expected table cell') } - tableCell.toggleHeaderStyle(TableCellHeaderStates.COLUMN) + tableCell.setHeaderStyles(newStyle, TableCellHeaderStates.COLUMN) + } + clearTableSelection() + onClose() + }) + }, [editor, tableCellNode, clearTableSelection, onClose]) + + const toggleRowStriping = useCallback(() => { + editor.update(() => { + if (tableCellNode.isAttached()) { + const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode) + if (tableNode) { + tableNode.setRowStriping(!tableNode.getRowStriping()) + } } clearTableSelection() @@ -470,6 +442,14 @@ function TableActionMenu({ ) : null} +