import { isFilled, type LinkResolverFunction, type RichTextField, type RTAnyNode, } from "@prismicio/client" import { composeSerializers, serialize, wrapMapSerializer, type RichTextFunctionSerializer, type RichTextMapSerializer, } from "@prismicio/client/richtext" import { DEV } from "esm-env" import { cloneElement, type ComponentType, type FC, Fragment, isValidElement, type ReactNode, } from "react" import { devMsg } from "./lib/devMsg.js" import { type LinkProps, PrismicLink } from "./PrismicLink.js" /** * A function mapping rich text block types to React Components. It is used to render rich text * fields. * * @see Templating rich text fields {@link https://prismic.io/docs/fields/rich-text} */ export type JSXFunctionSerializer = RichTextFunctionSerializer /** * A map of rich text block types to React Components. It is used to render rich text fields. * * @see Templating rich text fields {@link https://prismic.io/docs/fields/rich-text} */ export type RichTextComponents = RichTextMapSerializer /** @deprecated Use `RichTextComponents` instead. */ export type JSXMapSerializer = RichTextComponents /** Props for ``. */ export type PrismicRichTextProps = { /** The Prismic rich text field to render. */ field: RichTextField | null | undefined /** * The link resolver used to resolve links. * * @remarks * If your app uses route resolvers when querying for your Prismic repository's content, a link * resolver does not need to be provided. * @see Learn about link resolvers and route resolvers {@link https://prismic.io/docs/routes} */ linkResolver?: LinkResolverFunction /** * A map or function that maps a rich text block to a React component. * * @remarks * Prefer using a map serializer over the function serializer when possible. The map serializer * is simpler to maintain. * @example * A map serializer. * * ```jsx * { * heading1: ({children}) => {children} * } * ``` * * @example * A function serializer. * * ```jsx * (type, node, content, children) => { * switch (type) { * case "heading1": { * return {children}; * } * } * }; * ``` */ components?: RichTextComponents | JSXFunctionSerializer /** * The React component rendered for links when the URL is internal. * * @defaultValue `` */ internalLinkComponent?: ComponentType /** * The React component rendered for links when the URL is external. * * @defaultValue `` */ externalLinkComponent?: ComponentType /** * The value to be rendered when the field is empty. If a fallback is not given, `null` will be * rendered. */ fallback?: ReactNode } type CreateDefaultSerializerArgs = { linkResolver: LinkResolverFunction | undefined internalLinkComponent?: ComponentType externalLinkComponent?: ComponentType } const getDir = (node: RTAnyNode): "rtl" | undefined => { if ("direction" in node && node.direction === "rtl") { return "rtl" } } const createDefaultSerializer = (args: CreateDefaultSerializerArgs): JSXFunctionSerializer => wrapMapSerializer({ heading1: ({ node, children, key }) => (

{children}

), heading2: ({ node, children, key }) => (

{children}

), heading3: ({ node, children, key }) => (

{children}

), heading4: ({ node, children, key }) => (

{children}

), heading5: ({ node, children, key }) => (
{children}
), heading6: ({ node, children, key }) => (
{children}
), paragraph: ({ node, children, key }) => (

{children}

), preformatted: ({ node, key }) =>
{node.text}
, strong: ({ children, key }) => {children}, em: ({ children, key }) => {children}, listItem: ({ node, children, key }) => (
  • {children}
  • ), oListItem: ({ node, children, key }) => (
  • {children}
  • ), list: ({ children, key }) =>
      {children}
    , oList: ({ children, key }) =>
      {children}
    , image: ({ node, key }) => { const img = ( {node.alt ) return (

    {node.linkTo ? ( {img} ) : ( img )}

    ) }, embed: ({ node, key }) => (
    ), hyperlink: ({ node, children, key }) => ( {children} ), label: ({ node, children, key }) => ( {children} ), span: ({ text, key }) => { const result: ReactNode[] = [] let i = 0 for (const line of text.split("\n")) { if (i > 0) { result.push(
    ) } result.push({line}) i++ } return {result} }, }) /** * Renders content from a Prismic rich text field as React components. * * @example * ;```tsx * ; * ``` * * @see Learn how to style rich text, use custom components, and use labels for custom formatting: {@link https://prismic.io/docs/fields/rich-text} */ export const PrismicRichText: FC = (props) => { const { linkResolver, field, fallback, components, externalLinkComponent, internalLinkComponent, ...restProps } = props if (DEV) { if ("className" in restProps) { console.warn( `[PrismicRichText] className cannot be passed to since it renders an array without a wrapping component. For more details, see ${devMsg( "classname-is-not-a-valid-prop", )}.`, field, ) } } if (!isFilled.richText(field)) { return fallback != null ? <>{fallback} : null } const serializer = composeSerializers( typeof components === "object" ? wrapMapSerializer(components) : components, createDefaultSerializer({ linkResolver, internalLinkComponent, externalLinkComponent, }), ) // The serializer is wrapped in a higher-order function that // automatically applies a key to React Elements if one is not already // given. const serialized = serialize(field, (type, node, text, children, key) => { const result = serializer(type, node, text, children, key) if (isValidElement(result) && result.key == null) { return cloneElement(result, { key }) } else { return result } }) if (!serialized) { return fallback != null ? <>{fallback} : null } return <>{serialized} }