Skip to content

fix: preserve markdown tables in RichInput Source/Edit roundtrip#6441

Open
mvanhorn wants to merge 1 commit into
FlowiseAI:mainfrom
mvanhorn:fix/6387-fix-preserve-markdown-tables-and-inline-
Open

fix: preserve markdown tables in RichInput Source/Edit roundtrip#6441
mvanhorn wants to merge 1 commit into
FlowiseAI:mainfrom
mvanhorn:fix/6387-fix-preserve-markdown-tables-and-inline-

Conversation

@mvanhorn
Copy link
Copy Markdown

Summary

fix: preserve markdown tables and inline-code escaping in RichInput Source/Edit roundtrip

Closes #6387


AI was used for assistance.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for rendering tables in markdown by adding the @tiptap/extension-table dependency and implementing a custom MarkdownTable extension in both ExpandRichInputDialog.jsx and RichInput.jsx. The review feedback correctly identifies two issues with the custom markdown table serializer: it fails to escape pipe characters (|) inside table cells, which breaks the markdown structure, and it incorrectly handles tables without explicit header cells by rendering an empty header row. The reviewer has provided an actionable, simplified code suggestion for both files to resolve these issues by escaping pipes and falling back to treating the first row as the header.

Comment on lines +31 to +64
const MarkdownTable = Table.extend({
renderMarkdown: (node, h) => {
const rows =
node.content?.map((rowNode) =>
(rowNode.content || []).map((cellNode) => {
const text = (cellNode.content || [])
.map((childNode) => h.renderChildren(childNode))
.join(' ')
.replace(/\s+/g, ' ')
.trim()
return { text, isHeader: cellNode.type === 'tableHeader' }
})
) || []
const columnCount = rows.reduce((max, row) => Math.max(max, row.length), 0)

if (!columnCount) return ''

const renderRow = (row = []) =>
`| ${new Array(columnCount)
.fill(0)
.map((_, index) => row[index]?.text || '')
.join(' | ')} |`

const headerRow = rows[0] || []
const hasHeader = headerRow.some((cell) => cell.isHeader)
const bodyRows = hasHeader ? rows.slice(1) : rows

return [
renderRow(hasHeader ? headerRow : []),
`| ${new Array(columnCount).fill('---').join(' | ')} |`,
...bodyRows.map(renderRow)
].join('\n')
}
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current markdown table serializer has two issues:

  1. If a table cell contains a pipe character (|), it will break the markdown table structure. We should escape | as \| inside the cell text.
  2. If a table has no explicit header cells (hasHeader is false), the current logic renders an empty header row and shifts all actual data to the body. Since Markdown tables require a header row, we should fallback to treating the first row as the header row.

We can simplify the code and address both issues by removing the isHeader tracking and always treating the first row as the header row, which is the standard fallback for Markdown tables.

const MarkdownTable = Table.extend({
    renderMarkdown: (node, h) => {
        const rows =
            node.content?.map((rowNode) =>
                (rowNode.content || []).map((cellNode) =>
                    (cellNode.content || [])
                        .map((childNode) => h.renderChildren(childNode))
                        .join(' ')
                        .replace(/\s+/g, ' ')
                        .trim()
                        .replace(/\|/g, '\\|')
                )
            ) || []
        const columnCount = rows.reduce((max, row) => Math.max(max, row.length), 0)

        if (!columnCount) return ''

        const renderRow = (row = []) =>
            '| ' + new Array(columnCount)
                .fill(0)
                .map((_, index) => row[index] || '')
                .join(' | ') + ' |'

        const headerRow = rows[0] || []
        const bodyRows = rows.slice(1)

        return [
            renderRow(headerRow),
            '| ' + new Array(columnCount).fill('---').join(' | ') + ' |',
            ...bodyRows.map(renderRow)
        ].join('\n')
    }
})
References
  1. When using a heuristic for detection, ensure a safe fallback mechanism is in place to correctly handle cases where the heuristic fails.

Comment on lines +21 to +54
const MarkdownTable = Table.extend({
renderMarkdown: (node, h) => {
const rows =
node.content?.map((rowNode) =>
(rowNode.content || []).map((cellNode) => {
const text = (cellNode.content || [])
.map((childNode) => h.renderChildren(childNode))
.join(' ')
.replace(/\s+/g, ' ')
.trim()
return { text, isHeader: cellNode.type === 'tableHeader' }
})
) || []
const columnCount = rows.reduce((max, row) => Math.max(max, row.length), 0)

if (!columnCount) return ''

const renderRow = (row = []) =>
`| ${new Array(columnCount)
.fill(0)
.map((_, index) => row[index]?.text || '')
.join(' | ')} |`

const headerRow = rows[0] || []
const hasHeader = headerRow.some((cell) => cell.isHeader)
const bodyRows = hasHeader ? rows.slice(1) : rows

return [
renderRow(hasHeader ? headerRow : []),
`| ${new Array(columnCount).fill('---').join(' | ')} |`,
...bodyRows.map(renderRow)
].join('\n')
}
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current markdown table serializer has two issues:

  1. If a table cell contains a pipe character (|), it will break the markdown table structure. We should escape | as \| inside the cell text.
  2. If a table has no explicit header cells (hasHeader is false), the current logic renders an empty header row and shifts all actual data to the body. Since Markdown tables require a header row, we should fallback to treating the first row as the header row.

We can simplify the code and address both issues by removing the isHeader tracking and always treating the first row as the header row, which is the standard fallback for Markdown tables.

const MarkdownTable = Table.extend({
    renderMarkdown: (node, h) => {
        const rows =
            node.content?.map((rowNode) =>
                (rowNode.content || []).map((cellNode) =>
                    (cellNode.content || [])
                        .map((childNode) => h.renderChildren(childNode))
                        .join(' ')
                        .replace(/\s+/g, ' ')
                        .trim()
                        .replace(/\|/g, '\\|')
                )
            ) || []
        const columnCount = rows.reduce((max, row) => Math.max(max, row.length), 0)

        if (!columnCount) return ''

        const renderRow = (row = []) =>
            '| ' + new Array(columnCount)
                .fill(0)
                .map((_, index) => row[index] || '')
                .join(' | ') + ' |'

        const headerRow = rows[0] || []
        const bodyRows = rows.slice(1)

        return [
            renderRow(headerRow),
            '| ' + new Array(columnCount).fill('---').join(' | ') + ' |',
            ...bodyRows.map(renderRow)
        ].join('\n')
    }
})
References
  1. When using a heuristic for detection, ensure a safe fallback mechanism is in place to correctly handle cases where the heuristic fails.

@mvanhorn mvanhorn changed the title fix: preserve markdown tables and inline-code escaping in RichInput Source/Edit roundtrip fix: preserve markdown tables in RichInput Source/Edit roundtrip May 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Markdown problems in Agentflows V2 after flowise@3.1.1 release + no support for tables

1 participant