add latex input

This commit is contained in:
karimaldeen 2024-09-19 10:51:37 +03:00
parent 42bbdebf6f
commit c49f498309
26 changed files with 4081 additions and 5689 deletions

View File

@ -10,6 +10,7 @@
"Groupbutton",
"handelnavigate",
"Karim",
"katex",
"Popconfirm",
"queryqlent",
"registraion",

View File

@ -8,11 +8,16 @@
<link rel="icon" type="image/png" sizes="32x32" href="/App/Logo2.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/App/Logo2.png" />
<link rel="manifest" href="/site.webmanifest" />
<link
href="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.9.0/katex.min.css"
rel="stylesheet"
/>
<meta
name="description"
content="social networking platform with automated content moderation and context-based authentication system"
/>
<script type="module" src="/src/index.tsx"></script>
<title>NERD DASHBOARD</title>

347
package-lock.json generated
View File

@ -13,18 +13,26 @@
"@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@types/katex": "^0.16.7",
"antd": "^5.17.4",
"axios": "^1.7.2",
"better-react-mathjax": "^2.0.3",
"bootstrap": "^5.3.3",
"dayjs": "^1.11.11",
"formik": "^2.4.6",
"i18next": "^23.11.5",
"katex": "^0.16.11",
"lottie-react": "^2.4.0",
"mathjax-full": "^3.2.2",
"mathml-to-latex": "^1.4.1",
"react": "^18.3.1",
"react-beautiful-dnd": "^13.1.1",
"react-dom": "^18.3.1",
"react-i18next": "^13.5.0",
"react-icons": "^4.12.0",
"react-katex": "^3.0.1",
"react-latex": "^2.0.0",
"react-latex-next": "^3.0.0",
"react-query": "^3.39.3",
"react-router-dom": "^6.23.1",
"react-toastify": "^9.1.3",
@ -42,6 +50,8 @@
"@types/react-beautiful-dnd": "^13.1.8",
"@types/react-dom": "^18.3.0",
"@types/react-helmet": "^6.1.11",
"@types/react-katex": "^3.0.4",
"@types/react-latex": "^2.0.3",
"@vitejs/plugin-legacy": "^5.4.1",
"@vitejs/plugin-react": "^4.3.0",
"jest": "^29.7.0",
@ -3566,6 +3576,11 @@
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true
},
"node_modules/@types/katex": {
"version": "0.16.7",
"resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz",
"integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ=="
},
"node_modules/@types/node": {
"version": "20.14.15",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.15.tgz",
@ -3616,6 +3631,24 @@
"@types/react": "*"
}
},
"node_modules/@types/react-katex": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/react-katex/-/react-katex-3.0.4.tgz",
"integrity": "sha512-aLkykKzSKLpXI6REJ3uClao6P47HAFfR1gcHOZwDeTuALsyjgMhz+oynLV4gX0kiJVnvHrBKF/TLXqyNTpHDUg==",
"dev": true,
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/react-latex": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/react-latex/-/react-latex-2.0.3.tgz",
"integrity": "sha512-PdH5UI5AT2aj+sBJkQbmXc/0S7pR/7i/u5oFhDrFHTeI2TedMk+RwQsVNcHMXleT1L7RqyNjm0FE4AtUHsv/DA==",
"dev": true,
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/react-redux": {
"version": "7.1.33",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.33.tgz",
@ -3892,6 +3925,14 @@
}
}
},
"node_modules/@xmldom/xmldom": {
"version": "0.8.10",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
"integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/@xtuc/ieee754": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
@ -4325,6 +4366,17 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"node_modules/better-react-mathjax": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/better-react-mathjax/-/better-react-mathjax-2.0.3.tgz",
"integrity": "sha512-wfifT8GFOKb1TWm2+E50I6DJpLZ5kLbch283Lu043EJtwSv0XvZDjr4YfR4d2MjAhqP6SH4VjjrKgbX8R00oCQ==",
"dependencies": {
"mathjax-full": "^3.2.2"
},
"peerDependencies": {
"react": ">=16.8"
}
},
"node_modules/big-integer": {
"version": "1.6.52",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
@ -4701,6 +4753,14 @@
"node": ">= 0.8"
}
},
"node_modules/commander": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
"engines": {
"node": ">= 12"
}
},
"node_modules/compute-scroll-into-view": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.0.tgz",
@ -5208,6 +5268,14 @@
"node": ">=8.0.0"
}
},
"node_modules/esm": {
"version": "3.2.25",
"resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
"integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==",
"engines": {
"node": ">=6"
}
},
"node_modules/esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
@ -7372,6 +7440,21 @@
"node": ">= 10.0.0"
}
},
"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"
],
"dependencies": {
"commander": "^8.3.0"
},
"bin": {
"katex": "cli.js"
}
},
"node_modules/kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@ -7554,6 +7637,25 @@
"remove-accents": "0.5.0"
}
},
"node_modules/mathjax-full": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/mathjax-full/-/mathjax-full-3.2.2.tgz",
"integrity": "sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==",
"dependencies": {
"esm": "^3.2.25",
"mhchemparser": "^4.1.0",
"mj-context-menu": "^0.6.1",
"speech-rule-engine": "^4.0.6"
}
},
"node_modules/mathml-to-latex": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/mathml-to-latex/-/mathml-to-latex-1.4.1.tgz",
"integrity": "sha512-3B+q88sVnQCqWG0UN5scYZRsUE0O2GFNfqCA0AMY/+iNkrwm3n4eiqFNKpJQ0lguHhbLfuKKoJuPixDKuqiLCQ==",
"dependencies": {
"@xmldom/xmldom": "^0.8.10"
}
},
"node_modules/memoize-one": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
@ -7577,6 +7679,11 @@
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
"dev": true
},
"node_modules/mhchemparser": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/mhchemparser/-/mhchemparser-4.2.1.tgz",
"integrity": "sha512-kYmyrCirqJf3zZ9t/0wGgRZ4/ZJw//VwaRVGA75C4nhE60vtnIzhl9J9ndkX/h6hxSN7pjg/cE0VxbnNM+bnDQ=="
},
"node_modules/micromatch": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
@ -7643,6 +7750,11 @@
"node": "*"
}
},
"node_modules/mj-context-menu": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz",
"integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA=="
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@ -8809,6 +8921,58 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/react-katex": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/react-katex/-/react-katex-3.0.1.tgz",
"integrity": "sha512-wIUW1fU5dHlkKvq4POfDkHruQsYp3fM8xNb/jnc8dnQ+nNCnaj0sx5pw7E6UyuEdLRyFKK0HZjmXBo+AtXXy0A==",
"dependencies": {
"katex": "^0.16.0"
},
"peerDependencies": {
"prop-types": "^15.8.1",
"react": ">=15.3.2 <=18"
}
},
"node_modules/react-latex": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/react-latex/-/react-latex-2.0.0.tgz",
"integrity": "sha512-x17uDCfqBgR+5ZF/zplRCuHdEbX22CJlVipOqMUinRMoiOwh5fr3jbjD4zqVQ8pIs4AnF0BWPDR2S7Fyd8Snxw==",
"dependencies": {
"katex": "^0.10.2"
}
},
"node_modules/react-latex-next": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/react-latex-next/-/react-latex-next-3.0.0.tgz",
"integrity": "sha512-x70f1b1G7TronVigsRgKHKYYVUNfZk/3bciFyYX1lYLQH2y3/TXku3+5Vap8MDbJhtopePSYBsYWS6jhzIdz+g==",
"dependencies": {
"katex": "^0.16.0"
},
"engines": {
"node": ">=12",
"npm": ">=5"
},
"peerDependencies": {
"react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-latex/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
},
"node_modules/react-latex/node_modules/katex": {
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.10.2.tgz",
"integrity": "sha512-cQOmyIRoMloCoSIOZ1+gEwsksdJZ1EW4SWm3QzxSza/QsnZr6D4U1V9S4q+B/OLm2OQ8TCBecQ8MaIfnScI7cw==",
"dependencies": {
"commander": "^2.19.0"
},
"bin": {
"katex": "cli.js"
}
},
"node_modules/react-popper": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz",
@ -9483,6 +9647,27 @@
"source-map": "^0.6.0"
}
},
"node_modules/speech-rule-engine": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-4.0.7.tgz",
"integrity": "sha512-sJrL3/wHzNwJRLBdf6CjJWIlxC04iYKkyXvYSVsWVOiC2DSkHmxsqOhEeMsBA9XK+CHuNcsdkbFDnoUfAsmp9g==",
"dependencies": {
"commander": "9.2.0",
"wicked-good-xpath": "1.3.0",
"xmldom-sre": "0.1.31"
},
"bin": {
"sre": "bin/sre"
}
},
"node_modules/speech-rule-engine/node_modules/commander": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz",
"integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==",
"engines": {
"node": "^12.20.0 || >=14"
}
},
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@ -10539,6 +10724,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/wicked-good-xpath": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz",
"integrity": "sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw=="
},
"node_modules/wildcard": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz",
@ -10616,6 +10806,14 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"dev": true
},
"node_modules/xmldom-sre": {
"version": "0.1.31",
"resolved": "https://registry.npmjs.org/xmldom-sre/-/xmldom-sre-0.1.31.tgz",
"integrity": "sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw==",
"engines": {
"node": ">=0.1"
}
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
@ -13154,6 +13352,11 @@
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true
},
"@types/katex": {
"version": "0.16.7",
"resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz",
"integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ=="
},
"@types/node": {
"version": "20.14.15",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.15.tgz",
@ -13204,6 +13407,24 @@
"@types/react": "*"
}
},
"@types/react-katex": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/react-katex/-/react-katex-3.0.4.tgz",
"integrity": "sha512-aLkykKzSKLpXI6REJ3uClao6P47HAFfR1gcHOZwDeTuALsyjgMhz+oynLV4gX0kiJVnvHrBKF/TLXqyNTpHDUg==",
"dev": true,
"requires": {
"@types/react": "*"
}
},
"@types/react-latex": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/react-latex/-/react-latex-2.0.3.tgz",
"integrity": "sha512-PdH5UI5AT2aj+sBJkQbmXc/0S7pR/7i/u5oFhDrFHTeI2TedMk+RwQsVNcHMXleT1L7RqyNjm0FE4AtUHsv/DA==",
"dev": true,
"requires": {
"@types/react": "*"
}
},
"@types/react-redux": {
"version": "7.1.33",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.33.tgz",
@ -13441,6 +13662,11 @@
"dev": true,
"requires": {}
},
"@xmldom/xmldom": {
"version": "0.8.10",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
"integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw=="
},
"@xtuc/ieee754": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
@ -13785,6 +14011,14 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"better-react-mathjax": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/better-react-mathjax/-/better-react-mathjax-2.0.3.tgz",
"integrity": "sha512-wfifT8GFOKb1TWm2+E50I6DJpLZ5kLbch283Lu043EJtwSv0XvZDjr4YfR4d2MjAhqP6SH4VjjrKgbX8R00oCQ==",
"requires": {
"mathjax-full": "^3.2.2"
}
},
"big-integer": {
"version": "1.6.52",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
@ -14031,6 +14265,11 @@
"delayed-stream": "~1.0.0"
}
},
"commander": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="
},
"compute-scroll-into-view": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.0.tgz",
@ -14418,6 +14657,11 @@
"estraverse": "^4.1.1"
}
},
"esm": {
"version": "3.2.25",
"resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
"integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA=="
},
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
@ -15984,6 +16228,14 @@
}
}
},
"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==",
"requires": {
"commander": "^8.3.0"
}
},
"kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@ -16131,6 +16383,25 @@
"remove-accents": "0.5.0"
}
},
"mathjax-full": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/mathjax-full/-/mathjax-full-3.2.2.tgz",
"integrity": "sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==",
"requires": {
"esm": "^3.2.25",
"mhchemparser": "^4.1.0",
"mj-context-menu": "^0.6.1",
"speech-rule-engine": "^4.0.6"
}
},
"mathml-to-latex": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/mathml-to-latex/-/mathml-to-latex-1.4.1.tgz",
"integrity": "sha512-3B+q88sVnQCqWG0UN5scYZRsUE0O2GFNfqCA0AMY/+iNkrwm3n4eiqFNKpJQ0lguHhbLfuKKoJuPixDKuqiLCQ==",
"requires": {
"@xmldom/xmldom": "^0.8.10"
}
},
"memoize-one": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
@ -16148,6 +16419,11 @@
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
"dev": true
},
"mhchemparser": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/mhchemparser/-/mhchemparser-4.2.1.tgz",
"integrity": "sha512-kYmyrCirqJf3zZ9t/0wGgRZ4/ZJw//VwaRVGA75C4nhE60vtnIzhl9J9ndkX/h6hxSN7pjg/cE0VxbnNM+bnDQ=="
},
"micromatch": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
@ -16196,6 +16472,11 @@
"brace-expansion": "^1.1.7"
}
},
"mj-context-menu": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz",
"integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA=="
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@ -17001,6 +17282,45 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"react-katex": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/react-katex/-/react-katex-3.0.1.tgz",
"integrity": "sha512-wIUW1fU5dHlkKvq4POfDkHruQsYp3fM8xNb/jnc8dnQ+nNCnaj0sx5pw7E6UyuEdLRyFKK0HZjmXBo+AtXXy0A==",
"requires": {
"katex": "^0.16.0"
}
},
"react-latex": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/react-latex/-/react-latex-2.0.0.tgz",
"integrity": "sha512-x17uDCfqBgR+5ZF/zplRCuHdEbX22CJlVipOqMUinRMoiOwh5fr3jbjD4zqVQ8pIs4AnF0BWPDR2S7Fyd8Snxw==",
"requires": {
"katex": "^0.10.2"
},
"dependencies": {
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
},
"katex": {
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.10.2.tgz",
"integrity": "sha512-cQOmyIRoMloCoSIOZ1+gEwsksdJZ1EW4SWm3QzxSza/QsnZr6D4U1V9S4q+B/OLm2OQ8TCBecQ8MaIfnScI7cw==",
"requires": {
"commander": "^2.19.0"
}
}
}
},
"react-latex-next": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/react-latex-next/-/react-latex-next-3.0.0.tgz",
"integrity": "sha512-x70f1b1G7TronVigsRgKHKYYVUNfZk/3bciFyYX1lYLQH2y3/TXku3+5Vap8MDbJhtopePSYBsYWS6jhzIdz+g==",
"requires": {
"katex": "^0.16.0"
}
},
"react-popper": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz",
@ -17497,6 +17817,23 @@
"source-map": "^0.6.0"
}
},
"speech-rule-engine": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-4.0.7.tgz",
"integrity": "sha512-sJrL3/wHzNwJRLBdf6CjJWIlxC04iYKkyXvYSVsWVOiC2DSkHmxsqOhEeMsBA9XK+CHuNcsdkbFDnoUfAsmp9g==",
"requires": {
"commander": "9.2.0",
"wicked-good-xpath": "1.3.0",
"xmldom-sre": "0.1.31"
},
"dependencies": {
"commander": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz",
"integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w=="
}
}
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@ -18194,6 +18531,11 @@
"has-tostringtag": "^1.0.2"
}
},
"wicked-good-xpath": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz",
"integrity": "sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw=="
},
"wildcard": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz",
@ -18245,6 +18587,11 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"dev": true
},
"xmldom-sre": {
"version": "0.1.31",
"resolved": "https://registry.npmjs.org/xmldom-sre/-/xmldom-sre-0.1.31.tgz",
"integrity": "sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw=="
},
"y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",

View File

@ -8,18 +8,26 @@
"@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@types/katex": "^0.16.7",
"antd": "^5.17.4",
"axios": "^1.7.2",
"better-react-mathjax": "^2.0.3",
"bootstrap": "^5.3.3",
"dayjs": "^1.11.11",
"formik": "^2.4.6",
"i18next": "^23.11.5",
"katex": "^0.16.11",
"lottie-react": "^2.4.0",
"mathjax-full": "^3.2.2",
"mathml-to-latex": "^1.4.1",
"react": "^18.3.1",
"react-beautiful-dnd": "^13.1.1",
"react-dom": "^18.3.1",
"react-i18next": "^13.5.0",
"react-icons": "^4.12.0",
"react-katex": "^3.0.1",
"react-latex": "^2.0.0",
"react-latex-next": "^3.0.0",
"react-query": "^3.39.3",
"react-router-dom": "^6.23.1",
"react-toastify": "^9.1.3",
@ -63,6 +71,8 @@
"@types/react-beautiful-dnd": "^13.1.8",
"@types/react-dom": "^18.3.0",
"@types/react-helmet": "^6.1.11",
"@types/react-katex": "^3.0.4",
"@types/react-latex": "^2.0.3",
"@vitejs/plugin-legacy": "^5.4.1",
"@vitejs/plugin-react": "^4.3.0",
"jest": "^29.7.0",

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,14 @@
import React from 'react';
import 'katex/dist/katex.min.css';
import { BlockMath } from 'react-katex';
import { convertMathMLToLaTeX } from '../../utils/convertMathMLToLaTeX';
const LatexPreview = ({Latex}:{Latex:string}) => {
return(
<BlockMath math={Latex} />
)
};
export default LatexPreview;

View File

@ -0,0 +1,84 @@
import React, { useState } from "react";
import { Select, Spin } from "antd";
import { useTranslation } from "react-i18next";
import { useDebounce } from "../../utils/useDebounce";
import { useGetAllTag } from "../../api/tags";
import { useFormikContext } from "formik";
const SelectTagV2: React.FC = () => {
const [searchValue, setSearchValue] = useState<string>("");
const [fieldValue, setFieldValue] = useState<string>("");
const formik = useFormikContext<any>();
// Fetch tags based on search value
const { data, isLoading } = useGetAllTag({ name: searchValue });
const { t } = useTranslation();
// Get selected tags from Formik
const CurrentTags = formik.values.tags ?? []; // Assuming tags are stored as array of objects
console.log(CurrentTags,"CurrentTags");
const NewShapeTags = CurrentTags?.map((item:any)=> {return item?.name ?? item })
const handleChange = (_value: any[],option:any) => {
console.log(option,"option");
const NewShapeOption = option?.map((item:any)=> {return ({name:item?.name,id:item?.id})})
console.log(NewShapeOption);
formik.setFieldValue("tags", NewShapeOption);
setSearchValue("");
setFieldValue("");
};
const handleSearch = useDebounce((value: string) => {
setSearchValue(value);
});
const handleBlur = () => {
setSearchValue("");
setFieldValue("");
};
const options = data?.data ?? [];
const additionalData =
options.length < 1 && searchValue.length >= 1 && !isLoading
? [{ id: searchValue, name: searchValue }]
: options;
console.log(additionalData);
return (
<div className="SelectTag">
<label>{t("models.tag")}</label>
<Select
mode="multiple"
allowClear
style={{ width: "100%", height: "40px" }}
placeholder=""
fieldNames={{ label: "name", value: "name" }}
onChange={handleChange}
options={additionalData}
filterOption={false}
loading={isLoading}
notFoundContent={isLoading ? <Spin /> : t("practical.not_found")}
onSearch={(value) => {
handleSearch(value);
setFieldValue(value);
}}
searchValue={fieldValue}
onDropdownVisibleChange={(open) => {
if (!open) {
handleBlur();
}
}}
value={NewShapeTags}
/>
</div>
);
};
export default SelectTagV2;

View File

@ -0,0 +1,88 @@
import TextArea from 'antd/es/input/TextArea'
import { useFormikContext } from 'formik';
import React, { useState } from 'react'
import { convertMathMLToLaTeX } from '../../utils/convertMathMLToLaTeX';
import { parseTextAndLatex } from '../../utils/parseTextAndLatex';
import LatexPreview from '../../Components/CustomFields/MathComponent';
import { Checkbox, Modal } from 'antd';
import { CheckboxProps } from 'antd/lib';
import { useTranslation } from 'react-i18next';
import LaTexModal from './LaTexModal';
import { FaPlus } from 'react-icons/fa';
const LaTeXInput = ({name}:{name:string}) => {
const {values,setFieldValue} = useFormikContext<any>()
const [showPreview, setShowPreview] = useState(false) ;
console.log(values?.[name]);
const handleChangeInput =
(
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
setFieldValue(name,e.target.value);
};
const Preview = parseTextAndLatex(values?.[name]) ;
const onPreviewChange: CheckboxProps['onChange'] = (e) => {
const value = e.target.checked
setShowPreview(value)
};
const [t] = useTranslation()
const [isModalOpen, setIsModalOpen] = useState(false);
const [Latex, setLatex] = useState<string>("")
const showModal = () => {
setIsModalOpen(true);
};
return (
<div className='LaTeXInput'>
<div className='LaTeXInputArea'>
<TextArea
size="large"
showCount
maxLength={1000}
autoSize={{ minRows: 6, maxRows: 10 }}
style={{height:"400px"}}
onChange={handleChangeInput}
value={values?.[name]}
/>
<div className='LaTeXInputOptions'>
<Checkbox onChange={onPreviewChange}>{t("header.show_preview")}</Checkbox>
<button className='addMML' onClick={showModal}> <FaPlus/> {t("MML")} </button>
</div>
{showPreview &&
<div className='showPreviewInput'>
{Preview?.map((item:any,index:number)=>{
if(item?.isLatex){
return <div key={index} className='LatexPreview'> <LatexPreview Latex={item?.text} /> </div>
}
return <div key={index}>
{item?.text}
</div>
})}
</div>
}
</div>
<LaTexModal name={name} Latex={Latex} isModalOpen={isModalOpen} setIsModalOpen={setIsModalOpen} setLatex={setLatex} />
</div>
)
}
export default LaTeXInput

View File

@ -0,0 +1,83 @@
import { Modal } from 'antd'
import TextArea from 'antd/es/input/TextArea'
import { useFormikContext } from 'formik';
import React, { useState } from 'react'
import { convertMathMLToLaTeX } from '../../utils/convertMathMLToLaTeX';
import { useTranslation } from 'react-i18next';
import { toast } from 'react-toastify';
const LaTexModal = ({name,setLatex,Latex,setIsModalOpen,isModalOpen}:{
name:string,
setLatex: (value:string)=> void,
Latex:string,
setIsModalOpen: (value:boolean)=> void ,
isModalOpen:boolean,
}) => {
const {values,setFieldValue} = useFormikContext<any>()
const handleOk = () => {
console.log(1);
const oldValue = values?.[name];
const newLatex = convertMathMLToLaTeX(Latex);
console.log(newLatex,'Latex');
console.log(newLatex);
if(newLatex){
setFieldValue(name, oldValue + " $$ " +newLatex +" $$ ");
setLatex("")
setIsModalOpen(false);
}else{
setLatex("")
toast.error(t("validation.that_is_not_a_valid_mml"))
}
};
const handleCancel = () => {
setIsModalOpen(false);
setLatex("")
};
const handleChangeInputLatex = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
const newValue = e.target.value;
setLatex(newValue)
};
const [t] = useTranslation()
return (
<Modal footer={false} open={isModalOpen} onOk={handleOk} onCancel={handleCancel}>
<div className='latexModal'>
<label className='mb-3'> {t("header.past_your_MMl_text")} </label>
<TextArea
size="large"
showCount
maxLength={1000}
autoSize={{ minRows: 4, maxRows: 10 }}
style={{height:"400px"}}
onChange={handleChangeInputLatex}
value={Latex}
/>
<div className="buttons">
<div className="back_button pointer" onClick={handleCancel}>
{t("practical.cancel")}
</div>
<button
className="add_button"
type="submit"
onClick={handleOk}
>
{t(`practical.${ "add"}`)}
</button>
</div>
</div>
</Modal>
)
}
export default LaTexModal

View File

@ -11,6 +11,7 @@ import {
SearchField,
TextField,
DropFile,
TextAreaMML,
Default,
} from "./View";
import { ValidationFieldProps, ValidationFieldType } from "./utils/types";
@ -23,6 +24,7 @@ const components: { [key: string]: React.FC<any> } = {
LocalSearch: LocalSearchField,
DataRange: DataRange,
TextArea: TextField,
TextAreaMML: TextAreaMML,
Date: Date,
Time: Time,
File: File,

View File

@ -38,8 +38,7 @@ const TextField = ({
/>
<ValidationFieldContainer isError={isError} errorMsg={errorMsg}>
<Field
as={TextArea}
<TextArea
placeholder={t(`input.${placeholder ? placeholder : name}`)}
name={name}
disabled={isDisabled}

View File

@ -0,0 +1,61 @@
import { Input } from "antd";
import React from "react";
import useFormField from "../../../Hooks/useFormField";
import { ValidationFieldLabel } from "../components/ValidationFieldLabel";
import { ValidationFieldContainer } from "../components/ValidationFieldContainer";
import LatexPreview from "../../CustomFields/MathComponent";
const { TextArea } = Input;
const TextFieldMML = ({
name,
label,
placeholder,
isDisabled,
onChange,
no_label,
label_icon,
className,
mathContent, // Add mathContent prop
...props
}: any) => {
const { formik, isError, errorMsg, t } = useFormField(name, props);
const TextFilehandleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
formik.setFieldValue(name, e.target.value);
};
return (
<div className={`ValidationField w-100 ${className ?? ""} `}>
<ValidationFieldLabel
name={name}
label={label}
label_icon={label_icon}
no_label={no_label}
placeholder={placeholder}
t={t}
/>
<ValidationFieldContainer isError={isError} errorMsg={errorMsg}>
<TextArea
placeholder={t(`input.${placeholder ? placeholder : name}`)}
name={name}
disabled={isDisabled}
size="large"
showCount
maxLength={1000}
autoSize={{ minRows: 4, maxRows: 10 }}
onChange={onChange || TextFilehandleChange}
id={name}
{...props}
/>
</ValidationFieldContainer>
</div>
);
};
export default React.memo(TextFieldMML);

View File

@ -9,6 +9,7 @@ import MaltyFile from "./MaltyFile";
import SearchField from "./SearchField";
import TextField from "./TextField";
import DropFile from "./DropFile.tsx";
import TextAreaMML from "./TextFieldMML";
export {
Time,
@ -22,4 +23,5 @@ export {
SearchField,
TextField,
DropFile,
TextAreaMML
};

View File

@ -61,6 +61,7 @@ export type FieldProps = BaseFieldProps &
| "password"
| "email"
| "TextArea"
| "TextAreaMML"
| "NumberFormate";
label2?: string;
Group?: boolean;

View File

@ -1,81 +0,0 @@
import React, { useState, useMemo } from "react";
import { Select, Spin } from "antd";
import { useTranslation } from "react-i18next";
import { useFormikContext } from "formik";
import { useDebounce } from "../../../../utils/useDebounce";
import { useGetAllTag } from "../../../../api/tags";
const SelectTag: React.FC = () => {
const [searchValue, setSearchValue] = useState<string>("");
const [fieldValue, setFieldValue] = useState<string>("");
const formik = useFormikContext<any>();
const handleChange = (value: string[]) => {
console.log(value,"value");
formik.setFieldValue("tags", value);
setSearchValue("");
setFieldValue("");
};
const handleSearch = useDebounce((value: string) => {
setSearchValue(value);
});
const handleFieldChange = (value: string) => {
setFieldValue(value);
};
const handleBlur = () => {
setSearchValue("");
setFieldValue("");
};
const { data, isLoading } = useGetAllTag({
name: searchValue,
});
const [t] = useTranslation();
const options = data?.data ?? [];
const additionalData =
options.length < 1 && searchValue.length > 1 && !isLoading
? [{ id: searchValue, name: searchValue }]
: [];
const value =
formik?.values?.tags?.map((item: any) => item?.id ?? item) ?? [];
const AllOptions = [...options, ...additionalData];
return (
<div className="SelectTag">
<label htmlFor="">{t("models.tag")}</label>
<Select
mode="multiple"
allowClear
style={{ width: "100%", height: "40px" }}
placeholder=""
fieldNames={{ label: "name", value: "id" }}
onChange={handleChange}
options={AllOptions}
filterOption={false}
loading={isLoading}
notFoundContent={isLoading ? <Spin /> : t("practical.not_found")}
onSearch={(value) => {
handleSearch(value);
handleFieldChange(value);
}}
searchValue={fieldValue}
onDropdownVisibleChange={(open) => {
if (!open) {
handleBlur();
}
}}
value={value}
/>
</div>
);
};
export default SelectTag;

View File

@ -20,14 +20,13 @@ import ModelForm from "./Model/ModelForm";
import { toast } from "react-toastify";
import { Form, Formik } from "formik";
import { MdOutlineArrowForwardIos } from "react-icons/md";
import { getCharFromNumber } from "../../../utils/getCharFromNumber";
const AddPage: React.FC = () => {
const location = useLocation();
const { mutateAsync,isLoading:LoadingAsync } = useAddQuestionAsync();
const { mutate, isLoading, isSuccess } = useAddQuestion();
const { isBseQuestion, setTagsSearch, setSuccess , ShowHint,setShowHint } =
const { isBseQuestion, setTagsSearch, setSuccess } =
useObjectToEdit();
const [t] = useTranslation();

View File

@ -45,6 +45,7 @@ const EditPage: React.FC = () => {
const { data: Questions, isLoading: QuestionsDataLoading } =
useGetAllQuestion({
parent_id: question_id,
paginate:false
});
const objectToEdit = { ...data?.data, Questions: Questions?.data };

View File

@ -2,7 +2,6 @@ import { Row } from "reactstrap";
import ValidationField from "../../../../Components/ValidationField/ValidationField";
import { useFormikContext } from "formik";
import { FaCirclePlus } from "react-icons/fa6";
import { Choice } from "../../../../types/Item";
import { useTranslation } from "react-i18next";
import { useObjectToEdit } from "../../../../zustand/ObjectToEditState";
import { useEffect } from "react";
@ -56,7 +55,9 @@ const Form = () => {
name="content"
label="answer_content"
type="TextArea"
/>
<ImageBoxField name="content_image" />
</div>

View File

@ -2,6 +2,8 @@ import React from "react";
import { useTranslation } from "react-i18next";
import useFilter from "../../Components/FilterField/components/useFilter";
import { Select } from "antd";
import { Form, Formik } from "formik";
import LaTeXInput from "../../Components/LatextInput/LaTeXInput";
const Dummy = () => {
const [t] = useTranslation();
@ -18,6 +20,13 @@ const Dummy = () => {
showSearch
/>
*/}
<Formik initialValues={{name:""}} onSubmit={()=>{}}>
<Form>
<LaTeXInput name={"name"}/>
</Form>
</Formik>
</div>
);
};

View File

@ -110,3 +110,8 @@ svg{
scale: 1.1;
}
}
.LaTeXRenderer{
direction: ltr;
}

View File

@ -17,3 +17,6 @@
@import "../DataTable/index.scss";
@import "../Pages/index.scss";
@import '../components/index.scss';

View File

@ -0,0 +1,62 @@
.DummyHomePage{
padding: 30px;
}
.LaTeXInputArea{
width: 600px;
position: relative;
}
.showPreviewInput{
background-color: var(--bg);
position: absolute;
top: 0;
right: 0;
width: 100%;
height: calc(100% - 44px);
word-wrap: break-word;
display: flex;
gap: 20px;
align-items: baseline;
align-content: flex-start;
flex-wrap: wrap;
border-radius: 8px;
border: 1px solid #d9d9d9;
padding: 5px 10px;
row-gap: 0px;
}
.addMML{
all: unset;
font-size: 12px;
font-weight: bold;
background: var(--primary);
color: var(--white);
padding: 2px 14px;
display: flex;
gap: 5px;
align-items: center;
}
.latexModal{
padding: 30px;
display: flex;
flex-direction: column;
.buttons{
display: flex;
align-items: center;
justify-content: flex-end;
padding: 20px;
gap: 20px;
margin-top: 20px;
}
}
.LatexPreview{
height: 40px;
display: flex;
align-items: center;
}
.LaTeXInputOptions{
display: flex;
padding: 10px;
}

View File

@ -0,0 +1 @@
@import './LaTeXInput.scss' ;

View File

@ -51,7 +51,8 @@
"it_should_have_more_than_one_correct_answers": "يجب أن يحتوي على إجابة صحيحة",
"File_size_exceeds_2_MB_limit.":"حجم الملف يتجاوز الحد الأقصى البالغ 2 ميجابايت",
"one_of_image_and_content_should_be_enter_in_answer":"يجب إدخال صورة أو محتوى واحد على الأقل في الاجابة",
"one_of_image_and_content_should_be_enter_in_question":"يجب إدخال صورة أو محتوى واحد على الأقل في السؤال"
"one_of_image_and_content_should_be_enter_in_question":"يجب إدخال صورة أو محتوى واحد على الأقل في السؤال",
"that_is_not_a_valid_mml":"هذا ليس mml صالح"
},
"header": {
"register_students": "تسجيل الطلاب",
@ -142,7 +143,10 @@
"managers":"مدراء",
"sales":"المبيعات",
"hide_hint":"اخفاء الشرح",
"show_hint":"عرض الشرح"
"show_hint":"عرض الشرح",
"past_your_MMl_text":"ضع نص MMl الخاص بك",
"add_MML":"إضافة MML",
"show_preview":"عرض المعاينة"
},
"columns": {
"id": "الرقم التعريفي",

View File

@ -0,0 +1,5 @@
import { MathMLToLaTeX } from 'mathml-to-latex';
export function convertMathMLToLaTeX(mathml: string): string {
return MathMLToLaTeX.convert(mathml);
}

View File

@ -0,0 +1,23 @@
interface TextLatexPart {
text: string;
isLatex: boolean;
}
export const parseTextAndLatex = (input: string): TextLatexPart[] => {
const result: TextLatexPart[] = [];
const parts = input.split(/(\$\$[^$]+\$\$)/g);
parts.forEach(part => {
if (part.startsWith('$$') && part.endsWith('$$')) {
result.push({ text: part.slice(2, -2), isLatex: true });
} else if (part.trim()) {
result.push({ text: part, isLatex: false });
}
});
return result;
};