diff --git a/package.json b/package.json index 96286d2d..1f1c4a04 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@vap/shiki": "0.10.5", "fflate": "^0.8.2", "gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3", + "katex": "^0.16.11", "monaco-editor": "^0.50.0", "nanoid": "^5.0.7", "virtual-merge": "^1.0.1" @@ -49,6 +50,7 @@ "@stylistic/eslint-plugin": "^2.6.1", "@types/chrome": "^0.0.269", "@types/diff": "^5.2.1", + "@types/katex": "^0.16.7", "@types/lodash": "^4.17.7", "@types/node": "^22.0.3", "@types/react": "^18.3.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a62c40cd..c6bda454 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -34,6 +34,9 @@ importers: gifenc: specifier: github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3 version: https://codeload.github.com/mattdesl/gifenc/tar.gz/64842fca317b112a8590f8fef2bf3825da8f6fe3 + katex: + specifier: ^0.16.11 + version: 0.16.11 monaco-editor: specifier: ^0.50.0 version: 0.50.0 @@ -53,6 +56,9 @@ importers: '@types/diff': specifier: ^5.2.1 version: 5.2.1 + '@types/katex': + specifier: ^0.16.7 + version: 0.16.7 '@types/lodash': specifier: ^4.17.7 version: 4.17.7 @@ -649,6 +655,9 @@ packages: '@types/jsonfile@6.1.4': resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} + '@types/katex@0.16.7': + resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==} + '@types/lodash@4.14.194': resolution: {integrity: sha512-r22s9tAS7imvBt2lyHC9B8AGwWnXaYb1tY09oyLkXDs4vArpYJzw09nj8MLx5VfciBPGIb+ZwG0ssYnEPJxn/g==} @@ -995,6 +1004,10 @@ packages: commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + component-emitter@1.3.0: resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==} @@ -1945,6 +1958,10 @@ packages: jszip@2.7.0: resolution: {integrity: sha512-JIsRKRVC3gTRo2vM4Wy9WBC3TRcfnIZU8k65Phi3izkvPH975FowRYtKGT6PxevA0XnJ/yO8b0QwV0ydVyQwfw==} + katex@0.16.11: + resolution: {integrity: sha512-RQrI8rlHY92OLf3rho/Ts8i/XvjgguEjOkO1BEXcU3N8BqPpSzBNwV/G0Ukr+P/l3ivvJUE/Fa/CwbS6HesGNQ==} + hasBin: true + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -3080,6 +3097,8 @@ snapshots: dependencies: '@types/node': 18.16.3 + '@types/katex@0.16.7': {} + '@types/lodash@4.14.194': {} '@types/lodash@4.17.7': {} @@ -3494,6 +3513,8 @@ snapshots: commander@2.20.3: {} + commander@8.3.0: {} + component-emitter@1.3.0: {} concat-map@0.0.1: {} @@ -4503,6 +4524,10 @@ snapshots: dependencies: pako: 1.0.11 + katex@0.16.11: + dependencies: + commander: 8.3.0 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 diff --git a/src/plugins/katex/components/ReactKatex.tsx b/src/plugins/katex/components/ReactKatex.tsx new file mode 100644 index 00000000..1d1b9503 --- /dev/null +++ b/src/plugins/katex/components/ReactKatex.tsx @@ -0,0 +1,55 @@ +import { React, useMemo } from '@webpack/common'; +import type { ReactNode } from "react"; +import KaTeX from 'katex'; + +type ErrorRenderer = (error: Error) => ReactNode; + +interface MathComponentProps { + math: string, + children?: ReactNode, + errorColor?: string, + renderError?: ErrorRenderer; +} + +const createMathComponent = (Component, { displayMode }) => { + const MathComponent = ({ children, errorColor, math, renderError }: MathComponentProps) => { + const formula = math ?? children; + + const { html, error } = useMemo(() => { + try { + const html = KaTeX.renderToString(formula, { + displayMode, + errorColor, + throwOnError: !!renderError, + }); + + return { html, error: undefined }; + } catch (error) { + if (error instanceof KaTeX.ParseError || error instanceof TypeError) { + return { error }; + } + + throw error; + } + }, [formula, errorColor, renderError]); + + if (error) { + return renderError ? renderError(error) : ; + } + + return ; + }; + + return MathComponent; +}; + +const InternalBlockMath = ({ html }) => { + return
; +}; + +const InternalInlineMath = ({ html }) => { + return ; +}; + +export const BlockMath = createMathComponent(InternalBlockMath, { displayMode: true }); +export const InlineMath = createMathComponent(InternalInlineMath, { displayMode: false }); \ No newline at end of file diff --git a/src/plugins/katex/index.tsx b/src/plugins/katex/index.tsx new file mode 100644 index 00000000..ed851395 --- /dev/null +++ b/src/plugins/katex/index.tsx @@ -0,0 +1,65 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2022 Vendicated and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +import "./katex.css"; + +import { Devs } from "@utils/constants"; +import definePlugin, { ReporterTestable } from "@utils/types"; +import { BlockMath, InlineMath } from "./components/ReactKatex"; + +export interface HighlighterProps { + lang?: string; + content: string; + isPreview: boolean; + tempSettings?: Record; +} + +export default definePlugin({ + name: "KaTeX", + description: "TeX typesetting in discord", + authors: [Devs.skyevg], + reporterTestable: ReporterTestable.Patches, + + patches: [ + { + find: "codeBlock:{react(", + replacement: { + match: /codeBlock:\{react\((\i),(\i),(\i)\)\{/, + replace: "$&if($1.lang == 'latex') return $self.createBlock($1,$2,$3);" + } + }, + { + find: "inlineCode:{react:(", + replacement: { + match: /inlineCode:\{react:\((\i),(\i),(\i)\)=>/, + replace: "$&($1.content.startsWith('$$') && $1.content.endsWith('$$'))?$self.createInline($1,$2,$3):" + } + } + ], + start: async () => { + }, + stop: () => { + }, + + createBlock: (props: HighlighterProps) => ( + + ), + createInline: (props: HighlighterProps) => ( + + ), +}); \ No newline at end of file diff --git a/src/plugins/katex/katex.css b/src/plugins/katex/katex.css new file mode 100644 index 00000000..8eb02a0c --- /dev/null +++ b/src/plugins/katex/katex.css @@ -0,0 +1 @@ +@import url("https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css"); \ No newline at end of file