@stackbit/cms-sanity
Version:
Stackbit Sanity CMS Interface
122 lines (114 loc) • 5.04 kB
text/typescript
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);
}