This commit is contained in:
Ubuntu
2026-07-03 16:58:28 +08:00
commit 32b43f85bd
6240 changed files with 1987678 additions and 0 deletions
+177
View File
@@ -0,0 +1,177 @@
import Link from 'next/link'
import type {
DefaultNodeTypes,
SerializedUploadNode,
} from '@payloadcms/richtext-lexical'
import {
type JSXConvertersFunction,
} from '@payloadcms/richtext-lexical/react'
import { Text } from '@/components/retroui'
import { RichTextCodeBlock } from '@/components/RichTextCodeBlock'
const CustomUploadComponent: React.FC<{
node: SerializedUploadNode
}> = ({ node }) => {
if (node.relationTo === 'media') {
const uploadDoc = node.value
if (typeof uploadDoc !== 'object') {
return null
}
const { alt, height, url, width } = uploadDoc
return (
<img
alt={alt}
height={height}
src={`https://cms.retroui.dev${url}`}
width={width}
className="mx-auto w-full max-w-[600px] my-8"
/>
)
}
return null
}
type NodeTypes = DefaultNodeTypes
export const JSXConverters: JSXConvertersFunction<NodeTypes> = ({
defaultConverters,
}) => {
return {
...defaultConverters,
heading: ({ node, nodesToJSX }) => {
const children = nodesToJSX({ nodes: node.children })
const Tag = node.tag as 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
const classNames: Record<string, string> = {
h1: '',
h2: 'mb-4 mt-8 lg:text-3xl',
h3: 'mb-4',
h4: 'mb-2',
h5: '',
h6: '',
}
return (
<Text as={Tag} className={classNames[Tag]}>
{children}
</Text>
)
},
paragraph: ({ node, nodesToJSX }) => {
// Handle empty paragraphs as spacing
if (!node.children || node.children.length === 0) {
return <br />
}
const children = nodesToJSX({ nodes: node.children })
return <Text className="text-lg text-foreground">{children}</Text>
},
text: ({ node }) => {
const format = node.format ?? 0
let text: React.ReactNode = node.text
// Format is a bitmask: 1=bold, 2=italic, 4=strikethrough, 8=underline, 16=code
if (format & 16) {
// Inline code
text = (
<code className="rounded-sm bg-[#282A36] px-1.5 py-0.5 text-primary text-sm">
{text}
</code>
)
}
if (format & 1) {
text = <strong>{text}</strong>
}
if (format & 2) {
text = <em>{text}</em>
}
if (format & 4) {
text = <s>{text}</s>
}
if (format & 8) {
text = <u>{text}</u>
}
return <>{text}</>
},
list: ({ node, nodesToJSX }) => {
const children = nodesToJSX({ nodes: node.children })
if (node.listType === 'bullet') {
return <ul className="list-disc pl-4 lg:pl-8 my-4">{children}</ul>
}
return <ol className="list-decimal pl-4 lg:pl-8 my-4">{children}</ol>
},
listitem: ({ node, nodesToJSX }) => {
const children = nodesToJSX({ nodes: node.children })
return (
<Text as="li" className="text-lg text-foreground ml-4 lg:ml-8 mb-2">
{children}
</Text>
)
},
link: ({ node, nodesToJSX }) => {
const children = nodesToJSX({ nodes: node.children })
const href = node.fields?.url || ''
const isExternal = href.startsWith('http')
const linkClass = 'underline underline-offset-4 hover:decoration-primary'
if (isExternal) {
return (
<a
href={href}
target="_blank"
rel="noopener noreferrer"
className={linkClass}
>
{children}
</a>
)
}
return (
<Link href={href} className={linkClass}>
{children}
</Link>
)
},
quote: ({ node, nodesToJSX }) => {
const children = nodesToJSX({ nodes: node.children })
return (
<blockquote className="border-l-4 border-primary pl-4 my-4 italic">
{children}
</blockquote>
)
},
linebreak: () => {
return <br />
},
upload: ({ node }) => {
return <CustomUploadComponent node={node} />
},
// Handle autolink nodes
autolink: ({ node, nodesToJSX }) => {
const children = nodesToJSX({ nodes: (node as any).children })
const href = (node as any).fields?.url || ''
return (
<a
href={href}
target="_blank"
rel="noopener noreferrer"
className="underline underline-offset-4 hover:decoration-primary break-all"
>
{children}
</a>
)
},
// Blocks converter - nested object with slug as key
blocks: {
Code: ({ node }: any) => {
const fields = (node as any).fields
return (
<RichTextCodeBlock
code={fields?.code || ''}
language={fields?.language}
title={fields?.blockName}
/>
)
},
},
}
}