Skip to content

Commit

Permalink
refactor: use map for default PrismicRichText serializer
Browse files Browse the repository at this point in the history
  • Loading branch information
angeloashmore committed Jul 29, 2021
1 parent a384efc commit 0ba98f0
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 120 deletions.
199 changes: 79 additions & 120 deletions src/PrismicRichText.tsx
@@ -1,25 +1,22 @@
import {
RichTextMapSerializer,
RichTextFunctionSerializer,
Element,
composeSerializers,
wrapMapSerializer,
serialize,
} from "@prismicio/richtext";
/* eslint-disable react/display-name */
/* eslint-disable react/prop-types */

import * as React from "react";
import * as prismicH from "@prismicio/helpers";
import * as prismicT from "@prismicio/types";
import { usePrismicContext } from "./PrismicProvider";
import { JSXFunctionSerializer } from "./types";
import * as prismicH from "@prismicio/helpers";
import * as prismicR from "@prismicio/richtext";

import { JSXFunctionSerializer, JSXMapSerializer } from "./types";
import { PrismicLink, PrismicLinkProps } from "./PrismicLink";
import { usePrismicContext } from "./PrismicProvider";

/**
* Props for `<PrismicRichText>`.
*/
export type PrismicRichTextProps = {
/** The Prismic Rich Text field to render. */
field: prismicT.RichTextField;

/**
* The Link Resolver used to resolve links.
*
Expand All @@ -28,14 +25,16 @@ export type PrismicRichTextProps = {
* @see Learn about Link Resolvers and Route Resolvers {@link https://prismic.io/docs/core-concepts/link-resolver-route-resolver}
*/
linkResolver?: PrismicLinkProps["linkResolver"];

/**
* A function that maps a Rich Text block to a React component.
*
* @see Learn about Rich Text serializers {@link https://prismic.io/docs/core-concepts/html-serializer}
*
* @deprecated Use the `components` prop instead. Prefer using a map serializer when possible.
* */
htmlSerializer?: RichTextFunctionSerializer<React.ComponentType>;
htmlSerializer?: JSXFunctionSerializer;

/**
* A map or function that maps a Rich Text block to a React component.
*
Expand Down Expand Up @@ -63,15 +62,15 @@ export type PrismicRichTextProps = {
* }
* ```
*/
components?:
| RichTextMapSerializer<JSX.Element>
| RichTextFunctionSerializer<JSX.Element>;
components?: JSXMapSerializer | JSXFunctionSerializer;

/**
* The React component rendered for links when the URL is internal.
*
* @default `<a>`
*/
internalLinkComponent?: PrismicLinkProps["internalComponent"];

/**
* The React component rendered for links when the URL is external.
*
Expand All @@ -80,61 +79,31 @@ export type PrismicRichTextProps = {
externalLinkComponent?: PrismicLinkProps["externalComponent"];
};

/**
* fallback component serializer
*/
function defaultComponentSerializer(
linkResolver: prismicH.LinkResolverFunction<string> | undefined,
internalLinkComponent: PrismicRichTextProps["internalLinkComponent"],
externalLinkComponent: PrismicRichTextProps["externalLinkComponent"],
_type: Parameters<JSXFunctionSerializer>[0],
node: Parameters<JSXFunctionSerializer>[1],
content: Parameters<JSXFunctionSerializer>[2],
children: Parameters<JSXFunctionSerializer>[3],
_key: Parameters<JSXFunctionSerializer>[4],
): JSX.Element {
switch (node.type) {
case Element.heading1:
return <h1>{children}</h1>;

case Element.heading2:
return <h2>{children}</h2>;

case Element.heading3:
return <h3>{children}</h3>;

case Element.heading4:
return <h4>{children}</h4>;

case Element.heading5:
return <h5>{children}</h5>;

case Element.heading6:
return <h6>{children}</h6>;

case Element.paragraph:
return <p>{children}</p>;

case Element.preformatted:
return <pre>{node.text}</pre>;

case Element.strong:
return <strong>{children}</strong>;

case Element.em:
return <em>{children}</em>;

case Element.listItem:
case Element.oListItem:
return <li>{children}</li>;

case Element.list:
return <ul>{children}</ul>;

case Element.oList:
return <ol>{children}</ol>;
type CreateDefaultSerializerArgs = {
linkResolver: prismicH.LinkResolverFunction<string> | undefined;
internalLinkComponent: PrismicRichTextProps["internalLinkComponent"];
externalLinkComponent: PrismicRichTextProps["externalLinkComponent"];
};

case Element.image:
const createDefaultSerializer = (
args: CreateDefaultSerializerArgs,
): JSXFunctionSerializer =>
prismicR.wrapMapSerializer({
heading1: ({ children }) => <h1>{children}</h1>,
heading2: ({ children }) => <h2>{children}</h2>,
heading3: ({ children }) => <h3>{children}</h3>,
heading4: ({ children }) => <h4>{children}</h4>,
heading5: ({ children }) => <h5>{children}</h5>,
heading6: ({ children }) => <h6>{children}</h6>,
paragraph: ({ children }) => <p>{children}</p>,
preformatted: ({ node }) => <pre>{node.text}</pre>,
strong: ({ children }) => <strong>{children}</strong>,
em: ({ children }) => <em>{children}</em>,
listItem: ({ children }) => <li>{children}</li>,
oListItem: ({ children }) => <li>{children}</li>,
list: ({ children }) => <ul>{children}</ul>,
oList: ({ children }) => <ol>{children}</ol>,
image: ({ node }) => {
const img = (
<img
src={node.url}
Expand All @@ -147,9 +116,9 @@ function defaultComponentSerializer(
<p className="block-img">
{node.linkTo ? (
<PrismicLink
linkResolver={linkResolver}
internalComponent={internalLinkComponent}
externalComponent={externalLinkComponent}
linkResolver={args.linkResolver}
internalComponent={args.internalLinkComponent}
externalComponent={args.externalLinkComponent}
field={node.linkTo}
>
{img}
Expand All @@ -159,35 +128,30 @@ function defaultComponentSerializer(
)}
</p>
);

case Element.embed:
return (
<div
data-oembed={node.oembed.embed_url}
data-oembed-type={node.oembed.type}
data-oembed-provider={node.oembed.provider_name}
dangerouslySetInnerHTML={{ __html: node.oembed.html }}
/>
);

case Element.hyperlink:
return (
<PrismicLink
field={node.data}
linkResolver={linkResolver}
internalComponent={internalLinkComponent}
externalComponent={externalLinkComponent}
>
{children}
</PrismicLink>
);
case Element.label:
return <span className={node.data.label}>{children}</span>;
case Element.span:
default:
return <>{content}</>;
}
}
},
embed: ({ node }) => (
<div
data-oembed={node.oembed.embed_url}
data-oembed-type={node.oembed.type}
data-oembed-provider={node.oembed.provider_name}
dangerouslySetInnerHTML={{ __html: node.oembed.html }}
/>
),
hyperlink: ({ node, children }) => (
<PrismicLink
field={node.data}
linkResolver={args.linkResolver}
internalComponent={args.internalLinkComponent}
externalComponent={args.externalLinkComponent}
>
{children}
</PrismicLink>
),
label: ({ node, children }) => (
<span className={node.data.label}>{children}</span>
),
span: ({ text }) => <>{text}</>,
});

/**
* React component that renders content from a Prismic Rich Text field. By default, HTML elements are rendered for each piece of content. A `heading1` block will render an `<h1>` HTML element, for example. Links will use `<PrismicLink>` by default which can be customized using the `internalLinkComponent` and `externalLinkComponent` props.
Expand Down Expand Up @@ -223,30 +187,27 @@ function defaultComponentSerializer(
export const PrismicRichText = (props: PrismicRichTextProps): JSX.Element => {
const context = usePrismicContext();

const val = React.useMemo(() => {
return React.useMemo(() => {
const linkResolver = props.linkResolver || context.linkResolver;
const components = props.components || context.richTextComponents;
const defaultSerializer = createDefaultSerializer({
linkResolver,
internalLinkComponent: props.internalLinkComponent,
externalLinkComponent: props.externalLinkComponent,
});

const serializer = components
? composeSerializers(
? prismicR.composeSerializers(
typeof components === "object"
? wrapMapSerializer(components)
? prismicR.wrapMapSerializer(components)
: components,
defaultComponentSerializer.bind(
null,
linkResolver,
props.internalLinkComponent,
props.externalLinkComponent,
),
defaultSerializer,
)
: defaultComponentSerializer.bind(
null,
linkResolver,
props.internalLinkComponent,
props.externalLinkComponent,
);
const val = serialize(props.field, serializer);
: defaultSerializer;

return val;
const serialized = prismicR.serialize(props.field, serializer);

return <>{serialized}</>;
}, [
props.field,
props.internalLinkComponent,
Expand All @@ -256,6 +217,4 @@ export const PrismicRichText = (props: PrismicRichTextProps): JSX.Element => {
context.linkResolver,
context.richTextComponents,
]);

return <>{val}</>;
};
83 changes: 83 additions & 0 deletions test/PrismicRichText.test.tsx
Expand Up @@ -270,6 +270,33 @@ test("returns <image /> if type is image", (t) => {
t.deepEqual(actual, expected);
});

test("returns <image /> with undefined copyright if not provided", (t) => {
const url = "url";
const alt = "alt";

const field: prismicT.RichTextField = [
{
type: prismicT.RichTextNodeType.image,
url,
alt,
copyright: null,
dimensions: {
width: 100,
height: 100,
},
},
];

const actual = renderJSON(<PrismicRichText field={field} />);
const expected = renderJSON(
<p className="block-img">
<img src={url} alt={alt} data-copyright={undefined} />
</p>,
);

t.deepEqual(actual, expected);
});

test("returns <image /> wrapped in <PrismicLink />", (t) => {
const url = "url";
const alt = "alt";
Expand Down Expand Up @@ -311,6 +338,33 @@ test("returns <image /> wrapped in <PrismicLink />", (t) => {
t.deepEqual(actual, expected);
});

test("returns <div /> with embedded html if type is embed", (t) => {
const oembed = {
embed_url: "https://example.com",
type: "modern html elements",
provider_name: "Prismic",
html: "<marquee>Prismic is fun</marquee>",
};
const field: prismicT.RichTextField = [
{
type: prismicT.RichTextNodeType.embed,
oembed,
},
];

const actual = renderJSON(<PrismicRichText field={field} />);
const expected = renderJSON(
<div
data-oembed={oembed.embed_url}
data-oembed-type={oembed.type}
data-oembed-provider={oembed.provider_name}
dangerouslySetInnerHTML={{ __html: oembed.html }}
/>,
);

t.deepEqual(actual, expected);
});

test("Returns <PrismicLink /> when type is hyperlink", (t) => {
const data: prismicT.FilledLinkToDocumentField = {
id: "id",
Expand Down Expand Up @@ -389,3 +443,32 @@ test("Returns <PrismicLink /> with internalComponent from props", (t) => {

t.deepEqual(actual, expected);
});

test("returns <span /> with label className if type is label", (t) => {
const data = {
label: "label",
};
const field: prismicT.RichTextField = [
{
type: prismicT.RichTextNodeType.paragraph,
text: "label",
spans: [
{
type: prismicT.RichTextNodeType.label,
start: 0,
end: 5,
data,
},
],
},
];

const actual = renderJSON(<PrismicRichText field={field} />);
const expected = renderJSON(
<p>
<span className={data.label}>label</span>
</p>,
);

t.deepEqual(actual, expected);
});

0 comments on commit 0ba98f0

Please sign in to comment.