From 184f182b3a390afeee284d149513c88fe8e0872c Mon Sep 17 00:00:00 2001 From: David Bolack Date: Sun, 18 Aug 2024 21:52:29 -0500 Subject: [PATCH 01/12] Short term fix for the colons in codeblocks issue. --- shared/naturalcrit/markdown.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index 9388e912a..894c084b6 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -69,6 +69,20 @@ renderer.html = function (html) { return html; }; +renderer.code = function(code, infostring, escaped) { + const lang = (infostring || '').match(/^\S*/)?.[0]; + + code = code.replace(/
<\/div>/gm, (match)=>`${`:`}`); + + code = `${code.replace(/\n$/, '')}\n`; + + if(!lang) { + return `
${escaped ? code : escape(code, true)}
\n`; + } + + return `
${escaped ? code : escape(code, true)}
\n`; +}; + // Don't wrap {{ Spans alone on a line, or {{ Divs in

tags renderer.paragraph = function(text){ let match; @@ -834,7 +848,7 @@ module.exports = { globalPageNumber = pageNumber; rawBrewText = rawBrewText.replace(/^\\column$/gm, `\n

\n`) - .replace(/^(:+)$/gm, (match)=>`${`
`.repeat(match.length)}\n`); + .replace(/^(:+)$/gm, (match)=>`${`
`.repeat(match.length)}\n`); const opts = Marked.defaults; rawBrewText = opts.hooks.preprocess(rawBrewText); From a30608a8ae808361b4dd9fd6f0aa638f7c527e75 Mon Sep 17 00:00:00 2001 From: David Bolack Date: Mon, 19 Aug 2024 10:36:26 -0500 Subject: [PATCH 02/12] Promote `:` paragraph breaks shortcut to a token. --- shared/naturalcrit/markdown.js | 45 ++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index 894c084b6..fcc0a27bf 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -69,20 +69,6 @@ renderer.html = function (html) { return html; }; -renderer.code = function(code, infostring, escaped) { - const lang = (infostring || '').match(/^\S*/)?.[0]; - - code = code.replace(/
<\/div>/gm, (match)=>`${`:`}`); - - code = `${code.replace(/\n$/, '')}\n`; - - if(!lang) { - return `
${escaped ? code : escape(code, true)}
\n`; - } - - return `
${escaped ? code : escape(code, true)}
\n`; -}; - // Don't wrap {{ Spans alone on a line, or {{ Divs in

tags renderer.paragraph = function(text){ let match; @@ -370,6 +356,28 @@ const superSubScripts = { } }; +const forcedParagraphBreaks = { + name : 'colonParagraphs', + level : 'inline', + start(src) { return src.match(/^:+$/m)?.index; }, // Hint to Marked.js to stop and check for a match + tokenizer(src, tokens) { + const regex = /^(:+)$/m; + const match = regex.exec(src); + if(match?.length) { + return { + type : 'colonParagraphs', // Should match "name" above + raw : match[0], // Text to consume from the source + length : match[1].length, + text : '' + }; + } + }, + renderer(token) { + return `

`.repeat(token.length); + } +}; + + const definitionListsSingleLine = { name : 'definitionListsSingleLine', level : 'block', @@ -387,7 +395,8 @@ const definitionListsSingleLine = { .map((emoji)=>firstLine = firstLine.replace(emoji.raw, 'x'.repeat(emoji.raw.length))); const newMatch = /^([^\n]*?)::([^\n]*)(?:\n|$)/ym.exec(firstLine); - if(newMatch) { + if((newMatch) && (newMatch[0].length > 0) && (newMatch[1].length > 0)) { + // Test the lengths to handle two : paragraph breaks exception definitions.push({ dt : this.lexer.inlineTokens(originalLine.slice(0, newMatch[1].length).trim()), dd : this.lexer.inlineTokens(originalLine.slice(newMatch[1].length + 2).trim()) @@ -721,7 +730,8 @@ const MarkedEmojiOptions = { }; Marked.use(MarkedVariables()); -Marked.use({ extensions: [definitionListsMultiLine, definitionListsSingleLine, superSubScripts, mustacheSpans, mustacheDivs, mustacheInjectInline] }); +Marked.use({ extensions : [forcedParagraphBreaks, definitionListsMultiLine, definitionListsSingleLine, superSubScripts, + mustacheSpans, mustacheDivs, mustacheInjectInline] }); Marked.use(mustacheInjectBlock); Marked.use({ renderer: renderer, tokenizer: tokenizer, mangle: false }); Marked.use(MarkedExtendedTables(), MarkedGFMHeadingId(), MarkedSmartypantsLite(), MarkedEmojis(MarkedEmojiOptions)); @@ -847,8 +857,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); From 3a81521e6ff64b4bc11bf1f5f600a7cfccc59010 Mon Sep 17 00:00:00 2001 From: David Bolack Date: Mon, 19 Aug 2024 19:40:55 -0500 Subject: [PATCH 03/12] Work around users using Definition lists as paragraph indents. --- shared/naturalcrit/markdown.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index fcc0a27bf..804d6b2cf 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -395,7 +395,7 @@ const definitionListsSingleLine = { .map((emoji)=>firstLine = firstLine.replace(emoji.raw, 'x'.repeat(emoji.raw.length))); const newMatch = /^([^\n]*?)::([^\n]*)(?:\n|$)/ym.exec(firstLine); - if((newMatch) && (newMatch[0].length > 0) && (newMatch[1].length > 0)) { + if((newMatch) && newMatch[1].length > 0) { // Test the lengths to handle two : paragraph breaks exception definitions.push({ dt : this.lexer.inlineTokens(originalLine.slice(0, newMatch[1].length).trim()), From 3985afade9662db5bcc4825780fb99ae433fc577 Mon Sep 17 00:00:00 2001 From: David Bolack Date: Tue, 20 Aug 2024 11:46:43 -0500 Subject: [PATCH 04/12] Remove need for Definition exceptios Retool original `:` break to insert `\n\n`s between each `:` so that the definition lists never have a chance for a mismatch. Rename token to "hardbreak" Add tests for hardBreaks for `:`, `::`, and `:::` which should verify it works and does not collide with definitionlists. --- shared/naturalcrit/markdown.js | 12 ++++++------ tests/markdown/hardbreaks.test.js | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 tests/markdown/hardbreaks.test.js diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index 804d6b2cf..2cc5e3ad0 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -357,7 +357,7 @@ const superSubScripts = { }; const forcedParagraphBreaks = { - name : 'colonParagraphs', + name : 'hardBreaks', level : 'inline', start(src) { return src.match(/^:+$/m)?.index; }, // Hint to Marked.js to stop and check for a match tokenizer(src, tokens) { @@ -365,7 +365,7 @@ const forcedParagraphBreaks = { const match = regex.exec(src); if(match?.length) { return { - type : 'colonParagraphs', // Should match "name" above + type : 'hardBreaks', // Should match "name" above raw : match[0], // Text to consume from the source length : match[1].length, text : '' @@ -373,7 +373,7 @@ const forcedParagraphBreaks = { } }, renderer(token) { - return `
`.repeat(token.length); + return `
`.repeat(token.length).concat('\n'); } }; @@ -395,8 +395,7 @@ const definitionListsSingleLine = { .map((emoji)=>firstLine = firstLine.replace(emoji.raw, 'x'.repeat(emoji.raw.length))); const newMatch = /^([^\n]*?)::([^\n]*)(?:\n|$)/ym.exec(firstLine); - if((newMatch) && newMatch[1].length > 0) { - // Test the lengths to handle two : paragraph breaks exception + if(newMatch) { definitions.push({ dt : this.lexer.inlineTokens(originalLine.slice(0, newMatch[1].length).trim()), dd : this.lexer.inlineTokens(originalLine.slice(newMatch[1].length + 2).trim()) @@ -857,7 +856,8 @@ module.exports = { varsQueue = []; //Could move into MarkedVariables() globalPageNumber = pageNumber; - rawBrewText = rawBrewText.replace(/^\\column$/gm, `\n
\n`); + rawBrewText = rawBrewText.replace(/^\\column$/gm, `\n
\n`) + .replace(/^(:+)$/gm, (match)=>`${`:\n\n`.repeat(match.length)}\n`); const opts = Marked.defaults; rawBrewText = opts.hooks.preprocess(rawBrewText); diff --git a/tests/markdown/hardbreaks.test.js b/tests/markdown/hardbreaks.test.js new file mode 100644 index 000000000..b27c6859c --- /dev/null +++ b/tests/markdown/hardbreaks.test.js @@ -0,0 +1,23 @@ +/* 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('
\n
'); + }); + + test('Triple Break', function() { + const source = ':::\n\n'; + const rendered = Markdown.render(source).trim(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
\n
\n
'); + }); +}); From e957f4077500d2fa96d21586bb53aaee1e396e35 Mon Sep 17 00:00:00 2001 From: David Bolack Date: Tue, 20 Aug 2024 12:17:51 -0500 Subject: [PATCH 05/12] Revert previous simplification as it breaks the original purpose of this PR Added test for inside a code block. --- shared/naturalcrit/markdown.js | 7 ++++--- tests/markdown/hardbreaks.test.js | 11 +++++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index 2cc5e3ad0..71c267108 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -395,7 +395,9 @@ const definitionListsSingleLine = { .map((emoji)=>firstLine = firstLine.replace(emoji.raw, 'x'.repeat(emoji.raw.length))); const newMatch = /^([^\n]*?)::([^\n]*)(?:\n|$)/ym.exec(firstLine); - if(newMatch) { + if((newMatch) && ((newMatch[2].length > 0) && newMatch[2][0] != ':')) { + // Test the length to handle two : paragraph breaks exception + // Test the first position on the dictionary term to handle three + paragraph breaks exception definitions.push({ dt : this.lexer.inlineTokens(originalLine.slice(0, newMatch[1].length).trim()), dd : this.lexer.inlineTokens(originalLine.slice(newMatch[1].length + 2).trim()) @@ -856,8 +858,7 @@ module.exports = { varsQueue = []; //Could move into MarkedVariables() globalPageNumber = pageNumber; - rawBrewText = rawBrewText.replace(/^\\column$/gm, `\n
\n`) - .replace(/^(:+)$/gm, (match)=>`${`:\n\n`.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/hardbreaks.test.js b/tests/markdown/hardbreaks.test.js index b27c6859c..4728e7674 100644 --- a/tests/markdown/hardbreaks.test.js +++ b/tests/markdown/hardbreaks.test.js @@ -12,12 +12,19 @@ describe('Hard Breaks', ()=>{ test('Double Break', function() { const source = '::\n\n'; const rendered = Markdown.render(source).trim(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
\n
'); + 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('
\n
\n
'); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
'); }); + + 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
'); + }); + }); From 0f969ce383bb3e564a332b585e527de68493d881 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Tue, 20 Aug 2024 17:11:50 -0400 Subject: [PATCH 06/12] Add catch-all for invalid paths res.route is the currently-matched route. If nothing has been matched by this point (route = undefined), we have an invalid route. --- server/app.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/server/app.js b/server/app.js index b419c5cea..b15e9946c 100644 --- a/server/app.js +++ b/server/app.js @@ -420,6 +420,14 @@ if(isLocalEnvironment){ }); } +// Catch-all route for invalid routes +app.use((req, res, next) => { + if (!req.route) + return res.redirect('/'); + + return next(); +}); + //Render the page const templateFn = require('./../client/template.js'); const renderPage = async (req, res)=>{ From c75ebb36cb4d25abdb3edd41e436e1b1c0221180 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Aug 2024 03:10:12 +0000 Subject: [PATCH 07/12] Bump stylelint-config-recess-order from 5.0.1 to 5.1.0 Bumps [stylelint-config-recess-order](https://github.com/stormwarning/stylelint-config-recess-order) from 5.0.1 to 5.1.0. - [Release notes](https://github.com/stormwarning/stylelint-config-recess-order/releases) - [Changelog](https://github.com/stormwarning/stylelint-config-recess-order/blob/main/CHANGELOG.md) - [Commits](https://github.com/stormwarning/stylelint-config-recess-order/compare/v5.0.1...v5.1.0) --- updated-dependencies: - dependency-name: stylelint-config-recess-order dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 9 ++++----- package.json | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 08b542f46..9d832091a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,7 +58,7 @@ "jest-expect-message": "^1.1.3", "postcss-less": "^6.0.0", "stylelint": "^16.8.2", - "stylelint-config-recess-order": "^5.0.1", + "stylelint-config-recess-order": "^5.1.0", "stylelint-config-recommended": "^14.0.1", "supertest": "^7.0.0" }, @@ -13678,11 +13678,10 @@ } }, "node_modules/stylelint-config-recess-order": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/stylelint-config-recess-order/-/stylelint-config-recess-order-5.0.1.tgz", - "integrity": "sha512-rKbGkoa3h0rINrGln9TFVowvSCLgPJC5O0EuPiqlqWcJMb1lImEtXktcjFCVz+hwtSUiHD3ijJc3vP9muFOgJg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recess-order/-/stylelint-config-recess-order-5.1.0.tgz", + "integrity": "sha512-ddapCF6B/kEtQYIFhQFReQ0dvK1ZdgJDM/SGFtIyeooYDbqaJqcOlGkRRGaVErCQYJY/bPSPsLRS2LdQtLJUVQ==", "dev": true, - "license": "ISC", "dependencies": { "stylelint-order": "^6.0.4" }, diff --git a/package.json b/package.json index 675d76f39..0dda383d5 100644 --- a/package.json +++ b/package.json @@ -131,7 +131,7 @@ "jest-expect-message": "^1.1.3", "postcss-less": "^6.0.0", "stylelint": "^16.8.2", - "stylelint-config-recess-order": "^5.0.1", + "stylelint-config-recess-order": "^5.1.0", "stylelint-config-recommended": "^14.0.1", "supertest": "^7.0.0" } From 40ab2c2283f64c22978a83ffbaebe7b92e247434 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Thu, 22 Aug 2024 14:24:33 -0400 Subject: [PATCH 08/12] rearrange code --- server/app.js | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/server/app.js b/server/app.js index b15e9946c..8b3e4652b 100644 --- a/server/app.js +++ b/server/app.js @@ -14,6 +14,7 @@ const GoogleActions = require('./googleActions.js'); const serveCompressedStaticAssets = require('./static-assets.mv.js'); const sanitizeFilename = require('sanitize-filename'); const asyncHandler = require('express-async-handler'); +const templateFn = require('./../client/template.js'); const { DEFAULT_BREW } = require('./brewDefaults.js'); @@ -420,16 +421,16 @@ if(isLocalEnvironment){ }); } -// Catch-all route for invalid routes -app.use((req, res, next) => { - if (!req.route) - return res.redirect('/'); - - return next(); -}); +//Send rendered page +app.use(asyncHandler(async (req, res, next)=>{ + if (!req.route) return res.redirect('/'); // Catch-all for invalid routes + + const page = await renderPage(req, res); + if(!page) return; + res.send(page); +})); //Render the page -const templateFn = require('./../client/template.js'); const renderPage = async (req, res)=>{ // Create configuration object const configuration = { @@ -458,13 +459,6 @@ const renderPage = async (req, res)=>{ return page; }; -//Send rendered page -app.use(asyncHandler(async (req, res, next)=>{ - const page = await renderPage(req, res); - if(!page) return; - res.send(page); -})); - //v=====----- Error-Handling Middleware -----=====v// //Format Errors as plain objects so all fields will appear in the string sent const formatErrors = (key, value)=>{ From 26866c337fc1b777c7376dd68e312eae7509c3aa Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Thu, 22 Aug 2024 21:45:44 -0400 Subject: [PATCH 09/12] Add tests to circleci --- .circleci/config.yml | 3 +++ package.json | 1 + 2 files changed, 4 insertions(+) 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", From e6c5e6451c55f356ebcb0603a46be15c9c88ac68 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Thu, 22 Aug 2024 21:46:42 -0400 Subject: [PATCH 10/12] Add more tests for edge conflicts with definition lists --- tests/markdown/definition-lists.test.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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
Inline definition 1
\n
Inline definition 2 (no DT)
\n
'); }); + + test('Multiline Definition Term must have at least one non-empty Definition', function() { + const source = 'Term 1\n::'; + 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
\n

Definition 1

`); + }); }); From e0b69dce1476cc3f143005fa3ae012aef5601bc1 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Thu, 22 Aug 2024 21:49:40 -0400 Subject: [PATCH 11/12] add more test cases Test a large number of breaks, and interaction with paragraphs --- tests/markdown/hard-breaks.test.js | 47 ++++++++++++++++++++++++++++++ tests/markdown/hardbreaks.test.js | 30 ------------------- 2 files changed, 47 insertions(+), 30 deletions(-) create mode 100644 tests/markdown/hard-breaks.test.js delete mode 100644 tests/markdown/hardbreaks.test.js 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
\n

Line 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
`); + }); +}); diff --git a/tests/markdown/hardbreaks.test.js b/tests/markdown/hardbreaks.test.js deleted file mode 100644 index 4728e7674..000000000 --- a/tests/markdown/hardbreaks.test.js +++ /dev/null @@ -1,30 +0,0 @@ -/* 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('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
'); - }); - -}); From fff357d08bc089bb61942dcde1cc2a086997ab37 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Thu, 22 Aug 2024 21:52:40 -0400 Subject: [PATCH 12/12] Make 'block-level' extension. Tweaks to pass new tests 'block-level' because it never occurs inside another block ('inline-level' always occurs inside something else); starts and stops on on its own line without anything else on the same line. --- shared/naturalcrit/markdown.js | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index 71c267108..c5d99afcc 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -358,15 +358,15 @@ const superSubScripts = { const forcedParagraphBreaks = { name : 'hardBreaks', - level : 'inline', - start(src) { return src.match(/^:+$/m)?.index; }, // Hint to Marked.js to stop and check for a match + 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 = /^(:+)$/m; + 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 + raw : match[0], // Text to consume from the source length : match[1].length, text : '' }; @@ -377,7 +377,6 @@ const forcedParagraphBreaks = { } }; - const definitionListsSingleLine = { name : 'definitionListsSingleLine', level : 'block', @@ -395,9 +394,7 @@ const definitionListsSingleLine = { .map((emoji)=>firstLine = firstLine.replace(emoji.raw, 'x'.repeat(emoji.raw.length))); const newMatch = /^([^\n]*?)::([^\n]*)(?:\n|$)/ym.exec(firstLine); - if((newMatch) && ((newMatch[2].length > 0) && newMatch[2][0] != ':')) { - // Test the length to handle two : paragraph breaks exception - // Test the first position on the dictionary term to handle three + paragraph breaks exception + if(newMatch) { definitions.push({ dt : this.lexer.inlineTokens(originalLine.slice(0, newMatch[1].length).trim()), dd : this.lexer.inlineTokens(originalLine.slice(newMatch[1].length + 2).trim()) @@ -424,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 = []; @@ -731,7 +728,7 @@ const MarkedEmojiOptions = { }; Marked.use(MarkedVariables()); -Marked.use({ extensions : [forcedParagraphBreaks, definitionListsMultiLine, definitionListsSingleLine, superSubScripts, +Marked.use({ extensions : [definitionListsMultiLine, definitionListsSingleLine, forcedParagraphBreaks, superSubScripts, mustacheSpans, mustacheDivs, mustacheInjectInline] }); Marked.use(mustacheInjectBlock); Marked.use({ renderer: renderer, tokenizer: tokenizer, mangle: false });