Skip to content

Commit

Permalink
fix: markdown inconsistency with bold and italics (#33157)
Browse files Browse the repository at this point in the history
  • Loading branch information
csuadev committed Sep 20, 2024
1 parent 274f4f5 commit 9c119a0
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/sweet-nails-grin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": patch
---

Fixed inconsistency between the markdown parser from the composer and the rest of the application when using bold and italics in a text.
92 changes: 92 additions & 0 deletions apps/meteor/client/components/MarkdownText.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { mockAppRoot } from '@rocket.chat/mock-providers';
import { render } from '@testing-library/react';
import React from 'react';

import MarkdownText from './MarkdownText';

import '@testing-library/jest-dom';

const normalizeHtml = (html: any) => {
return html.replace(/\s+/g, ' ').trim();
};

const markdownText = `
# Heading 1
**Paragraph text**: *Bold with one asterisk* **Bold with two asterisks** Lorem ipsum dolor sit amet, consectetur adipiscing elit.
## Heading 2
_Italic Text_: _Italic with one underscore_ __Italic with two underscores__ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
### Heading 3
Lists, Links and elements
**Unordered List**
- List Item 1
- List Item 2
- List Item 3
- List Item 4
**Ordered List**
1. List Item 1
2. List Item 2
3. List Item 3
4. List Item 4
**Links:**
[Rocket.Chat](rocket.chat)
gabriel.engel@rocket.chat
+55991999999
\`Inline code\`
\`\`\`typescript
const test = 'this is code'
\`\`\`
`;

it('should render html elements as expected using default parser', async () => {
const { container } = render(<MarkdownText content={markdownText} variant='document' />, {
wrapper: mockAppRoot().build(),
legacyRoot: true,
});

const normalizedHtml = normalizeHtml(container.innerHTML);

expect(normalizedHtml).toContain('<h1>Heading 1</h1>');
expect(normalizedHtml).toContain(
'<strong>Paragraph text</strong>: <strong>Bold with one asterisk</strong> <strong>Bold with two asterisks</strong> Lorem ipsum dolor sit amet',
);
expect(normalizedHtml).toContain('<h2>Heading 2</h2>');
expect(normalizedHtml).toContain(
'<em>Italic Text</em>: <em>Italic with one underscore</em> <em>Italic with two underscores</em> Lorem ipsum dolor sit amet',
);
expect(normalizedHtml).toContain('<h3>Heading 3</h3>');
expect(normalizedHtml).toContain('<ul> <li>List Item 1 </li><li>List Item 2 </li><li>List Item 3 </li><li>List Item 4');
expect(normalizedHtml).toContain('<ol> <li>List Item 1</li><li>List Item 2</li><li>List Item 3</li><li>List Item 4');
expect(normalizedHtml).toContain('<a title="" rel="nofollow noopener noreferrer" target="_blank">Rocket.Chat</a>');
expect(normalizedHtml).toContain('gabriel.engel@rocket.chat');
expect(normalizedHtml).toContain('+55991999999');
expect(normalizedHtml).toContain('<code>Inline code</code>');
expect(normalizedHtml).toContain('<pre><code class="language-typescript">const test = \'this is code\' </code></pre>');
});

it('should render html elements as expected using inline parser', async () => {
const { container } = render(<MarkdownText content={markdownText} variant='inline' />, {
wrapper: mockAppRoot().build(),
legacyRoot: true,
});

const normalizedHtml = normalizeHtml(container.innerHTML);

expect(normalizedHtml).toContain('# Heading 1');
expect(normalizedHtml).toContain(
'<strong>Bold with one asterisk</strong> <strong>Bold with two asterisks</strong> Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
);
expect(normalizedHtml).toContain('## Heading 2');
expect(normalizedHtml).toContain(
'<em>Italic Text</em>: <em>Italic with one underscore</em> <em>Italic with two underscores</em> Lorem ipsum dolor sit amet',
);
expect(normalizedHtml).toContain('### Heading 3');
expect(normalizedHtml).toContain('<strong>Unordered List</strong> - List Item 1 - List Item 2 - List Item 3 - List Item 4');
expect(normalizedHtml).toContain('<strong>Ordered List</strong> 1. List Item 1 2. List Item 2 3. List Item 3 4. List Item 4');
expect(normalizedHtml).toContain(`<a title=\"\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">Rocket.Chat</a>`);
expect(normalizedHtml).toContain(
`<a href=\"mailto:gabriel.engel@rocket.chat\" title=\"mailto:gabriel.engel@rocket.chat\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">gabriel.engel@rocket.chat</a>`,
);
expect(normalizedHtml).toContain('+55991999999');
expect(normalizedHtml).toContain('Inline code');
expect(normalizedHtml).toContain(`typescript const test = 'this is code'`);
});
14 changes: 10 additions & 4 deletions apps/meteor/client/components/MarkdownText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,18 @@ const documentRenderer = new marked.Renderer();
const inlineRenderer = new marked.Renderer();
const inlineWithoutBreaks = new marked.Renderer();

marked.Lexer.rules.gfm = {
...marked.Lexer.rules.gfm,
strong: /^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/,
em: /^__(?=\S)([\s\S]*?\S)__(?!_)|^_(?=\S)([\s\S]*?\S)_(?!_)/,
const walkTokens = (token: marked.Token) => {
const boldPattern = /^\*[^*]+\*$|^\*\*[^*]+\*\*$/;
const italicPattern = /^__(?=\S)([\s\S]*?\S)__(?!_)|^_(?=\S)([\s\S]*?\S)_(?!_)/;
if (boldPattern.test(token.raw)) {
token.type = 'strong';
} else if (italicPattern.test(token.raw)) {
token.type = 'em';
}
};

marked.use({ walkTokens });

const linkMarked = (href: string | null, _title: string | null, text: string): string =>
`<a href="${href}" rel="nofollow noopener noreferrer">${text}</a> `;
const paragraphMarked = (text: string): string => text;
Expand Down

0 comments on commit 9c119a0

Please sign in to comment.