import _ from 'lodash'; import { Field } from '@stackbit/types'; import * as StackbitTypes from '@stackbit/types'; import { ModelWithContext } from './sanity-schema-converter'; export function resolveLabelFieldForModel(model: any, modelLabelFieldPath: string, fields: Field[]): string { let labelField = _.get(model, modelLabelFieldPath, null); if (labelField) { return labelField; } // see if there is a field named 'title' let titleField = _.find(fields, (field) => field.name === 'title' && ['string', 'text'].includes(field.type)); if (!titleField) { // see if there is a field named 'label' titleField = _.find(fields, (field) => field.name === 'label' && ['string', 'text'].includes(field.type)); } if (!titleField) { // get the first 'string' field titleField = _.find(fields, { type: 'string' }); } if (titleField) { labelField = _.get(titleField, 'name'); } return labelField || null; } export function getSanityAliasFieldType({ resolvedType, model, modelFieldPath }: { resolvedType: string; model: ModelWithContext; modelFieldPath: string[] }) { const fieldAlias = model.context?.fieldAliasMap?.[modelFieldPath.join('.')] ?? []; return fieldAlias?.find((alias) => alias.resolvedTypeName === resolvedType)?.origTypeName ?? resolvedType; } export function resolvedFieldType({ sanityFieldType, model, modelFieldPath }: { sanityFieldType: string; model: ModelWithContext; modelFieldPath: string[] }) { const fieldAlias = model.context?.fieldAliasMap?.[modelFieldPath.join('.')] ?? []; return fieldAlias?.find((alias) => alias.origTypeName === sanityFieldType)?.resolvedTypeName ?? sanityFieldType; } export function isLocalizedModelField(modelField: StackbitTypes.Field | StackbitTypes.FieldListItems) { return 'localized' in modelField && modelField.localized; } export interface FieldListMultiItem { type: 'list'; items: StackbitTypes.FieldListItems | StackbitTypes.FieldListItems[]; } /** * Sanity 'Array' field type can hold multiple field types. * * For example, Sanity Arrays can simultaneously include items of `model` * and `reference` types. https://www.sanity.io/docs/array-type#wT47gyCx * * With that, Sanity Arrays cannot include both primitive and complex types: * https://www.sanity.io/docs/array-type#fNBIr84P * * TODO: * This is not yet supported by Stackbit's TypeScript types, so the `any` * must be used. Additionally, if a Sanity array has multiple types of items one * of which is the 'object' type, then it will also have the 'name' property to * allow matching 'object' items to their types. The 'name' property is not * supported in Stackbit list items, so '@ts-ignore' is used. * * However, Stackbit client app should be able to render this types of lists correctly. * * @example A list that can include items of type 'model', 'reference' and 'object'. * { * type: 'list', * items: [{ * type: 'model', * models: [...] * }, { * type: 'reference', * models: [...] * }, { * type: 'object', * name: 'nested_object_name', * fields: {...} * }] * } */ export function getItemTypeForListItem(listItem: any, fieldModel: FieldListMultiItem): StackbitTypes.FieldListItems | null { const itemModels = fieldModel.items ?? { type: 'string' }; // in Sanity, list items may have multiple types, in this case, 'items' will be an array if (!_.isArray(itemModels)) { return itemModels; } // Handle primitive list item types // For primitive list items, the list will hold the primitive values as is, // therefore, use JavaScript's `typeof` to infer the type of the values const type = _.get(listItem, '_type'); if (!type) { const type = typeof listItem; if (typeIsPrimitive(type)) { return { type: type }; } return null; } if (type === 'reference') { return _.find(itemModels, { type: 'reference' }) ?? null; } else if (type === 'block') { return _.find(itemModels, { type: 'richText' }) ?? null; } else { return ( _.find(itemModels, (itemModel) => { if (itemModel.type === 'model') { return _.includes(itemModel.models, type); } else { // if field was one of base types (object, image, slug, etc.) // and it had a "name" property, then the "_type" will be equal to that name, // otherwise the "_type" will be equal to the base type // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore return itemModel.name === type || itemModel.type === type; } }) ?? null ); } } export function typeIsPrimitive(type: string): type is 'string' | 'number' | 'boolean' { return ['string', 'number', 'boolean'].includes(type); }