diff --git a/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/messages/useMarkdown.test.ts b/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/messages/useMarkdown.test.ts new file mode 100644 index 00000000000..5279ee865a6 --- /dev/null +++ b/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/messages/useMarkdown.test.ts @@ -0,0 +1,29 @@ +import { useMarkdown } from './useMarkdown'; + +describe('useMarkdown', () => { + const { renderMarkdown } = useMarkdown(); + + it('should not add a trailing newline inside a fenced code block', () => { + const html = renderMarkdown('```\nhttps://example.com/webhook/abc\n```'); + + expect(html).toContain('https://example.com/webhook/abc'); + }); + + it('should keep newlines between lines of a fenced code block', () => { + const html = renderMarkdown('```\nline 1\nline 2\n```'); + + expect(html).toContain('line 1\nline 2'); + }); + + it('should keep the language class on a fenced code block', () => { + const html = renderMarkdown('```js\nconst a = 1;\n```'); + + expect(html).toContain('const a = 1;'); + }); + + it('should keep an intentional blank last line in a fenced code block', () => { + const html = renderMarkdown('```\nline 1\n\n```'); + + expect(html).toContain('line 1\n'); + }); +}); diff --git a/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/messages/useMarkdown.ts b/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/messages/useMarkdown.ts index c123f8aa733..4ac7b07c1e0 100644 --- a/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/messages/useMarkdown.ts +++ b/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/messages/useMarkdown.ts @@ -17,6 +17,15 @@ export function useMarkdown() { }, }); + // markdown-it ends fenced code with a newline. Triple-click copy would include it. + const renderFence = md.renderer.rules.fence; + md.renderer.rules.fence = (tokens, idx, options, env, self) => { + tokens[idx].content = tokens[idx].content.replace(/\n$/, ''); + return renderFence + ? renderFence(tokens, idx, options, env, self) + : self.renderToken(tokens, idx, options); + }; + function renderMarkdown(content: string) { try { return md.render(content);