diff --git a/package-lock.json b/package-lock.json index 988d0c554..71d2693a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,6 +37,7 @@ "marked-emoji": "^2.0.0", "marked-extended-tables": "^2.0.1", "marked-gfm-heading-id": "^4.0.1", + "marked-nonbreaking-spaces": "^1.0.1", "marked-smartypants-lite": "^1.0.3", "marked-subsuper-text": "^1.0.3", "markedLegacy": "npm:marked@^0.3.19", @@ -378,13 +379,13 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz", - "integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", + "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", "license": "MIT", "dependencies": { - "@babel/template": "^7.26.9", - "@babel/types": "^7.26.10" + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0" }, "engines": { "node": ">=6.9.0" @@ -1866,9 +1867,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.0.tgz", - "integrity": "sha512-yJLLmLexii32mGrhW29qvU3QBVTu0GUmEf/J4XsBtVhp4JkIUFN/BjWqTF63yRvGApIDpZm5fa97LtYtINmfeQ==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz", + "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1946,19 +1947,32 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", - "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", + "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.12.0", + "@eslint/core": "^0.13.0", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", + "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@googleapis/drive": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/@googleapis/drive/-/drive-11.0.0.tgz", @@ -2727,9 +2741,9 @@ } }, "node_modules/@mongodb-js/saslprep": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.2.0.tgz", - "integrity": "sha512-+ywrb0AqkfaYuhHs6LxKWgqbh3I72EpEgESCw37o+9qPx9WTCkgDm2B+eMrwehGtHBWHFU4GXvnSCNiFhhausg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.2.2.tgz", + "integrity": "sha512-EB0O3SCSNRUFk66iRCpI+cXzIjdswfCs7F6nOC3RAGJ7xr5YhaicvsRwJ9eyzYvYRlCSDUO/c7g4yNulxKC1WA==", "license": "MIT", "dependencies": { "sparse-bitfield": "^3.0.3" @@ -3889,16 +3903,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, "node_modules/bn.js": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", @@ -4259,20 +4263,20 @@ } }, "node_modules/cacheable": { - "version": "1.8.9", - "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.8.9.tgz", - "integrity": "sha512-FicwAUyWnrtnd4QqYAoRlNs44/a1jTL7XDKqm5gJ90wz1DQPlC7U2Rd1Tydpv+E7WAr4sQHuw8Q8M3nZMAyecQ==", + "version": "1.8.10", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.8.10.tgz", + "integrity": "sha512-0ZnbicB/N2R6uziva8l6O6BieBklArWyiGx4GkwAhLKhSHyQtRfM9T1nx7HHuHDKkYB/efJQhz3QJ6x/YqoZzA==", "dev": true, "license": "MIT", "dependencies": { - "hookified": "^1.7.1", - "keyv": "^5.3.1" + "hookified": "^1.8.1", + "keyv": "^5.3.2" } }, "node_modules/cacheable/node_modules/keyv": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.3.1.tgz", - "integrity": "sha512-13hQT2q2VIwOoaJdJa7nY3J8UVbYtMTJFHnwm9LI+SaQRfUiM6Em9KZeOVTCKbMnGcRIL3NSUFpAdjZCq24nLQ==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.3.2.tgz", + "integrity": "sha512-Lji2XRxqqa5Wg+CHLVfFKBImfJZ4pCSccu9eVWK6w4c2SDFLd8JAn1zqTuSFnsxb7ope6rMsnIHfp+eBbRBRZQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5419,9 +5423,9 @@ "license": "ISC" }, "node_modules/elliptic": { - "version": "6.5.7", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", - "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.0.tgz", + "integrity": "sha512-dpwoQcLc/2WLQvJvLRHKZ+f9FgOdjnq11rurqwekGQygGPsYSK29OMMD2WalatiqQ+XGFDglTNixpPfI+lpaAA==", "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -6446,13 +6450,6 @@ "node": ">=16.0.0" } }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "license": "MIT", - "optional": true - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -10012,6 +10009,15 @@ "marked": ">=13 <16" } }, + "node_modules/marked-nonbreaking-spaces": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/marked-nonbreaking-spaces/-/marked-nonbreaking-spaces-1.0.1.tgz", + "integrity": "sha512-CUeFRc6OdMMSJThgOM7WAkUjL59VfSf79urKyKYtH9fs3hnrhC3+syFBimYh4vpvUZmjnXoZX0K6V3vZKmyRWQ==", + "license": "MIT", + "peerDependencies": { + "marked": ">=3 <16" + } + }, "node_modules/marked-smartypants-lite": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/marked-smartypants-lite/-/marked-smartypants-lite-1.0.3.tgz", @@ -13423,25 +13429,25 @@ "license": "MIT" }, "node_modules/stylelint/node_modules/file-entry-cache": { - "version": "10.0.7", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.0.7.tgz", - "integrity": "sha512-txsf5fu3anp2ff3+gOJJzRImtrtm/oa9tYLN0iTuINZ++EyVR/nRrg2fKYwvG/pXDofcrvvb0scEbX3NyW/COw==", + "version": "10.0.8", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.0.8.tgz", + "integrity": "sha512-FGXHpfmI4XyzbLd3HQ8cbUcsFGohJpZtmQRHr8z8FxxtCe2PcpgIlVLwIgunqjvRmXypBETvwhV4ptJizA+Y1Q==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^6.1.7" + "flat-cache": "^6.1.8" } }, "node_modules/stylelint/node_modules/flat-cache": { - "version": "6.1.7", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.7.tgz", - "integrity": "sha512-qwZ4xf1v1m7Rc9XiORly31YaChvKt6oNVHuqqZcoED/7O+ToyNVGobKsIAopY9ODcWpEDKEBAbrSOCBHtNQvew==", + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.8.tgz", + "integrity": "sha512-R6MaD3nrJAtO7C3QOuS79ficm2pEAy++TgEUD8ii1LVlbcgZ9DtASLkt9B+RZSFCzm7QHDMlXPsqqB6W2Pfr1Q==", "dev": true, "license": "MIT", "dependencies": { "cacheable": "^1.8.9", "flatted": "^3.3.3", - "hookified": "^1.7.1" + "hookified": "^1.8.1" } }, "node_modules/stylelint/node_modules/ignore": { diff --git a/package.json b/package.json index 6a9eddf86..a98cf59ff 100644 --- a/package.json +++ b/package.json @@ -111,6 +111,7 @@ "marked-emoji": "^2.0.0", "marked-extended-tables": "^2.0.1", "marked-gfm-heading-id": "^4.0.1", + "marked-nonbreaking-spaces": "^1.0.1", "marked-smartypants-lite": "^1.0.3", "marked-subsuper-text": "^1.0.3", "markedLegacy": "npm:marked@^0.3.19", diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index 6391b511b..6f5fa6fc6 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -7,6 +7,7 @@ import MarkedExtendedTables from 'marked-extended-tables'; import { markedSmartypantsLite as MarkedSmartypantsLite } from 'marked-smartypants-lite'; import { gfmHeadingId as MarkedGFMHeadingId, resetHeadings as MarkedGFMResetHeadingIDs } from 'marked-gfm-heading-id'; import { markedEmoji as MarkedEmojis } from 'marked-emoji'; +import MarkedNonbreakingSpaces from 'marked-nonbreaking-spaces'; import MarkedSubSuperText from 'marked-subsuper-text'; import { romanize } from 'romans'; import writtenNumber from 'written-number'; @@ -444,27 +445,6 @@ const forcedParagraphBreaks = { } }; -const nonbreakingSpaces = { - name : 'nonbreakingSpaces', - 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 = /:(>+)/ym; - const match = regex.exec(src); - if(match?.length) { - return { - type : 'nonbreakingSpaces', // 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(''); - } -}; - const definitionListsSingleLine = { name : 'definitionListsSingleLine', level : 'block', @@ -821,9 +801,10 @@ const tableTerminators = [ Marked.use(MarkedVariables()); Marked.use({ extensions : [justifiedParagraphs, definitionListsMultiLine, definitionListsSingleLine, forcedParagraphBreaks, - nonbreakingSpaces, mustacheSpans, mustacheDivs, mustacheInjectInline] }); + mustacheSpans, mustacheDivs, mustacheInjectInline] }); Marked.use(mustacheInjectBlock); Marked.use(MarkedSubSuperText()); +Marked.use(MarkedNonbreakingSpaces()); Marked.use({ renderer: renderer, tokenizer: tokenizer, mangle: false }); Marked.use(MarkedExtendedTables({ interruptPatterns: tableTerminators }), MarkedGFMHeadingId({ globalSlugs: true }), MarkedSmartypantsLite(), MarkedEmojis(MarkedEmojiOptions)); diff --git a/tests/markdown/non-breaking-spaces.test.js b/tests/markdown/non-breaking-spaces.test.js index 83fb55509..e498645a6 100644 --- a/tests/markdown/non-breaking-spaces.test.js +++ b/tests/markdown/non-breaking-spaces.test.js @@ -2,55 +2,7 @@ import Markdown from 'naturalcrit/markdown.js'; -describe('Non-Breaking Spaces', ()=>{ - test('Single Space', function() { - const source = ':>\n\n'; - const rendered = Markdown.render(source).trim(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

 

`); - }); - - test('Double Space', function() { - const source = ':>>\n\n'; - const rendered = Markdown.render(source).trim(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

  

`); - }); - - test('Triple Space', function() { - const source = ':>>>\n\n'; - const rendered = Markdown.render(source).trim(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

   

`); - }); - - test('Many Space', function() { - const source = ':>>>>>>>>>>\n\n'; - const rendered = Markdown.render(source).trim(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

          

`); - }); - - test('Multiple sets of Spaces', function() { - const source = ':>>>\n:>>>\n:>>>'; - const rendered = Markdown.render(source).trim(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

   \n   \n   

`); - }); - - test('Pair of inline Spaces', function() { - const source = ':>>:>>'; - const rendered = Markdown.render(source).trim(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

    

`); - }); - - test('Space 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
`); - }); - +describe('Non-Breaking Spaces Interactions', ()=>{ test('I am actually a single-line definition list!', function() { const source = 'Term ::> Definition 1\n'; const rendered = Markdown.render(source).trim();