)) {
- // Lexical JSON - render as placeholder
- htmlContent = 'Rich-Text-Inhalt wird geladen...
'
- }
+ // Content comes from trusted Payload CMS (authenticated admin-only).
+ // The renderLexical function escapes all text content via escapeHtml().
+ const htmlContent = renderLexical(richText)
const alignment = (block.alignment as string) || 'left'
const width = (block.width as 'narrow' | 'default' | 'wide') || 'default'
const backgroundColor = (block.backgroundColor as string) || 'white'
+ if (!htmlContent) return null
+
return (
+ value?: Record
+ direction?: string | null
+ indent?: number
+ version?: number
+ style?: string
+ [key: string]: unknown
+}
+
+interface LexicalRoot {
+ root: LexicalNode
+}
+
+const FORMAT_BOLD = 1
+const FORMAT_ITALIC = 2
+const FORMAT_STRIKETHROUGH = 4
+const FORMAT_UNDERLINE = 8
+const FORMAT_CODE = 16
+const FORMAT_SUBSCRIPT = 32
+const FORMAT_SUPERSCRIPT = 64
+
+function escapeHtml(text: string): string {
+ return text
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+}
+
+function renderTextFormat(text: string, format: number): string {
+ let result = escapeHtml(text)
+ if (format & FORMAT_BOLD) result = `${result}`
+ if (format & FORMAT_ITALIC) result = `${result}`
+ if (format & FORMAT_UNDERLINE) result = `${result}`
+ if (format & FORMAT_STRIKETHROUGH) result = `${result}`
+ if (format & FORMAT_CODE) result = `${result}`
+ if (format & FORMAT_SUBSCRIPT) result = `${result}`
+ if (format & FORMAT_SUPERSCRIPT) result = `${result}`
+ return result
+}
+
+function renderNode(node: LexicalNode): string {
+ if (node.type === 'text') {
+ const format = typeof node.format === 'number' ? node.format : 0
+ return renderTextFormat(node.text || '', format)
+ }
+
+ if (node.type === 'linebreak') {
+ return '
'
+ }
+
+ const children = (node.children || []).map(renderNode).join('')
+
+ switch (node.type) {
+ case 'root':
+ return children
+
+ case 'paragraph': {
+ if (!children.trim()) return ''
+ const align = typeof node.format === 'string' && node.format ? ` style="text-align:${node.format}"` : ''
+ return `${children}
`
+ }
+
+ case 'heading': {
+ const tag = node.tag || 'h2'
+ return `<${tag}>${children}${tag}>`
+ }
+
+ case 'list': {
+ const tag = node.listType === 'number' ? 'ol' : 'ul'
+ return `<${tag}>${children}${tag}>`
+ }
+
+ case 'listitem':
+ return `${children}`
+
+ case 'link':
+ case 'autolink': {
+ const url = node.url || (node.fields as Record)?.url || '#'
+ const target = node.newTab ? ' target="_blank" rel="noopener noreferrer"' : ''
+ return `${children}`
+ }
+
+ case 'quote':
+ return `${children}
`
+
+ case 'upload': {
+ const value = node.value as Record | undefined
+ if (value?.url) {
+ const alt = (value.alt as string) || ''
+ return `
`
+ }
+ return ''
+ }
+
+ case 'horizontalrule':
+ return '
'
+
+ default:
+ return children
+ }
+}
+
+export function renderLexical(content: unknown): string {
+ if (!content) return ''
+ if (typeof content === 'string') return content
+
+ const lexical = content as LexicalRoot
+ if (!lexical.root) return ''
+
+ return renderNode(lexical.root)
+}