UNPKG

@prismicio/client

Version:

The official JavaScript + TypeScript client library for Prismic

410 lines (381 loc) 13.7 kB
import { serializeEmbed, serializeHyperlink, serializeImage, serializePreFormatted, serializeSpan, serializeStandardTag, } from "../lib/serializerHelpers" import type { RichTextField } from "../types/value/richText" import type { RichTextFunctionSerializer, RichTextMapSerializer, RichTextMapSerializerFunction, } from "../richtext" import { composeSerializers, serialize, wrapMapSerializer } from "../richtext" import type { LinkResolverFunction } from "./asLink" /** * Serializes a node from a rich text or title field with a function to HTML. * * Unlike a typical `@prismicio/client/richtext` function serializer, this * serializer converts the `children` argument to a single string rather than an * array of strings. * * @see Templating rich text and title fields from Prismic {@link https://prismic.io/docs/template-content-vanilla-javascript#rich-text-and-title} */ export type HTMLRichTextFunctionSerializer = ( type: Parameters<RichTextFunctionSerializer<string>>[0], node: Parameters<RichTextFunctionSerializer<string>>[1], text: Parameters<RichTextFunctionSerializer<string>>[2], children: Parameters<RichTextFunctionSerializer<string>>[3][number], key: Parameters<RichTextFunctionSerializer<string>>[4], ) => string | null | undefined /** * Serializes a node from a rich text or title field with a map to HTML * * Unlike a typical `@prismicio/client/richtext` map serializer, this serializer * converts the `children` property to a single string rather than an array of * strings and accepts shorthand declarations. * * @see Templating rich text and title fields from Prismic {@link https://prismic.io/docs/template-content-vanilla-javascript#rich-text-and-title} */ export type HTMLRichTextMapSerializer = { [P in keyof RichTextMapSerializer<string>]: P extends RichTextMapSerializer<string>["span"] ? HTMLStrictRichTextMapSerializer[P] : HTMLStrictRichTextMapSerializer[P] | HTMLRichTextMapSerializerShorthand } /** * Serializes a node from a rich text or title field with a map to HTML * * Unlike a typical `@prismicio/client/richtext` map serializer, this serializer * converts the `children` property to a single string rather than an array of * strings but doesn't accept shorthand declarations. * * @see Templating rich text and title fields from Prismic {@link https://prismic.io/docs/template-content-vanilla-javascript#rich-text-and-title} */ export type HTMLStrictRichTextMapSerializer = { [P in keyof RichTextMapSerializer<string>]: (payload: { type: Parameters<HTMLRichTextMapSerializerFunction<P>>[0]["type"] node: Parameters<HTMLRichTextMapSerializerFunction<P>>[0]["node"] text: Parameters<HTMLRichTextMapSerializerFunction<P>>[0]["text"] children: Parameters< HTMLRichTextMapSerializerFunction<P> >[0]["children"][number] key: Parameters<HTMLRichTextMapSerializerFunction<P>>[0]["key"] }) => string | null | undefined } /** * A {@link RichTextMapSerializerFunction} type specifically for * {@link HTMLRichTextMapSerializer}. * * @typeParam BlockName - The serializer's rich text block type. */ type HTMLRichTextMapSerializerFunction< BlockType extends keyof RichTextMapSerializer<string>, > = RichTextMapSerializerFunction< string, ExtractNodeGeneric<RichTextMapSerializer<string>[BlockType]>, ExtractTextTypeGeneric<RichTextMapSerializer<string>[BlockType]> > /** * Returns the `Node` generic from {@link RichTextMapSerializerFunction}. * * @typeParam T - The `RichTextMapSerializerFunction` containing the needed * `Node` generic. */ type ExtractNodeGeneric<T> = T extends RichTextMapSerializerFunction< // eslint-disable-next-line @typescript-eslint/no-explicit-any any, infer U, // eslint-disable-next-line @typescript-eslint/no-explicit-any any > ? U : never /** * Returns the `TextType` generic from {@link RichTextMapSerializerFunction}. * * @typeParam T - The `RichTextMapSerializerFunction` containing the needed * `TextType` generic. */ type ExtractTextTypeGeneric<T> = T extends RichTextMapSerializerFunction< // eslint-disable-next-line @typescript-eslint/no-explicit-any any, // eslint-disable-next-line @typescript-eslint/no-explicit-any any, infer U > ? U : never /** * A shorthand definition for {@link HTMLRichTextMapSerializer} element types. */ export type HTMLRichTextMapSerializerShorthand = { /** * Classes to apply to the element type. */ class?: string /** * Other attributes to apply to the element type. */ [Attribute: string]: string | boolean | null | undefined } /** * Serializes a node from a rich text or title field with a map or a function to * HTML * * @see {@link HTMLRichTextMapSerializer} and {@link HTMLRichTextFunctionSerializer} * @see Templating rich text and title fields from Prismic {@link https://prismic.io/docs/template-content-vanilla-javascript#rich-text-and-title} */ export type HTMLRichTextSerializer = | HTMLRichTextMapSerializer | HTMLRichTextFunctionSerializer /** * Creates a HTML rich text serializer with a given link resolver and provide * sensible and safe defaults for every node type * * @internal */ const createHTMLRichTextSerializer = ( linkResolver: LinkResolverFunction | undefined | null, serializer?: HTMLRichTextMapSerializer | null, ): RichTextFunctionSerializer<string> => { const useSerializerOrDefault = < BlockType extends keyof RichTextMapSerializer<string>, >( nodeSerializerOrShorthand: HTMLRichTextMapSerializer[BlockType], defaultWithShorthand: NonNullable< HTMLStrictRichTextMapSerializer[BlockType] >, ): NonNullable<HTMLStrictRichTextMapSerializer[BlockType]> => { if (typeof nodeSerializerOrShorthand === "function") { return ((payload) => { return ( ( nodeSerializerOrShorthand as HTMLStrictRichTextMapSerializer[BlockType] )?.(payload) || defaultWithShorthand(payload) ) }) as NonNullable<HTMLStrictRichTextMapSerializer[BlockType]> } return defaultWithShorthand } const mapSerializer: Required<HTMLStrictRichTextMapSerializer> = { heading1: useSerializerOrDefault<"heading1">( serializer?.heading1, serializeStandardTag<"heading1">("h1", serializer?.heading1), ), heading2: useSerializerOrDefault<"heading2">( serializer?.heading2, serializeStandardTag<"heading2">("h2", serializer?.heading2), ), heading3: useSerializerOrDefault<"heading3">( serializer?.heading3, serializeStandardTag<"heading3">("h3", serializer?.heading3), ), heading4: useSerializerOrDefault<"heading4">( serializer?.heading4, serializeStandardTag<"heading4">("h4", serializer?.heading4), ), heading5: useSerializerOrDefault<"heading5">( serializer?.heading5, serializeStandardTag<"heading5">("h5", serializer?.heading5), ), heading6: useSerializerOrDefault<"heading6">( serializer?.heading6, serializeStandardTag<"heading6">("h6", serializer?.heading6), ), paragraph: useSerializerOrDefault<"paragraph">( serializer?.paragraph, serializeStandardTag<"paragraph">("p", serializer?.paragraph), ), preformatted: useSerializerOrDefault<"preformatted">( serializer?.preformatted, serializePreFormatted(serializer?.preformatted), ), strong: useSerializerOrDefault<"strong">( serializer?.strong, serializeStandardTag<"strong">("strong", serializer?.strong), ), em: useSerializerOrDefault<"em">( serializer?.em, serializeStandardTag<"em">("em", serializer?.em), ), listItem: useSerializerOrDefault<"listItem">( serializer?.listItem, serializeStandardTag<"listItem">("li", serializer?.listItem), ), oListItem: useSerializerOrDefault<"oListItem">( serializer?.oListItem, serializeStandardTag<"oListItem">("li", serializer?.oListItem), ), list: useSerializerOrDefault<"list">( serializer?.list, serializeStandardTag<"list">("ul", serializer?.list), ), oList: useSerializerOrDefault<"oList">( serializer?.oList, serializeStandardTag<"oList">("ol", serializer?.oList), ), image: useSerializerOrDefault<"image">( serializer?.image, serializeImage(linkResolver, serializer?.image), ), embed: useSerializerOrDefault<"embed">( serializer?.embed, serializeEmbed(serializer?.embed), ), hyperlink: useSerializerOrDefault<"hyperlink">( serializer?.hyperlink, serializeHyperlink(linkResolver, serializer?.hyperlink), ), label: useSerializerOrDefault<"label">( serializer?.label, serializeStandardTag<"label">("span", serializer?.label), ), span: useSerializerOrDefault<"span">(serializer?.span, serializeSpan()), } return wrapMapSerializerWithStringChildren(mapSerializer) } /** * Wraps a map serializer into a regular function serializer. The given map * serializer should accept children as a string, not as an array of strings * like `@prismicio/client/richtext`'s `wrapMapSerializer`. * * @param mapSerializer - Map serializer to wrap * * @returns A regular function serializer */ const wrapMapSerializerWithStringChildren = ( mapSerializer: HTMLStrictRichTextMapSerializer, ): RichTextFunctionSerializer<string> => { const modifiedMapSerializer = {} as RichTextMapSerializer<string> for (const tag in mapSerializer) { const tagSerializer = mapSerializer[tag as keyof typeof mapSerializer] if (tagSerializer) { modifiedMapSerializer[tag as keyof typeof mapSerializer] = (payload) => { return tagSerializer({ ...payload, // @ts-expect-error - merging blockSerializer types causes TS to bail to a never type children: payload.children.join(""), }) } } } return wrapMapSerializer(modifiedMapSerializer) } /** * Configuration that determines the output of `asHTML()`. */ type AsHTMLConfig = { /** * An optional link resolver function to resolve links. Without it you're * expected to use the `routes` options from the API. */ linkResolver?: LinkResolverFunction | null /** * An optional rich text serializer, unhandled cases will fallback to the * default serializer */ serializer?: HTMLRichTextSerializer | null } // TODO: Remove when we remove support for deprecated tuple-style configuration. /** * @deprecated Use object-style configuration instead. */ type AsHTMLDeprecatedTupleConfig = [ linkResolver?: LinkResolverFunction | null, serializer?: HTMLRichTextSerializer | null, ] /** * The return type of `asHTML()`. */ type AsHTMLReturnType<Field extends RichTextField | null | undefined> = Field extends RichTextField ? string : null // TODO: Remove overload when we remove support for deprecated tuple-style configuration. export const asHTML: { /** * Serializes a rich text or title field to an HTML string. * * @param richTextField - A rich text or title field from Prismic * @param config - Configuration that determines the output of `asHTML()` * * @returns HTML equivalent of the provided rich text or title field * * @see Templating rich text and title fields from Prismic {@link https://prismic.io/docs/template-content-vanilla-javascript#rich-text-and-title} */ <Field extends RichTextField | null | undefined>( richTextField: Field, config?: AsHTMLConfig, ): AsHTMLReturnType<Field> /** * Serializes a rich text or title field to an HTML string. * * @deprecated Use object-style configuration instead. * * @param richTextField - A rich text or title field from Prismic * @param linkResolver - An optional link resolver function to resolve links, * without it you're expected to use the `routes` options from the API * @param serializer - An optional rich text serializer, unhandled cases will * fallback to the default serializer * * @returns HTML equivalent of the provided rich text or title field * * @see Templating rich text and title fields from Prismic {@link https://prismic.io/docs/template-content-vanilla-javascript#rich-text-and-title} */ <Field extends RichTextField | null | undefined>( richTextField: Field, ...config: AsHTMLDeprecatedTupleConfig ): AsHTMLReturnType<Field> } = <Field extends RichTextField | null | undefined>( richTextField: Field, // TODO: Rename to `config` when we remove support for deprecated tuple-style configuration. ...configObjectOrTuple: [config?: AsHTMLConfig] | AsHTMLDeprecatedTupleConfig ): AsHTMLReturnType<Field> => { if (richTextField) { // TODO: Remove when we remove support for deprecated tuple-style configuration. const [configObjectOrLinkResolver, maybeSerializer] = configObjectOrTuple let config: AsHTMLConfig if ( typeof configObjectOrLinkResolver === "function" || configObjectOrLinkResolver == null ) { config = { linkResolver: configObjectOrLinkResolver, serializer: maybeSerializer, } } else { config = { ...configObjectOrLinkResolver } } let serializer: RichTextFunctionSerializer<string> if (config.serializer) { if (typeof config.serializer === "function") { serializer = composeSerializers( (type, node, text, children, key) => // TypeScript doesn't narrow the type correctly here since it is now in a callback function, so we have to cast it here. (config.serializer as HTMLRichTextFunctionSerializer)( type, node, text, children.join(""), key, ), createHTMLRichTextSerializer(config.linkResolver), ) } else { serializer = createHTMLRichTextSerializer( config.linkResolver, config.serializer, ) } } else { serializer = createHTMLRichTextSerializer(config.linkResolver) } return serialize(richTextField, serializer).join( "", ) as AsHTMLReturnType<Field> } else { return null as AsHTMLReturnType<Field> } }