diff --git a/src/components/Text/converters.tsx b/src/components/Text/converters.tsx
index 054caac..6c2bc48 100644
--- a/src/components/Text/converters.tsx
+++ b/src/components/Text/converters.tsx
@@ -1,41 +1,58 @@
import type { JSXConvertersFunction } from '@payloadcms/richtext-lexical/react'
+import { LinkJSXConverter } from '@payloadcms/richtext-lexical/react'
import type { SerializedLinkNode } from '@payloadcms/richtext-lexical'
import { Button } from '@/components/Button/Button'
-// Lexical link nodes carry their editable metadata (url, newTab, linkType, ...)
-// in `fields`. We extend that shape with the custom `appearance` select that is
-// added to LinkFeature in `payload.config.ts`, so editors can mark a link as a
-// call-to-action button.
type LinkFields = SerializedLinkNode['fields'] & {
appearance?: 'link' | 'button'
}
-// Custom JSX converters passed to wherever rich text is rendered.
-// Only the `link` converter is overridden — every other node keeps Payload's
-// default rendering via the spread of `defaultConverters`.
+// Maps an internal Payload document to its public URL based on collection slug.
+const internalDocToHref = ({ linkNode }: { linkNode: SerializedLinkNode }): string => {
+ const { doc } = linkNode.fields
+ if (!doc?.value || typeof doc.value !== 'object') return '#'
+
+ const document = doc.value as Record
+
+ switch (doc.relationTo) {
+ case 'group':
+ return `/gruppe/${document.slug ?? document.id}`
+ case 'event':
+ return `/veranstaltungen/${document.id}`
+ case 'worship':
+ return `/gottesdienst/${document.id}`
+ case 'pages':
+ return `/${document.slug ?? ''}`
+ case 'parish':
+ return `/gemeinde/${document.slug ?? document.id}`
+ case 'blog':
+ return `/blog/${document.id}`
+ default:
+ return '#'
+ }
+}
+
+const linkConverters = LinkJSXConverter({ internalDocToHref })
+
export const jsxConverters: JSXConvertersFunction = ({ defaultConverters }) => ({
...defaultConverters,
+ ...linkConverters,
link: (args) => {
const { node, nodesToJSX } = args
const fields = node.fields as LinkFields
- // Normal link → delegate to Payload's built-in link converter.
- // The default converter from the package is a function, but the type
- // also allows a static ReactNode, so we narrow before calling.
+ // Normal link → delegate to the internalDocToHref-aware link converter.
if (fields?.appearance !== 'button') {
- const defaultLink = defaultConverters.link
- return typeof defaultLink === 'function' ? defaultLink(args) : defaultLink
+ const linkConverter = linkConverters.link
+ return typeof linkConverter === 'function' ? linkConverter(args) : linkConverter
}
- // Button link → resolve href the same way Payload's default converter does:
- // internal links point at the related doc's slug, custom links use `url`.
+ // Button link → resolve href using internalDocToHref for internal links.
const href =
- fields.linkType === 'internal' && typeof fields.doc?.value === 'object'
- ? `/${(fields.doc.value as { slug?: string }).slug ?? ''}`
+ fields.linkType === 'internal'
+ ? internalDocToHref({ linkNode: node })
: (fields.url ?? '#')
- // Render the existing Button component. Schema and size are intentionally
- // hardcoded — editors only choose link vs. button, not the styling.
return (