diff --git a/.circleci/config.yml b/.circleci/config.yml index 8a756b3de..274ec25ac 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -67,6 +67,9 @@ jobs: - run: name: Test - Definition Lists command: npm run test:definition-lists + - run: + name: Test - Hard Breaks + command: npm run test:hard-breaks - run: name: Test - Variables command: npm run test:variables diff --git a/package.json b/package.json index 675d76f39..a8c068f2b 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "test:mustache-syntax:block": "jest \".*(mustache-syntax).*\" -t '^Block:.*' --verbose --noStackTrace", "test:mustache-syntax:injection": "jest \".*(mustache-syntax).*\" -t '^Injection:.*' --verbose --noStackTrace", "test:definition-lists": "jest tests/markdown/definition-lists.test.js --verbose --noStackTrace", + "test:hard-breaks": "jest tests/markdown/hard-breaks.test.js --verbose --noStackTrace", "test:emojis": "jest tests/markdown/emojis.test.js --verbose --noStackTrace", "test:route": "jest tests/routes/static-pages.test.js --verbose", "phb": "node scripts/phb.js", diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index be2f56af9..2ee99f72b 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -356,6 +356,27 @@ const superSubScripts = { } }; +const forcedParagraphBreaks = { + name : 'hardBreaks', + level : 'block', + start(src) { return src.match(/\n:+$/m)?.index; }, // Hint to Marked.js to stop and check for a match + tokenizer(src, tokens) { + const regex = /^(:+)(?:\n|$)/ym; + const match = regex.exec(src); + if(match?.length) { + return { + type : 'hardBreaks', // Should match "name" above + raw : match[0], // Text to consume from the source + length : match[1].length, + text : '' + }; + } + }, + renderer(token) { + return `
`.repeat(token.length).concat('\n'); + } +}; + const definitionListsSingleLine = { name : 'definitionListsSingleLine', level : 'block', @@ -400,9 +421,9 @@ const definitionListsSingleLine = { const definitionListsMultiLine = { name : 'definitionListsMultiLine', level : 'block', - start(src) { return src.match(/\n[^\n]*\n::/m)?.index; }, // Hint to Marked.js to stop and check for a match + start(src) { return src.match(/\n[^\n]*\n::[^:\n]/m)?.index; }, // Hint to Marked.js to stop and check for a match tokenizer(src, tokens) { - const regex = /(\n?\n?(?!::)[^\n]+?(?=\n::))|\n::(.(?:.|\n)*?(?=(?:\n::)|(?:\n\n)|$))/y; + const regex = /(\n?\n?(?!::)[^\n]+?(?=\n::[^:\n]))|\n::([^:\n](?:.|\n)*?(?=(?:\n::)|(?:\n\n)|$))/y; let match; let endIndex = 0; const definitions = []; @@ -707,7 +728,8 @@ const MarkedEmojiOptions = { }; Marked.use(MarkedVariables()); -Marked.use({ extensions: [definitionListsMultiLine, definitionListsSingleLine, superSubScripts, mustacheSpans, mustacheDivs, mustacheInjectInline] }); +Marked.use({ extensions : [definitionListsMultiLine, definitionListsSingleLine, forcedParagraphBreaks, superSubScripts, + mustacheSpans, mustacheDivs, mustacheInjectInline] }); Marked.use(mustacheInjectBlock); Marked.use({ renderer: renderer, tokenizer: tokenizer, mangle: false }); Marked.use(MarkedExtendedTables(), MarkedGFMHeadingId(), MarkedSmartypantsLite(), MarkedEmojis(MarkedEmojiOptions)); @@ -819,8 +841,7 @@ module.exports = { varsQueue = []; //Could move into MarkedVariables() globalPageNumber = pageNumber; - rawBrewText = rawBrewText.replace(/^\\column$/gm, `\n\n`) - .replace(/^(:+)$/gm, (match)=>`${``.repeat(match.length)}\n`); + rawBrewText = rawBrewText.replace(/^\\column$/gm, `\n\n`); const opts = Marked.defaults; rawBrewText = opts.hooks.preprocess(rawBrewText); diff --git a/tests/markdown/definition-lists.test.js b/tests/markdown/definition-lists.test.js index 9f5025d73..9c0bdf6b0 100644 --- a/tests/markdown/definition-lists.test.js +++ b/tests/markdown/definition-lists.test.js @@ -88,4 +88,16 @@ describe('Multiline Definition Lists', ()=>{ const rendered = Markdown.render(source).trim(); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('Term 1
\n`); + }); + + test('Multiline Definition List must have at least one non-newline character after ::', function() { + const source = 'Term 1\n::\nDefinition 1\n\n'; + const rendered = Markdown.render(source).trim(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`Term 1
\n\nDefinition 1
`); + }); }); diff --git a/tests/markdown/hard-breaks.test.js b/tests/markdown/hard-breaks.test.js new file mode 100644 index 000000000..3d0f59a41 --- /dev/null +++ b/tests/markdown/hard-breaks.test.js @@ -0,0 +1,47 @@ +/* eslint-disable max-lines */ + +const Markdown = require('naturalcrit/markdown.js'); + +describe('Hard Breaks', ()=>{ + test('Single Break', function() { + const source = ':\n\n'; + const rendered = Markdown.render(source).trim(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(``); + }); + + test('Double Break', function() { + const source = '::\n\n'; + const rendered = Markdown.render(source).trim(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(``); + }); + + test('Triple Break', function() { + const source = ':::\n\n'; + const rendered = Markdown.render(source).trim(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(``); + }); + + test('Many Break', function() { + const source = '::::::::::\n\n'; + const rendered = Markdown.render(source).trim(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(``); + }); + + test('Multiple sets of Breaks', function() { + const source = ':::\n:::\n:::'; + const rendered = Markdown.render(source).trim(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`\n\n`); + }); + + test('Break directly between two paragraphs', function() { + const source = 'Line 1\n::\nLine 2'; + const rendered = Markdown.render(source).trim(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`Line 1
\n\nLine 2
`); + }); + + test('Ignored inside a code block', function() { + const source = '```\n\n:\n\n```\n'; + const rendered = Markdown.render(source).trim(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`\n:\n`);
+ });
+});