diff --git a/web/package.json b/web/package.json
index f4198af8..ec1de848 100644
--- a/web/package.json
+++ b/web/package.json
@@ -11,6 +11,7 @@
"@bufbuild/protobuf": "^1.3.1",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
+ "@matejmazur/react-katex": "^3.1.3",
"@mui/joy": "5.0.0-beta.2",
"@reduxjs/toolkit": "^1.9.5",
"axios": "^0.27.2",
@@ -19,6 +20,7 @@
"highlight.js": "^11.8.0",
"i18next": "^21.10.0",
"i18next-browser-languagedetector": "^7.1.0",
+ "katex": "^0.16.8",
"lodash-es": "^4.17.21",
"lucide-react": "^0.263.1",
"qrcode.react": "^3.1.0",
@@ -37,6 +39,7 @@
},
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^3.4.0",
+ "@types/katex": "^0.16.2",
"@types/lodash-es": "^4.17.9",
"@types/node": "^18.17.15",
"@types/qs": "^6.9.8",
diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml
index 83472662..eb43b0ff 100644
--- a/web/pnpm-lock.yaml
+++ b/web/pnpm-lock.yaml
@@ -14,6 +14,9 @@ dependencies:
'@emotion/styled':
specifier: ^11.11.0
version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.21)(react@18.2.0)
+ '@matejmazur/react-katex':
+ specifier: ^3.1.3
+ version: registry.npmmirror.com/@matejmazur/react-katex@3.1.3(katex@0.16.8)(react@18.2.0)
'@mui/joy':
specifier: 5.0.0-beta.2
version: 5.0.0-beta.2(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.21)(react-dom@18.2.0)(react@18.2.0)
@@ -38,6 +41,9 @@ dependencies:
i18next-browser-languagedetector:
specifier: ^7.1.0
version: 7.1.0
+ katex:
+ specifier: ^0.16.8
+ version: registry.npmmirror.com/katex@0.16.8
lodash-es:
specifier: ^4.17.21
version: 4.17.21
@@ -88,6 +94,9 @@ devDependencies:
'@trivago/prettier-plugin-sort-imports':
specifier: ^3.4.0
version: 3.4.0(prettier@2.6.2)
+ '@types/katex':
+ specifier: ^0.16.2
+ version: registry.npmmirror.com/@types/katex@0.16.2
'@types/lodash-es':
specifier: ^4.17.9
version: 4.17.9
@@ -216,7 +225,7 @@ packages:
dependencies:
'@babel/types': 7.17.0
jsesc: 2.5.2
- source-map: 0.5.7
+ source-map: registry.npmmirror.com/source-map@0.5.7
dev: true
/@babel/generator@7.22.15:
@@ -1730,11 +1739,6 @@ packages:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
dev: true
- /commander@4.1.1:
- resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
- engines: {node: '>= 6'}
- dev: false
-
/concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
@@ -1784,7 +1788,7 @@ packages:
engines: {node: '>=8.0.0'}
dependencies:
mdn-data: 2.0.14
- source-map: 0.6.1
+ source-map: registry.npmmirror.com/source-map@0.6.1
dev: false
/cssesc@3.0.0:
@@ -3669,18 +3673,15 @@ packages:
source-map: 0.6.1
dev: true
- /source-map@0.5.6:
- resolution: {integrity: sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==}
- engines: {node: '>=0.10.0'}
- dev: false
-
/source-map@0.5.7:
resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==}
engines: {node: '>=0.10.0'}
+ dev: false
/source-map@0.6.1:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
+ dev: true
/sourcemap-codec@1.4.8:
resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
@@ -3700,7 +3701,7 @@ packages:
/stacktrace-gps@3.1.2:
resolution: {integrity: sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ==}
dependencies:
- source-map: 0.5.6
+ source-map: registry.npmmirror.com/source-map@0.5.6
stackframe: 1.3.4
dev: false
@@ -3776,7 +3777,7 @@ packages:
hasBin: true
dependencies:
'@jridgewell/gen-mapping': 0.3.3
- commander: 4.1.1
+ commander: registry.npmmirror.com/commander@4.1.1
glob: 7.1.6
lines-and-columns: 1.2.4
mz: 2.7.0
@@ -4150,3 +4151,67 @@ packages:
react: 18.2.0
use-sync-external-store: 1.2.0(react@18.2.0)
dev: false
+
+ registry.npmmirror.com/@matejmazur/react-katex@3.1.3(katex@0.16.8)(react@18.2.0):
+ resolution: {integrity: sha512-rBp7mJ9An7ktNoU653BWOYdO4FoR4YNwofHZi+vaytX/nWbIlmHVIF+X8VFOn6c3WYmrLT5FFBjKqCZ1sjR5uQ==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/@matejmazur/react-katex/-/react-katex-3.1.3.tgz}
+ id: registry.npmmirror.com/@matejmazur/react-katex/3.1.3
+ name: '@matejmazur/react-katex'
+ version: 3.1.3
+ engines: {node: '>=12', yarn: '>=1.1'}
+ peerDependencies:
+ katex: '>=0.9'
+ react: '>=16'
+ dependencies:
+ katex: registry.npmmirror.com/katex@0.16.8
+ react: 18.2.0
+ dev: false
+
+ registry.npmmirror.com/@types/katex@0.16.2:
+ resolution: {integrity: sha512-dHsSjSlU/EWEEbeNADr3FtZZOAXPkFPUO457QCnoNqcZQXNqNEu/svQd0Nritvd3wNff4vvC/f4e6xgX3Llt8A==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/@types/katex/-/katex-0.16.2.tgz}
+ name: '@types/katex'
+ version: 0.16.2
+ dev: true
+
+ registry.npmmirror.com/commander@4.1.1:
+ resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz}
+ name: commander
+ version: 4.1.1
+ engines: {node: '>= 6'}
+ dev: false
+
+ registry.npmmirror.com/commander@8.3.0:
+ resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/commander/-/commander-8.3.0.tgz}
+ name: commander
+ version: 8.3.0
+ engines: {node: '>= 12'}
+ dev: false
+
+ registry.npmmirror.com/katex@0.16.8:
+ resolution: {integrity: sha512-ftuDnJbcbOckGY11OO+zg3OofESlbR5DRl2cmN8HeWeeFIV7wTXvAOx8kEjZjobhA+9wh2fbKeO6cdcA9Mnovg==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/katex/-/katex-0.16.8.tgz}
+ name: katex
+ version: 0.16.8
+ hasBin: true
+ dependencies:
+ commander: registry.npmmirror.com/commander@8.3.0
+ dev: false
+
+ registry.npmmirror.com/source-map@0.5.6:
+ resolution: {integrity: sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/source-map/-/source-map-0.5.6.tgz}
+ name: source-map
+ version: 0.5.6
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ registry.npmmirror.com/source-map@0.5.7:
+ resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/source-map/-/source-map-0.5.7.tgz}
+ name: source-map
+ version: 0.5.7
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ registry.npmmirror.com/source-map@0.6.1:
+ resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz}
+ name: source-map
+ version: 0.6.1
+ engines: {node: '>=0.10.0'}
+ dev: false
diff --git a/web/src/labs/marked/parser/BlockLatex.tsx b/web/src/labs/marked/parser/BlockLatex.tsx
new file mode 100644
index 00000000..7a98bad4
--- /dev/null
+++ b/web/src/labs/marked/parser/BlockLatex.tsx
@@ -0,0 +1,35 @@
+import TeX from "@matejmazur/react-katex";
+import "katex/dist/katex.min.css";
+import { matcher } from "../matcher";
+
+const BLOCK_LATEX_REG = new RegExp(
+ "\\$\\$(\\s*[^\\$\\s][^\\$]*?)\\$\\$|\\\\\\[(.+?)\\\\\\]|\\\\begin{equation}([\\s\\S]+?)\\\\end{equation}"
+);
+
+const blockRenderer = (rawStr: string) => {
+ const matchResult = matcher(rawStr, BLOCK_LATEX_REG);
+ if (!matchResult) {
+ return <>{rawStr}>;
+ }
+
+ let latexCode = "";
+
+ if (matchResult[1]) {
+ // $$
+ latexCode = matchResult[1];
+ } else if (matchResult[2]) {
+ // \[ and \]
+ latexCode = matchResult[2];
+ } else if (matchResult[3]) {
+ // \begin{equation} and \end{equation}
+ latexCode = matchResult[3];
+ }
+
+ return {latexCode};
+};
+
+export default {
+ name: "blockLatex",
+ regexp: BLOCK_LATEX_REG,
+ renderer: blockRenderer,
+};
diff --git a/web/src/labs/marked/parser/InlineLatex.tsx b/web/src/labs/marked/parser/InlineLatex.tsx
new file mode 100644
index 00000000..70aae24a
--- /dev/null
+++ b/web/src/labs/marked/parser/InlineLatex.tsx
@@ -0,0 +1,28 @@
+import TeX from "@matejmazur/react-katex";
+import "katex/dist/katex.min.css";
+
+export const LATEX_INLINE_REG = /\$(.+?)\$|\\\(([^\\]+)\\\)/g;
+
+const inlineRenderer = (rawStr: string) => {
+ const matchResult = LATEX_INLINE_REG.exec(rawStr);
+ if (!matchResult) {
+ return rawStr;
+ }
+
+ let latexCode = "";
+
+ if (matchResult[1]) {
+ // $
+ latexCode = matchResult[1];
+ } else if (matchResult[2]) {
+ // \( and \)
+ latexCode = matchResult[2];
+ }
+ return {latexCode};
+};
+
+export default {
+ name: "inlineLatex",
+ regexp: LATEX_INLINE_REG,
+ renderer: inlineRenderer,
+};
diff --git a/web/src/labs/marked/parser/index.ts b/web/src/labs/marked/parser/index.ts
index a469471e..94a26cba 100644
--- a/web/src/labs/marked/parser/index.ts
+++ b/web/src/labs/marked/parser/index.ts
@@ -1,3 +1,4 @@
+import BlockLatex from "./BlockLatex";
import Blockquote from "./Blockquote";
import Bold from "./Bold";
import BoldEmphasis from "./BoldEmphasis";
@@ -9,6 +10,7 @@ import Heading from "./Heading";
import HorizontalRules from "./HorizontalRules";
import Image from "./Image";
import InlineCode from "./InlineCode";
+import InlineLatex from "./InlineLatex";
import Link from "./Link";
import OrderedList from "./OrderedList";
import Paragraph from "./Paragraph";
@@ -25,6 +27,7 @@ export { PLAIN_LINK_REG } from "./PlainLink";
// The order determines the order of execution.
export const blockElementParserList = [
+ BlockLatex,
Br,
CodeBlock,
Blockquote,
@@ -36,5 +39,16 @@ export const blockElementParserList = [
HorizontalRules,
Paragraph,
];
-
-export const inlineElementParserList = [Image, BoldEmphasis, Bold, Emphasis, Link, InlineCode, PlainLink, Strikethrough, Tag, PlainText];
+export const inlineElementParserList = [
+ InlineLatex,
+ Image,
+ BoldEmphasis,
+ Bold,
+ Emphasis,
+ Link,
+ InlineCode,
+ PlainLink,
+ Strikethrough,
+ Tag,
+ PlainText,
+];