diff --git a/.circleci/config.yml b/.circleci/config.yml index 2025e8fe7..85e2ccb9b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -64,9 +64,6 @@ jobs: - run: name: Test - Mustache Spans command: npm run test:mustache-syntax - - run: - name: Test - Definition Lists - command: npm run test:definition-lists - run: name: Test - Hard Breaks command: npm run test:hard-breaks diff --git a/package-lock.json b/package-lock.json index add91f28e..a6acfde55 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "less": "^3.13.1", "lodash": "^4.17.21", "marked": "13.0.3", + "marked-definition-lists": "^1.0.0", "marked-emoji": "^1.4.3", "marked-extended-tables": "^1.1.0", "marked-gfm-heading-id": "^4.0.1", @@ -73,6 +74,37 @@ "npm": "^10.2.x" } }, + "../marked-definition-lists": { + "version": "1.0.0", + "extraneous": true, + "license": "MIT", + "devDependencies": { + "@babel/core": "^7.26.0", + "@babel/preset-env": "^7.26.0", + "@markedjs/testutils": "^15.0.0-0", + "@rollup/plugin-commonjs": "^28.0.2", + "@rollup/plugin-node-resolve": "^16.0.0", + "@semantic-release/changelog": "^6.0.3", + "@semantic-release/commit-analyzer": "^13.0.0", + "@semantic-release/git": "^10.0.1", + "@semantic-release/github": "^11.0.1", + "@semantic-release/npm": "^12.0.1", + "@semantic-release/release-notes-generator": "^14.0.2", + "babel-jest": "^29.5.0", + "eslint": "^8.57.1", + "eslint-config-standard": "^17.1.0", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^6.6.0", + "jest-cli": "^29.7.0", + "marked": "^15.0.4", + "rollup": "^4.29.2", + "semantic-release": "^24.2.0" + }, + "peerDependencies": { + "marked": ">=3 <16" + } + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -9846,6 +9878,15 @@ "node": ">= 18" } }, + "node_modules/marked-definition-lists": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/marked-definition-lists/-/marked-definition-lists-1.0.0.tgz", + "integrity": "sha512-ymeRwE/LiPCFMdZpihl9HP/jPqoWW4rp+g312Fh5RGfg5XWgbFgwXAhgf72Wc7FK56blY274B2seNq/UF0eY8w==", + "license": "MIT", + "peerDependencies": { + "marked": ">=3 <16" + } + }, "node_modules/marked-emoji": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/marked-emoji/-/marked-emoji-1.4.3.tgz", diff --git a/package.json b/package.json index 0097e5313..b8973ac1e 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,6 @@ "test:mustache-syntax:inline": "jest \".*(mustache-syntax).*\" -t '^Inline:.*' --verbose --noStackTrace", "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:non-breaking-spaces": "jest tests/markdown/non-breaking-spaces.test.js --verbose --noStackTrace", "test:emojis": "jest tests/markdown/emojis.test.js --verbose --noStackTrace", @@ -109,6 +108,7 @@ "less": "^3.13.1", "lodash": "^4.17.21", "marked": "13.0.3", + "marked-definition-lists": "^1.0.0", "marked-emoji": "^1.4.3", "marked-extended-tables": "^1.1.0", "marked-gfm-heading-id": "^4.0.1", diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index 99766b536..417efb512 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 MarkedDefinitionLists from 'marked-definition-lists'; //Icon fonts included so they can appear in emoji autosuggest dropdown import diceFont from '../../themes/fonts/iconFonts/diceFont.js'; @@ -440,93 +441,6 @@ const nonbreakingSpaces = { } }; -const definitionListsSingleLine = { - name : 'definitionListsSingleLine', - level : 'block', - start(src) { return src.match(/\n[^\n]*?::[^\n]*/m)?.index; }, // Hint to Marked.js to stop and check for a match - tokenizer(src, tokens) { - const regex = /^([^\n]*?)::([^\n]*)(?:\n|$)/ym; - let match; - let endIndex = 0; - const definitions = []; - while (match = regex.exec(src)) { - const originalLine = match[0]; // This line and below to handle conflict with emojis - let firstLine = originalLine; // Remove in V4 when definitionListsInline updated to - this.lexer.inlineTokens(firstLine.trim()) // require spaces around `::` - .filter((t)=>t.type == 'emoji') - .map((emoji)=>firstLine = firstLine.replace(emoji.raw, 'x'.repeat(emoji.raw.length))); - - const newMatch = /^([^\n]*?)::([^\n]*)(?:\n|$)/ym.exec(firstLine); - 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()) - }); - } // End of emoji hack. - endIndex = regex.lastIndex; - } - if(definitions.length) { - return { - type : 'definitionListsSingleLine', - raw : src.slice(0, endIndex), - definitions - }; - } - }, - renderer(token) { - return `
${token.definitions.reduce((html, def)=>{ - return `${html}
${this.parser.parseInline(def.dt)}
` - + `
${this.parser.parseInline(def.dd)}
\n`; - }, '')}
`; - } -}; - -const definitionListsMultiLine = { - name : 'definitionListsMultiLine', - level : 'block', - 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::)|(?:\n\n)|$))/y; - let match; - let endIndex = 0; - const definitions = []; - while (match = regex.exec(src)) { - if(match[1]) { - if(this.lexer.blockTokens(match[1].trim())[0]?.type !== 'paragraph') // DT must not be another block-level token besides

- break; - definitions.push({ - dt : this.lexer.inlineTokens(match[1].trim()), - dds : [] - }); - } - if(match[2] && definitions.length) { - definitions[definitions.length - 1].dds.push( - this.lexer.inlineTokens(match[2].trim().replace(/\s/g, ' ')) - ); - } - endIndex = regex.lastIndex; - } - if(definitions.length) { - return { - type : 'definitionListsMultiLine', - raw : src.slice(0, endIndex), - definitions - }; - } - }, - renderer(token) { - let returnVal = `

`; - token.definitions.forEach((def)=>{ - const dds = def.dds.map((s)=>{ - return `\n
${this.parser.parseInline(s).trim()}
`; - }).join(''); - returnVal += `
${this.parser.parseInline(def.dt)}
${dds}\n`; - }); - returnVal = returnVal.trim(); - return `${returnVal}
`; - } -}; - //v=====--------------------< Variable Handling >-------------------=====v// 242 lines const replaceVar = function(input, hoist=false, allowUnresolved=false) { const regex = /([!$]?)\[((?!\s*\])(?:\\.|[^\[\]\\])+)\]/g; @@ -795,7 +709,8 @@ const tableTerminators = [ ]; Marked.use(MarkedVariables()); -Marked.use({ extensions : [justifiedParagraphs, definitionListsMultiLine, definitionListsSingleLine, forcedParagraphBreaks, +Marked.use(MarkedDefinitionLists()); +Marked.use({ extensions : [justifiedParagraphs, forcedParagraphBreaks, nonbreakingSpaces, superSubScripts, mustacheSpans, mustacheDivs, mustacheInjectInline] }); Marked.use(mustacheInjectBlock); Marked.use({ renderer: renderer, tokenizer: tokenizer, mangle: false });