Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into snyk-fix-cb4f3e1b76d6…
Browse files Browse the repository at this point in the history
…d8793e32d09ac342fad8

# Conflicts:
#	package-lock.json
  • Loading branch information
MariaHCD committed Dec 11, 2023
2 parents b523305 + 3258b01 commit af94621
Show file tree
Hide file tree
Showing 4 changed files with 1,316 additions and 2,829 deletions.
108 changes: 107 additions & 1 deletion __tests__/ExpensiMark-HTML-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,6 @@ test('Test urls with unmatched closing parentheses autolinks correctly', () => {
testString: 'google.com/(toto))titi)',
resultString: '<a href="https://google.com/(toto)" target="_blank" rel="noreferrer noopener">google.com/(toto)</a>)titi)',
},

];
testCases.forEach(testCase => {
expect(parser.replace(testCase.testString)).toBe(testCase.resultString);
Expand Down Expand Up @@ -1609,3 +1608,110 @@ test('Mention', () => {
testString = '@user@DOMAIN.com';
expect(parser.replace(testString)).toBe('<mention-user>@user@DOMAIN.com</mention-user>');
});

describe('when should keep whitespace flag is enabled', () => {
test('quote without space', () => {
const quoteTestStartString = '>Hello world';
const quoteTestReplacedString = '<blockquote>Hello world</blockquote>';

expect(parser.replace(quoteTestStartString, {shouldKeepWhitespace: true})).toBe(quoteTestReplacedString);
});

test('quote with single space', () => {
const quoteTestStartString = '> Hello world';
const quoteTestReplacedString = '<blockquote> Hello world</blockquote>';

expect(parser.replace(quoteTestStartString, {shouldKeepWhitespace: true})).toBe(quoteTestReplacedString);
});

test('quote with multiple spaces', () => {
const quoteTestStartString = '> Hello world';
const quoteTestReplacedString = '<blockquote> Hello world</blockquote>';

expect(parser.replace(quoteTestStartString, {shouldKeepWhitespace: true})).toBe(quoteTestReplacedString);
});

test('multiple quotes', () => {
const quoteTestStartString = '>Hello my\n>beautiful\n>world\n';
const quoteTestReplacedString = '<blockquote>Hello my</blockquote>\n<blockquote>beautiful</blockquote>\n<blockquote>world</blockquote>\n';

expect(parser.replace(quoteTestStartString, {shouldKeepWhitespace: true})).toBe(quoteTestReplacedString);
});

test('separate blockqoutes', () => {
const quoteTestStartString = '>Lorem ipsum\ndolor\n>sit amet';
const quoteTestReplacedString = '<blockquote>Lorem ipsum</blockquote>\ndolor\n<blockquote>sit amet</blockquote>';

expect(parser.replace(quoteTestStartString, {shouldKeepWhitespace: true})).toBe(quoteTestReplacedString);
});

describe('nested heading in blockquote', () => {
test('without spaces', () => {
const quoteTestStartString = '># Hello world';
const quoteTestReplacedString = '<blockquote><h1>Hello world</h1></blockquote>';

expect(parser.replace(quoteTestStartString, {shouldKeepWhitespace: true})).toBe(quoteTestReplacedString);
});

test('with single space', () => {
const quoteTestStartString = '> # Hello world';
const quoteTestReplacedString = '<blockquote> <h1>Hello world</h1></blockquote>';

expect(parser.replace(quoteTestStartString, {shouldKeepWhitespace: true})).toBe(quoteTestReplacedString);
});

test('with multiple spaces after #', () => {
const quoteTestStartString = '># Hello world';
const quoteTestReplacedString = '<blockquote><h1> Hello world</h1></blockquote>';

expect(parser.replace(quoteTestStartString, {shouldKeepWhitespace: true})).toBe(quoteTestReplacedString);
});
});

describe('trailing whitespace after blockquote', () => {
test('nothing', () => {
const quoteTestStartString = '>Hello world!';
const quoteTestReplacedString = '<blockquote>Hello world!</blockquote>';

expect(parser.replace(quoteTestStartString, {shouldKeepWhitespace: true})).toBe(quoteTestReplacedString);
});

test('space', () => {
const quoteTestStartString = '>Hello world ';
const quoteTestReplacedString = '<blockquote>Hello world </blockquote>';

expect(parser.replace(quoteTestStartString, {shouldKeepWhitespace: true})).toBe(quoteTestReplacedString);
});

test('newline', () => {
const quoteTestStartString = '>Hello world\n';
const quoteTestReplacedString = '<blockquote>Hello world</blockquote>\n';

expect(parser.replace(quoteTestStartString, {shouldKeepWhitespace: true})).toBe(quoteTestReplacedString);
});
});

test('quote with other markdowns', () => {
const quoteTestStartString = '>This is a *quote* that started on a new line.\nHere is a >quote that did not\n```\nhere is a codefenced quote\n>it should not be quoted\n```';
const quoteTestReplacedString = '<blockquote>This is a <strong>quote</strong> that started on a new line.</blockquote>\nHere is a &gt;quote that did not\n<pre>here&#32;is&#32;a&#32;codefenced&#32;quote\n&gt;it&#32;should&#32;not&#32;be&#32;quoted\n</pre>';

expect(parser.replace(quoteTestStartString, {shouldKeepWhitespace: true})).toBe(quoteTestReplacedString);
});
});

test('Test code fence within inline code', () => {
let testString = 'Hello world `(```test```)` Hello world';
expect(parser.replace(testString)).toBe('Hello world &#x60;(<pre>test</pre>)&#x60; Hello world');

testString = 'Hello world `(```test\ntest```)` Hello world';
expect(parser.replace(testString)).toBe('Hello world &#x60;(<pre>test<br />test</pre>)&#x60; Hello world');

testString = 'Hello world ```(`test`)``` Hello world';
expect(parser.replace(testString)).toBe('Hello world <pre>(&#x60;test&#x60;)</pre> Hello world');

testString = 'Hello world `test`space```block``` Hello world';
expect(parser.replace(testString)).toBe('Hello world <code>test</code>space<pre>block</pre> Hello world');

testString = 'Hello world ```block```space`test` Hello world';
expect(parser.replace(testString)).toBe('Hello world <pre>block</pre>space<code>test</code> Hello world');
});
48 changes: 37 additions & 11 deletions lib/ExpensiMark.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ export default class ExpensiMark {

// Use the url escaped version of a backtick (`) symbol. Mobile platforms do not support lookbehinds,
// so capture the first and third group and place them in the replacement.
regex: /(\B|_|)&#x60;(.*?\S.*?)&#x60;(\B|_|)(?!&#x60;|[^<]*<\/pre>)/g,
// but we should not replace backtick symbols if they include <pre> tags between them.
regex: /(\B|_|)&#x60;(?:(?!(?:(?!&#x60;).)*?<pre>))(.*?\S.*?)&#x60;(\B|_|)(?!&#x60;|[^<]*<\/pre>)/g,
replacement: '$1<code>$2</code>$3',
},

Expand Down Expand Up @@ -77,7 +78,10 @@ export default class ExpensiMark {

{
name: 'heading1',
regex: /^# +(?! )((?:(?!<pre>|\n|\r\n).)+)/gm,
process: (textToProcess, replacement, shouldKeepWhitespace = false) => {
const regexp = shouldKeepWhitespace ? /^# ( *(?! )(?:(?!<pre>|\n|\r\n).)+)/gm : /^# +(?! )((?:(?!<pre>|\n|\r\n).)+)/gm;
return textToProcess.replace(regexp, replacement);
},
replacement: '<h1>$1</h1>',
},

Expand Down Expand Up @@ -204,15 +208,25 @@ export default class ExpensiMark {
// We also want to capture a blank line before or after the quote so that we do not add extra spaces.
// block quotes naturally appear on their own line. Blockquotes should not appear in code fences or
// inline code blocks. A single prepending space should be stripped if it exists
process: (textToProcess, replacement) => {
process: (textToProcess, replacement, shouldKeepWhitespace = false) => {
const regex = new RegExp(
/\n?^&gt; *(?! )(?![^<]*(?:<\/pre>|<\/code>))([^\v\n\r]+)\n?/gm,
/^&gt; *(?! )(?![^<]*(?:<\/pre>|<\/code>))([^\v\n\r]+)/gm,
);
if (shouldKeepWhitespace) {
return textToProcess.replace(regex, g1 => replacement(g1, shouldKeepWhitespace));
}
return this.modifyTextForQuote(regex, textToProcess, replacement);
},
replacement: (g1) => {
const replacedText = this.replace(g1, {filterRules: ['heading1'], shouldEscapeText: false});
return `<blockquote>${replacedText}</blockquote>`;
replacement: (g1, shouldKeepWhitespace = false) => {
// We want to enable 2 options of nested heading inside the blockquote: "># heading" and "> # heading".
// To do this we need to parse body of the quote without first space
let isStartingWithSpace = false;
const textToReplace = g1.replace(/^&gt;( )?/gm, (match, g2) => {
isStartingWithSpace = !!g2;
return '';
});
const replacedText = this.replace(textToReplace, {filterRules: ['heading1'], shouldEscapeText: false, shouldKeepWhitespace});
return `<blockquote>${isStartingWithSpace ? ' ' : ''}${replacedText}</blockquote>`;
},
},
{
Expand Down Expand Up @@ -397,6 +411,18 @@ export default class ExpensiMark {
replacement: '',
},
];

/**
* The list of rules that we have to exclude in shouldKeepWhitespaceRules list.
* @type {Object[]}
*/
this.whitespaceRulesToDisable = ['newline', 'replacepre', 'replacebr', 'replaceh1br'];

/**
* The list of rules that have to be applied when shouldKeepWhitespace flag is true.
* @type {Object[]}
*/
this.shouldKeepWhitespaceRules = this.rules.filter(rule => !this.whitespaceRulesToDisable.includes(rule.name)).map(rule => rule.name);
}

/**
Expand All @@ -410,11 +436,11 @@ export default class ExpensiMark {
*
* @returns {String}
*/
replace(text, {filterRules = [], shouldEscapeText = true} = {}) {
replace(text, {filterRules = [], shouldEscapeText = true, shouldKeepWhitespace = false} = {}) {
// This ensures that any html the user puts into the comment field shows as raw html
let replacedText = shouldEscapeText ? _.escape(text) : text;

const rules = _.isEmpty(filterRules) ? this.rules : _.filter(this.rules, rule => _.contains(filterRules, rule.name));
const excludeRules = shouldKeepWhitespace ? _.union(this.shouldKeepWhitespaceRules, filterRules) : filterRules;
const rules = _.isEmpty(excludeRules) ? this.rules : _.filter(this.rules, rule => _.contains(excludeRules, rule.name));

try {
rules.forEach((rule) => {
Expand All @@ -424,7 +450,7 @@ export default class ExpensiMark {
}

if (rule.process) {
replacedText = rule.process(replacedText, rule.replacement);
replacedText = rule.process(replacedText, rule.replacement, shouldKeepWhitespace);
} else {
replacedText = replacedText.replace(rule.regex, rule.replacement);
}
Expand Down
Loading

0 comments on commit af94621

Please sign in to comment.