All checks were successful
Deploy eolas-app / deploy (push) Successful in 1m4s
114 lines
3.7 KiB
TypeScript
114 lines
3.7 KiB
TypeScript
import ReactMarkdown from "react-markdown"
|
|
import remarkGfm from "remark-gfm"
|
|
import CodeBlock from "@/components/CodeBlock"
|
|
import remarkMath from "remark-math"
|
|
import rehypeKatex from "rehype-katex"
|
|
import "katex/dist/katex.min.css"
|
|
import BodyLink from "./BodyLink"
|
|
import EntryLoadingSkeleton from "./EntryLoadingSkeleton"
|
|
import { useSearchParams } from "react-router"
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./ui/table"
|
|
const ImagePreprocessor = (src) => {
|
|
const filename = src.src.split("/").pop()
|
|
const s3RootUrl = "https://eolas.s3.systemsobscure.net/"
|
|
return <img src={s3RootUrl + filename} />
|
|
}
|
|
|
|
const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
|
|
const highlighter = (children, highlight) => {
|
|
if (!highlight || typeof children !== "string") return children
|
|
const words = highlight.trim().split(/\s+/)
|
|
const pattern = words.length > 1 ? escapeRegex(highlight) : escapeRegex(words[0])
|
|
const regex = new RegExp(`\\b(${pattern})\\b`, "gi")
|
|
const parts = children.split(regex)
|
|
|
|
return parts.map((part, i) =>
|
|
regex.test(part) ? (
|
|
<mark key={i} className="dark:bg-[#4c1d95] dark:text-white bg-[#ddd6fe]">
|
|
{part}
|
|
</mark>
|
|
) : (
|
|
part
|
|
),
|
|
)
|
|
}
|
|
|
|
export default function EntryBody({ body, isLoading }) {
|
|
const [searchParams] = useSearchParams()
|
|
|
|
const highlight = searchParams.get("highlight")
|
|
|
|
if (isLoading) {
|
|
return <EntryLoadingSkeleton />
|
|
}
|
|
return (
|
|
<div className="max-w-2xl p-4 lg:p-6">
|
|
<ReactMarkdown
|
|
remarkPlugins={[remarkGfm, remarkMath]}
|
|
rehypePlugins={[rehypeKatex]}
|
|
components={{
|
|
h1: () => null,
|
|
h2: ({ children }) => (
|
|
<h2 className="scroll-m-20 font-semibold mt-8 mb-4 first:mt-0">
|
|
{highlighter(children, highlight)}
|
|
</h2>
|
|
),
|
|
h3: ({ children }) => (
|
|
<h3 className="scroll-m-20 font-semibold mt-8 mb-4 first:mt-0">
|
|
{highlighter(children, highlight)}
|
|
</h3>
|
|
),
|
|
h4: ({ children }) => (
|
|
<h4 className="scroll-m-20 font-semibold mt-8 mb-4 first:mt-0">
|
|
{highlighter(children, highlight)}
|
|
</h4>
|
|
),
|
|
p: ({ children }) => (
|
|
<p className="leading-[1.5] mb-4 not-first:mt-4">
|
|
{highlighter(children, highlight)}
|
|
</p>
|
|
),
|
|
ul: ({ children }) => <ul className="list-disc ml-10 mb-4 space-y-1">{children}</ul>,
|
|
ol: ({ children }) => (
|
|
<ol className="list-decimal ml-10 mb-4 space-y-1">{children}</ol>
|
|
),
|
|
li: ({ children }) => (
|
|
<li className="list-disc mb-4 space-y-1">{highlighter(children, highlight)}</li>
|
|
),
|
|
table: ({ children }) => (
|
|
<Table className="w-full mb-4 text-sm overflow-x-auto">{children}</Table>
|
|
),
|
|
thead: ({ children }) => <TableHeader>{children}</TableHeader>,
|
|
|
|
tr: ({ children }) => <TableRow>{highlighter(children, highlight)}</TableRow>,
|
|
|
|
th: ({ children }) => <TableHead>{highlighter(children, highlight)}</TableHead>,
|
|
td: ({ children }) => <TableCell>{highlighter(children, highlight)}</TableCell>,
|
|
tbody: ({ children }) => <TableBody>{children}</TableBody>,
|
|
|
|
blockquote: ({ children }) => (
|
|
<blockquote className="mt-4 border-l-2 pl-6 text-muted-foreground">
|
|
{highlighter(children, highlight)}
|
|
</blockquote>
|
|
),
|
|
pre: ({ children }) => {
|
|
const child = children.props
|
|
return <CodeBlock className={child.className}>{child.children}</CodeBlock>
|
|
},
|
|
code: ({ children }) => (
|
|
<code className="rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm">
|
|
{children}
|
|
</code>
|
|
),
|
|
img: ({ src }) => <ImagePreprocessor src={src} />,
|
|
a: ({ href, children }) => {
|
|
return <BodyLink link={href} children={children} />
|
|
},
|
|
}}
|
|
>
|
|
{body}
|
|
</ReactMarkdown>
|
|
</div>
|
|
)
|
|
}
|