From 6e3bae97364fe8227c98df065714470a4a46a97e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Fri, 15 Nov 2024 15:46:41 +0100 Subject: [PATCH 1/8] Unify the blockquote parsing for shouldKeepRawInput = false --- lib/ExpensiMark.ts | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/lib/ExpensiMark.ts b/lib/ExpensiMark.ts index dd8c4684..035b6b88 100644 --- a/lib/ExpensiMark.ts +++ b/lib/ExpensiMark.ts @@ -421,37 +421,41 @@ export default class ExpensiMark { // 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, shouldKeepRawInput = false) => { - const regex = /^(?:>)+ +(?! )(?![^<]*(?:<\/pre>|<\/code>|<\/video>))([^\v\n\r]+)/gm; + const regex = /^(?:>)+ +(?! )(?![^<]*(?:<\/pre>|<\/code>|<\/video>))([^\v\n\r]*)/gm; + + let replacedText = this.replaceTextWithExtras(textToProcess, regex, EXTRAS_DEFAULT, replacement); if (shouldKeepRawInput) { - const rawInputRegex = /^(?:>)+ +(?! )(?![^<]*(?:<\/pre>|<\/code>|<\/video>))([^\v\n\r]*)/gm; - return this.replaceTextWithExtras(textToProcess, rawInputRegex, EXTRAS_DEFAULT, replacement); + return replacedText; + } + + for (let i = this.maxQuoteDepth; i > 0; i--) { + replacedText = replacedText.replaceAll(`${''.repeat(i)}\n${'
'.repeat(i)}`, '\n'); } - return this.modifyTextForQuote(regex, textToProcess, replacement as ReplacementFn); + replacedText = replacedText.replaceAll('
\n', ''); + return replacedText; }, replacement: (_extras, g1) => { - // 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 - const handleMatch = (match: string) => match; + let isStartingWithSpace = false; + const handleMatch = (_match: string, g2: string) => { + isStartingWithSpace = !!g2; + return ''; + }; const textToReplace = g1.replace(/^>( )?/gm, handleMatch); const filterRules = ['heading1']; - // if we don't reach the max quote depth we allow the recursive call to process possible quote - if (this.currentQuoteDepth < this.maxQuoteDepth - 1) { + if (this.currentQuoteDepth < this.maxQuoteDepth - 1 && !isStartingWithSpace) { filterRules.push('quote'); this.currentQuoteDepth++; } - const replacedText = this.replace(textToReplace, { filterRules, shouldEscapeText: false, shouldKeepRawInput: false, }); this.currentQuoteDepth = 0; - return `
${replacedText}
`; + return `
${replacedText || ' '}
`; }, rawInputReplacement: (_extras, g1) => { - // 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 handleMatch = (_match: string, g2: string) => { isStartingWithSpace = !!g2; @@ -459,13 +463,11 @@ export default class ExpensiMark { }; const textToReplace = g1.replace(/^>( )?/gm, handleMatch); const filterRules = ['heading1']; - // if we don't reach the max quote depth we allow the recursive call to process possible quote - if (this.currentQuoteDepth < this.maxQuoteDepth - 1 || isStartingWithSpace) { + if (this.currentQuoteDepth < this.maxQuoteDepth - 1 && !isStartingWithSpace) { filterRules.push('quote'); this.currentQuoteDepth++; } - const replacedText = this.replace(textToReplace, { filterRules, shouldEscapeText: false, From 55a205145ffb4fcdc9b5c4c0d313d053c14b981c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Fri, 15 Nov 2024 16:20:07 +0100 Subject: [PATCH 2/8] Remove duplicated code --- lib/ExpensiMark.ts | 63 +++++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 37 deletions(-) diff --git a/lib/ExpensiMark.ts b/lib/ExpensiMark.ts index 035b6b88..ed6cfaa9 100644 --- a/lib/ExpensiMark.ts +++ b/lib/ExpensiMark.ts @@ -435,46 +435,12 @@ export default class ExpensiMark { return replacedText; }, replacement: (_extras, g1) => { - let isStartingWithSpace = false; - const handleMatch = (_match: string, g2: string) => { - isStartingWithSpace = !!g2; - return ''; - }; - const textToReplace = g1.replace(/^>( )?/gm, handleMatch); - const filterRules = ['heading1']; - // if we don't reach the max quote depth we allow the recursive call to process possible quote - if (this.currentQuoteDepth < this.maxQuoteDepth - 1 && !isStartingWithSpace) { - filterRules.push('quote'); - this.currentQuoteDepth++; - } - const replacedText = this.replace(textToReplace, { - filterRules, - shouldEscapeText: false, - shouldKeepRawInput: false, - }); - this.currentQuoteDepth = 0; + const {replacedText} = this.replaceQuoteText(g1, false); return `
${replacedText || ' '}
`; }, rawInputReplacement: (_extras, g1) => { - let isStartingWithSpace = false; - const handleMatch = (_match: string, g2: string) => { - isStartingWithSpace = !!g2; - return ''; - }; - const textToReplace = g1.replace(/^>( )?/gm, handleMatch); - const filterRules = ['heading1']; - // if we don't reach the max quote depth we allow the recursive call to process possible quote - if (this.currentQuoteDepth < this.maxQuoteDepth - 1 && !isStartingWithSpace) { - filterRules.push('quote'); - this.currentQuoteDepth++; - } - const replacedText = this.replace(textToReplace, { - filterRules, - shouldEscapeText: false, - shouldKeepRawInput: true, - }); - this.currentQuoteDepth = 0; - return `
${isStartingWithSpace ? ' ' : ''}${replacedText}
`; + const {replacedText, shouldAddSpace} = this.replaceQuoteText(g1, true); + return `
${shouldAddSpace ? ' ' : ''}${replacedText}
`; }, }, /** @@ -1275,6 +1241,29 @@ export default class ExpensiMark { return textToCheck; } + replaceQuoteText(text: string, shouldKeepRawInput: boolean): {replacedText: string; shouldAddSpace: boolean} { + let isStartingWithSpace = false; + const handleMatch = (_match: string, g2: string) => { + isStartingWithSpace = !!g2; + return ''; + }; + const textToReplace = text.replace(/^>( )?/gm, handleMatch); + const filterRules = ['heading1']; + // if we don't reach the max quote depth we allow the recursive call to process possible quote + if (this.currentQuoteDepth < this.maxQuoteDepth - 1 && !isStartingWithSpace) { + filterRules.push('quote'); + this.currentQuoteDepth++; + } + const replacedText = this.replace(textToReplace, { + filterRules, + shouldEscapeText: false, + shouldKeepRawInput, + }); + this.currentQuoteDepth = 0; + + return {replacedText, shouldAddSpace: isStartingWithSpace}; + } + /** * Check if the input text includes only the open or the close tag of an element. */ From 466502023527ebca2d62d5a1a5650becff53237b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Mon, 18 Nov 2024 09:33:44 +0100 Subject: [PATCH 3/8] Fix tests --- __tests__/ExpensiMark-HTML-test.js | 38 +++++++++++++----------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/__tests__/ExpensiMark-HTML-test.js b/__tests__/ExpensiMark-HTML-test.js index 67dabe84..24931b83 100644 --- a/__tests__/ExpensiMark-HTML-test.js +++ b/__tests__/ExpensiMark-HTML-test.js @@ -1125,13 +1125,13 @@ test('Test quotes markdown replacement and removing
from
 and  {
     const testString = '> \n>';
-    const resultString = '> 
>'; + const resultString = '
>'; expect(parser.replace(testString)).toBe(resultString); }); test('Test quotes markdown replacement with text starts with blank quote', () => { const testString = '> \ntest'; - const resultString = '>
test'; + const resultString = '
test'; expect(parser.replace(testString)).toBe(resultString); }); @@ -1143,7 +1143,7 @@ test('Test quotes markdown replacement with quotes starts with blank quote row', test('Test quotes markdown replacement with quotes ends with blank quote rows', () => { const testString = '> test\n> \n>'; - const resultString = '
test

'; + const resultString = '
test
>'; expect(parser.replace(testString)).toBe(resultString); }); @@ -1162,14 +1162,14 @@ test('Test quotes markdown replacement with quotes includes multiple middle blan test('Test quotes markdown replacement with text includes blank quotes', () => { const testString = '> \n> quote1 line a\n> quote1 line b\ntest\n> \ntest\n> quote2 line a\n> \n> \n> quote2 line b with an empty line above'; const resultString = - '

quote1 line a
quote1 line b
test
>
test
quote2 line a


quote2 line b with an empty line above
'; + '

quote1 line a
quote1 line b
test
test
quote2 line a


quote2 line b with an empty line above
'; expect(parser.replace(testString)).toBe(resultString); }); test('Test quotes markdown replacement with text includes multiple spaces', () => { - const quoteTestStartString = '> Indented\n>No indent\n> Indented \n> > Nested indented \n> Indented '; + const quoteTestStartString = '> Indented\n>No indent\n> Indented \n>> Nested indented \n> > Nested not indented \n> Indented '; const quoteTestReplacedString = - '
Indented
>No indent
Indented
Nested indented
Indented
'; + '
Indented
>No indent
Indented
Nested indented
> Nested not indented
Indented
'; expect(parser.replace(quoteTestStartString)).toBe(quoteTestReplacedString); }); @@ -1192,13 +1192,7 @@ test('Test markdown quotes without spaces after > should not be parsed', () => { test('Test markdown quotes without spaces after > should not be parsed', () => { const testString = '> > > test'; - const resultString = '
test
'; - expect(parser.replace(testString)).toBe(resultString); -}); - -test('Test markdown quotes without spaces after > should not be parsed', () => { - const testString = '>>> test'; - const resultString = '>>> test'; + const resultString = '
> > test
'; expect(parser.replace(testString)).toBe(resultString); }); @@ -2027,54 +2021,54 @@ test('Test italic/bold/strikethrough markdown to keep consistency', () => { describe('multi-level blockquote', () => { test('test max level of blockquote (3)', () => { - const quoteTestStartString = '> > > > > Hello world'; - const quoteTestReplacedString = '
> > Hello world
'; + const quoteTestStartString = '>>>>> Hello world'; + const quoteTestReplacedString = '
>> Hello world
'; expect(parser.replace(quoteTestStartString)).toBe(quoteTestReplacedString); }); test('multi-level blockquote with single space', () => { - const quoteTestStartString = '> > > Hello world'; + const quoteTestStartString = '>>> Hello world'; const quoteTestReplacedString = '
Hello world
'; expect(parser.replace(quoteTestStartString)).toBe(quoteTestReplacedString); }); test('multi-level blockquote with multiple spaces', () => { const quoteTestStartString = '> > > Hello world'; - const quoteTestReplacedString = '
Hello world
'; + const quoteTestReplacedString = '
> > Hello world
'; expect(parser.replace(quoteTestStartString)).toBe(quoteTestReplacedString); }); test('multi-level blockquote with mixed spaces', () => { const quoteTestStartString = '> > > Hello world'; - const quoteTestReplacedString = '
Hello world
'; + const quoteTestReplacedString = '
> > Hello world
'; expect(parser.replace(quoteTestStartString)).toBe(quoteTestReplacedString); }); test('multi-level blockquote with diffrent syntax', () => { - const quoteTestStartString = '> > _Hello_ *world*'; + const quoteTestStartString = '>> _Hello_ *world*'; const quoteTestReplacedString = '
Hello world
'; expect(parser.replace(quoteTestStartString)).toBe(quoteTestReplacedString); }); test('multi-level blockquote with nested heading', () => { - const quoteTestStartString = '> > # Hello world'; + const quoteTestStartString = '>> # Hello world'; const quoteTestReplacedString = '

Hello world

'; expect(parser.replace(quoteTestStartString)).toBe(quoteTestReplacedString); }); test('multiline multi-level blockquote', () => { - const quoteTestStartString = '> > Hello my\n> > beautiful\n> > world\n'; + const quoteTestStartString = '>> Hello my\n>> beautiful\n>> world\n'; const quoteTestReplacedString = '
Hello my
beautiful
world
'; expect(parser.replace(quoteTestStartString)).toBe(quoteTestReplacedString); }); test('multiline blockquote with diffrent levels', () => { - const quoteTestStartString = '> > > Hello my\n> > beautiful\n> world\n'; + const quoteTestStartString = '>>> Hello my\n>> beautiful\n> world\n'; const quoteTestReplacedString = '
Hello my
beautiful
world
'; expect(parser.replace(quoteTestStartString)).toBe(quoteTestReplacedString); From a1511ab5a486c6da8f43f31bf823020bd3d5ce41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Fri, 22 Nov 2024 17:51:22 +0100 Subject: [PATCH 4/8] Fix HTML to markdown parsing --- lib/ExpensiMark.ts | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/lib/ExpensiMark.ts b/lib/ExpensiMark.ts index ed6cfaa9..9f083bfc 100644 --- a/lib/ExpensiMark.ts +++ b/lib/ExpensiMark.ts @@ -1110,6 +1110,43 @@ export default class ExpensiMark { return joinedText; } + unpackNestedQuotes(text: string): string { + let parsedText = text.replace(/(<\/blockquote>)+/g, (match) => { + return `${match.slice(0, match.lastIndexOf(''))}
`; + }); + const splittedText = parsedText.split('
'); + if (splittedText.length > 0 && splittedText[splittedText.length - 1] === '') { + splittedText.pop(); + } + + let count = 0; + parsedText = splittedText + .map((line, index, arr) => { + if (line === '') { + return ''; + } + + if (line.startsWith('
')) { + count += (line.match(/
/g) || []).length; + } + if (line.endsWith('
')) { + count -= (line.match(/<\/blockquote>/g) || []).length; + if (count > 0) { + return `${line}${'
'.repeat(count)}`; + } + } + + if (count > 0) { + return `${line}${'
'}${'
'.repeat(count)}`; + } + + return line + (index < arr.length - 1 ? '
' : ''); + }) + .join(''); + + return parsedText; + } + /** * Replaces HTML with markdown */ @@ -1122,6 +1159,7 @@ export default class ExpensiMark { if (parseBodyTag) { generatedMarkdown = parseBodyTag[2]; } + generatedMarkdown = this.unpackNestedQuotes(generatedMarkdown); const processRule = (rule: RuleWithRegex) => { // Pre-processes input HTML before applying regex From e93727f3a966f6d8e0a4f32f8bcdaafc478c2829 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Fri, 22 Nov 2024 19:24:33 +0100 Subject: [PATCH 5/8] Fix html to markdown tests --- lib/ExpensiMark.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/ExpensiMark.ts b/lib/ExpensiMark.ts index 9f083bfc..32de59a6 100644 --- a/lib/ExpensiMark.ts +++ b/lib/ExpensiMark.ts @@ -1097,8 +1097,9 @@ export default class ExpensiMark { return; } + const nextItem = splitText?.[index + 1]; // Insert '\n' unless it ends with '\n' or '>' or it's the last element, or if it's a header ('# ') with a space. - if (text.match(/[\n|>][>]?[\s]?$/) || index === splitText.length - 1 || text === '# ') { + if ((nextItem && text.match(/>[\s]?$/) && !nextItem.startsWith('> ')) || text.match(/\n[\s]?$/) || index === splitText.length - 1 || text === '# ') { joinedText += text; } else { joinedText += `${text}\n`; @@ -1111,20 +1112,22 @@ export default class ExpensiMark { } unpackNestedQuotes(text: string): string { - let parsedText = text.replace(/(<\/blockquote>)+/g, (match) => { - return `${match.slice(0, match.lastIndexOf('
'))}

`; + let parsedText = text.replace(/((<\/blockquote>)+(
)?)|(
)/g, (match) => { + return `${match}`; }); - const splittedText = parsedText.split('
'); + const splittedText = parsedText.split(''); if (splittedText.length > 0 && splittedText[splittedText.length - 1] === '') { splittedText.pop(); } let count = 0; parsedText = splittedText - .map((line, index, arr) => { - if (line === '') { + .map((l) => { + const hasBR = l.endsWith('
'); + if (l === '' && count === 0) { return ''; } + const line = l.replace(/(
)$/g, ''); if (line.startsWith('
')) { count += (line.match(/
/g) || []).length; @@ -1140,7 +1143,7 @@ export default class ExpensiMark { return `${line}${'
'}${'
'.repeat(count)}`; } - return line + (index < arr.length - 1 ? '
' : ''); + return line + (hasBR ? '
' : ''); }) .join(''); From 7633d6d3539711a107a476910e691016ea01f9a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Mon, 25 Nov 2024 14:25:43 +0100 Subject: [PATCH 6/8] Add function description --- lib/ExpensiMark.ts | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/lib/ExpensiMark.ts b/lib/ExpensiMark.ts index 32de59a6..129d3dcb 100644 --- a/lib/ExpensiMark.ts +++ b/lib/ExpensiMark.ts @@ -1111,6 +1111,9 @@ export default class ExpensiMark { return joinedText; } + /** + * Unpacks nested quotes HTML tags that have been packed by the 'quote' rule in this.rules for shouldKeepRawInput = false + */ unpackNestedQuotes(text: string): string { let parsedText = text.replace(/((<\/blockquote>)+(
)?)|(
)/g, (match) => { return `${match}`; @@ -1122,28 +1125,28 @@ export default class ExpensiMark { let count = 0; parsedText = splittedText - .map((l) => { - const hasBR = l.endsWith('
'); - if (l === '' && count === 0) { + .map((line) => { + const hasBR = line.endsWith('
'); + if (line === '' && count === 0) { return ''; } - const line = l.replace(/(
)$/g, ''); + const textLine = line.replace(/(
)$/g, ''); - if (line.startsWith('
')) { - count += (line.match(/
/g) || []).length; + if (textLine.startsWith('
')) { + count += (textLine.match(/
/g) || []).length; } - if (line.endsWith('
')) { - count -= (line.match(/<\/blockquote>/g) || []).length; + if (textLine.endsWith('
')) { + count -= (textLine.match(/<\/blockquote>/g) || []).length; if (count > 0) { - return `${line}${'
'.repeat(count)}`; + return `${textLine}${'
'.repeat(count)}`; } } if (count > 0) { - return `${line}${'
'}${'
'.repeat(count)}`; + return `${textLine}${'
'}${'
'.repeat(count)}`; } - return line + (hasBR ? '
' : ''); + return textLine + (hasBR ? '
' : ''); }) .join(''); From d12e9edf9fb4139a5007e3ef3c6259304a91bb2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Wed, 4 Dec 2024 10:39:19 +0100 Subject: [PATCH 7/8] Add review changes --- lib/ExpensiMark.ts | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/lib/ExpensiMark.ts b/lib/ExpensiMark.ts index 129d3dcb..8c5343dd 100644 --- a/lib/ExpensiMark.ts +++ b/lib/ExpensiMark.ts @@ -1112,7 +1112,24 @@ export default class ExpensiMark { } /** - * Unpacks nested quotes HTML tags that have been packed by the 'quote' rule in this.rules for shouldKeepRawInput = false + * Unpacks nested quote HTML tags that have been packed by the 'quote' rule in this.rules for shouldKeepRawInput = false + * + * For example, it parses the following HTML: + *
+ * quote 1 + *
+ * quote 2 + *
+ * quote 3 + *
+ * + * into: + *
quote 1
+ *
quote 2
+ *
quote 3
+ * + * Note that there will always be only a single closing tag, even if multiple opening tags exist. + * Only one closing tag is needed to detect if a nested quote has ended. */ unpackNestedQuotes(text: string): string { let parsedText = text.replace(/((<\/blockquote>)+(
)?)|(
)/g, (match) => { @@ -1130,8 +1147,8 @@ export default class ExpensiMark { if (line === '' && count === 0) { return ''; } - const textLine = line.replace(/(
)$/g, ''); + const textLine = line.replace(/(
)$/g, ''); if (textLine.startsWith('
')) { count += (textLine.match(/
/g) || []).length; } @@ -1285,6 +1302,11 @@ export default class ExpensiMark { return textToCheck; } + /** + * Main text to html 'quote' parsing logic. + * Removes >( ) from text and recursively calls replace function to process nested quotes and build blockquote HTML result. + * @param shouldKeepRawInput determines if the raw input should be kept for nested quotes. + */ replaceQuoteText(text: string, shouldKeepRawInput: boolean): {replacedText: string; shouldAddSpace: boolean} { let isStartingWithSpace = false; const handleMatch = (_match: string, g2: string) => { @@ -1293,7 +1315,7 @@ export default class ExpensiMark { }; const textToReplace = text.replace(/^>( )?/gm, handleMatch); const filterRules = ['heading1']; - // if we don't reach the max quote depth we allow the recursive call to process possible quote + // If we don't reach the max quote depth, we allow the recursive call to process other possible quotes if (this.currentQuoteDepth < this.maxQuoteDepth - 1 && !isStartingWithSpace) { filterRules.push('quote'); this.currentQuoteDepth++; From 6611dab75bcbf6ec5950401990299f883536a6c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Wed, 4 Dec 2024 10:40:55 +0100 Subject: [PATCH 8/8] Remove unused code --- lib/ExpensiMark.ts | 88 ---------------------------------------------- 1 file changed, 88 deletions(-) diff --git a/lib/ExpensiMark.ts b/lib/ExpensiMark.ts index 8c5343dd..e81d39a0 100644 --- a/lib/ExpensiMark.ts +++ b/lib/ExpensiMark.ts @@ -1214,94 +1214,6 @@ export default class ExpensiMark { return replacedText; } - /** - * Modify text for Quotes replacing chevrons with html elements - */ - modifyTextForQuote(regex: RegExp, textToCheck: string, replacement: ReplacementFn): string { - let replacedText = ''; - let textToFormat = ''; - const match = textToCheck.match(regex); - - // If there's matches we need to modify the quotes - if (match !== null) { - let insideCodefence = false; - - // Split the textToCheck in lines - const textSplitted = textToCheck.split('\n'); - - for (let i = 0; i < textSplitted.length; i++) { - if (!insideCodefence) { - // We need to know when there is a start of codefence so we dont quote - insideCodefence = Str.contains(textSplitted[i], '
');
-                }
-
-                // Since the last space will be trimmed and would incorrectly disable a condition we check it manually
-                const isLastBlockquote = textSplitted[i] === '>' && i === textSplitted.length - 1;
-
-                // We only want to modify lines starting with "> " that is not codefence
-                if ((Str.startsWith(textSplitted[i], '> ') || isLastBlockquote) && !insideCodefence) {
-                    if (textSplitted[i] === '>') {
-                        textToFormat += `${textSplitted[i]} \n`;
-                        insideCodefence = true;
-                    } else {
-                        textToFormat += `${textSplitted[i]}\n`;
-                    }
-                } else {
-                    // Make sure we will only modify if we have Text needed to be formatted for quote
-                    if (textToFormat !== '') {
-                        replacedText += this.formatTextForQuote(regex, textToFormat, replacement);
-                        textToFormat = '';
-                    }
-
-                    // We dont want a \n after the textSplitted if it is the last row
-                    if (i === textSplitted.length - 1) {
-                        replacedText += `${textSplitted[i]}`;
-                    } else {
-                        replacedText += `${textSplitted[i]}\n`;
-                    }
-
-                    // We need to know when we are not inside codefence anymore
-                    if (insideCodefence) {
-                        insideCodefence = !Str.contains(textSplitted[i], '
'); - } - } - } - - // When loop ends we need the last quote to be formatted if we have quotes at last rows - if (textToFormat !== '') { - replacedText += this.formatTextForQuote(regex, textToFormat, replacement); - } - } else { - // If we doesn't have matches make sure the function will return the same textToCheck - replacedText = textToCheck; - } - return replacedText; - } - - /** - * Format the content of blockquote if the text matches the regex or else just return the original text - */ - formatTextForQuote(regex: RegExp, textToCheck: string, replacement: ReplacementFn): string { - if (textToCheck.match(regex)) { - // Remove '>' and trim the spaces between nested quotes - const formatRow = (row: string) => { - let quoteContent = row[4] === ' ' ? row.substr(5) : row.substr(4); - if (row === '> ') quoteContent = row.substr(4); - - if (quoteContent.trimStart().startsWith('>')) { - return quoteContent.trimStart(); - } - return quoteContent; - }; - let textToFormat = textToCheck.split('\n').map(formatRow).join('\n'); - - // Remove leading and trailing line breaks - textToFormat = textToFormat.replace(/^\n+|\n+$/g, ''); - return replacement(EXTRAS_DEFAULT, textToFormat); - } - return textToCheck; - } - /** * Main text to html 'quote' parsing logic. * Removes >( ) from text and recursively calls replace function to process nested quotes and build blockquote HTML result.