Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIX] Nested Markdown blocks not parsed properly #12998

Merged
merged 3 commits into from
Dec 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions packages/rocketchat-markdown/lib/markdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,31 @@ class MarkdownClass {
return parsers.original(message);
}

mountTokensBack(message, useHtml = true) {
if (message.tokens && message.tokens.length > 0) {
for (const { token, text, noHtml } of message.tokens) {
message.html = message.html.replace(token, () => (useHtml ? text : noHtml)); // Uses lambda so doesn't need to escape $
mountTokensBackRecursively(message, tokenList, useHtml = true) {
rodrigok marked this conversation as resolved.
Show resolved Hide resolved
const missingTokens = [];

if (tokenList.length > 0) {
for (const { token, text, noHtml } of tokenList) {
if (message.html.indexOf(token) >= 0) {
message.html = message.html.replace(token, () => (useHtml ? text : noHtml)); // Uses lambda so doesn't need to escape $
} else {
missingTokens.push({ token, text, noHtml });
}
}
}

// If there are tokens that were missing from the string, but the last iteration replaced at least one token, then go again
// this is done because one of the tokens may have been hidden by another one
if (missingTokens.length > 0 && missingTokens.length < tokenList.length) {
this.mountTokensBackRecursively(message, missingTokens, useHtml);
}
}

mountTokensBack(message, useHtml = true) {
if (message.tokens) {
this.mountTokensBackRecursively(message, message.tokens, useHtml);
}

return message;
}

Expand All @@ -60,7 +78,7 @@ class MarkdownClass {
}
}

const Markdown = new MarkdownClass;
export const Markdown = new MarkdownClass;
RocketChat.Markdown = Markdown;

// renderMessage already did html escape
Expand Down
2 changes: 1 addition & 1 deletion packages/rocketchat-markdown/lib/parser/original/code.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import hljs from 'highlight.js';
const inlinecode = (message) =>
// Support `text`
message.html = message.html.replace(/\`([^`\r\n]+)\`([<_*~]|\B|\b|$)/gm, (match, p1, p2) => {
const token = ` =!=${ Random.id() }=!=`;
const token = `=!=${ Random.id() }=!=`;

message.tokens.push({
token,
Expand Down
14 changes: 14 additions & 0 deletions packages/rocketchat-markdown/tests/client.mocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,19 @@ mock('meteor/meteor', {
},
});

mock('meteor/blaze', {
Blaze: {},
});

mock('meteor/rocketchat:lib', {
RocketChat: {
settings: {
get(setting) {
switch (setting) {
case 'Markdown_SupportSchemesForLink':
return 'http,https';
case 'Markdown_Parser':
return 'original';
case 'Markdown_Headers':
// case 'Markdown_Marked_GFM':
// case 'Markdown_Marked_Tables':
Expand All @@ -31,6 +37,14 @@ mock('meteor/rocketchat:lib', {
}
},
},
callbacks: {
add() {

},
priority: {
HIGH: 1,
},
},
},
});

Expand Down
112 changes: 60 additions & 52 deletions packages/rocketchat-markdown/tests/client.tests.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/* eslint-env mocha */
import 'babel-polyfill';
import assert from 'assert';
import s from 'underscore.string';
import './client.mocks.js';
import { original } from '../lib/parser/original/original';
import { Markdown } from '../lib/markdown';
// import {marked} from '../parser/marked/marked';

const wrapper = (text, tag) => `<span class="copyonly">${ tag }</span>${ text }<span class="copyonly">${ tag }</span>`;
Expand All @@ -11,7 +13,7 @@ const italicWrapper = (text) => wrapper(`<em>${ text }</em>`, '_');
const strikeWrapper = (text) => wrapper(`<strike>${ text }</strike>`, '~');
const headerWrapper = (text, level) => `<h${ level }>${ text }</h${ level }>`;
const quoteWrapper = (text) => `<blockquote class="background-transparent-darker-before"><span class="copyonly">&gt;</span>${ text }</blockquote>`;
const linkWrapped = (link, title) => `<a href="${ link }" target="_blank" rel="noopener noreferrer">${ title }</a>`;
const linkWrapped = (link, title) => `<a href="${ s.escapeHTML(link) }" target="_blank" rel="noopener noreferrer">${ s.escapeHTML(title) }</a>`;
const inlinecodeWrapper = (text) => wrapper(`<span><code class="code-colors inline">${ text }</code></span>`, '`');
const codeWrapper = (text, lang) => `<pre><code class='code-colors hljs ${ lang }'><span class='copyonly'>\`\`\`<br></span>${ text }<span class='copyonly'><br>\`\`\`</span></code></pre>`;

Expand Down Expand Up @@ -121,62 +123,62 @@ const headersLevel4 = {
};

const quote = {
'&gt;Hello': quoteWrapper('Hello'),
'&gt;Rocket.Cat': quoteWrapper('Rocket.Cat'),
'&gt;Hi': quoteWrapper('Hi'),
'&gt; Hello this is dog': quoteWrapper(' Hello this is dog'),
'&gt; Rocket cat says Hello': quoteWrapper(' Rocket cat says Hello'),
'&gt; He said Hello to her': quoteWrapper(' He said Hello to her'),
'&gt; He said Hello to her ': quoteWrapper(' He said Hello to her '),
'&lt;Hello': '&lt;Hello',
'&lt;Rocket.Cat&gt;': '&lt;Rocket.Cat&gt;',
' &gt;Hi': ' &gt;Hi',
'Hello &gt; this is dog': 'Hello &gt; this is dog',
'Roc&gt;ket cat says Hello': 'Roc&gt;ket cat says Hello',
'He said Hello to her&gt;': 'He said Hello to her&gt;',
'>Hello': '>Hello',
'>Rocket.Cat': '>Rocket.Cat',
'>Hi': '>Hi',
'> Hello this is dog': '> Hello this is dog',
'> Rocket cat says Hello': '> Rocket cat says Hello',
'> He said Hello to her': '> He said Hello to her',
'<Hello': '<Hello',
'<Rocket.Cat>': '<Rocket.Cat>',
' >Hi': ' >Hi',
'Hello > this is dog': 'Hello > this is dog',
'Roc>ket cat says Hello': 'Roc>ket cat says Hello',
'He said Hello to her>': 'He said Hello to her>',
'&gt;Hello': s.escapeHTML('&gt;Hello'),
'&gt;Rocket.Cat': s.escapeHTML('&gt;Rocket.Cat'),
'&gt;Hi': s.escapeHTML('&gt;Hi'),
'&gt; Hello this is dog': s.escapeHTML('&gt; Hello this is dog'),
'&gt; Rocket cat says Hello': s.escapeHTML('&gt; Rocket cat says Hello'),
'&gt; He said Hello to her': s.escapeHTML('&gt; He said Hello to her'),
'&gt; He said Hello to her ': s.escapeHTML('&gt; He said Hello to her '),
'&lt;Hello': s.escapeHTML('&lt;Hello'),
'&lt;Rocket.Cat&gt;': s.escapeHTML('&lt;Rocket.Cat&gt;'),
' &gt;Hi': s.escapeHTML(' &gt;Hi'),
'Hello &gt; this is dog': s.escapeHTML('Hello &gt; this is dog'),
'Roc&gt;ket cat says Hello': s.escapeHTML('Roc&gt;ket cat says Hello'),
'He said Hello to her&gt;': s.escapeHTML('He said Hello to her&gt;'),
'>Hello': quoteWrapper('Hello'),
'>Rocket.Cat': quoteWrapper('Rocket.Cat'),
'>Hi': quoteWrapper('Hi'),
'> Hello this is dog': quoteWrapper(' Hello this is dog'),
'> Rocket cat says Hello': quoteWrapper(' Rocket cat says Hello'),
'> He said Hello to her': quoteWrapper(' He said Hello to her'),
'<Hello': s.escapeHTML('<Hello'),
'<Rocket.Cat>': s.escapeHTML('<Rocket.Cat>'),
' >Hi': s.escapeHTML(' >Hi'),
'Hello > this is dog': s.escapeHTML('Hello > this is dog'),
'Roc>ket cat says Hello': s.escapeHTML('Roc>ket cat says Hello'),
'He said Hello to her>': s.escapeHTML('He said Hello to her>'),
};

const link = {
'&lt;http://link|Text&gt;': linkWrapped('http://link', 'Text'),
'&lt;https://open.rocket.chat/|Open Site For Rocket.Chat&gt;': linkWrapped('https://open.rocket.chat/', 'Open Site For Rocket.Chat'),
'&lt;https://open.rocket.chat/ | Open Site For Rocket.Chat&gt;': linkWrapped('https://open.rocket.chat/ ', ' Open Site For Rocket.Chat'),
'&lt;https://rocket.chat/|Rocket.Chat Site&gt;': linkWrapped('https://rocket.chat/', 'Rocket.Chat Site'),
'&lt;https://rocket.chat/docs/developer-guides/testing/#testing|Testing Entry on Rocket.Chat Docs Site&gt;': linkWrapped('https://rocket.chat/docs/developer-guides/testing/#testing', 'Testing Entry on Rocket.Chat Docs Site'),
'&lt;http://linkText&gt;': '&lt;http://linkText&gt;',
'&lt;https:open.rocket.chat/ | Open Site For Rocket.Chat&gt;': '&lt;https:open.rocket.chat/ | Open Site For Rocket.Chat&gt;',
'https://open.rocket.chat/|Open Site For Rocket.Chat': 'https://open.rocket.chat/|Open Site For Rocket.Chat',
'&lt;www.open.rocket.chat/|Open Site For Rocket.Chat&gt;': '&lt;www.open.rocket.chat/|Open Site For Rocket.Chat&gt;',
'&lt;htps://rocket.chat/|Rocket.Chat Site&gt;': '&lt;htps://rocket.chat/|Rocket.Chat Site&gt;',
'&lt;ttps://rocket.chat/|Rocket.Chat Site&gt;': '&lt;ttps://rocket.chat/|Rocket.Chat Site&gt;',
'&lt;tps://rocket.chat/|Rocket.Chat Site&gt;': '&lt;tps://rocket.chat/|Rocket.Chat Site&gt;',
'&lt;open.rocket.chat/|Open Site For Rocket.Chat&gt;': '&lt;open.rocket.chat/|Open Site For Rocket.Chat&gt;',
'&lt;htts://rocket.chat/docs/developer-guides/testing/#testing|Testing Entry on Rocket.Chat Docs Site&gt;': '&lt;htts://rocket.chat/docs/developer-guides/testing/#testing|Testing Entry on Rocket.Chat Docs Site&gt;',
'&lt;http://link|Text&gt;': s.escapeHTML('&lt;http://link|Text&gt;'),
'&lt;https://open.rocket.chat/|Open Site For Rocket.Chat&gt;': s.escapeHTML('&lt;https://open.rocket.chat/|Open Site For Rocket.Chat&gt;'),
'&lt;https://open.rocket.chat/ | Open Site For Rocket.Chat&gt;': s.escapeHTML('&lt;https://open.rocket.chat/ | Open Site For Rocket.Chat&gt;'),
'&lt;https://rocket.chat/|Rocket.Chat Site&gt;': s.escapeHTML('&lt;https://rocket.chat/|Rocket.Chat Site&gt;'),
'&lt;https://rocket.chat/docs/developer-guides/testing/#testing|Testing Entry on Rocket.Chat Docs Site&gt;': s.escapeHTML('&lt;https://rocket.chat/docs/developer-guides/testing/#testing|Testing Entry on Rocket.Chat Docs Site&gt;'),
'&lt;http://linkText&gt;': s.escapeHTML('&lt;http://linkText&gt;'),
'&lt;https:open.rocket.chat/ | Open Site For Rocket.Chat&gt;': s.escapeHTML('&lt;https:open.rocket.chat/ | Open Site For Rocket.Chat&gt;'),
'https://open.rocket.chat/|Open Site For Rocket.Chat': s.escapeHTML('https://open.rocket.chat/|Open Site For Rocket.Chat'),
'&lt;www.open.rocket.chat/|Open Site For Rocket.Chat&gt;': s.escapeHTML('&lt;www.open.rocket.chat/|Open Site For Rocket.Chat&gt;'),
'&lt;htps://rocket.chat/|Rocket.Chat Site&gt;': s.escapeHTML('&lt;htps://rocket.chat/|Rocket.Chat Site&gt;'),
'&lt;ttps://rocket.chat/|Rocket.Chat Site&gt;': s.escapeHTML('&lt;ttps://rocket.chat/|Rocket.Chat Site&gt;'),
'&lt;tps://rocket.chat/|Rocket.Chat Site&gt;': s.escapeHTML('&lt;tps://rocket.chat/|Rocket.Chat Site&gt;'),
'&lt;open.rocket.chat/|Open Site For Rocket.Chat&gt;': s.escapeHTML('&lt;open.rocket.chat/|Open Site For Rocket.Chat&gt;'),
'&lt;htts://rocket.chat/docs/developer-guides/testing/#testing|Testing Entry on Rocket.Chat Docs Site&gt;': s.escapeHTML('&lt;htts://rocket.chat/docs/developer-guides/testing/#testing|Testing Entry on Rocket.Chat Docs Site&gt;'),

'<http://link|Text>': linkWrapped('http://link', 'Text'),
'<https://open.rocket.chat/|Open Site For Rocket.Chat>': linkWrapped('https://open.rocket.chat/', 'Open Site For Rocket.Chat'),
'<https://open.rocket.chat/ | Open Site For Rocket.Chat>': linkWrapped('https://open.rocket.chat/ ', ' Open Site For Rocket.Chat'),
'<https://rocket.chat/|Rocket.Chat Site>': linkWrapped('https://rocket.chat/', 'Rocket.Chat Site'),
'<https://rocket.chat/docs/developer-guides/testing/#testing|Testing Entry on Rocket.Chat Docs Site>': linkWrapped('https://rocket.chat/docs/developer-guides/testing/#testing', 'Testing Entry on Rocket.Chat Docs Site'),
'<http://linkText>': '<http://linkText>',
'<https:open.rocket.chat/ | Open Site For Rocket.Chat>': '<https:open.rocket.chat/ | Open Site For Rocket.Chat>',
'<www.open.rocket.chat/|Open Site For Rocket.Chat>': '<www.open.rocket.chat/|Open Site For Rocket.Chat>',
'<htps://rocket.chat/|Rocket.Chat Site>': '<htps://rocket.chat/|Rocket.Chat Site>',
'<ttps://rocket.chat/|Rocket.Chat Site>': '<ttps://rocket.chat/|Rocket.Chat Site>',
'<tps://rocket.chat/|Rocket.Chat Site>': '<tps://rocket.chat/|Rocket.Chat Site>',
'<open.rocket.chat/|Open Site For Rocket.Chat>': '<open.rocket.chat/|Open Site For Rocket.Chat>',
'<htts://rocket.chat/docs/developer-guides/testing/#testing|Testing Entry on Rocket.Chat Docs Site>': '<htts://rocket.chat/docs/developer-guides/testing/#testing|Testing Entry on Rocket.Chat Docs Site>',
'<http://linkText>': s.escapeHTML('<http://linkText>'),
'<https:open.rocket.chat/ | Open Site For Rocket.Chat>': s.escapeHTML('<https:open.rocket.chat/ | Open Site For Rocket.Chat>'),
'<www.open.rocket.chat/|Open Site For Rocket.Chat>': s.escapeHTML('<www.open.rocket.chat/|Open Site For Rocket.Chat>'),
'<htps://rocket.chat/|Rocket.Chat Site>': s.escapeHTML('<htps://rocket.chat/|Rocket.Chat Site>'),
'<ttps://rocket.chat/|Rocket.Chat Site>': s.escapeHTML('<ttps://rocket.chat/|Rocket.Chat Site>'),
'<tps://rocket.chat/|Rocket.Chat Site>': s.escapeHTML('<tps://rocket.chat/|Rocket.Chat Site>'),
'<open.rocket.chat/|Open Site For Rocket.Chat>': s.escapeHTML('<open.rocket.chat/|Open Site For Rocket.Chat>'),
'<htts://rocket.chat/docs/developer-guides/testing/#testing|Testing Entry on Rocket.Chat Docs Site>': s.escapeHTML('<htts://rocket.chat/docs/developer-guides/testing/#testing|Testing Entry on Rocket.Chat Docs Site>'),

'[Text](http://link)': linkWrapped('http://link', 'Text'),
'[Open Site For Rocket.Chat](https://open.rocket.chat/)': linkWrapped('https://open.rocket.chat/', 'Open Site For Rocket.Chat'),
Expand Down Expand Up @@ -219,15 +221,19 @@ const code = {
'```__code__```': codeWrapper('<span class="hljs-strong">__code__</span>', 'markdown'),
};

const nested = {
'> some quote\n`window.location.reload();`': `${ quoteWrapper(' some quote') }${ inlinecodeWrapper('window.location.reload();') }`,
};

const defaultObjectTest = (result, object, objectKey) => assert.equal(result.html, object[objectKey]);

const testObject = (object, parser = original, test = defaultObjectTest) => {
Object.keys(object).forEach((objectKey) => {
describe(objectKey, () => {
const result = parser({ html: objectKey });
result.tokens.forEach((token) => {
result.html = result.html.replace(token.token, token.text);
});
const message = {
html: s.escapeHTML(objectKey),
};
const result = Markdown.mountTokensBack(parser(message));
it(`should be equal to ${ object[objectKey] }`, () => {
test(result, object, objectKey);
});
Expand Down Expand Up @@ -259,6 +265,8 @@ describe('Original', function() {
describe('Inline Code', () => testObject(inlinecode));

describe('Code', () => testObject(code));

describe('Nested', () => testObject(nested));
});

// describe.only('Marked', function() {
Expand Down