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

refactor(helpers): standardize helpers interface #288

Merged
merged 5 commits into from
May 3, 2023
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
124 changes: 99 additions & 25 deletions src/helpers/asHTML.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,7 @@ export type HTMLRichTextSerializer =
* @internal
*/
const createDefaultHTMLRichTextSerializer = (
linkResolver:
| LinkResolverFunction<string | null | undefined>
| undefined
| null,
linkResolver: LinkResolverFunction | undefined | null,
): RichTextFunctionSerializer<string> => {
return (_type, node, text, children, _key) => {
switch (node.type) {
Expand Down Expand Up @@ -203,41 +200,118 @@ const wrapMapSerializerWithStringChildren = (
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
*/
htmlRichTextSerializer?: HTMLRichTextSerializer | null;
};

// TODO: Remove when we remove support for deprecated tuple-style configuration.
/**
* @deprecated Use object-style configuration instead.
*/
type AsHTMLDeprecatedTupleConfig = [
linkResolver?: LinkResolverFunction | null,
htmlRichTextSerializer?: HTMLRichTextSerializer | null,
];

/**
* The return type of `asHTML()`.
*/
type AsHTMLReturnType<Field extends RichTextField | null | undefined> =
Field extends RichTextField ? string : null;

/**
* Serializes a Rich Text or Title field to an HTML string
*
* @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 htmlRichTextSerializer - 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}
*/
export const asHTML = <Field extends RichTextField | null | undefined>(
// 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.
*
* @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 htmlRichTextSerializer - 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}
*
* @deprecated Use object-style configuration instead.
*
* ```ts
* asHTML(field);
* asHTML(field, { linkResolver });
* asHTML(field, { htmlRichTextSerializer });
* asHTML(field, { linkResolver, htmlRichTextSerializer });
* ```
*/
<Field extends RichTextField | null | undefined>(
richTextField: Field,
...config: AsHTMLDeprecatedTupleConfig
): AsHTMLReturnType<Field>;
} = <Field extends RichTextField | null | undefined>(
richTextField: Field,
linkResolver?: LinkResolverFunction<string | null | undefined> | null,
htmlRichTextSerializer?: HTMLRichTextSerializer | null,
// TODO: Rename to `config` when we remove support for deprecated tuple-style configuration.
...configObjectOrTuple: [config?: AsHTMLConfig] | AsHTMLDeprecatedTupleConfig
lihbr marked this conversation as resolved.
Show resolved Hide resolved
): AsHTMLReturnType<Field> => {
if (richTextField) {
// TODO: Remove when we remove support for deprecated tuple-style configuration.
const [configObjectOrLinkResolver, maybeHTMLRichTextSerializer] =
configObjectOrTuple;
let config: AsHTMLConfig;
if (
typeof configObjectOrLinkResolver === "function" ||
configObjectOrLinkResolver == null
) {
config = {
linkResolver: configObjectOrLinkResolver,
htmlRichTextSerializer: maybeHTMLRichTextSerializer,
};
} else {
config = { ...configObjectOrLinkResolver };
}
angeloashmore marked this conversation as resolved.
Show resolved Hide resolved

let serializer: RichTextFunctionSerializer<string>;
if (htmlRichTextSerializer) {
if (config.htmlRichTextSerializer) {
serializer = composeSerializers(
typeof htmlRichTextSerializer === "object"
? wrapMapSerializerWithStringChildren(htmlRichTextSerializer)
typeof config.htmlRichTextSerializer === "object"
? wrapMapSerializerWithStringChildren(config.htmlRichTextSerializer)
: (type, node, text, children, key) =>
htmlRichTextSerializer(type, node, text, children.join(""), key),
createDefaultHTMLRichTextSerializer(linkResolver),
// TypeScript doesn't narrow the type correctly here since it is now in a callback function, so we have to cast it here.
(config.htmlRichTextSerializer as HTMLRichTextFunctionSerializer)(
type,
node,
text,
children.join(""),
key,
),
createDefaultHTMLRichTextSerializer(config.linkResolver),
);
} else {
serializer = createDefaultHTMLRichTextSerializer(linkResolver);
serializer = createDefaultHTMLRichTextSerializer(config.linkResolver);
}

return serialize(richTextField, serializer).join(
Expand Down
6 changes: 3 additions & 3 deletions src/helpers/asImagePixelDensitySrcSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ type AsImagePixelDensitySrcSetReturnType<
*
* @param field - Image field (or one of its responsive views) from which to get
* an image URL.
* @param params - An object of Imgix URL API parameters. The `pixelDensities`
* @param config - An object of Imgix URL API parameters. The `pixelDensities`
* parameter defines the resulting `srcset` widths.
*
* @returns A `srcset` attribute value for the Image field with Imgix URL
Expand All @@ -76,12 +76,12 @@ export const asImagePixelDensitySrcSet = <
Field extends ImageFieldImage | null | undefined,
>(
field: Field,
params: AsImagePixelDensitySrcSetConfig = {},
config: AsImagePixelDensitySrcSetConfig = {},
): AsImagePixelDensitySrcSetReturnType<Field> => {
if (field && isImageThumbnailFilled(field)) {
// We are using destructuring to omit `pixelDensities` from the
// object we will pass to `buildURL()`.
const { pixelDensities = DEFAULT_PIXEL_DENSITIES, ...imgixParams } = params;
const { pixelDensities = DEFAULT_PIXEL_DENSITIES, ...imgixParams } = config;

return {
src: buildURL(field.url, imgixParams),
Expand Down
6 changes: 3 additions & 3 deletions src/helpers/asImageSrc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,18 @@ type AsImageSrcReturnType<Field extends ImageFieldImage | null | undefined> =
*
* @param field - Image field (or one of its responsive views) from which to get
* an image URL.
* @param params - An object of Imgix URL API parameters to transform the image.
* @param config - An object of Imgix URL API parameters to transform the image.
*
* @returns The Image field's image URL with transformations applied (if given).
* If the Image field is empty, `null` is returned.
* @see Imgix URL parameters reference: https://docs.imgix.com/apis/rendering
*/
export const asImageSrc = <Field extends ImageFieldImage | null | undefined>(
field: Field,
params: ImgixURLParams = {},
config: ImgixURLParams = {},
): AsImageSrcReturnType<Field> => {
if (field && isImageThumbnailFilled(field)) {
return buildURL(field.url, params) as AsImageSrcReturnType<Field>;
return buildURL(field.url, config) as AsImageSrcReturnType<Field>;
} else {
return null as AsImageSrcReturnType<Field>;
}
Expand Down
6 changes: 3 additions & 3 deletions src/helpers/asImageWidthSrcSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ type AsImageWidthSrcSetConfig = Omit<BuildWidthSrcSetParams, "widths"> & {
*
* @param field - Image field (or one of its responsive views) from which to get
* an image URL.
* @param params - An object of Imgix URL API parameters. The `widths` parameter
* @param config - An object of Imgix URL API parameters. The `widths` parameter
* defines the resulting `srcset` widths. Pass `"thumbnails"` to automatically
* use the field's responsive views.
*
Expand All @@ -80,7 +80,7 @@ export const asImageWidthSrcSet = <
Field extends ImageFieldImage | null | undefined,
>(
field: Field,
params: AsImageWidthSrcSetConfig = {},
config: AsImageWidthSrcSetConfig = {},
): AsImageWidthSrcSetReturnType<Field> => {
if (field && isFilled.imageThumbnail(field)) {
// We are using destructuring to omit `widths` from the object
Expand All @@ -89,7 +89,7 @@ export const asImageWidthSrcSet = <
widths = DEFAULT_WIDTHS,
// eslint-disable-next-line prefer-const
...imgixParams
} = params;
} = config;
const {
url,
dimensions,
Expand Down
120 changes: 102 additions & 18 deletions src/helpers/asLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,28 @@ export type LinkResolverFunction<ReturnType = string | null | undefined> = (
linkToDocumentField: FilledContentRelationshipField,
) => ReturnType;

/**
* Configuration that determines the output of `asLink()`.
*/
type AsLinkConfig<LinkResolverFunctionReturnType = string | null | undefined> =
{
/**
* An optional Link Resolver function. Without it, you are
* expected to use the `routes` options from the API.
*/
linkResolver?: LinkResolverFunction<LinkResolverFunctionReturnType> | null;
};

// TODO: Remove when we remove support for deprecated tuple-style configuration.
/**
* @deprecated Use object-style configuration instead.
*/
type AsLinkDeprecatedTupleConfig<
LinkResolverFunctionReturnType = string | null | undefined,
> = [
linkResolver?: LinkResolverFunction<LinkResolverFunctionReturnType> | null,
];

/**
* The return type of `asLink()`.
*/
Expand All @@ -37,21 +59,66 @@ export type AsLinkReturnType<
? LinkResolverFunctionReturnType | string | null
: null;

/**
* Resolves any type of Link field or Prismic document to a URL.
*
* @typeParam LinkResolverFunctionReturnType - Link Resolver function return
* type
* @typeParam Field - Link field or Prismic document to resolve to a URL
* @param linkFieldOrDocument - Any kind of Link field or a document to resolve
* @param linkResolver - An optional Link Resolver function. Without it, you are
* expected to use the `routes` options from the API
*
* @returns Resolved URL or, if the provided Link field or document is empty, `null`
* @see Prismic Link Resolver documentation: {@link https://prismic.io/docs/route-resolver#link-resolver}
* @see Prismic API `routes` options documentation: {@link https://prismic.io/docs/route-resolver}
*/
export const asLink = <
// TODO: Remove overload when we remove support for deprecated tuple-style configuration.
export const asLink: {
/**
* Resolves any type of Link field or Prismic document to a URL.
*
* @typeParam LinkResolverFunctionReturnType - Link Resolver function return
* type
* @typeParam Field - Link field or Prismic document to resolve to a URL
* @param linkFieldOrDocument - Any kind of Link field or a document to resolve
* @param config - Configuration that determines the output of `asLink()`
*
* @returns Resolved URL or, if the provided Link field or document is empty, `null`
* @see Prismic Link Resolver documentation: {@link https://prismic.io/docs/route-resolver#link-resolver}
* @see Prismic API `routes` options documentation: {@link https://prismic.io/docs/route-resolver}
*/
<
LinkResolverFunctionReturnType = string | null | undefined,
Field extends LinkField | PrismicDocument | null | undefined =
| LinkField
| PrismicDocument
| null
| undefined,
>(
linkFieldOrDocument: Field,
config?: AsLinkConfig<LinkResolverFunctionReturnType>,
): AsLinkReturnType<LinkResolverFunctionReturnType, Field>;

/**
* Resolves any type of Link field or Prismic document to a URL.
*
* @typeParam LinkResolverFunctionReturnType - Link Resolver function return
* type
* @typeParam Field - Link field or Prismic document to resolve to a URL
* @param linkFieldOrDocument - Any kind of Link field or a document to resolve
* @param linkResolver - An optional Link Resolver function. Without it, you are
* expected to use the `routes` options from the API
*
* @returns Resolved URL or, if the provided Link field or document is empty, `null`
* @see Prismic Link Resolver documentation: {@link https://prismic.io/docs/route-resolver#link-resolver}
* @see Prismic API `routes` options documentation: {@link https://prismic.io/docs/route-resolver}
*
* @deprecated Use object-style configuration instead.
*
* ```ts
* asLink(field);
* asLink(field, { linkResolver });
* ```
*/
<
LinkResolverFunctionReturnType = string | null | undefined,
Field extends LinkField | PrismicDocument | null | undefined =
| LinkField
| PrismicDocument
| null
| undefined,
>(
linkFieldOrDocument: Field,
...config: AsLinkDeprecatedTupleConfig<LinkResolverFunctionReturnType>
): AsLinkReturnType<LinkResolverFunctionReturnType, Field>;
} = <
LinkResolverFunctionReturnType = string | null | undefined,
Field extends LinkField | PrismicDocument | null | undefined =
| LinkField
Expand All @@ -60,7 +127,10 @@ export const asLink = <
| undefined,
>(
linkFieldOrDocument: Field,
linkResolver?: LinkResolverFunction<LinkResolverFunctionReturnType> | null,
// TODO: Rename to `config` when we remove support for deprecated tuple-style configuration.
...configObjectOrTuple:
| [config?: AsLinkConfig<LinkResolverFunctionReturnType>]
| AsLinkDeprecatedTupleConfig<LinkResolverFunctionReturnType>
lihbr marked this conversation as resolved.
Show resolved Hide resolved
): AsLinkReturnType<LinkResolverFunctionReturnType, Field> => {
if (!linkFieldOrDocument) {
return null as AsLinkReturnType<LinkResolverFunctionReturnType, Field>;
Expand All @@ -78,6 +148,20 @@ export const asLink = <
: documentToLinkField(linkFieldOrDocument)
) as LinkField;

// TODO: Remove when we remove support for deprecated tuple-style configuration.
const [configObjectOrLinkResolver] = configObjectOrTuple;
let config: AsLinkConfig<LinkResolverFunctionReturnType>;
if (
typeof configObjectOrLinkResolver === "function" ||
configObjectOrLinkResolver == null
) {
config = {
linkResolver: configObjectOrLinkResolver,
};
} else {
config = { ...configObjectOrLinkResolver };
}

switch (linkField.link_type) {
case LinkType.Media:
case LinkType.Web:
Expand All @@ -87,9 +171,9 @@ export const asLink = <
>;

case LinkType.Document: {
if ("id" in linkField && linkResolver) {
if ("id" in linkField && config.linkResolver) {
// When using Link Resolver...
const resolvedURL = linkResolver(linkField);
const resolvedURL = config.linkResolver(linkField);

if (resolvedURL != null) {
return resolvedURL as AsLinkReturnType<
Expand Down
Loading