Skip to content

Commit

Permalink
fix: mermaid rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
DIYgod committed Jan 18, 2024
1 parent 97072c5 commit 9e30074
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 156 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
"hast-util-from-html": "2.0.1",
"hast-util-to-html": "9.0.0",
"hast-util-to-jsx-runtime": "2.3.0",
"hast-util-to-text": "4.0.0",
"image-size": "1.1.1",
"immer": "10.0.3",
"ioredis": "5.3.2",
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

143 changes: 71 additions & 72 deletions src/components/ui/Mermaid.tsx
Original file line number Diff line number Diff line change
@@ -1,93 +1,92 @@
"use client"

import type { Element } from "hast"
import { toText } from "hast-util-to-text"
import { nanoid } from "nanoid"
import { memo, useEffect, useState } from "react"

import { useIsDark } from "~/hooks/useDarkMode"

import AdvancedImage from "./AdvancedImage"

const Mermaid = memo(
function Mermaid(props: { children: string }) {
const [loading, setLoading] = useState(true)
const [error, setError] = useState("")
const [svg, setSvg] = useState("")
const [width, setWidth] = useState<number>()
const [height, setHeight] = useState<number>()
const Mermaid = memo(function Mermaid(props: {
children: string
node: Element
}) {
const [loading, setLoading] = useState(true)
const [error, setError] = useState("")
const [svg, setSvg] = useState("")
const [width, setWidth] = useState<number>()
const [height, setHeight] = useState<number>()
const text = toText(props.node, {
whitespace: "pre",
})

const isDark = useIsDark()
const isDark = useIsDark()

useEffect(() => {
import("mermaid").then(async (mo) => {
const mermaid = mo.default
mermaid.initialize({
theme: isDark ? "dark" : "default",
})
useEffect(() => {
import("mermaid").then(async (mo) => {
const mermaid = mo.default
mermaid.initialize({
theme: isDark ? "dark" : "default",
})
}, [isDark])
})
}, [isDark])

useEffect(() => {
if (typeof props.children === "string") {
setError("")
setLoading(true)
useEffect(() => {
if (text) {
setError("")
setLoading(true)

import("mermaid").then(async (mo) => {
const mermaid = mo.default
const id = nanoid()
let result
try {
result = await mermaid.render(`mermaid-${id}`, props.children)
} catch (error) {
document.getElementById(`dmermaid-${id}`)?.remove()
if (error instanceof Error) {
setError(error.message)
}
setSvg("")
setWidth(undefined)
setHeight(undefined)
import("mermaid").then(async (mo) => {
const mermaid = mo.default
const id = nanoid()
let result
try {
result = await mermaid.render(`mermaid-${id}`, text)
} catch (error) {
document.getElementById(`dmermaid-${id}`)?.remove()
if (error instanceof Error) {
setError(error.message)
}
setSvg("")
setWidth(undefined)
setHeight(undefined)
}

if (result) {
setSvg(result.svg)
if (result) {
setSvg(result.svg)

const match = result.svg.match(
/viewBox="[^"]*\s([\d.]+)\s([\d.]+)"/,
)
if (match?.[1] && match?.[2]) {
setWidth(parseInt(match?.[1]))
setHeight(parseInt(match?.[2]))
}
setError("")
const match = result.svg.match(/viewBox="[^"]*\s([\d.]+)\s([\d.]+)"/)
if (match?.[1] && match?.[2]) {
setWidth(parseInt(match?.[1]))
setHeight(parseInt(match?.[2]))
}
setLoading(false)
})
}
}, [props.children])
setError("")
}
setLoading(false)
})
}
}, [text])

return loading ? (
<div className="min-h-[50px] rounded-lg flex items-center justify-center bg-[#ECECFD] dark:bg-[#1F2020] text-sm">
Mermaid Loading...
</div>
) : svg ? (
<div>
<AdvancedImage
alt="mermaid"
src={
"data:image/svg+xml;base64," + Buffer.from(svg).toString("base64")
}
width={width}
height={height}
/>
</div>
) : (
<div className="min-h-[50px] rounded-lg flex items-center justify-center bg-red-100 text-sm">
{error || "Error"}
</div>
)
},
(prevProps, nextProps) => {
return prevProps.children?.[0] === nextProps.children?.[0]
},
)
return loading ? (
<div className="min-h-[50px] rounded-lg flex items-center justify-center bg-[#ECECFD] dark:bg-[#1F2020] text-sm">
Mermaid Loading...
</div>
) : svg ? (
<div>
<AdvancedImage
alt="mermaid"
src={"data:image/svg+xml;base64," + Buffer.from(svg).toString("base64")}
width={width}
height={height}
/>
</div>
) : (
<div className="min-h-[50px] rounded-lg flex items-center justify-center bg-red-100 text-sm">
{error || "Error"}
</div>
)
})

export default Mermaid
19 changes: 5 additions & 14 deletions src/markdown/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import rehypeAutolinkHeadings from "rehype-autolink-headings"
import rehypeInferDescriptionMeta from "rehype-infer-description-meta"
import rehypeKatex from "rehype-katex"
import rehypePrismGenerator from "rehype-prism-plus/generator"
import rehypeRaw, { Options as RehypeRawOptions } from "rehype-raw"
import rehypeRaw from "rehype-raw"
import rehypeSanitize from "rehype-sanitize"
import rehypeSlug from "rehype-slug"
import remarkBreaks from "remark-breaks"
Expand All @@ -40,19 +40,14 @@ import AdvancedImage from "~/components/ui/AdvancedImage"
import { isServerSide } from "~/lib/utils"

import { transformers } from "./embed-transformers"
import {
allowedCustomWrappers,
defaultRules,
rehypeCustomWrapper,
} from "./rehype-custom-wrapper"
import { rehypeEmbed } from "./rehype-embed"
import { rehypeIpfs } from "./rehype-ipfs"
import { rehypeMention } from "./rehype-mention"
import { rehypeMermaid } from "./rehype-mermaid"
import { rehypeRemoveH1 } from "./rehype-remove-h1"
import { rehypeTable } from "./rehype-table"
import { rehypeWrapCode } from "./rehype-wrap-code"
import { rehypeExternalLink } from "./rehyper-external-link"
import { remarkMermaid } from "./remark-mermaid"
import { remarkPangu } from "./remark-pangu"
import { remarkYoutube } from "./remark-youtube"
import sanitizeScheme from "./sanitize-schema"
Expand Down Expand Up @@ -91,19 +86,13 @@ export const renderPageContent = (content: string, strictMode?: boolean) => {
.use(remarkDirectiveRehype)
.use(remarkCalloutDirectives)
.use(remarkYoutube)
.use(remarkMermaid)
.use(remarkMath, {
singleDollarTextMath: false,
})
.use(remarkPangu)
.use(emoji)
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeRaw, {
passThrough: allowedCustomWrappers,
} as RehypeRawOptions)
.use(rehypeCustomWrapper, {
rules: defaultRules,
})
.use(rehypeRaw)
.use(rehypeIpfs)
.use(rehypeSlug)
.use(rehypeAutolinkHeadings, {
Expand All @@ -125,6 +114,7 @@ export const renderPageContent = (content: string, strictMode?: boolean) => {
.use(rehypeSanitize, strictMode ? undefined : sanitizeScheme)
.use(rehypeTable)
.use(rehypeExternalLink)
.use(rehypeMermaid)
.use(rehypeWrapCode)
.use(rehypeInferDescriptionMeta)
.use(rehypeEmbed, {
Expand Down Expand Up @@ -183,6 +173,7 @@ export const renderPageContent = (content: string, strictMode?: boolean) => {
ignoreInvalidStyle: true,
jsx,
jsxs,
passNode: true,
}),
toMetadata: () => {
let metadata = {
Expand Down
51 changes: 0 additions & 51 deletions src/markdown/rehype-custom-wrapper.ts

This file was deleted.

16 changes: 16 additions & 0 deletions src/markdown/rehype-mermaid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Root } from "hast"
import type { Plugin } from "unified"
import { visit } from "unist-util-visit"

export const rehypeMermaid: Plugin<[], Root> = () => (tree: Root) => {
visit(tree, { tagName: "code" }, (node, i, parent) => {
if (
Array.isArray(node.properties.className) &&
node.properties.className.includes("language-mermaid") &&
parent?.type === "element"
) {
parent.tagName = "mermaid"
node.tagName = "div"
}
})
}
7 changes: 1 addition & 6 deletions src/markdown/rehype-wrap-code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,7 @@ import { visit } from "unist-util-visit"
export const rehypeWrapCode: Plugin<Array<void>, Root> = () => {
return (tree: Root) => {
visit(tree, { type: "element", tagName: "pre" }, (node, index, parent) => {
if (
parent &&
typeof index === "number" &&
// @ts-ignore
node?.properties?.className?.[0] !== "mermaid"
) {
if (parent && typeof index === "number") {
const wrapper = u("element", {
tagName: "div",
properties: {
Expand Down
13 changes: 0 additions & 13 deletions src/markdown/remark-mermaid.ts

This file was deleted.

0 comments on commit 9e30074

Please sign in to comment.