chore(templates): migrate to new richtext component in website template (#9615)
In addition to requiring fewer files, it supports more nodes. If you currently initialize a website template and want to use features such as images or tables, they are not rendered. With this change that happens automatically. Credits to @AlessioGr for the [JSX serializer](https://github.com/payloadcms/payload/pull/8795). --------- Co-authored-by: Paul Popus <paul@nouance.io>
This commit is contained in:
6770
templates/website/pnpm-lock.yaml
generated
6770
templates/website/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -58,7 +58,7 @@ export default async function Post({ params: paramsPromise }: Args) {
|
|||||||
|
|
||||||
<div className="flex flex-col items-center gap-4 pt-8">
|
<div className="flex flex-col items-center gap-4 pt-8">
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<RichText className="max-w-[48rem] mx-auto" content={post.content} enableGutter={false} />
|
<RichText className="max-w-[48rem] mx-auto" data={post.content} enableGutter={false} />
|
||||||
{post.relatedPosts && post.relatedPosts.length > 0 && (
|
{post.relatedPosts && post.relatedPosts.length > 0 && (
|
||||||
<RelatedPosts
|
<RelatedPosts
|
||||||
className="mt-12 max-w-[52rem] lg:grid lg:grid-cols-subgrid col-start-1 col-span-3 grid-rows-[2fr]"
|
className="mt-12 max-w-[52rem] lg:grid lg:grid-cols-subgrid col-start-1 col-span-3 grid-rows-[2fr]"
|
||||||
|
|||||||
@@ -22,43 +22,26 @@ import { default as default_1a7510af427896d367a49dbf838d2de6 } from '@/component
|
|||||||
import { default as default_8a7ab0eb7ab5c511aba12e68480bfe5e } from '@/components/BeforeLogin'
|
import { default as default_8a7ab0eb7ab5c511aba12e68480bfe5e } from '@/components/BeforeLogin'
|
||||||
|
|
||||||
export const importMap = {
|
export const importMap = {
|
||||||
'@payloadcms/richtext-lexical/rsc#RscEntryLexicalCell':
|
"@payloadcms/richtext-lexical/rsc#RscEntryLexicalCell": RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e,
|
||||||
RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e,
|
"@payloadcms/richtext-lexical/rsc#RscEntryLexicalField": RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e,
|
||||||
'@payloadcms/richtext-lexical/rsc#RscEntryLexicalField':
|
"@payloadcms/richtext-lexical/client#InlineToolbarFeatureClient": InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||||
RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e,
|
"@payloadcms/richtext-lexical/client#FixedToolbarFeatureClient": FixedToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||||
'@payloadcms/richtext-lexical/client#InlineToolbarFeatureClient':
|
"@payloadcms/richtext-lexical/client#HeadingFeatureClient": HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||||
InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
"@payloadcms/richtext-lexical/client#ParagraphFeatureClient": ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||||
'@payloadcms/richtext-lexical/client#FixedToolbarFeatureClient':
|
"@payloadcms/richtext-lexical/client#UnderlineFeatureClient": UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||||
FixedToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
"@payloadcms/richtext-lexical/client#BoldFeatureClient": BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||||
'@payloadcms/richtext-lexical/client#HeadingFeatureClient':
|
"@payloadcms/richtext-lexical/client#ItalicFeatureClient": ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||||
HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
"@payloadcms/richtext-lexical/client#LinkFeatureClient": LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||||
'@payloadcms/richtext-lexical/client#ParagraphFeatureClient':
|
"@payloadcms/plugin-seo/client#OverviewComponent": OverviewComponent_a8a977ebc872c5d5ea7ee689724c0860,
|
||||||
ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
"@payloadcms/plugin-seo/client#MetaTitleComponent": MetaTitleComponent_a8a977ebc872c5d5ea7ee689724c0860,
|
||||||
'@payloadcms/richtext-lexical/client#UnderlineFeatureClient':
|
"@payloadcms/plugin-seo/client#MetaImageComponent": MetaImageComponent_a8a977ebc872c5d5ea7ee689724c0860,
|
||||||
UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
"@payloadcms/plugin-seo/client#MetaDescriptionComponent": MetaDescriptionComponent_a8a977ebc872c5d5ea7ee689724c0860,
|
||||||
'@payloadcms/richtext-lexical/client#BoldFeatureClient':
|
"@payloadcms/plugin-seo/client#PreviewComponent": PreviewComponent_a8a977ebc872c5d5ea7ee689724c0860,
|
||||||
BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
"@/fields/slug/SlugComponent#SlugComponent": SlugComponent_92cc057d0a2abb4f6cf0307edf59f986,
|
||||||
'@payloadcms/richtext-lexical/client#ItalicFeatureClient':
|
"@payloadcms/richtext-lexical/client#HorizontalRuleFeatureClient": HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||||
ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
"@payloadcms/richtext-lexical/client#BlocksFeatureClient": BlocksFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||||
'@payloadcms/richtext-lexical/client#LinkFeatureClient':
|
"@payloadcms/plugin-search/client#LinkToDoc": LinkToDoc_aead06e4cbf6b2620c5c51c9ab283634,
|
||||||
LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
"@payloadcms/plugin-search/client#ReindexButton": ReindexButton_aead06e4cbf6b2620c5c51c9ab283634,
|
||||||
'@payloadcms/plugin-seo/client#OverviewComponent':
|
"@/components/BeforeDashboard#default": default_1a7510af427896d367a49dbf838d2de6,
|
||||||
OverviewComponent_a8a977ebc872c5d5ea7ee689724c0860,
|
"@/components/BeforeLogin#default": default_8a7ab0eb7ab5c511aba12e68480bfe5e
|
||||||
'@payloadcms/plugin-seo/client#MetaTitleComponent':
|
|
||||||
MetaTitleComponent_a8a977ebc872c5d5ea7ee689724c0860,
|
|
||||||
'@payloadcms/plugin-seo/client#MetaImageComponent':
|
|
||||||
MetaImageComponent_a8a977ebc872c5d5ea7ee689724c0860,
|
|
||||||
'@payloadcms/plugin-seo/client#MetaDescriptionComponent':
|
|
||||||
MetaDescriptionComponent_a8a977ebc872c5d5ea7ee689724c0860,
|
|
||||||
'@payloadcms/plugin-seo/client#PreviewComponent':
|
|
||||||
PreviewComponent_a8a977ebc872c5d5ea7ee689724c0860,
|
|
||||||
'@/fields/slug/SlugComponent#SlugComponent': SlugComponent_92cc057d0a2abb4f6cf0307edf59f986,
|
|
||||||
'@payloadcms/richtext-lexical/client#HorizontalRuleFeatureClient':
|
|
||||||
HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
|
||||||
'@payloadcms/richtext-lexical/client#BlocksFeatureClient':
|
|
||||||
BlocksFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
|
||||||
'@payloadcms/plugin-search/client#LinkToDoc': LinkToDoc_aead06e4cbf6b2620c5c51c9ab283634,
|
|
||||||
'@payloadcms/plugin-search/client#ReindexButton': ReindexButton_aead06e4cbf6b2620c5c51c9ab283634,
|
|
||||||
'@/components/BeforeDashboard#default': default_1a7510af427896d367a49dbf838d2de6,
|
|
||||||
'@/components/BeforeLogin#default': default_8a7ab0eb7ab5c511aba12e68480bfe5e,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export const ArchiveBlock: React.FC<
|
|||||||
<div className="my-16" id={`block-${id}`}>
|
<div className="my-16" id={`block-${id}`}>
|
||||||
{introContent && (
|
{introContent && (
|
||||||
<div className="container mb-16">
|
<div className="container mb-16">
|
||||||
<RichText className="ml-0 max-w-[48rem]" content={introContent} enableGutter={false} />
|
<RichText className="ml-0 max-w-[48rem]" data={introContent} enableGutter={false} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<CollectionArchive posts={posts} />
|
<CollectionArchive posts={posts} />
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const BannerBlock: React.FC<Props> = ({ className, content, style }) => {
|
|||||||
'border-warning bg-warning/30': style === 'warning',
|
'border-warning bg-warning/30': style === 'warning',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<RichText content={content} enableGutter={false} enableProse={false} />
|
<RichText data={content} enableGutter={false} enableProse={false} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export const CallToActionBlock: React.FC<CTABlockProps> = ({ links, richText })
|
|||||||
<div className="container">
|
<div className="container">
|
||||||
<div className="bg-card rounded border-border border p-4 flex flex-col gap-8 md:flex-row md:justify-between md:items-center">
|
<div className="bg-card rounded border-border border p-4 flex flex-col gap-8 md:flex-row md:justify-between md:items-center">
|
||||||
<div className="max-w-[48rem] flex items-center">
|
<div className="max-w-[48rem] flex items-center">
|
||||||
{richText && <RichText className="mb-0" content={richText} enableGutter={false} />}
|
{richText && <RichText className="mb-0" data={richText} enableGutter={false} />}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-8">
|
<div className="flex flex-col gap-8">
|
||||||
{(links || []).map(({ link }, i) => {
|
{(links || []).map(({ link }, i) => {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export const ContentBlock: React.FC<ContentBlockProps> = (props) => {
|
|||||||
})}
|
})}
|
||||||
key={index}
|
key={index}
|
||||||
>
|
>
|
||||||
{richText && <RichText content={richText} enableGutter={false} />}
|
{richText && <RichText data={richText} enableGutter={false} />}
|
||||||
|
|
||||||
{enableLink && <CMSLink {...link} />}
|
{enableLink && <CMSLink {...link} />}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import React, { useCallback, useState } from 'react'
|
|||||||
import { useForm, FormProvider } from 'react-hook-form'
|
import { useForm, FormProvider } from 'react-hook-form'
|
||||||
import RichText from '@/components/RichText'
|
import RichText from '@/components/RichText'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
|
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
|
||||||
|
|
||||||
import { buildInitialFormState } from './buildInitialFormState'
|
import { buildInitialFormState } from './buildInitialFormState'
|
||||||
import { fields } from './fields'
|
import { fields } from './fields'
|
||||||
@@ -26,9 +27,7 @@ export type FormBlockType = {
|
|||||||
blockType?: 'formBlock'
|
blockType?: 'formBlock'
|
||||||
enableIntro: boolean
|
enableIntro: boolean
|
||||||
form: FormType
|
form: FormType
|
||||||
introContent?: {
|
introContent?: SerializedEditorState
|
||||||
[k: string]: unknown
|
|
||||||
}[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FormBlock: React.FC<
|
export const FormBlock: React.FC<
|
||||||
@@ -128,12 +127,12 @@ export const FormBlock: React.FC<
|
|||||||
return (
|
return (
|
||||||
<div className="container lg:max-w-[48rem]">
|
<div className="container lg:max-w-[48rem]">
|
||||||
{enableIntro && introContent && !hasSubmitted && (
|
{enableIntro && introContent && !hasSubmitted && (
|
||||||
<RichText className="mb-8 lg:mb-12" content={introContent} enableGutter={false} />
|
<RichText className="mb-8 lg:mb-12" data={introContent} enableGutter={false} />
|
||||||
)}
|
)}
|
||||||
<div className="p-4 lg:p-6 border border-border rounded-[0.8rem]">
|
<div className="p-4 lg:p-6 border border-border rounded-[0.8rem]">
|
||||||
<FormProvider {...formMethods}>
|
<FormProvider {...formMethods}>
|
||||||
{!isLoading && hasSubmitted && confirmationType === 'message' && (
|
{!isLoading && hasSubmitted && confirmationType === 'message' && (
|
||||||
<RichText content={confirmationMessage} />
|
<RichText data={confirmationMessage} />
|
||||||
)}
|
)}
|
||||||
{isLoading && !hasSubmitted && <p>Loading, please wait...</p>}
|
{isLoading && !hasSubmitted && <p>Loading, please wait...</p>}
|
||||||
{error && <div>{`${error.status || '500'}: ${error.message || ''}`}</div>}
|
{error && <div>{`${error.status || '500'}: ${error.message || ''}`}</div>}
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ import RichText from '@/components/RichText'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import { Width } from '../Width'
|
import { Width } from '../Width'
|
||||||
|
import { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
|
||||||
|
|
||||||
export const Message: React.FC = ({ message }: { message: Record<string, any> }) => {
|
export const Message: React.FC = ({ message }: { message: SerializedEditorState }) => {
|
||||||
return (
|
return (
|
||||||
<Width className="my-12" width="100">
|
<Width className="my-12" width="100">
|
||||||
{message && <RichText content={message} />}
|
{message && <RichText data={message} />}
|
||||||
</Width>
|
</Width>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export const MediaBlock: React.FC<Props> = (props) => {
|
|||||||
captionClassName,
|
captionClassName,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<RichText content={caption} enableGutter={false} />
|
<RichText data={caption} enableGutter={false} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const RelatedPosts: React.FC<RelatedPostsProps> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx('container', className)}>
|
<div className={clsx('container', className)}>
|
||||||
{introContent && <RichText content={introContent} enableGutter={false} />}
|
{introContent && <RichText data={introContent} enableGutter={false} />}
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-8 items-stretch">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-8 items-stretch">
|
||||||
{docs?.map((doc, index) => {
|
{docs?.map((doc, index) => {
|
||||||
|
|||||||
@@ -1,27 +1,56 @@
|
|||||||
import { cn } from '@/utilities/cn'
|
import { MediaBlock } from '@/blocks/MediaBlock/Component'
|
||||||
import React from 'react'
|
import { DefaultNodeTypes, SerializedBlockNode } from '@payloadcms/richtext-lexical'
|
||||||
|
import { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
|
||||||
|
import {
|
||||||
|
JSXConvertersFunction,
|
||||||
|
RichText as RichTextWithoutBlocks,
|
||||||
|
} from '@payloadcms/richtext-lexical/react'
|
||||||
|
|
||||||
import { serializeLexical } from './serialize'
|
import { CodeBlock, CodeBlockProps } from '@/blocks/Code/Component'
|
||||||
|
|
||||||
|
import type {
|
||||||
|
BannerBlock as BannerBlockProps,
|
||||||
|
CallToActionBlock as CTABlockProps,
|
||||||
|
MediaBlock as MediaBlockProps,
|
||||||
|
} from '@/payload-types'
|
||||||
|
import { BannerBlock } from '@/blocks/Banner/Component'
|
||||||
|
import { CallToActionBlock } from '@/blocks/CallToAction/Component'
|
||||||
|
import { cn } from '@/utilities/cn'
|
||||||
|
|
||||||
|
type NodeTypes =
|
||||||
|
| DefaultNodeTypes
|
||||||
|
| SerializedBlockNode<CTABlockProps | MediaBlockProps | BannerBlockProps | CodeBlockProps>
|
||||||
|
|
||||||
|
const jsxConverters: JSXConvertersFunction<NodeTypes> = ({ defaultConverters }) => ({
|
||||||
|
...defaultConverters,
|
||||||
|
blocks: {
|
||||||
|
banner: ({ node }) => <BannerBlock className="col-start-2 mb-4" {...node.fields} />,
|
||||||
|
mediaBlock: ({ node }) => (
|
||||||
|
<MediaBlock
|
||||||
|
className="col-start-1 col-span-3"
|
||||||
|
imgClassName="m-0"
|
||||||
|
{...node.fields}
|
||||||
|
captionClassName="mx-auto max-w-[48rem]"
|
||||||
|
enableGutter={false}
|
||||||
|
disableInnerContainer={true}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
code: ({ node }) => <CodeBlock className="col-start-2" {...node.fields} />,
|
||||||
|
cta: ({ node }) => <CallToActionBlock {...node.fields} />,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
className?: string
|
data: SerializedEditorState
|
||||||
content: Record<string, any>
|
|
||||||
enableGutter?: boolean
|
enableGutter?: boolean
|
||||||
enableProse?: boolean
|
enableProse?: boolean
|
||||||
}
|
} & React.HTMLAttributes<HTMLDivElement>
|
||||||
|
|
||||||
const RichText: React.FC<Props> = ({
|
|
||||||
className,
|
|
||||||
content,
|
|
||||||
enableGutter = true,
|
|
||||||
enableProse = true,
|
|
||||||
}) => {
|
|
||||||
if (!content) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
|
export default function RichText(props: Props) {
|
||||||
|
const { className, enableProse = true, enableGutter = true, ...rest } = props
|
||||||
return (
|
return (
|
||||||
<div
|
<RichTextWithoutBlocks
|
||||||
|
converters={jsxConverters}
|
||||||
className={cn(
|
className={cn(
|
||||||
{
|
{
|
||||||
'container ': enableGutter,
|
'container ': enableGutter,
|
||||||
@@ -30,14 +59,7 @@ const RichText: React.FC<Props> = ({
|
|||||||
},
|
},
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
{...rest}
|
||||||
{content &&
|
/>
|
||||||
!Array.isArray(content) &&
|
|
||||||
typeof content === 'object' &&
|
|
||||||
'root' in content &&
|
|
||||||
serializeLexical({ nodes: content?.root?.children })}
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RichText
|
|
||||||
|
|||||||
@@ -1,128 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
//This copy-and-pasted from lexical here: https://github.com/facebook/lexical/blob/c2ceee223f46543d12c574e62155e619f9a18a5d/packages/lexical/src/LexicalConstants.ts
|
|
||||||
|
|
||||||
import type { ElementFormatType, TextFormatType } from '@payloadcms/richtext-lexical/lexical'
|
|
||||||
import type {
|
|
||||||
TextDetailType,
|
|
||||||
TextModeType,
|
|
||||||
} from '@payloadcms/richtext-lexical/lexical/nodes/LexicalTextNode'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
// DOM
|
|
||||||
export const DOM_ELEMENT_TYPE = 1
|
|
||||||
export const DOM_TEXT_TYPE = 3
|
|
||||||
|
|
||||||
// Reconciling
|
|
||||||
export const NO_DIRTY_NODES = 0
|
|
||||||
export const HAS_DIRTY_NODES = 1
|
|
||||||
export const FULL_RECONCILE = 2
|
|
||||||
|
|
||||||
// Text node modes
|
|
||||||
export const IS_NORMAL = 0
|
|
||||||
export const IS_TOKEN = 1
|
|
||||||
export const IS_SEGMENTED = 2
|
|
||||||
// IS_INERT = 3
|
|
||||||
|
|
||||||
// Text node formatting
|
|
||||||
export const IS_BOLD = 1
|
|
||||||
export const IS_ITALIC = 1 << 1
|
|
||||||
export const IS_STRIKETHROUGH = 1 << 2
|
|
||||||
export const IS_UNDERLINE = 1 << 3
|
|
||||||
export const IS_CODE = 1 << 4
|
|
||||||
export const IS_SUBSCRIPT = 1 << 5
|
|
||||||
export const IS_SUPERSCRIPT = 1 << 6
|
|
||||||
export const IS_HIGHLIGHT = 1 << 7
|
|
||||||
|
|
||||||
export const IS_ALL_FORMATTING =
|
|
||||||
IS_BOLD |
|
|
||||||
IS_ITALIC |
|
|
||||||
IS_STRIKETHROUGH |
|
|
||||||
IS_UNDERLINE |
|
|
||||||
IS_CODE |
|
|
||||||
IS_SUBSCRIPT |
|
|
||||||
IS_SUPERSCRIPT |
|
|
||||||
IS_HIGHLIGHT
|
|
||||||
|
|
||||||
// Text node details
|
|
||||||
export const IS_DIRECTIONLESS = 1
|
|
||||||
export const IS_UNMERGEABLE = 1 << 1
|
|
||||||
|
|
||||||
// Element node formatting
|
|
||||||
export const IS_ALIGN_LEFT = 1
|
|
||||||
export const IS_ALIGN_CENTER = 2
|
|
||||||
export const IS_ALIGN_RIGHT = 3
|
|
||||||
export const IS_ALIGN_JUSTIFY = 4
|
|
||||||
export const IS_ALIGN_START = 5
|
|
||||||
export const IS_ALIGN_END = 6
|
|
||||||
|
|
||||||
// Reconciliation
|
|
||||||
export const NON_BREAKING_SPACE = '\u00A0'
|
|
||||||
const ZERO_WIDTH_SPACE = '\u200b'
|
|
||||||
|
|
||||||
export const DOUBLE_LINE_BREAK = '\n\n'
|
|
||||||
|
|
||||||
// For FF, we need to use a non-breaking space, or it gets composition
|
|
||||||
// in a stuck state.
|
|
||||||
|
|
||||||
const RTL = '\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC'
|
|
||||||
const LTR =
|
|
||||||
'A-Za-z\u00C0-\u00D6\u00D8-\u00F6' +
|
|
||||||
'\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF\u200E\u2C00-\uFB1C' +
|
|
||||||
'\uFE00-\uFE6F\uFEFD-\uFFFF'
|
|
||||||
|
|
||||||
export const RTL_REGEX = new RegExp('^[^' + LTR + ']*[' + RTL + ']')
|
|
||||||
|
|
||||||
export const LTR_REGEX = new RegExp('^[^' + RTL + ']*[' + LTR + ']')
|
|
||||||
|
|
||||||
export const TEXT_TYPE_TO_FORMAT: Record<TextFormatType | string, number> = {
|
|
||||||
bold: IS_BOLD,
|
|
||||||
code: IS_CODE,
|
|
||||||
highlight: IS_HIGHLIGHT,
|
|
||||||
italic: IS_ITALIC,
|
|
||||||
strikethrough: IS_STRIKETHROUGH,
|
|
||||||
subscript: IS_SUBSCRIPT,
|
|
||||||
superscript: IS_SUPERSCRIPT,
|
|
||||||
underline: IS_UNDERLINE,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DETAIL_TYPE_TO_DETAIL: Record<TextDetailType | string, number> = {
|
|
||||||
directionless: IS_DIRECTIONLESS,
|
|
||||||
unmergeable: IS_UNMERGEABLE,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ELEMENT_TYPE_TO_FORMAT: Record<Exclude<ElementFormatType, ''>, number> = {
|
|
||||||
center: IS_ALIGN_CENTER,
|
|
||||||
end: IS_ALIGN_END,
|
|
||||||
justify: IS_ALIGN_JUSTIFY,
|
|
||||||
left: IS_ALIGN_LEFT,
|
|
||||||
right: IS_ALIGN_RIGHT,
|
|
||||||
start: IS_ALIGN_START,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ELEMENT_FORMAT_TO_TYPE: Record<number, ElementFormatType> = {
|
|
||||||
[IS_ALIGN_CENTER]: 'center',
|
|
||||||
[IS_ALIGN_END]: 'end',
|
|
||||||
[IS_ALIGN_JUSTIFY]: 'justify',
|
|
||||||
[IS_ALIGN_LEFT]: 'left',
|
|
||||||
[IS_ALIGN_RIGHT]: 'right',
|
|
||||||
[IS_ALIGN_START]: 'start',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TEXT_MODE_TO_TYPE: Record<TextModeType, 0 | 1 | 2> = {
|
|
||||||
normal: IS_NORMAL,
|
|
||||||
segmented: IS_SEGMENTED,
|
|
||||||
token: IS_TOKEN,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TEXT_TYPE_TO_MODE: Record<number, TextModeType> = {
|
|
||||||
[IS_NORMAL]: 'normal',
|
|
||||||
[IS_SEGMENTED]: 'segmented',
|
|
||||||
[IS_TOKEN]: 'token',
|
|
||||||
}
|
|
||||||
@@ -1,209 +0,0 @@
|
|||||||
import { BannerBlock } from '@/blocks/Banner/Component'
|
|
||||||
import { CallToActionBlock } from '@/blocks/CallToAction/Component'
|
|
||||||
import { CodeBlock, CodeBlockProps } from '@/blocks/Code/Component'
|
|
||||||
import { MediaBlock } from '@/blocks/MediaBlock/Component'
|
|
||||||
import React, { Fragment, JSX } from 'react'
|
|
||||||
import { CMSLink } from '@/components/Link'
|
|
||||||
import { DefaultNodeTypes, SerializedBlockNode } from '@payloadcms/richtext-lexical'
|
|
||||||
import type { BannerBlock as BannerBlockProps } from '@/payload-types'
|
|
||||||
|
|
||||||
import {
|
|
||||||
IS_BOLD,
|
|
||||||
IS_CODE,
|
|
||||||
IS_ITALIC,
|
|
||||||
IS_STRIKETHROUGH,
|
|
||||||
IS_SUBSCRIPT,
|
|
||||||
IS_SUPERSCRIPT,
|
|
||||||
IS_UNDERLINE,
|
|
||||||
} from './nodeFormat'
|
|
||||||
import type {
|
|
||||||
CallToActionBlock as CTABlockProps,
|
|
||||||
MediaBlock as MediaBlockProps,
|
|
||||||
} from '@/payload-types'
|
|
||||||
|
|
||||||
export type NodeTypes =
|
|
||||||
| DefaultNodeTypes
|
|
||||||
| SerializedBlockNode<CTABlockProps | MediaBlockProps | BannerBlockProps | CodeBlockProps>
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
nodes: NodeTypes[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function serializeLexical({ nodes }: Props): JSX.Element {
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
{nodes?.map((node, index): JSX.Element | null => {
|
|
||||||
if (node == null) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.type === 'text') {
|
|
||||||
let text = <React.Fragment key={index}>{node.text}</React.Fragment>
|
|
||||||
if (node.format & IS_BOLD) {
|
|
||||||
text = <strong key={index}>{text}</strong>
|
|
||||||
}
|
|
||||||
if (node.format & IS_ITALIC) {
|
|
||||||
text = <em key={index}>{text}</em>
|
|
||||||
}
|
|
||||||
if (node.format & IS_STRIKETHROUGH) {
|
|
||||||
text = (
|
|
||||||
<span key={index} style={{ textDecoration: 'line-through' }}>
|
|
||||||
{text}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (node.format & IS_UNDERLINE) {
|
|
||||||
text = (
|
|
||||||
<span key={index} style={{ textDecoration: 'underline' }}>
|
|
||||||
{text}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (node.format & IS_CODE) {
|
|
||||||
text = <code key={index}>{node.text}</code>
|
|
||||||
}
|
|
||||||
if (node.format & IS_SUBSCRIPT) {
|
|
||||||
text = <sub key={index}>{text}</sub>
|
|
||||||
}
|
|
||||||
if (node.format & IS_SUPERSCRIPT) {
|
|
||||||
text = <sup key={index}>{text}</sup>
|
|
||||||
}
|
|
||||||
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: Hacky fix for
|
|
||||||
// https://github.com/facebook/lexical/blob/d10c4e6e55261b2fdd7d1845aed46151d0f06a8c/packages/lexical-list/src/LexicalListItemNode.ts#L133
|
|
||||||
// which does not return checked: false (only true - i.e. there is no prop for false)
|
|
||||||
const serializedChildrenFn = (node: NodeTypes): JSX.Element | null => {
|
|
||||||
if (node.children == null) {
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
if (node?.type === 'list' && node?.listType === 'check') {
|
|
||||||
for (const item of node.children) {
|
|
||||||
if ('checked' in item) {
|
|
||||||
if (!item?.checked) {
|
|
||||||
item.checked = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return serializeLexical({ nodes: node.children as NodeTypes[] })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const serializedChildren = 'children' in node ? serializedChildrenFn(node) : ''
|
|
||||||
|
|
||||||
if (node.type === 'block') {
|
|
||||||
const block = node.fields
|
|
||||||
|
|
||||||
const blockType = block?.blockType
|
|
||||||
|
|
||||||
if (!block || !blockType) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (blockType) {
|
|
||||||
case 'cta':
|
|
||||||
return <CallToActionBlock key={index} {...block} />
|
|
||||||
case 'mediaBlock':
|
|
||||||
return (
|
|
||||||
<MediaBlock
|
|
||||||
className="col-start-1 col-span-3"
|
|
||||||
imgClassName="m-0"
|
|
||||||
key={index}
|
|
||||||
{...block}
|
|
||||||
captionClassName="mx-auto max-w-[48rem]"
|
|
||||||
enableGutter={false}
|
|
||||||
disableInnerContainer={true}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
case 'banner':
|
|
||||||
return <BannerBlock className="col-start-2 mb-4" key={index} {...block} />
|
|
||||||
case 'code':
|
|
||||||
return <CodeBlock className="col-start-2" key={index} {...block} />
|
|
||||||
default:
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch (node.type) {
|
|
||||||
case 'linebreak': {
|
|
||||||
return <br className="col-start-2" key={index} />
|
|
||||||
}
|
|
||||||
case 'paragraph': {
|
|
||||||
return (
|
|
||||||
<p className="col-start-2" key={index}>
|
|
||||||
{serializedChildren}
|
|
||||||
</p>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
case 'heading': {
|
|
||||||
const Tag = node?.tag
|
|
||||||
return (
|
|
||||||
<Tag className="col-start-2" key={index}>
|
|
||||||
{serializedChildren}
|
|
||||||
</Tag>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
case 'list': {
|
|
||||||
const Tag = node?.tag
|
|
||||||
return (
|
|
||||||
<Tag className="list col-start-2" key={index}>
|
|
||||||
{serializedChildren}
|
|
||||||
</Tag>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
case 'listitem': {
|
|
||||||
if (node?.checked != null) {
|
|
||||||
return (
|
|
||||||
<li
|
|
||||||
aria-checked={node.checked ? 'true' : 'false'}
|
|
||||||
className={` ${node.checked ? '' : ''}`}
|
|
||||||
key={index}
|
|
||||||
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-to-interactive-role
|
|
||||||
role="checkbox"
|
|
||||||
tabIndex={-1}
|
|
||||||
value={node?.value}
|
|
||||||
>
|
|
||||||
{serializedChildren}
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<li key={index} value={node?.value}>
|
|
||||||
{serializedChildren}
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 'quote': {
|
|
||||||
return (
|
|
||||||
<blockquote className="col-start-2" key={index}>
|
|
||||||
{serializedChildren}
|
|
||||||
</blockquote>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
case 'link': {
|
|
||||||
const fields = node.fields
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CMSLink
|
|
||||||
key={index}
|
|
||||||
newTab={Boolean(fields?.newTab)}
|
|
||||||
reference={fields.doc as any}
|
|
||||||
type={fields.linkType === 'internal' ? 'reference' : 'custom'}
|
|
||||||
url={fields.url}
|
|
||||||
>
|
|
||||||
{serializedChildren}
|
|
||||||
</CMSLink>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
</Fragment>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -22,7 +22,7 @@ export const HighImpactHero: React.FC<Page['hero']> = ({ links, media, richText
|
|||||||
>
|
>
|
||||||
<div className="container mb-8 z-10 relative flex items-center justify-center">
|
<div className="container mb-8 z-10 relative flex items-center justify-center">
|
||||||
<div className="max-w-[36.5rem] text-center">
|
<div className="max-w-[36.5rem] text-center">
|
||||||
{richText && <RichText className="mb-6" content={richText} enableGutter={false} />}
|
{richText && <RichText className="mb-6" data={richText} enableGutter={false} />}
|
||||||
{Array.isArray(links) && links.length > 0 && (
|
{Array.isArray(links) && links.length > 0 && (
|
||||||
<ul className="flex justify-center gap-4">
|
<ul className="flex justify-center gap-4">
|
||||||
{links.map(({ link }, i) => {
|
{links.map(({ link }, i) => {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export const LowImpactHero: React.FC<LowImpactHeroType> = ({ children, richText
|
|||||||
return (
|
return (
|
||||||
<div className="container mt-16">
|
<div className="container mt-16">
|
||||||
<div className="max-w-[48rem]">
|
<div className="max-w-[48rem]">
|
||||||
{children || (richText && <RichText content={richText} enableGutter={false} />)}
|
{children || (richText && <RichText data={richText} enableGutter={false} />)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export const MediumImpactHero: React.FC<Page['hero']> = ({ links, media, richTex
|
|||||||
return (
|
return (
|
||||||
<div className="">
|
<div className="">
|
||||||
<div className="container mb-8">
|
<div className="container mb-8">
|
||||||
{richText && <RichText className="mb-6" content={richText} enableGutter={false} />}
|
{richText && <RichText className="mb-6" data={richText} enableGutter={false} />}
|
||||||
|
|
||||||
{Array.isArray(links) && links.length > 0 && (
|
{Array.isArray(links) && links.length > 0 && (
|
||||||
<ul className="flex gap-4">
|
<ul className="flex gap-4">
|
||||||
@@ -36,7 +36,7 @@ export const MediumImpactHero: React.FC<Page['hero']> = ({ links, media, richTex
|
|||||||
/>
|
/>
|
||||||
{media?.caption && (
|
{media?.caption && (
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<RichText content={media.caption} enableGutter={false} />
|
<RichText data={media.caption} enableGutter={false} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user