This should fix it https://github.com/payloadcms/payload/issues/10387 I don't know why we had 2 different copies of normalizeMarkdown. Also, the most up-to-date one still had a bug where lines were considered as if they were inside codeblocks when they weren't. How I tested that it works: 1. I copied the `normalizeMarkdown` implementation from this PR into the website repo, and made sure it is called before the conversion to editorState. 2. In the admin panel, sync docs. 3. In the admin panel, refresh mdx to lexical (new button, below sync docs). 4. Look for the examples from bug #10387 and verify that they have been resolved. An extra pair of eyes would be nice to make sure I'm not getting confused with the imports.
177 lines
4.6 KiB
TypeScript
177 lines
4.6 KiB
TypeScript
import type { LexicalNode } from 'lexical'
|
|
|
|
import {
|
|
$createTableCellNode,
|
|
$createTableNode,
|
|
$createTableRowNode,
|
|
$isTableCellNode,
|
|
$isTableNode,
|
|
$isTableRowNode,
|
|
TableCellHeaderStates,
|
|
TableCellNode,
|
|
TableNode,
|
|
TableRowNode,
|
|
} from '@lexical/table'
|
|
import { $isParagraphNode, $isTextNode } from 'lexical'
|
|
|
|
import {
|
|
$convertFromMarkdownString,
|
|
$convertToMarkdownString,
|
|
type ElementTransformer,
|
|
type Transformer,
|
|
} from '../../packages/@lexical/markdown/index.js'
|
|
|
|
// Very primitive table setup
|
|
const TABLE_ROW_REG_EXP = /^\|(.+)\|\s?$/
|
|
// eslint-disable-next-line regexp/no-unused-capturing-group
|
|
const TABLE_ROW_DIVIDER_REG_EXP = /^(\| ?:?-*:? ?)+\|\s?$/
|
|
|
|
export const TableMarkdownTransformer: (props: {
|
|
allTransformers: Transformer[]
|
|
}) => ElementTransformer = ({ allTransformers }) => ({
|
|
type: 'element',
|
|
dependencies: [TableNode, TableRowNode, TableCellNode],
|
|
export: (node: LexicalNode) => {
|
|
if (!$isTableNode(node)) {
|
|
return null
|
|
}
|
|
|
|
const output: string[] = []
|
|
|
|
for (const row of node.getChildren()) {
|
|
const rowOutput: string[] = []
|
|
if (!$isTableRowNode(row)) {
|
|
continue
|
|
}
|
|
|
|
let isHeaderRow = false
|
|
for (const cell of row.getChildren()) {
|
|
// It's TableCellNode, so it's just to make flow happy
|
|
if ($isTableCellNode(cell)) {
|
|
rowOutput.push($convertToMarkdownString(allTransformers, cell).replace(/\n/g, '\\n'))
|
|
if (cell.__headerState === TableCellHeaderStates.ROW) {
|
|
isHeaderRow = true
|
|
}
|
|
}
|
|
}
|
|
|
|
output.push(`| ${rowOutput.join(' | ')} |`)
|
|
if (isHeaderRow) {
|
|
output.push(`| ${rowOutput.map((_) => '---').join(' | ')} |`)
|
|
}
|
|
}
|
|
|
|
return output.join('\n')
|
|
},
|
|
regExp: TABLE_ROW_REG_EXP,
|
|
replace: (parentNode, _1, match) => {
|
|
// Header row
|
|
if (TABLE_ROW_DIVIDER_REG_EXP.test(match[0])) {
|
|
const table = parentNode.getPreviousSibling()
|
|
if (!table || !$isTableNode(table)) {
|
|
return
|
|
}
|
|
|
|
const rows = table.getChildren()
|
|
const lastRow = rows[rows.length - 1]
|
|
if (!lastRow || !$isTableRowNode(lastRow)) {
|
|
return
|
|
}
|
|
|
|
// Add header state to row cells
|
|
lastRow.getChildren().forEach((cell) => {
|
|
if (!$isTableCellNode(cell)) {
|
|
return
|
|
}
|
|
cell.setHeaderStyles(TableCellHeaderStates.ROW, TableCellHeaderStates.ROW)
|
|
})
|
|
|
|
// Remove line
|
|
parentNode.remove()
|
|
return
|
|
}
|
|
|
|
const matchCells = mapToTableCells(match[0], allTransformers)
|
|
|
|
if (matchCells == null) {
|
|
return
|
|
}
|
|
|
|
const rows = [matchCells]
|
|
let sibling = parentNode.getPreviousSibling()
|
|
let maxCells = matchCells.length
|
|
|
|
while (sibling) {
|
|
if (!$isParagraphNode(sibling)) {
|
|
break
|
|
}
|
|
|
|
if (sibling.getChildrenSize() !== 1) {
|
|
break
|
|
}
|
|
|
|
const firstChild = sibling.getFirstChild()
|
|
|
|
if (!$isTextNode(firstChild)) {
|
|
break
|
|
}
|
|
|
|
const cells = mapToTableCells(firstChild.getTextContent(), allTransformers)
|
|
|
|
if (cells == null) {
|
|
break
|
|
}
|
|
|
|
maxCells = Math.max(maxCells, cells.length)
|
|
rows.unshift(cells)
|
|
const previousSibling = sibling.getPreviousSibling()
|
|
sibling.remove()
|
|
sibling = previousSibling
|
|
}
|
|
|
|
const table = $createTableNode()
|
|
|
|
for (const cells of rows) {
|
|
const tableRow = $createTableRowNode()
|
|
table.append(tableRow)
|
|
|
|
for (let i = 0; i < maxCells; i++) {
|
|
tableRow.append(i < cells.length ? cells[i] : $createTableCell('', allTransformers))
|
|
}
|
|
}
|
|
|
|
const previousSibling = parentNode.getPreviousSibling()
|
|
if ($isTableNode(previousSibling) && getTableColumnsSize(previousSibling) === maxCells) {
|
|
previousSibling.append(...table.getChildren())
|
|
parentNode.remove()
|
|
} else {
|
|
parentNode.replace(table)
|
|
}
|
|
|
|
table.selectEnd()
|
|
},
|
|
})
|
|
|
|
function getTableColumnsSize(table: TableNode) {
|
|
const row = table.getFirstChild()
|
|
return $isTableRowNode(row) ? row.getChildrenSize() : 0
|
|
}
|
|
|
|
const $createTableCell = (textContent: string, allTransformers: Transformer[]): TableCellNode => {
|
|
textContent = textContent.replace(/\\n/g, '\n')
|
|
const cell = $createTableCellNode(TableCellHeaderStates.NO_STATUS)
|
|
$convertFromMarkdownString(textContent, allTransformers, cell)
|
|
return cell
|
|
}
|
|
|
|
const mapToTableCells = (
|
|
textContent: string,
|
|
allTransformers: Transformer[],
|
|
): Array<TableCellNode> | null => {
|
|
const match = textContent.match(TABLE_ROW_REG_EXP)
|
|
if (!match || !match[1]) {
|
|
return null
|
|
}
|
|
return match[1].split('|').map((text) => $createTableCell(text, allTransformers))
|
|
}
|