Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: deprecate markdown-to-jsx and support Latex in markdown #1623

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions apps/console/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.51.0",
"react-markdown": "^8.0.3",
"reactflow": "^11.8.3",
"remark-frontmatter": "^4.0.1",
"sharp": "^0.32.6",
"shiki": "^0.11.1",
"tailwindcss-animate": "^1.0.6",
Expand Down
54 changes: 0 additions & 54 deletions apps/console/src/components/ModelReadmeMarkdown.tsx

This file was deleted.

1 change: 0 additions & 1 deletion apps/console/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export * from "./AuthPageBase";
export * from "./ChangePasswordForm";
export * from "./LoginForm";
export * from "./ModelReadmeMarkdown";
export * from "./OnboardingForm";
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"turbo": "latest",
"typescript": "^5.5.4",
"yaml": "^2.5.0"

},
"engines": {
"node": ">=20.0.0"
Expand Down
6 changes: 5 additions & 1 deletion packages/toolkit/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@instill-ai/toolkit",
"version": "0.112.0",
"version": "0.113.0-rc.2",
"description": "Instill AI's frontend toolkit",
"repository": "https://github.com/instill-ai/design-system.git",
"bugs": "https://github.com/instill-ai/design-system/issues",
Expand Down Expand Up @@ -158,9 +158,13 @@
"react-avatar-editor": "^13.0.2",
"react-error-boundary": "^4.0.13",
"react-hook-form": "^7.51.0",
"react-markdown": "^9.0.1",
"react-syntax-highlighter": "^15.5.0",
"reactflow": "^11.10.0",
"recharts": "2.12.7",
"rehype-katex": "^7.0.1",
"remark-gfm": "^4.0.0",
"remark-math": "^6.0.0",
"sanitize-html": "^2.13.0",
"semver": "^7.5.4",
"server-only": "^0.0.1",
Expand Down
49 changes: 9 additions & 40 deletions packages/toolkit/src/components/ReadmeEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"use client";

import * as React from "react";
import Markdown from "markdown-to-jsx";

import { Button, cn, Icons, ToggleGroup } from "@instill-ai/design-system";

import { debounce } from "../lib";
import { MarkdownViewer } from "../lib/markdown";
import { MarkdownEditor } from "./";

type ViewMode = "view" | "edit";
Expand Down Expand Up @@ -71,11 +71,14 @@ export const ReadmeEditor = ({

const renderMarkdown = () => {
return (
<div className="markdown-body w-full overflow-x-auto p-6">
<Markdown options={{ disableParsingRawHTML: true }}>
{content || placeholder || ""}
</Markdown>
</div>
<MarkdownViewer
markdown={content || placeholder || ""}
skipHtml
className="!overflow-x-auto !rounded-none !p-6"
style={{
height: `calc(100vh - ${editorTopOffset + 32}px)`,
}}
/>
);
};

Expand All @@ -97,40 +100,6 @@ export const ReadmeEditor = ({
.mdxeditor-popup-container {
display: none;
}

.markdown-body a {
word-break: break-all !important;
}

.markdown-body pre code {
white-space: pre-wrap !important;
}

.markdown-body p {
white-space: pre-wrap !important;
}

.markdown-body ul > li {
white-space: pre-wrap !important;
}

.markdown-body ol > li {
white-space: pre-wrap !important;
}

.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
white-space: pre-wrap !important;
}

.markdown-body img {
max-width: 100%;
object-fit: contain;
}
`}
</style>
<div
Expand Down
92 changes: 92 additions & 0 deletions packages/toolkit/src/lib/markdown/MarkdownViewer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"use client";

import * as React from "react";
import ReactMarkdown from "react-markdown";
import rehypeKatex from "rehype-katex";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import sanitizeHtml from "sanitize-html";

import { cn } from "@instill-ai/design-system";

import { preprocessLaTeX } from "./preprocessLatex";

export const MarkdownViewer = ({
className,
markdown,
skipHtml,
style,
}: {
className?: string;
markdown: string;
skipHtml?: boolean;
style?: React.CSSProperties;
}) => {
const sanitizedHtmlText = sanitizeHtml(markdown ?? "");

const processedText = preprocessLaTeX(sanitizedHtmlText);

const remarkPlugins = [
remarkGfm,
[remarkMath, { singleDollarTextMath: true }],
];

const rehypePlugins = [[rehypeKatex, { output: "mathml" }]];

return (
<React.Fragment>
<style jsx={true}>{`
.markdown-body a {
word-break: break-all !important;
}

.markdown-body pre code {
white-space: pre-wrap !important;
}

.markdown-body p {
white-space: pre-wrap !important;
}

.markdown-body ul > li {
white-space: pre-wrap !important;
}

.markdown-body ol > li {
white-space: pre-wrap !important;
}

.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
white-space: pre-wrap !important;
}

.markdown-body img {
max-width: 100%;
object-fit: contain;
}
`}</style>
<article
className={cn(
"markdown-body w-full overflow-x-scroll rounded-b-sm px-1.5 py-1",
className,
)}
style={style}
>
<ReactMarkdown
skipHtml={skipHtml}
/* @ts-expect-error remark and rehype has type conflicts */
remarkPlugins={remarkPlugins}
/* @ts-expect-error remark and rehype has type conflicts */
rehypePlugins={rehypePlugins}
>
{processedText}
</ReactMarkdown>
</article>
</React.Fragment>
);
};
2 changes: 2 additions & 0 deletions packages/toolkit/src/lib/markdown/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./MarkdownViewer";
export * from "./preprocessLatex";
92 changes: 92 additions & 0 deletions packages/toolkit/src/lib/markdown/preprocessLatex.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { describe, expect, test } from "vitest";

import { preprocessLaTeX } from "./preprocessLatex";

describe("preprocessLaTeX", () => {
test("returns the same string if no LaTeX patterns are found", () => {
const content = "This is a test string without LaTeX";
expect(preprocessLaTeX(content)).toBe(content);
});

test("escapes dollar signs followed by digits", () => {
const content = "Price is $50 and $100";
const expected = "Price is \\$50 and \\$100";
expect(preprocessLaTeX(content)).toBe(expected);
});

test("does not escape dollar signs not followed by digits", () => {
const content = "This $variable is not escaped";
expect(preprocessLaTeX(content)).toBe(content);
});

test("preserves existing LaTeX expressions", () => {
const content = "Inline $x^2 + y^2 = z^2$ and block $$E = mc^2$$";
expect(preprocessLaTeX(content)).toBe(content);
});

test("handles mixed LaTeX and currency", () => {
const content = "LaTeX $x^2$ and price $50";
const expected = "LaTeX $x^2$ and price \\$50";
expect(preprocessLaTeX(content)).toBe(expected);
});

test("converts LaTeX delimiters", () => {
const content = "Brackets \\[x^2\\] and parentheses \\(y^2\\)";
const expected = "Brackets $$x^2$$ and parentheses $y^2$";
expect(preprocessLaTeX(content)).toBe(expected);
});

test("escapes mhchem commands", () => {
const content = "$\\ce{H2O}$ and $\\pu{123 J}$";
const expected = "$\\\\ce{H2O}$ and $\\\\pu{123 J}$";
expect(preprocessLaTeX(content)).toBe(expected);
});

test("handles complex mixed content", () => {
const content = `
LaTeX inline $x^2$ and block $$y^2$$
Currency $100 and $200
Chemical $\\ce{H2O}$
Brackets \\[z^2\\]
`;
const expected = `
LaTeX inline $x^2$ and block $$y^2$$
Currency \\$100 and \\$200
Chemical $\\\\ce{H2O}$
Brackets $$z^2$$
`;
expect(preprocessLaTeX(content)).toBe(expected);
});

test("handles empty string", () => {
expect(preprocessLaTeX("")).toBe("");
});

test("preserves code blocks", () => {
const content = "```\n$100\n```\nOutside $200";
const expected = "```\n$100\n```\nOutside \\$200";
expect(preprocessLaTeX(content)).toBe(expected);
});

test("handles multiple currency values in a sentence", () => {
const content = "I have $50 in my wallet and $100 in the bank.";
const expected = "I have \\$50 in my wallet and \\$100 in the bank.";
expect(preprocessLaTeX(content)).toBe(expected);
});

test("preserves LaTeX expressions with numbers", () => {
const content = "The equation is $f(x) = 2x + 3$ where x is a variable.";
expect(preprocessLaTeX(content)).toBe(content);
});

test("handles currency values with commas", () => {
const content = "The price is $1,000,000 for this item.";
const expected = "The price is \\$1,000,000 for this item.";
expect(preprocessLaTeX(content)).toBe(expected);
});

test("preserves LaTeX expressions with special characters", () => {
const content = "The set is defined as $\\{x | x > 0\\}$.";
expect(preprocessLaTeX(content)).toBe(content);
});
});
Loading
Loading