UNPKG

contentful-image

Version:

A TypeScript implementation of the Contentful images API

263 lines (240 loc) 8.66 kB
/** * Defines all types that can be provided to the contentfulImage function as * source objects. The URL from this object is extracted in the function. * * You can provide the source as * - direct URL string * - any object containing a string property `"url"`(usually from the * contentful image field with `imageField.fields.file`) * - any object that has a string url in the `"file.url"` property (usually from * the contentful image field with `imageField.fields`) * - any object that has a string url in the `"fields.file.url"` property * (usually from the contentful image field directly). */ export type ContentfulImageSource = | string | { url: string } | { file: { url: string } } | { fields: { file: { url: string } } }; /** * Defines the image format. Defaults to the original image format. * * Refer to [the contentful images api documentation]( * https://www.contentful.com/developers/docs/references/images-api/#/reference/image-manipulation) * for more info. */ export type ContentfulImageOptionsFormat = | "jpg" | "png" | "webp" | "gif" | "avif" | "jpg/progressive" | "png/png8"; /** * Defines the resizing behaviour. Defaults to `fit`. * * Refer to [the contentful images api documentation]( * https://www.contentful.com/developers/docs/references/images-api/#/reference/image-manipulation) * for more info. */ export type ContentfulImageOptionsFit = | "pad" | "fill" | "scale" | "crop" | "thumb"; /** * Defines the focus area when resizing with the `pad`, `fill`, `crop` or * `thumb` option to either a position in the image or automatically detected * faces. Defaults to `center`. * * Refer to [the contentful images api documentation]( * https://www.contentful.com/developers/docs/references/images-api/#/reference/image-manipulation) * for more info. */ export type ContentfulImageOptionsFocusArea = | "center" | "top" | "left" | "right" | "bottom" | "top_right" | "bottom_right" | "top_left" | "bottom_left" | "face" | "faces"; /** * Round the corners by a given pixel amount or to a circle or ellipse with * `max`. Padding color is defined by the background color property. Defaults * to `0`. * * Refer to [the contentful images api documentation]( * https://www.contentful.com/developers/docs/references/images-api/#/reference/image-manipulation) * for more info. */ export type ContentfulImageOptionsRadius = number | "max"; /** * Resize an image to the desired height. Max value is `4000`. Defaults to the * original image height. * * Refer to [the contentful images api documentation]( * https://www.contentful.com/developers/docs/references/images-api/#/reference/image-manipulation) * for more info. */ export type ContentfulImageOptionsHeight = number; /** * Resize an image to the desired width. Max value is `4000`. Defaults to the * original image height. * * Refer to [the contentful images api documentation]( * https://www.contentful.com/developers/docs/references/images-api/#/reference/image-manipulation) * for more info. */ export type ContentfulImageOptionsWidth = number; /** * Define the quality as an integer between `1` and `100`. * * Refer to [the contentful images api documentation]( * https://www.contentful.com/developers/docs/references/images-api/#/reference/image-manipulation) * for more info. */ export type ContentfulImageOptionsQuality = number; /** * Define the background color for the image when padding is required, for * example when using border radius or resizing with specific resizing * behaviours. * * Unlike in the documentation, provide a RGB string (the "#" will be ignored * if provided). The contentfulImage function will automatically apply the * `rgb:` prefix to the color. * * Refer to [the contentful images api documentation]( * https://www.contentful.com/developers/docs/references/images-api/#/reference/image-manipulation) * for more info. */ export type ContentfulImageOptionsBackgroundColor = string; /** * All image options that can be specified when retrieving an image from the * Contentful images API. */ export type ContentfulImageOptions = { format?: ContentfulImageOptionsFormat; width?: ContentfulImageOptionsWidth; height?: ContentfulImageOptionsHeight; fit?: ContentfulImageOptionsFit; focusArea?: ContentfulImageOptionsFocusArea; radius?: ContentfulImageOptionsRadius; quality?: ContentfulImageOptionsQuality; backgroundColor?: ContentfulImageOptionsBackgroundColor; }; /** * For each specifiable option in the `ContentfulImageOptions` object, define * a list of all query parameters that should or could be provided. * * Some properties may be defined by multiple query parameters such as the * image format (for example 8-bit pngs require two query parameters: fm=png and * fl=png8 to work). */ const optionQueryKeys: Record<keyof ContentfulImageOptions, string[]> = { backgroundColor: ["bg"], quality: ["q"], radius: ["r"], focusArea: ["f"], fit: ["fit"], height: ["h"], width: ["w"], format: ["fm", "fl"], }; /** * Each option can be provided an option trasformer that takes as input the * stringified value for the given option and returns the transformed value * before being applied to the query string. */ const transformers: Partial< Record<keyof ContentfulImageOptions, (value: string) => string> > = { // For background color, prepend the "rgb:" string and omit the "#" character backgroundColor: (value) => "rgb:" + value.replace("#", ""), // Clamp and round quality to int between 1 and 100, quality: (value) => Math.round( Math.min(100, Math.max(1, Number.parseInt(value, 10))) ).toString(), }; /** * Takes as input `ContentfulImageSource` and returns the base URL for the image. * Removes any query if defined and prepends "https:" if necessary. */ export function getContentfulImageSrcUrl(src: ContentfulImageSource) { // Get provided raw URL string let url = typeof src === "string" ? src : "fields" in src ? src.fields.file.url : "file" in src ? src.file.url : src.url; // Prepend https: if necessary if (url.startsWith("//")) url = "https:" + url; // Remove query if (url.includes("?")) url = url.split("?")[0]; return url; } /** * Construct query from arguments. * * Some options may be split into multiple parameters in the query. For * example, image format for progressive jpeg. We receive the value * "jpg/progressive" (which is split into an array ["jpg", "progressive"]) * and read the query parameters corresponding to the option "format" * which are ["fm", "fl"]. We then match by index and get the resulting query * "fm=jpg&fl=progressive". We do not always use all query parameters, thus * if either the query parameter name or value is undefined or an empty string * it is not included in the final query. */ export function getContentfulImageQuery(options: ContentfulImageOptions) { return Object.entries(options) .map(([key, value]) => { // Get list of all parameter names for current option const queryKeys = optionQueryKeys[key as keyof ContentfulImageOptions]; // Get transformer for preprocessing before applying to query if exists. const transformer = transformers[key as keyof ContentfulImageOptions]; // Convert value to string, apply transformer if exists and split // into list of values at "/". const values = ( transformer ? transformer(value.toString()) : value.toString() ).split("/"); // By index, match each parameter name and value to a "{name}={value}" // pair. If either the name or value for a pair is falsy, omit it. return queryKeys .map((name, i) => { if (!name || !values[i]) return ""; return name + "=" + values[i]; }) .filter((_) => !!_); }) .flat() .join("&"); } /** * Given an image source from a Contentful image and a list of options, returns * a URL that can be used to fetch the specified image with the specified * options from the Contentful Images API. * * Read more about [the contentful images api documentation]( * https://www.contentful.com/developers/docs/references/images-api/#/reference/image-manipulation) * for more info. */ export default function contentfulImage( src: ContentfulImageSource, options: ContentfulImageOptions = {} ) { // Get URL from src and query from options. const url = getContentfulImageSrcUrl(src); const query = getContentfulImageQuery(options); // Append query if one constructed return url + (query ? `?${query}` : ""); }