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, +];