Skip to content

Commit

Permalink
Merge pull request #29 from prismicio/aa/string-children
Browse files Browse the repository at this point in the history
  • Loading branch information
lihbr authored Nov 5, 2021
2 parents 9b0f7d5 + 05bd482 commit d7d6368
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 7 deletions.
39 changes: 36 additions & 3 deletions src/asHTML.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import {
serialize,
Element,
composeSerializers,
wrapMapSerializer,
RichTextFunctionSerializer,
RichTextMapSerializer,
wrapMapSerializer,
} from "@prismicio/richtext";
import { RichTextField } from "@prismicio/types";

Expand Down Expand Up @@ -75,6 +76,37 @@ const createDefaultHTMLSerializer = (
};
};

/**
* 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/richtext`'s `wrapMapSerializer`.
*
* @param mapSerializer - Map serializer to wrap
*
* @returns A regular function serializer
*/
const wrapMapSerializerWithStringChildren = (
mapSerializer: HTMLMapSerializer,
): 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);
};

/**
* Serializes a rich text or title field to an HTML string
*
Expand All @@ -96,8 +128,9 @@ export const asHTML = (
if (htmlSerializer) {
serializer = composeSerializers(
typeof htmlSerializer === "object"
? wrapMapSerializer(htmlSerializer)
: htmlSerializer,
? wrapMapSerializerWithStringChildren(htmlSerializer)
: (type, node, text, children, key) =>
htmlSerializer(type, node, text, children.join(""), key),
createDefaultHTMLSerializer(linkResolver),
);
} else {
Expand Down
70 changes: 68 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { FilledLinkToDocumentField } from "@prismicio/types";
import {
RichTextFunctionSerializer,
RichTextMapSerializer,
RichTextMapSerializerFunction,
} from "@prismicio/richtext";

/**
Expand All @@ -21,13 +22,78 @@ export type LinkResolverFunction<ReturnType = string> = (
/**
* Serializes a node from a rich text or title field with a function to HTML
*
* Unlike a typical `@prismicio/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/technologies/templating-rich-text-and-title-fields-javascript}
*/
export type HTMLFunctionSerializer = RichTextFunctionSerializer<string>;
export type HTMLFunctionSerializer = (
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/richtext` map serializer, this serializer
* converts the `children` property to a single string rather than an array of strings.
*
* @see Templating rich text and title fields from Prismic {@link https://prismic.io/docs/technologies/templating-rich-text-and-title-fields-javascript}
*/
export type HTMLMapSerializer = RichTextMapSerializer<string>;
export type HTMLMapSerializer = {
[P in keyof RichTextMapSerializer<string>]: (payload: {
type: Parameters<HTMLMapSerializerFunction<P>>[0]["type"];
node: Parameters<HTMLMapSerializerFunction<P>>[0]["node"];
text: Parameters<HTMLMapSerializerFunction<P>>[0]["text"];
children: Parameters<HTMLMapSerializerFunction<P>>[0]["children"][number];
key: Parameters<HTMLMapSerializerFunction<P>>[0]["key"];
}) => string | null | undefined;
};

/**
* A {@link RichTextMapSerializerFunction} type specifically for {@link HTMLMapSerializer}.
*
* @typeParam BlockName - The serializer's Rich Text block type.
*/
type HTMLMapSerializerFunction<
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;
2 changes: 1 addition & 1 deletion test/__testutils__/htmlFunctionSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const htmlFunctionSerializer: HTMLFunctionSerializer = (
) => {
switch (node.type) {
case Element.heading1: {
return `<h2>${children.join("")}</h2>`;
return `<h2>${children}</h2>`;
}
}

Expand Down
4 changes: 3 additions & 1 deletion test/__testutils__/htmlMapSerializer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { HTMLMapSerializer } from "../../src";

export const htmlMapSerializer: HTMLMapSerializer = {
heading1: ({ children }) => `<h2>${children.join("")}</h2>`,
heading1: ({ children }) => `<h2>${children}</h2>`,
// `undefined` serializers should be treated the same as not including it.
heading2: undefined,
};

0 comments on commit d7d6368

Please sign in to comment.