From a85c0e609a998d4f4d83f01894760b50fcfe3b53 Mon Sep 17 00:00:00 2001 From: Benno Tielen Date: Fri, 5 Jun 2026 10:21:11 +0200 Subject: [PATCH] fix: internal link --- src/components/Text/converters.tsx | 53 ++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 18 deletions(-) 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 (