diff --git a/src/components/CopyPageDropdown.tsx b/src/components/CopyPageDropdown.tsx index b9e5e0c4..8f4cdf73 100644 --- a/src/components/CopyPageDropdown.tsx +++ b/src/components/CopyPageDropdown.tsx @@ -10,10 +10,7 @@ import { DropdownContent, DropdownItem, } from './Dropdown' -import { - getPackageManager, - PACKAGE_MANAGERS, -} from '~/utils/markdown/installCommand' +import { getPackageManager } from '~/utils/markdown/installCommand' // Markdown icon component matching the screenshot function MarkdownIcon({ className }: { className?: string }) { diff --git a/src/components/markdown/CodeBlock.tsx b/src/components/markdown/CodeBlock.tsx index db753985..2bbaba8c 100644 --- a/src/components/markdown/CodeBlock.tsx +++ b/src/components/markdown/CodeBlock.tsx @@ -155,7 +155,10 @@ export function CodeBlock({ const code = children?.props.children const [codeElement, setCodeElement] = React.useState( -
+, ) @@ -163,49 +166,60 @@ export function CodeBlock({ React[ typeof document !== 'undefined' ? 'useLayoutEffect' : 'useEffect' ](() => { + let cancelled = false ;(async () => { - const themes = ['github-light', 'aurora-x'] - const langStr = lang || 'plaintext' + try { + const themes = ['github-light', 'aurora-x'] + const langStr = lang || 'plaintext' - const { highlighter, effectiveLang } = await getHighlighter(langStr) - // Trim trailing newlines to prevent empty lines at end of code block - const trimmedCode = (code || '').trimEnd() + const { highlighter, effectiveLang } = await getHighlighter(langStr) + // Trim trailing newlines to prevent empty lines at end of code block + const trimmedCode = (code || '').trimEnd() - const htmls = await Promise.all( - themes.map(async (theme) => { - const output = highlighter.codeToHtml(trimmedCode, { - lang: effectiveLang, - theme, - transformers: [transformerNotationDiff()], - }) + const htmls = await Promise.all( + themes.map(async (theme) => { + const output = highlighter.codeToHtml(trimmedCode, { + lang: effectiveLang, + theme, + transformers: [transformerNotationDiff()], + }) - if (lang === 'mermaid') { - const preAttributes = extractPreAttributes(output) - let svgHtml = genSvgMap.get(trimmedCode) - if (!svgHtml) { - const mermaid = await getMermaid() - const { svg } = await mermaid.render('foo', trimmedCode) - genSvgMap.set(trimmedCode, svg) - svgHtml = svg + if (lang === 'mermaid') { + const preAttributes = extractPreAttributes(output) + let svgHtml = genSvgMap.get(trimmedCode) + if (!svgHtml) { + const mermaid = await getMermaid() + const { svg } = await mermaid.render('foo', trimmedCode) + genSvgMap.set(trimmedCode, svg) + svgHtml = svg + } + const cls = twMerge(preAttributes.class, 'py-4 bg-neutral-50') + return `{lang === 'mermaid' ? : code}${svgHtml}` } - return `${svgHtml}` - } - return output - }), - ) + return output + }), + ) - setCodeElement( -pre]:h-full [&>pre]:rounded-none' : '', - )} - dangerouslySetInnerHTML={{ __html: htmls.join('') }} - ref={ref} - />, - ) + if (!cancelled) { + setCodeElement( +pre]:h-full [&>pre]:rounded-none' : '', + )} + dangerouslySetInnerHTML={{ __html: htmls.join('') }} + ref={ref} + />, + ) + } + } catch (err) { + console.warn('Shiki highlighting failed:', err) + } })() - }, [code, lang]) + return () => { + cancelled = true + } + }, [code, lang, isEmbedded]) return (