feat: add remark-breaks plugin to preserve single newlines in markdown (#4217)
Signed-off-by: Abhijay007 <Abhijay007j@gmail.com>
This commit is contained in:
Generated
+30
@@ -48,6 +48,7 @@
|
|||||||
"react-select": "^5.10.2",
|
"react-select": "^5.10.2",
|
||||||
"react-syntax-highlighter": "^15.6.1",
|
"react-syntax-highlighter": "^15.6.1",
|
||||||
"react-toastify": "^11.0.5",
|
"react-toastify": "^11.0.5",
|
||||||
|
"remark-breaks": "^4.0.0",
|
||||||
"remark-gfm": "^4.0.1",
|
"remark-gfm": "^4.0.1",
|
||||||
"split-type": "^0.3.4",
|
"split-type": "^0.3.4",
|
||||||
"swr": "^2.3.6",
|
"swr": "^2.3.6",
|
||||||
@@ -13417,6 +13418,20 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mdast-util-newline-to-break": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mdast-util-newline-to-break/-/mdast-util-newline-to-break-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/mdast": "^4.0.0",
|
||||||
|
"mdast-util-find-and-replace": "^3.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mdast-util-phrasing": {
|
"node_modules/mdast-util-phrasing": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
|
||||||
@@ -16386,6 +16401,21 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/remark-breaks": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/remark-breaks/-/remark-breaks-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/mdast": "^4.0.0",
|
||||||
|
"mdast-util-newline-to-break": "^2.0.0",
|
||||||
|
"unified": "^11.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/remark-gfm": {
|
"node_modules/remark-gfm": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
|
||||||
|
|||||||
@@ -78,6 +78,7 @@
|
|||||||
"react-select": "^5.10.2",
|
"react-select": "^5.10.2",
|
||||||
"react-syntax-highlighter": "^15.6.1",
|
"react-syntax-highlighter": "^15.6.1",
|
||||||
"react-toastify": "^11.0.5",
|
"react-toastify": "^11.0.5",
|
||||||
|
"remark-breaks": "^4.0.0",
|
||||||
"remark-gfm": "^4.0.1",
|
"remark-gfm": "^4.0.1",
|
||||||
"split-type": "^0.3.4",
|
"split-type": "^0.3.4",
|
||||||
"swr": "^2.3.6",
|
"swr": "^2.3.6",
|
||||||
|
|||||||
@@ -260,4 +260,66 @@ Unclosed code block`;
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Line Break Functionality', () => {
|
||||||
|
it('preserves single line breaks with remark-breaks plugin', async () => {
|
||||||
|
const content = `First line
|
||||||
|
Second line
|
||||||
|
Third line`;
|
||||||
|
|
||||||
|
const { container } = render(<MarkdownContent content={content} />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
// Check that all text content is present (text may be split by <br> tags)
|
||||||
|
expect(container).toHaveTextContent('First line');
|
||||||
|
expect(container).toHaveTextContent('Second line');
|
||||||
|
expect(container).toHaveTextContent('Third line');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check that line breaks are preserved (rendered as <br> tags)
|
||||||
|
const brElements = container.querySelectorAll('br');
|
||||||
|
expect(brElements.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles mixed content with line breaks', async () => {
|
||||||
|
const content = `# Header
|
||||||
|
Paragraph with
|
||||||
|
line breaks.
|
||||||
|
|
||||||
|
- List item 1
|
||||||
|
- List item 2`;
|
||||||
|
|
||||||
|
const { container } = render(<MarkdownContent content={content} />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByRole('heading', { level: 1, name: 'Header' })).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Check that text content is present (text may be split by <br> tags)
|
||||||
|
expect(container).toHaveTextContent('Paragraph with');
|
||||||
|
expect(container).toHaveTextContent('line breaks.');
|
||||||
|
expect(screen.getByText('List item 1')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('List item 2')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('maintains existing markdown features with line breaks', async () => {
|
||||||
|
const content = `**Bold text**
|
||||||
|
with line break
|
||||||
|
|
||||||
|
\`code\` and
|
||||||
|
more text`;
|
||||||
|
|
||||||
|
const { container } = render(<MarkdownContent content={content} />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
// Bold text should still work
|
||||||
|
const boldElement = container.querySelector('strong');
|
||||||
|
expect(boldElement).toBeInTheDocument();
|
||||||
|
expect(boldElement).toHaveTextContent('Bold text');
|
||||||
|
|
||||||
|
// Code should still work
|
||||||
|
expect(screen.getByText('code')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import remarkGfm from 'remark-gfm';
|
import remarkGfm from 'remark-gfm';
|
||||||
|
import remarkBreaks from 'remark-breaks';
|
||||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||||
import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||||
import { Check, Copy } from './icons';
|
import { Check, Copy } from './icons';
|
||||||
@@ -126,7 +127,7 @@ export default function MarkdownContent({ content, className = '' }: MarkdownCon
|
|||||||
prose-li:m-0prose-li:font-sans ${className}`}
|
prose-li:m-0prose-li:font-sans ${className}`}
|
||||||
>
|
>
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
remarkPlugins={[remarkGfm]}
|
remarkPlugins={[remarkGfm, remarkBreaks]}
|
||||||
components={{
|
components={{
|
||||||
a: ({ ...props }) => <a {...props} target="_blank" rel="noopener noreferrer" />,
|
a: ({ ...props }) => <a {...props} target="_blank" rel="noopener noreferrer" />,
|
||||||
code: MarkdownCode,
|
code: MarkdownCode,
|
||||||
|
|||||||
Reference in New Issue
Block a user