From 17894b6e968d18da9290dc35cb024f9bc57037be Mon Sep 17 00:00:00 2001 From: cfngc4594 Date: Fri, 21 Feb 2025 00:08:21 +0800 Subject: [PATCH] feat(mdx): implement MdxPreview component with error handling and loading state --- src/components/problem-description.tsx | 123 +++++++++++++++++++------ 1 file changed, 96 insertions(+), 27 deletions(-) diff --git a/src/components/problem-description.tsx b/src/components/problem-description.tsx index 16069ca..5b985d5 100644 --- a/src/components/problem-description.tsx +++ b/src/components/problem-description.tsx @@ -1,36 +1,105 @@ -import { Suspense } from "react"; -import remarkGfm from "remark-gfm"; -import { compileMDX } from "next-mdx-remote/rsc"; -import { Skeleton } from "@/components/ui/skeleton"; -import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; +"use client"; -interface ProblemDescriptionProps { - mdxSource: string; +import remarkGfm from "remark-gfm"; +import { useTheme } from "next-themes"; +import rehypePretty from "rehype-pretty-code"; +import { Skeleton } from "@/components/ui/skeleton"; +import { serialize } from "next-mdx-remote/serialize"; +import { useCallback, useEffect, useState } from "react"; +import { CircleAlert, TriangleAlert } from "lucide-react"; +import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; +import { MDXRemote, MDXRemoteSerializeResult } from "next-mdx-remote"; + +interface MdxPreviewProps { + source: string; } -export async function ProblemDescription({ mdxSource }: ProblemDescriptionProps) { - try { - const { content } = await compileMDX({ - source: mdxSource, - options: { +export default function MdxPreview({ source }: MdxPreviewProps) { + const { resolvedTheme } = useTheme(); + const [error, setError] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [mdxSource, setMdxSource] = useState(null); + + const components = { + // Define your custom components here + // For example: + // Test: ({ name }: { name: string }) =>

Test Component: {name}

, + }; + + const getMdxSource = useCallback(async () => { + setIsLoading(true); + setError(null); + + try { + const mdxSource = await serialize(source, { mdxOptions: { + rehypePlugins: [ + [ + rehypePretty, + { + theme: resolvedTheme === "light" ? "github-light-default" : "github-dark-default", + keepBackground: false, + }, + ], + ], remarkPlugins: [remarkGfm], }, - }, - }); + }); + setMdxSource(mdxSource); + } catch (error) { + console.error("Failed to serialize Mdx:", error); + setError("Failed to load mdx content."); + } finally { + setIsLoading(false); + } + }, [source, resolvedTheme]); - return ( - - }> -
- {content} -
-
- -
- ); - } catch (error) { - console.error("Error compiling MDX:", error); - return ; + // Delay the serialize process to the next event loop to avoid flickering + // when copying code to the editor and the MDX preview shrinks. + useEffect(() => { + const timeoutId = setTimeout(() => { + getMdxSource(); // Execute serializeMdx in the next event loop + }, 0); + + return () => clearTimeout(timeoutId); // Cleanup timeout on component unmount + }, [getMdxSource]); + + if (isLoading) { + return ; } + + if (error) { + return ( +
+
+

+

+
+
+ ); + } + + if (!source) { + return ( +
+
+

+

+
+
+ ); + } + + return ( + +
+ +
+ +
+ ); }