95 lines
3.0 KiB
TypeScript
95 lines
3.0 KiB
TypeScript
import { defaultJSXConverters, RichText } from '@payloadcms/richtext-lexical/react';
|
|
import type { JSXConverters } from '@payloadcms/richtext-lexical/react';
|
|
import Image from 'next/image';
|
|
import { Fragment } from 'react';
|
|
import { allBlocks } from '@/src/payload/blocks/allBlocks';
|
|
import ObfuscatedEmail from '@/components/ObfuscatedEmail';
|
|
import ObfuscatedPhone from '@/components/ObfuscatedPhone';
|
|
|
|
/**
|
|
* Splits a text string on \n and intersperses <br /> elements.
|
|
*/
|
|
function textWithLineBreaks(text: string, key: string) {
|
|
const parts = text.split('\n');
|
|
if (parts.length === 1) return text;
|
|
return parts.map((part, i) => (
|
|
<Fragment key={`${key}-${i}`}>
|
|
{part}
|
|
{i < parts.length - 1 && <br />}
|
|
</Fragment>
|
|
));
|
|
}
|
|
|
|
const jsxConverters: JSXConverters = {
|
|
...defaultJSXConverters,
|
|
linebreak: () => <br />,
|
|
text: ({ node }: any) => {
|
|
let content: React.ReactNode = node.text || '';
|
|
if (typeof content === 'string' && content.includes('\n')) {
|
|
content = textWithLineBreaks(content, `t-${(node.text || '').slice(0, 8)}`);
|
|
}
|
|
|
|
if (typeof content === 'string' && content.includes('@')) {
|
|
const emailRegex = /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/g;
|
|
const parts = content.split(emailRegex);
|
|
content = parts.map((part, i) => {
|
|
if (part.match(emailRegex)) {
|
|
return <ObfuscatedEmail key={`e-${i}`} email={part} />;
|
|
}
|
|
return part;
|
|
});
|
|
}
|
|
|
|
if (typeof content === 'string' && content.match(/\+\d+/)) {
|
|
const phoneRegex = /(\+\d{1,4}[\d\s-]{5,15})/g;
|
|
const parts = content.split(phoneRegex);
|
|
content = parts.map((part, i) => {
|
|
if (part.match(phoneRegex)) {
|
|
return <ObfuscatedPhone key={`p-${i}`} phone={part} />;
|
|
}
|
|
return part;
|
|
});
|
|
}
|
|
|
|
return content;
|
|
},
|
|
upload: ({ node }: any) => {
|
|
return (
|
|
<div className="relative w-full aspect-video my-8 rounded-3xl overflow-hidden shadow-2xl group">
|
|
<Image
|
|
src={node.value.url}
|
|
alt={node.value.alt || ''}
|
|
fill
|
|
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
|
className="object-cover transition-transform duration-700 group-hover:scale-105"
|
|
/>
|
|
</div>
|
|
);
|
|
},
|
|
blocks: allBlocks.reduce((acc, block) => {
|
|
const Component = block.render;
|
|
if (!Component) return acc;
|
|
|
|
const renderFn = ({ node }: any) => {
|
|
// Pass all fields as props to the component, and also as 'data' prop
|
|
return <Component {...node.fields} data={node.fields} />;
|
|
};
|
|
|
|
// Map both the direct slug and the block- prefixed slug
|
|
return {
|
|
...acc,
|
|
[block.slug]: renderFn,
|
|
[`block-${block.slug}`]: renderFn,
|
|
};
|
|
}, {}),
|
|
};
|
|
|
|
export default function PayloadRichText({ data, className, references }: { data: any; className?: string; references?: any[] }) {
|
|
if (!data) return null;
|
|
return (
|
|
<div className={className}>
|
|
<RichText data={data} converters={jsxConverters} />
|
|
</div>
|
|
);
|
|
}
|