From 48373057acf83cfae45b0e612fe0995b4b286c07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Musia=C5=82?= Date: Wed, 18 Jun 2025 17:26:36 +0200 Subject: [PATCH] add info string to code fence --- .gitignore | 6 ++ __tests__/ExpensiMark-test.js | 50 +++++++--- lib/ExpensiMark.ts | 16 ++-- package-lock.json | 170 ++++++++++++++++++++++++++++------ package.json | 6 +- 5 files changed, 199 insertions(+), 49 deletions(-) diff --git a/.gitignore b/.gitignore index 375853db..850f5572 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,9 @@ dist # Published package *.tgz +.build_complete + +# jest +/coverage + + diff --git a/__tests__/ExpensiMark-test.js b/__tests__/ExpensiMark-test.js index befe1958..fb16552d 100644 --- a/__tests__/ExpensiMark-test.js +++ b/__tests__/ExpensiMark-test.js @@ -16,6 +16,30 @@ test('Test text is unescaped', () => { expect(parser.htmlToText(htmlString)).toBe(resultString); }); +describe('codeFence', () => { + + test('Test standard code fence', () => { + const markdownString = '```\ncodeblock\nsecond line\n```'; + const htmlString = '
codeblock
second line
'; + const rawInputHtmlString = '
\ncodeblock\nsecond line\n
'; + expect(parser.replace(markdownString)).toBe(htmlString); + expect(parser.htmlToMarkdown(htmlString)).toBe(markdownString); + expect(parser.replace(markdownString, {shouldKeepRawInput: true})).toBe(rawInputHtmlString); + expect(parser.htmlToMarkdown(htmlString)).toBe(markdownString); + }); + + test('Test code fence with code variant', () => { + const markdownString = '\n```bash\ncodeblock\nsecond line\n```'; + const htmlString = '
codeblock
second line
'; + const rawInputHtmlString = '\n
\ncodeblock\nsecond line\n
'; + + expect(parser.replace(markdownString)).toBe(htmlString); + expect(parser.htmlToMarkdown(htmlString)).toBe(markdownString); + expect(parser.replace(markdownString, {shouldKeepRawInput: true})).toBe(rawInputHtmlString); + expect(parser.htmlToMarkdown(htmlString)).toBe(markdownString); + }); +}); + test('Test with regex Maximum regex stack depth reached error', () => { const testString = '

heading

asjjssjdjdjdjdjdjjeiwiwiwowkdjdjdieikdjfidekjcjdkekejdcjdkeekcjcdidjjcdkekdiccjdkejdjcjxisdjjdkedncicdjejejcckdsijcjdsodjcicdkejdicdjejajasjjssjdjdjdjdjdjjeiwiwiwowkdjdjdieikdjfisjksksjsjssskssjskskssksksksksskdkddkddkdksskskdkdkdksskskskdkdkdkdkekeekdkddenejeodxkdndekkdjddkeemdjxkdenendkdjddekjcjdkekejdcjdkeekcjcdidjjcdkekdiccjdkejdjcjxisdjjdkedncicdjejejcckdsijcjdsodjcicdkejdicdjejajasjjssjdjdjdjdjdjjeiwiwiwowkdjdjdieikdjfidekjcjdkekejdcjdkeekcjcdidjjcdkekdiccjdkejdjcjxisdjjdkedncicdjejejcckdsijcjdsodjcicdkejdi.cdjd'; @@ -56,47 +80,47 @@ describe('Test ExpensiMark getAttributeCache', () => { test('If mediaAttributeCachingFn is provided, returns it', () => { const extras = { mediaAttributeCachingFn: jest.fn(), - } + }; expect(expensiMark.getAttributeCache(extras).attrCachingFn).toBe(extras.mediaAttributeCachingFn); - }) + }); test('If mediaAttributeCachingFn is not provided, returns cacheVideoAttributes', () => { const extras = { cacheVideoAttributes: jest.fn(), - } + }; expect(expensiMark.getAttributeCache(extras).attrCachingFn).toBe(extras.cacheVideoAttributes); - }) + }); test('If mediaAttributeCachingFn and cacheVideoAttributes are not provided, returns undefined', () => { - const extras = {} + const extras = {}; expect(expensiMark.getAttributeCache(extras).attrCachingFn).toBe(undefined); - }) + }); }); describe('For attrCache', () => { test('If mediaAttributeCache is provided, returns it', () => { const extras = { mediaAttributeCache: jest.fn(), - } + }; expect(expensiMark.getAttributeCache(extras).attrCache).toBe(extras.mediaAttributeCache); - }) + }); test('If mediaAttributeCache is not provided, returns videoAttributeCache', () => { const extras = { videoAttributeCache: jest.fn(), - } + }; expect(expensiMark.getAttributeCache(extras).attrCache).toBe(extras.videoAttributeCache); - }) + }); test('If mediaAttributeCache and videoAttributeCache are not provided, returns undefined', () => { - const extras = {} + const extras = {}; expect(expensiMark.getAttributeCache(extras).attrCache).toBe(undefined); - }) + }); }); test('If no extras are undefined, returns undefined for both attrCachingFn and attrCache', () => { const {attrCachingFn, attrCache} = expensiMark.getAttributeCache(undefined); expect(attrCachingFn).toBe(undefined); expect(attrCache).toBe(undefined); - }) + }); }); diff --git a/lib/ExpensiMark.ts b/lib/ExpensiMark.ts index bb392659..3e7a3e1b 100644 --- a/lib/ExpensiMark.ts +++ b/lib/ExpensiMark.ts @@ -171,7 +171,7 @@ export default class ExpensiMark { name: 'codeFence', // ` is a backtick symbol we are matching on three of them before then after a new line character - regex: /(```.*?(\r\n|\n))((?:\s*?(?!(?:\r\n|\n)?```(?!`))[\S])+\s*?(?:\r\n|\n))(```)/g, + regex: /(? tags and does @@ -179,13 +179,13 @@ export default class ExpensiMark { // with the new lines here since they need to be converted into
. And we don't // want to do this anywhere else since that would break HTML. //   will create styling issues so use - replacement: (_extras, _match, _g1, _g2, textWithinFences) => { + replacement: (_extras, _match, _g1, code, _g2, textWithinFences) => { const group = textWithinFences.replace(/(?:(?![\n\r])\s)/g, ' '); - return `
${group}
`; + return `${group}`; }, - rawInputReplacement: (_extras, _match, _g1, newLineCharacter, textWithinFences) => { + rawInputReplacement: (_extras, _match, _g1, code, newLineCharacter, textWithinFences) => { const group = textWithinFences.replace(/(?:(?![\n\r])\s)/g, ' ').replace(/|<\/emoji>/g, ''); - return `
${newLineCharacter}${group}
`; + return `${newLineCharacter}${group}`; }, }, @@ -691,8 +691,10 @@ export default class ExpensiMark { }, { name: 'codeFence', - regex: /<(pre)(?:"[^"]*"|'[^']*'|[^'">])*>([\s\S]*?)(\n?)<\/\1>(?![^<]*(<\/pre>|<\/code>))/gi, - replacement: (_extras, _match, _g1, g2) => `\`\`\`\n${g2}\n\`\`\``, + regex: /<(pre)(?:\s+[^>]*?\b(?:data-info-string)\s*=\s*["']?([^\s"'<>]+)["']?[^>]*)?\s*>([\s\S]*?)(\n?)<\/\1>(?![^<]*(<\/pre>|<\/code>))/gi, + replacement: (_extras, _match, _g1, code, g2) => { + return `\`\`\`${code ?? ''}\n${g2}\n\`\`\``; + }, }, { name: 'anchor', diff --git a/package-lock.json b/package-lock.json index e866473b..1d06313a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,6 +48,7 @@ "jest": "^29.0.0", "jest-environment-jsdom": "^29.7.0", "jit-grunt": "^0.10.0", + "nodemon": "^3.1.3", "prettier": "^3.3.3", "typescript": "^5.7.2" } @@ -3943,10 +3944,11 @@ } }, "node_modules/anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, + "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -4648,39 +4650,28 @@ } }, "node_modules/chokidar": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", - "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, + "license": "MIT", "dependencies": { - "anymatch": "~3.1.1", + "anymatch": "~3.1.2", "braces": "~3.0.2", - "glob-parent": "~5.1.0", + "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" + "readdirp": "~3.6.0" }, "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { - "fsevents": "~2.1.2" - } - }, - "node_modules/chokidar/node_modules/fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "deprecated": "\"Please update to latest v2.3 or v2.2\"", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "fsevents": "~2.3.2" } }, "node_modules/ci-info": { @@ -7600,6 +7591,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -7781,6 +7782,13 @@ "node": ">= 4" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", @@ -10386,6 +10394,19 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "node_modules/link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/link/-/link-2.1.1.tgz", + "integrity": "sha512-NV3AUVYBovJ6eVQcTeRoPnZSxzt2LOijNd+ugEZKRy/XeQlpTRhVRkuDv5kOlXwMAUx30vfUc7asRFb9RT65yg==", + "dev": true, + "license": "MIT", + "bin": { + "link": "dist/cli.js" + }, + "funding": { + "url": "https://github.com/privatenumber/link?sponsor=1" + } + }, "node_modules/livereload-js": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.4.0.tgz", @@ -10589,6 +10610,48 @@ "dev": true, "license": "MIT" }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", @@ -11127,6 +11190,13 @@ "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", "dev": true }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -11236,10 +11306,11 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, + "license": "MIT", "dependencies": { "picomatch": "^2.2.1" }, @@ -11673,6 +11744,19 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/simply-deferred": { "version": "3.0.0", "resolved": "git+https://git@github.com/Expensify/simply-deferred.git#77a08a95754660c7bd6e0b6979fdf84e8e831bf5", @@ -11901,6 +11985,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -12002,6 +12099,16 @@ "node": ">=8.0" } }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/tough-cookie": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", @@ -12239,6 +12346,13 @@ "node": ">=0.10.0" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, "node_modules/underscore": { "version": "1.13.6", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", diff --git a/package.json b/package.json index 4396d911..e0fdd9f9 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "grunt": "grunt", "typecheck": "tsc --noEmit", "build": "tsc -p tsconfig.build.json && cp ./lib/*.d.ts ./dist", + "build:watch": "nodemon --watch lib --ext .js,.jsx,.ts,.tsx,.css --exec \"rm -f .build_complete && npm run build && npm pack && touch .build_complete\"", "test": "jest", "lint": "eslint lib/", "prettier": "prettier --write lib/", @@ -43,6 +44,7 @@ "ua-parser-js": "^1.0.38" }, "devDependencies": { + "link": "2.1.1", "@babel/preset-env": "^7.26.0", "@babel/preset-typescript": "^7.24.7", "@lwc/eslint-plugin-lwc": "^1.8.2", @@ -68,7 +70,9 @@ "jest-environment-jsdom": "^29.7.0", "jit-grunt": "^0.10.0", "prettier": "^3.3.3", - "typescript": "^5.7.2" + "typescript": "^5.7.2", + "nodemon": "^3.1.10" + }, "browserify": { "transform": [