feat: render Markdown formulas with KaTeX
This commit is contained in:
Generated
+311
@@ -19,6 +19,7 @@
|
||||
"html-to-text": "^10.0.0",
|
||||
"i18next": "^25.10.10",
|
||||
"json5": "^2.2.3",
|
||||
"katex": "^0.16.47",
|
||||
"lucide-react": "^1.27.0",
|
||||
"onnxruntime-web": "^1.23.2",
|
||||
"pdfjs-dist": "^6.2.108",
|
||||
@@ -29,7 +30,9 @@
|
||||
"react-dom": "^19.2.8",
|
||||
"react-i18next": "^16.6.6",
|
||||
"react-markdown": "^10.1.0",
|
||||
"rehype-katex": "^7.0.1",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-math": "^6.0.0",
|
||||
"sherpa-onnx": "1.13.4",
|
||||
"undici": "^7.29.0",
|
||||
"yaml": "^2.9.0",
|
||||
@@ -3547,6 +3550,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/katex": {
|
||||
"version": "0.16.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.8.tgz",
|
||||
"integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/keyv": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz",
|
||||
@@ -7308,6 +7317,125 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-from-dom": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.1.tgz",
|
||||
"integrity": "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0",
|
||||
"hastscript": "^9.0.0",
|
||||
"web-namespaces": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-from-html": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz",
|
||||
"integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0",
|
||||
"devlop": "^1.1.0",
|
||||
"hast-util-from-parse5": "^8.0.0",
|
||||
"parse5": "^7.0.0",
|
||||
"vfile": "^6.0.0",
|
||||
"vfile-message": "^4.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-from-html-isomorphic": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz",
|
||||
"integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0",
|
||||
"hast-util-from-dom": "^5.0.0",
|
||||
"hast-util-from-html": "^2.0.0",
|
||||
"unist-util-remove-position": "^5.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-from-html/node_modules/entities": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
|
||||
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-from-html/node_modules/parse5": {
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
|
||||
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"entities": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-from-parse5": {
|
||||
"version": "8.0.3",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz",
|
||||
"integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0",
|
||||
"@types/unist": "^3.0.0",
|
||||
"devlop": "^1.0.0",
|
||||
"hastscript": "^9.0.0",
|
||||
"property-information": "^7.0.0",
|
||||
"vfile": "^6.0.0",
|
||||
"vfile-location": "^5.0.0",
|
||||
"web-namespaces": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-is-element": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz",
|
||||
"integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-parse-selector": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz",
|
||||
"integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-to-jsx-runtime": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
|
||||
@@ -7335,6 +7463,22 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-to-text": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz",
|
||||
"integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0",
|
||||
"@types/unist": "^3.0.0",
|
||||
"hast-util-is-element": "^3.0.0",
|
||||
"unist-util-find-after": "^5.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-whitespace": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
|
||||
@@ -7348,6 +7492,23 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hastscript": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz",
|
||||
"integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0",
|
||||
"comma-separated-tokens": "^2.0.0",
|
||||
"hast-util-parse-selector": "^4.0.0",
|
||||
"property-information": "^7.0.0",
|
||||
"space-separated-tokens": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hermes-estree": {
|
||||
"version": "0.25.1",
|
||||
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
|
||||
@@ -8038,6 +8199,31 @@
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/katex": {
|
||||
"version": "0.16.47",
|
||||
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz",
|
||||
"integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==",
|
||||
"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/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
@@ -8371,6 +8557,25 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-math": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz",
|
||||
"integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0",
|
||||
"@types/mdast": "^4.0.0",
|
||||
"devlop": "^1.0.0",
|
||||
"longest-streak": "^3.0.0",
|
||||
"mdast-util-from-markdown": "^2.0.0",
|
||||
"mdast-util-to-markdown": "^2.1.0",
|
||||
"unist-util-remove-position": "^5.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-mdx-expression": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz",
|
||||
@@ -8722,6 +8927,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.1",
|
||||
"resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz",
|
||||
@@ -10763,6 +10987,25 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/rehype-katex": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-7.0.1.tgz",
|
||||
"integrity": "sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0",
|
||||
"@types/katex": "^0.16.0",
|
||||
"hast-util-from-html-isomorphic": "^2.0.0",
|
||||
"hast-util-to-text": "^4.0.0",
|
||||
"katex": "^0.16.0",
|
||||
"unist-util-visit-parents": "^6.0.0",
|
||||
"vfile": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/remark-gfm": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
|
||||
@@ -10781,6 +11024,22 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/remark-math": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz",
|
||||
"integrity": "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/mdast": "^4.0.0",
|
||||
"mdast-util-math": "^3.0.0",
|
||||
"micromark-extension-math": "^3.0.0",
|
||||
"unified": "^11.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/remark-parse": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
|
||||
@@ -11951,6 +12210,20 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/unist-util-find-after": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz",
|
||||
"integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/unist": "^3.0.0",
|
||||
"unist-util-is": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/unist-util-is": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz",
|
||||
@@ -11977,6 +12250,20 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/unist-util-remove-position": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz",
|
||||
"integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/unist": "^3.0.0",
|
||||
"unist-util-visit": "^5.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/unist-util-stringify-position": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
|
||||
@@ -12163,6 +12450,20 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/vfile-location": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz",
|
||||
"integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/unist": "^3.0.0",
|
||||
"vfile": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/vfile-message": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz",
|
||||
@@ -12848,6 +13149,16 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/web-namespaces": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
|
||||
"integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/webcrypto-core": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.9.2.tgz",
|
||||
|
||||
@@ -116,6 +116,10 @@
|
||||
{
|
||||
"from": "node_modules/onnxruntime-web/LICENSE",
|
||||
"to": "licenses/onnxruntime-web-MIT.txt"
|
||||
},
|
||||
{
|
||||
"from": "node_modules/katex/LICENSE",
|
||||
"to": "licenses/katex-MIT.txt"
|
||||
}
|
||||
],
|
||||
"win": {
|
||||
@@ -165,6 +169,7 @@
|
||||
"html-to-text": "^10.0.0",
|
||||
"i18next": "^25.10.10",
|
||||
"json5": "^2.2.3",
|
||||
"katex": "^0.16.47",
|
||||
"lucide-react": "^1.27.0",
|
||||
"onnxruntime-web": "^1.23.2",
|
||||
"pdfjs-dist": "^6.2.108",
|
||||
@@ -175,7 +180,9 @@
|
||||
"react-dom": "^19.2.8",
|
||||
"react-i18next": "^16.6.6",
|
||||
"react-markdown": "^10.1.0",
|
||||
"rehype-katex": "^7.0.1",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-math": "^6.0.0",
|
||||
"sherpa-onnx": "1.13.4",
|
||||
"undici": "^7.29.0",
|
||||
"yaml": "^2.9.0",
|
||||
|
||||
@@ -73,6 +73,98 @@ const ready = true
|
||||
expect(container.querySelector('pre')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders inline and display LaTeX formulas with KaTeX', () => {
|
||||
const { container } = render(
|
||||
<MarkdownRenderer>{`质能方程为 $E = mc^2$。
|
||||
|
||||
$$
|
||||
\\int_0^1 x^2\\,dx = \\frac{1}{3}
|
||||
$$`}</MarkdownRenderer>
|
||||
)
|
||||
|
||||
expect(container.querySelectorAll('.katex')).toHaveLength(2)
|
||||
expect(container.querySelector('.katex-display')).toBeInTheDocument()
|
||||
expect(
|
||||
container.querySelector(
|
||||
'annotation[encoding="application/x-tex"]'
|
||||
)
|
||||
).toHaveTextContent('E = mc^2')
|
||||
expect(
|
||||
[...container.querySelectorAll(
|
||||
'annotation[encoding="application/x-tex"]'
|
||||
)].at(-1)
|
||||
).toHaveTextContent('\\int_0^1 x^2\\,dx = \\frac{1}{3}')
|
||||
})
|
||||
|
||||
it('supports common model-style LaTeX delimiters outside code', () => {
|
||||
const { container } = render(
|
||||
<MarkdownRenderer>{`行内公式 \\(a^2 + b^2 = c^2\\)。
|
||||
|
||||
\\[
|
||||
\\sum_{i=1}^{n} i = \\frac{n(n+1)}{2}
|
||||
\\]
|
||||
|
||||
\\[ \\sqrt{x^2} = |x| \\]
|
||||
|
||||
\`\\(not rendered\\)\`
|
||||
|
||||
\`\`\`tex
|
||||
\\[
|
||||
also not rendered
|
||||
\\]
|
||||
\`\`\``}</MarkdownRenderer>
|
||||
)
|
||||
|
||||
expect(container.querySelectorAll('.katex')).toHaveLength(3)
|
||||
expect(container.querySelectorAll('.katex-display')).toHaveLength(2)
|
||||
expect(screen.getByText('\\(not rendered\\)')).toBeInTheDocument()
|
||||
expect(screen.getByText(/also not rendered/u)).toBeInTheDocument()
|
||||
expect(container.querySelectorAll('code .katex')).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('promotes formulas that occupy a whole line to centered display math', () => {
|
||||
const { container } = render(
|
||||
<MarkdownRenderer>{`段落内仍是 $E = mc^2$ 行内公式。
|
||||
|
||||
$S(A) = \\frac{\\operatorname{Area}(\\gamma_A)}{4G_N}$
|
||||
|
||||
\\(S \\leq \\frac{A}{4G_N}\\)`}</MarkdownRenderer>
|
||||
)
|
||||
|
||||
expect(container.querySelectorAll('.katex')).toHaveLength(3)
|
||||
expect(container.querySelectorAll('.katex-display')).toHaveLength(2)
|
||||
expect(
|
||||
container.querySelector(
|
||||
'p:first-child .katex-display'
|
||||
)
|
||||
).not.toBeInTheDocument()
|
||||
for (const display of container.querySelectorAll('.katex-display')) {
|
||||
expect(display.querySelector(':scope > .katex')).toBeInTheDocument()
|
||||
}
|
||||
})
|
||||
|
||||
it('shows invalid formulas without crashing or rendering raw HTML', () => {
|
||||
const { container } = render(
|
||||
<MarkdownRenderer>{`$\\notARealCommand{value}$
|
||||
|
||||
$\\href{javascript:alert(1)}{unsafe}$`}</MarkdownRenderer>
|
||||
)
|
||||
|
||||
expect(
|
||||
container.querySelector(
|
||||
'annotation[encoding="application/x-tex"]'
|
||||
)
|
||||
).toHaveTextContent('\\notARealCommand{value}')
|
||||
expect(container.querySelectorAll('.katex')).toHaveLength(2)
|
||||
expect(container).toHaveTextContent(
|
||||
'\\notARealCommand'
|
||||
)
|
||||
expect(container.querySelector('script')).not.toBeInTheDocument()
|
||||
expect(
|
||||
container.querySelector('a[href^="javascript:"]')
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('updates table accessibility copy when the locale changes', async () => {
|
||||
render(
|
||||
<MarkdownRenderer>{`| Name |
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { memo, useMemo } from 'react'
|
||||
import rehypeKatex from 'rehype-katex'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import remarkMath from 'remark-math'
|
||||
import type { Components } from 'react-markdown'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
@@ -43,6 +45,100 @@ type MarkdownRendererProps = {
|
||||
const wholeMarkdownFence =
|
||||
/^```(?:markdown|md)\s*\r?\n([\s\S]*?)\r?\n```$/iu
|
||||
|
||||
function replaceLatexDelimiters(line: string): string {
|
||||
let output = ''
|
||||
let index = 0
|
||||
let codeDelimiterLength = 0
|
||||
|
||||
while (index < line.length) {
|
||||
if (line[index] === '`') {
|
||||
let runLength = 1
|
||||
while (line[index + runLength] === '`') {
|
||||
runLength += 1
|
||||
}
|
||||
if (
|
||||
codeDelimiterLength === 0 ||
|
||||
codeDelimiterLength === runLength
|
||||
) {
|
||||
codeDelimiterLength =
|
||||
codeDelimiterLength === 0 ? runLength : 0
|
||||
}
|
||||
output += line.slice(index, index + runLength)
|
||||
index += runLength
|
||||
continue
|
||||
}
|
||||
if (
|
||||
codeDelimiterLength === 0 &&
|
||||
line[index] === '\\' &&
|
||||
line[index - 1] !== '\\' &&
|
||||
(line[index + 1] === '(' || line[index + 1] === ')')
|
||||
) {
|
||||
output += '$'
|
||||
index += 2
|
||||
continue
|
||||
}
|
||||
output += line[index]
|
||||
index += 1
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
function normalizeLatexDelimiters(content: string): string {
|
||||
let fence:
|
||||
| {
|
||||
character: '`' | '~'
|
||||
length: number
|
||||
}
|
||||
| undefined
|
||||
|
||||
return content
|
||||
.split(/\r?\n/u)
|
||||
.map((line) => {
|
||||
if (fence) {
|
||||
const closingFence = new RegExp(
|
||||
`^ {0,3}${fence.character}{${fence.length},}\\s*$`,
|
||||
'u'
|
||||
)
|
||||
if (closingFence.test(line)) {
|
||||
fence = undefined
|
||||
}
|
||||
return line
|
||||
}
|
||||
const openingFence = /^ {0,3}(`{3,}|~{3,})/u.exec(line)
|
||||
if (openingFence) {
|
||||
const marker = openingFence[1]!
|
||||
fence = {
|
||||
character: marker[0] as '`' | '~',
|
||||
length: marker.length
|
||||
}
|
||||
return line
|
||||
}
|
||||
const standaloneDollar =
|
||||
/^ {0,3}\$(?!\$)(.+?)(?<!\\)\$\s*$/u.exec(line)
|
||||
if (standaloneDollar) {
|
||||
return `$$\n${standaloneDollar[1]}\n$$`
|
||||
}
|
||||
const standaloneParentheses =
|
||||
/^ {0,3}\\\(\s*(.*?)\s*\\\)\s*$/u.exec(line)
|
||||
if (standaloneParentheses) {
|
||||
return `$$\n${standaloneParentheses[1]}\n$$`
|
||||
}
|
||||
const singleLineDisplay =
|
||||
/^ {0,3}\\\[\s*(.*?)\s*\\\]\s*$/u.exec(line)
|
||||
if (singleLineDisplay) {
|
||||
return `$$\n${singleLineDisplay[1]}\n$$`
|
||||
}
|
||||
if (/^ {0,3}\\\[\s*$/u.test(line)) {
|
||||
return '$$'
|
||||
}
|
||||
if (/^ {0,3}\\\]\s*$/u.test(line)) {
|
||||
return '$$'
|
||||
}
|
||||
return replaceLatexDelimiters(line)
|
||||
})
|
||||
.join('\n')
|
||||
}
|
||||
|
||||
function unwrapMarkdownFence(content: string): string {
|
||||
const fencedMarkdown = wholeMarkdownFence.exec(content.trim())
|
||||
return fencedMarkdown?.[1] ?? content
|
||||
@@ -60,10 +156,23 @@ export const MarkdownRenderer = memo(function MarkdownRenderer({
|
||||
return (
|
||||
<ReactMarkdown
|
||||
components={components}
|
||||
remarkPlugins={[remarkGfm]}
|
||||
rehypePlugins={[
|
||||
[
|
||||
rehypeKatex,
|
||||
{
|
||||
output: 'htmlAndMathml',
|
||||
strict: 'warn',
|
||||
trust: false
|
||||
}
|
||||
]
|
||||
]}
|
||||
remarkPlugins={[
|
||||
remarkGfm,
|
||||
[remarkMath, { singleDollarTextMath: true }]
|
||||
]}
|
||||
skipHtml
|
||||
>
|
||||
{unwrapMarkdownFence(children)}
|
||||
{normalizeLatexDelimiters(unwrapMarkdownFence(children))}
|
||||
</ReactMarkdown>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import '@fontsource-variable/noto-sans-sc/wght.css'
|
||||
import 'katex/dist/katex.min.css'
|
||||
import './markdown-math.css'
|
||||
import App from './App'
|
||||
import { installBundledUiFonts } from './fonts'
|
||||
import { changeUiLocale } from './i18n'
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
.markdown-content .katex {
|
||||
color: inherit;
|
||||
font-size: 1.05em;
|
||||
}
|
||||
|
||||
.markdown-content .katex-display {
|
||||
max-width: 100%;
|
||||
margin: 0.85em 0;
|
||||
padding: var(--space-1) 0;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.markdown-content .katex-display > .katex {
|
||||
width: max-content;
|
||||
min-width: max-content;
|
||||
margin-inline: auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.markdown-content .katex-error {
|
||||
color: var(--danger);
|
||||
font-family: var(--font-family-mono);
|
||||
font-size: 0.9em;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
Reference in New Issue
Block a user