diff --git a/gui/src/components/StyledMarkdownPreview/utils/remarkTables.tsx b/gui/src/components/StyledMarkdownPreview/utils/remarkTables.tsx index db26e4b3f28..a8ecc29b682 100644 --- a/gui/src/components/StyledMarkdownPreview/utils/remarkTables.tsx +++ b/gui/src/components/StyledMarkdownPreview/utils/remarkTables.tsx @@ -23,9 +23,34 @@ import { visit } from "unist-util-visit"; export function remarkTables() { return (tree: any) => { visit(tree, "paragraph", (paragraphNode, index, parentOfParagraphNode) => { - let buffer = ""; - visit(paragraphNode, "text", (textNode) => { - buffer += textNode.value; + // Collect all child nodes into a buffer, preserving their types + const buffer: any[] = []; + paragraphNode.children.forEach((child: any) => { + buffer.push(child); + }); + + // Flatten buffer to a string for regex matching, but keep track of positions + let bufferString = ""; + const positions: { start: number; end: number; node: any }[] = []; + + // Recursive renderer for inline nodes -> markdown-ish text + function renderInline(node: any): string { + if (!node) return ""; + if (Array.isArray(node.children)) { + return node.children.map(renderInline).join(""); + } + if (typeof node.value === "string") { + return node.value; + } + return ""; + } + + buffer.forEach((item) => { + const start = bufferString.length; + // renderInline returns a markdown-like string for inline nodes so decorations are preserved + const rendered = renderInline(item); + bufferString += rendered; + positions.push({ start, end: bufferString.length, node: item }); }); const tableRegex = @@ -33,7 +58,7 @@ export function remarkTables() { //// header // newline // |:---|----:| // new line // table rows // prevent modifying if no markdown tables are present - if (!buffer.match(tableRegex)) { + if (!bufferString.match(tableRegex)) { return; } @@ -42,7 +67,7 @@ export function remarkTables() { const newNodes = []; let failed = false; - while ((match = tableRegex.exec(buffer)) !== null) { + while ((match = tableRegex.exec(bufferString)) !== null) { const fullTableString = match[0]; const headerGroup = match[1]; const separatorGroup = match[2]; @@ -104,16 +129,46 @@ export function remarkTables() { ], }; - // Add any text before the table as a text node - if (match.index > lastIndex) { - newNodes.push({ - type: "text", - value: buffer.slice(lastIndex, match.index), - }); - } + // Add any nodes before/after the table in one go + const tableStart = match.index; + const tableEnd = match.index + fullTableString.length; + + // Process the text within the table and the surrounding text together + const beforeNodes: any[] = []; + const afterNodes: any[] = []; + + positions.forEach((pos) => { + if (pos.end <= tableStart) { + beforeNodes.push(pos.node); + } else if (pos.start >= tableEnd) { + afterNodes.push(pos.node); + } else if (pos.node.type === "text") { + // Node is text and overlaps with table, may need splitting + const beforeText = pos.node.value.slice( + 0, + Math.max(0, tableStart - pos.start), + ); + const afterText = pos.node.value.slice( + Math.max(0, tableEnd - pos.start), + ); + if (beforeText) + beforeNodes.push({ type: "text", value: beforeText }); + if (afterText) + afterNodes.push({ type: "text", value: afterText }); + } else { + // Node overlaps with table but isn't text (e.g., inlineCode, emphasis, etc.) + if (pos.start < tableStart) beforeNodes.push(pos.node); + if (pos.end > tableEnd) afterNodes.push(pos.node); + } + }); - // Add table node + // Add to new nodes + newNodes.push(...beforeNodes); newNodes.push(tableNode); + newNodes.push(...afterNodes); + + // Mark positions as consumed + positions.length = 0; } catch (e) { console.error("Failed to parse markdown table after regex match", e); newNodes.push({ @@ -129,14 +184,6 @@ export function remarkTables() { return; } - // Add any remaining text after the last table - if (lastIndex < buffer.length) { - newNodes.push({ - type: "text", - value: buffer.slice(lastIndex), - }); - } - // Replace the original paragraph node with the new nodes if (newNodes.length > 0) { parentOfParagraphNode.children.splice(index, 1, ...newNodes);