diff --git a/openmetadata-ui/src/main/resources/ui/package.json b/openmetadata-ui/src/main/resources/ui/package.json index e8d3a0cccd5e..e0d5db0f6c6f 100644 --- a/openmetadata-ui/src/main/resources/ui/package.json +++ b/openmetadata-ui/src/main/resources/ui/package.json @@ -178,7 +178,6 @@ "react-hook-form": "7.78.0", "react-i18next": "^15.5.3", "react-intersection-observer": "^9.16.0", - "react-latex-next": "^3.0.0", "react-markdown": "^10.1.0", "react-oidc": "^1.0.3", "react-papaparse": "^4.1.0", diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BlockEditorMathEquations.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BlockEditorMathEquations.spec.ts index c1b3d1bf3af6..410928b78d4a 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BlockEditorMathEquations.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BlockEditorMathEquations.spec.ts @@ -84,7 +84,7 @@ test.describe('BlockEditor math equations', { tag: ['@Discovery'] }, () => { await expect( page .getByTestId('asset-description-container') - .locator('.block-math-equation') + .locator('.block-math-equation .katex') ).toBeVisible(); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/MathEquation/MathEquationComponent.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/MathEquation/MathEquationComponent.test.tsx index 9d8822a1499e..b63f2af618c7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/MathEquation/MathEquationComponent.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/MathEquation/MathEquationComponent.test.tsx @@ -14,17 +14,13 @@ import { fireEvent, render, screen } from '@testing-library/react'; import { NodeViewProps } from '@tiptap/react'; import { MathEquationComponent } from './MathEquationComponent'; -jest.mock('react-latex-next', () => - jest.fn().mockImplementation(({ children }) => {children}) -); - const updateAttributes = jest.fn(); -const renderComponent = (isEditing: boolean) => +const renderComponent = (isEditing: boolean, equation = 'x^2') => render( { expect(screen.getByRole('textbox')).toHaveValue('x^2'); }); + + it('renders delimited math with KaTeX', () => { + const { container } = renderComponent(false, '$x^2$'); + + expect(container.querySelector('.katex')).toBeInTheDocument(); + }); + + it('renders text outside the delimiters as text, not HTML', () => { + const { container } = renderComponent( + false, + '$x^2$ ' + ); + + expect(container.querySelector('img')).not.toBeInTheDocument(); + expect(container).toHaveTextContent(''); + }); + + it('keeps malformed math as its source text', () => { + const { container } = renderComponent(false, '$\\frac{1}$'); + + expect(container.querySelector('.katex')).not.toBeInTheDocument(); + expect(container).toHaveTextContent('$\\frac{1}$'); + }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/MathEquation/MathEquationComponent.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/MathEquation/MathEquationComponent.tsx index e40d26f60b27..34e60eb7db4b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/MathEquation/MathEquationComponent.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/MathEquation/MathEquationComponent.tsx @@ -19,13 +19,42 @@ import { import { Check, XClose } from '@openmetadata/ui-core-components/icons'; import { NodeViewProps, NodeViewWrapper } from '@tiptap/react'; import classNames from 'classnames'; +import renderMathInElement from 'katex/contrib/auto-render'; import 'katex/dist/katex.min.css'; -import { FC, useRef, useState } from 'react'; +import { noop } from 'lodash'; +import { FC, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import Latex from 'react-latex-next'; import { ReactComponent as EditIcon } from '../../../../assets/svg/edit-new.svg'; import './math-equation.less'; +// The delimiters react-latex-next used, so stored `$…$` / `$$…$$` equations +// render as before. +const LATEX_DELIMITERS = [ + { left: '$$', right: '$$', display: true }, + { left: String.raw`\(`, right: String.raw`\)`, display: false }, + { left: '$', right: '$', display: false }, + { left: String.raw`\[`, right: String.raw`\]`, display: true }, +]; + +// auto-render keeps everything outside the delimiters as text nodes, so the +// stored attribute is never parsed as HTML. +const RenderedEquation: FC<{ equation: string }> = ({ equation }) => { + const ref = useRef(null); + + useEffect(() => { + if (ref.current) { + ref.current.textContent = equation; + renderMathInElement(ref.current, { + delimiters: LATEX_DELIMITERS, + // Malformed math stays as its source text, without logging per render. + errorCallback: noop, + }); + } + }, [equation]); + + return ; +}; + export const MathEquationComponent: FC = ({ node, updateAttributes, @@ -89,7 +118,7 @@ export const MathEquationComponent: FC = ({ ) : ( - {equation} + )} {/* Show edit button only when the editor is editable */} {!isEditing && editor.isEditable && ( diff --git a/openmetadata-ui/src/main/resources/ui/yarn.lock b/openmetadata-ui/src/main/resources/ui/yarn.lock index 35069dd302ed..9c4d3951dccf 100644 --- a/openmetadata-ui/src/main/resources/ui/yarn.lock +++ b/openmetadata-ui/src/main/resources/ui/yarn.lock @@ -6078,11 +6078,6 @@ commander@^2.19.0, commander@^2.20.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" - integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== - commander@^9.0.0: version "9.5.0" resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" @@ -9387,13 +9382,6 @@ jwt-decode@^3.1.2: resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59" integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A== -katex@^0.16.0: - version "0.16.45" - resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.45.tgz#ba60d39c54746b6b8d39ce0e7f6eace07143149c" - integrity sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA== - dependencies: - commander "^8.3.0" - katex@^0.19.0: version "0.19.0" resolved "https://registry.yarnpkg.com/katex/-/katex-0.19.0.tgz#b69c8cbcfc8c331be3f4f6e606b02b7146501ec7" @@ -11881,13 +11869,6 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-latex-next@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/react-latex-next/-/react-latex-next-3.0.0.tgz#3e347a13b1e701439b5fa52f75201bc86166854e" - integrity sha512-x70f1b1G7TronVigsRgKHKYYVUNfZk/3bciFyYX1lYLQH2y3/TXku3+5Vap8MDbJhtopePSYBsYWS6jhzIdz+g== - dependencies: - katex "^0.16.0" - react-markdown@^10.1.0: version "10.1.0" resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-10.1.0.tgz#e22bc20faddbc07605c15284255653c0f3bad5ca"