Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: pass HTML serializer children as string #29

Merged
merged 3 commits into from
Nov 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
};