diff --git a/CHANGELOG.md b/CHANGELOG.md index c16488a087..24c1bb22ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,7 @@ Notes: web developers are advised to use [`~` (tilde range)](https://github.com/ - Added new style options `borderAnimationColor1`, `borderAnimationColor2`, and `borderAnimationColor3` for customizing decorator colors, in PR [#5312](https://github.com/microsoft/BotFramework-WebChat/pull/5312) - Added `styleOptions.bubbleAttachmentMaxWidth`/`bubbleAttachmentMinWidth` and `styleOptions.bubbleMessageMaxWidth`/`bubbleMessageMinWidth`, in PR [#5321](https://github.com/microsoft/BotFramework-WebChat/pull/5321), by [@compulim](https://github.com/compulim) - (Experimental) Added more CSS variables support, in PR [#5321](https://github.com/microsoft/BotFramework-WebChat/pull/5321), by [@compulim](https://github.com/compulim) +- Added MathML/TeX block support in Markdown via [`micromark-extension-math`](https://npmjs.com/package/micromark-extension-math) and [`katex`](https://katex.org/), in PR [#5332](https://github.com/microsoft/BotFramework-WebChat/pull/5332), by [@compulim](https://github.com/compulim) ### Changed diff --git a/__tests__/html2/markdown/math.html b/__tests__/html2/markdown/math.html new file mode 100644 index 0000000000..c85b0ae2e6 --- /dev/null +++ b/__tests__/html2/markdown/math.html @@ -0,0 +1,68 @@ + + + + + + + + + + +
+ + + diff --git a/__tests__/html2/markdown/math.html.snap-1.png b/__tests__/html2/markdown/math.html.snap-1.png new file mode 100644 index 0000000000..73fad06762 Binary files /dev/null and b/__tests__/html2/markdown/math.html.snap-1.png differ diff --git a/__tests__/html2/markdown/math2.html b/__tests__/html2/markdown/math2.html new file mode 100644 index 0000000000..b734ef0a1b --- /dev/null +++ b/__tests__/html2/markdown/math2.html @@ -0,0 +1,46 @@ + + + + + + + + + + +
+ + + diff --git a/__tests__/html2/markdown/math2.html.snap-1.png b/__tests__/html2/markdown/math2.html.snap-1.png new file mode 100644 index 0000000000..9df09e9e51 Binary files /dev/null and b/__tests__/html2/markdown/math2.html.snap-1.png differ diff --git a/jest.legacy.config.js b/jest.legacy.config.js index 4ba4f4eb99..55f203263e 100644 --- a/jest.legacy.config.js +++ b/jest.legacy.config.js @@ -17,6 +17,7 @@ const TRANSFORM_IGNORE_PACKAGES = [ 'micromark-extension-gfm-table', 'micromark-extension-gfm-tagfilter', 'micromark-extension-gfm-task-list-item', + 'micromark-extension-math', 'micromark-factory-destination', 'micromark-factory-label', 'micromark-factory-space', diff --git a/package-lock.json b/package-lock.json index ca51aa7e1a..a5dd9bac21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4772,6 +4772,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/katex": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz", + "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==", + "license": "MIT" + }, "node_modules/@types/math-random": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/math-random/-/math-random-1.0.2.tgz", @@ -15058,6 +15064,31 @@ "node": ">=18" } }, + "node_modules/katex": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.11.tgz", + "integrity": "sha512-RQrI8rlHY92OLf3rho/Ts8i/XvjgguEjOkO1BEXcU3N8BqPpSzBNwV/G0Ukr+P/l3ivvJUE/Fa/CwbS6HesGNQ==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/keyborg": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/keyborg/-/keyborg-2.6.0.tgz", @@ -16296,6 +16327,25 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/micromark-extension-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "license": "MIT", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/micromark-factory-destination": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz", @@ -23837,6 +23887,7 @@ "memoize-one": "6.0.0", "micromark": "^4.0.0", "micromark-extension-gfm": "^3.0.0", + "micromark-extension-math": "^3.1.0", "microsoft-cognitiveservices-speech-sdk": "1.17.0", "prop-types": "15.8.1", "punycode": "2.3.1", diff --git a/packages/bundle/package.json b/packages/bundle/package.json index f26d6476f0..2322b9b220 100644 --- a/packages/bundle/package.json +++ b/packages/bundle/package.json @@ -134,6 +134,7 @@ "memoize-one": "6.0.0", "micromark": "^4.0.0", "micromark-extension-gfm": "^3.0.0", + "micromark-extension-math": "^3.1.0", "microsoft-cognitiveservices-speech-sdk": "1.17.0", "prop-types": "15.8.1", "punycode": "2.3.1", diff --git a/packages/bundle/src/markdown/renderMarkdown.ts b/packages/bundle/src/markdown/renderMarkdown.ts index ab160c48b0..ee687da01c 100644 --- a/packages/bundle/src/markdown/renderMarkdown.ts +++ b/packages/bundle/src/markdown/renderMarkdown.ts @@ -7,9 +7,10 @@ import { } from 'botframework-webchat-component/internal'; import { micromark } from 'micromark'; import { gfm, gfmHtml } from 'micromark-extension-gfm'; -import { pre as respectCRLFPre } from './private/respectCRLF'; +import { math, mathHtml } from 'micromark-extension-math'; import betterLinkDocumentMod, { BetterLinkDocumentModDecoration } from './private/betterLinkDocumentMod'; import iterateLinkDefinitions from './private/iterateLinkDefinitions'; +import { pre as respectCRLFPre } from './private/respectCRLF'; const SANITIZE_HTML_OPTIONS = Object.freeze({ allowedAttributes: { @@ -56,7 +57,38 @@ const SANITIZE_HTML_OPTIONS = Object.freeze({ 'th', 'thead', 'tr', - 'ul' + 'ul', + + // Followings are for MathML elements, from https://developer.mozilla.org/en-US/docs/Web/MathML. + 'annotation-xml', + 'annotation', + 'math', + 'merror', + 'mfrac', + 'mi', + 'mmultiscripts', + 'mn', + 'mo', + 'mover', + 'mpadded', + 'mphantom', + 'mprescripts', + 'mroot', + 'mrow', + 'ms', + 'mspace', + 'msqrt', + 'mstyle', + 'msub', + 'msubsup', + 'msup', + 'mtable', + 'mtd', + 'mtext', + 'mtr', + 'munder', + 'munderover', + 'semantics' ], // Bug of https://github.com/apostrophecms/sanitize-html/issues/633. // They should not remove `alt=""` even though it is empty. @@ -145,8 +177,12 @@ export default function render( // We need to handle links like cite:1 or other URL handlers. // And we will remove dangerous protocol during sanitization. allowDangerousProtocol: true, - extensions: [gfm()], - htmlExtensions: [gfmHtml()] + extensions: [ + gfm(), + // Disabling single dollar inline math block to prevent easy collision. + math({ singleDollarTextMath: false }) + ], + htmlExtensions: [gfmHtml(), mathHtml({ output: 'mathml' })] }); // TODO: [P1] In some future, we should apply "better link" and "sanitization" outside of the Markdown engine. diff --git a/packages/component/src/Styles/StyleSet/RenderMarkdown.ts b/packages/component/src/Styles/StyleSet/RenderMarkdown.ts index cf7d68b723..9b8f2b9cf0 100644 --- a/packages/component/src/Styles/StyleSet/RenderMarkdown.ts +++ b/packages/component/src/Styles/StyleSet/RenderMarkdown.ts @@ -65,6 +65,12 @@ export default function createMarkdownStyle() { '& .webchat__render-markdown__pure-identifier::before': { content: "'['" + }, + + '& math': { + alignItems: 'center', + display: 'flex', + flexDirection: 'column' } } };