sanity
Version:
Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches
257 lines (230 loc) • 7.37 kB
text/typescript
import {type I18nTextRecord} from 'sanity'
import {maybeSerializeMenuItem, type MenuItem, type MenuItemBuilder} from './MenuItem'
import {
maybeSerializeMenuItemGroup,
type MenuItemGroup,
type MenuItemGroupBuilder,
} from './MenuItemGroup'
import {HELP_URL, SerializeError} from './SerializeError'
import {
type Child,
type Serializable,
type SerializeOptions,
type StructureNode,
} from './StructureNodes'
import {type UserComponent} from './types'
import {getStructureNodeId} from './util/getStructureNodeId'
import {validateId} from './util/validateId'
/**
* Interface for component
*
* @public
*/
// TODO: rename to `StructureComponent` since it clashes with React?
export interface Component extends StructureNode {
/** Component of type {@link UserComponent} */
component: UserComponent
/** Component child of type {@link Child} */
child?: Child
/** Component menu items, array of type {@link MenuItem} */
menuItems: MenuItem[]
/** Component menu item group, array of type {@link MenuItemGroup} */
menuItemGroups: MenuItemGroup[]
/** Component options */
options: {[key: string]: unknown}
}
/**
* Interface for component input
*
* @public
*/
export interface ComponentInput extends StructureNode {
/** Component of type {@link UserComponent} */
component: UserComponent
/** Component child of type {@link Child} */
child?: Child
/** Component options */
options?: {[key: string]: unknown}
/** Component menu items. See {@link MenuItem} and {@link MenuItemBuilder} */
menuItems?: (MenuItem | MenuItemBuilder)[]
/** Component menu item groups. See {@link MenuItemGroup} and {@link MenuItemGroupBuilder} */
menuItemGroups?: (MenuItemGroup | MenuItemGroupBuilder)[]
}
/**
* Interface for buildable component
*
* @public
*/
export interface BuildableComponent extends Partial<StructureNode> {
/** Component of type {@link UserComponent} */
component?: UserComponent
/** Component child of type {@link Child} */
child?: Child
/** Component options */
options?: {[key: string]: unknown}
/** Component menu items. See {@link MenuItem} and {@link MenuItemBuilder} */
menuItems?: (MenuItem | MenuItemBuilder)[]
/** Component menu item groups. See {@link MenuItemGroup} and {@link MenuItemGroupBuilder} */
menuItemGroups?: (MenuItemGroup | MenuItemGroupBuilder)[]
}
/**
* Class for building components
*
* @public
*/
export class ComponentBuilder implements Serializable<Component> {
/** component builder option object */
protected spec: BuildableComponent
constructor(spec?: ComponentInput) {
this.spec = {options: {}, ...(spec ? spec : {})}
}
/** Set Component ID
* @param id - component ID
* @returns component builder based on ID provided
*/
id(id: string): ComponentBuilder {
return this.clone({id})
}
/** Get ID
* @returns ID
*/
getId(): BuildableComponent['id'] {
return this.spec.id
}
/** Set Component title
* @param title - component title
* @returns component builder based on title provided (and ID)
*/
title(title: string): ComponentBuilder {
return this.clone({title, id: getStructureNodeId(title, this.spec.id)})
}
/** Get Component title
* @returns title
*/
getTitle(): BuildableComponent['title'] {
return this.spec.title
}
/** Set the i18n key and namespace used to populate the localized title.
* @param i18n - the key and namespaced used to populate the localized title.
* @returns component builder based on i18n key and ns provided
*/
i18n(i18n: I18nTextRecord<'title'>): ComponentBuilder {
return this.clone({i18n})
}
/** Get i18n key and namespace used to populate the localized title
* @returns the i18n key and namespace used to populate the localized title
*/
getI18n(): I18nTextRecord<'title'> | undefined {
return this.spec.i18n
}
/** Set Component child
* @param child - child component
* @returns component builder based on child component provided
*/
child(child: Child): ComponentBuilder {
return this.clone({child})
}
/** Get Component child
* @returns child component
*/
getChild(): BuildableComponent['child'] {
return this.spec.child
}
/** Set component
* @param component - user built component
* @returns component builder based on component provided
*/
component(component: UserComponent): ComponentBuilder {
return this.clone({component})
}
/** Get Component
* @returns component
*/
getComponent(): BuildableComponent['component'] {
return this.spec.component
}
/** Set Component options
* @param options - component options
* @returns component builder based on options provided
*/
options(options: {[key: string]: unknown}): ComponentBuilder {
return this.clone({options})
}
/** Get Component options
* @returns component options
*/
getOptions(): NonNullable<BuildableComponent['options']> {
return this.spec.options || {}
}
/** Set Component menu items
* @param menuItems - component menu items
* @returns component builder based on menuItems provided
*/
menuItems(menuItems: (MenuItem | MenuItemBuilder)[]): ComponentBuilder {
return this.clone({menuItems})
}
/** Get Component menu items
* @returns menu items
*/
getMenuItems(): BuildableComponent['menuItems'] {
return this.spec.menuItems
}
/** Set Component menu item groups
* @param menuItemGroups - component menu item groups
* @returns component builder based on menuItemGroups provided
*/
menuItemGroups(menuItemGroups: (MenuItemGroup | MenuItemGroupBuilder)[]): ComponentBuilder {
return this.clone({menuItemGroups})
}
/** Get Component menu item groups
* @returns menu item groups
*/
getMenuItemGroups(): BuildableComponent['menuItemGroups'] {
return this.spec.menuItemGroups
}
/** Serialize component
* @param options - serialization options
* @returns component object based on path provided in options
*
*/
serialize(options: SerializeOptions = {path: []}): Component {
const {id, title, child, options: componentOptions, component} = this.spec
if (!id) {
throw new SerializeError(
'`id` is required for `component` structure item',
options.path,
options.index,
).withHelpUrl(HELP_URL.ID_REQUIRED)
}
if (!component) {
throw new SerializeError(
'`component` is required for `component` structure item',
options.path,
options.index,
).withHelpUrl(HELP_URL.ID_REQUIRED)
}
return {
id: validateId(id, options.path, options.index),
title,
type: 'component',
child,
component,
options: componentOptions || {},
menuItems: (this.spec.menuItems || []).map((item, i) =>
maybeSerializeMenuItem(item, i, options.path),
),
menuItemGroups: (this.spec.menuItemGroups || []).map((item, i) =>
maybeSerializeMenuItemGroup(item, i, options.path),
),
}
}
/** Clone component builder (allows for options overriding)
* @param withSpec - component builder options
* @returns cloned builder
*/
clone(withSpec?: BuildableComponent): ComponentBuilder {
const builder = new ComponentBuilder()
builder.spec = {...this.spec, ...(withSpec || {})}
return builder
}
}