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
366 lines (306 loc) • 11.6 kB
text/typescript
import {type AssetSource, type SchemaTypeDefinition} from '@sanity/types'
import {type ReactNode} from 'react'
import {type LocaleConfigContext, type LocaleDefinition, type LocaleResourceBundle} from '../i18n'
import {type Template, type TemplateItem} from '../templates'
import {getPrintableType} from '../util/getPrintableType'
import {
type DocumentActionComponent,
type DocumentBadgeComponent,
type DocumentInspector,
} from './document'
import {flattenConfig} from './flattenConfig'
import {
type AsyncConfigPropertyReducer,
type ConfigContext,
type ConfigPropertyReducer,
type DocumentActionsContext,
type DocumentBadgesContext,
type DocumentCommentsEnabledContext,
type DocumentInspectorContext,
type DocumentLanguageFilterComponent,
type DocumentLanguageFilterContext,
type NewDocumentOptionsContext,
type PluginOptions,
type ResolveProductionUrlContext,
type Tool,
} from './types'
export const initialDocumentBadges: DocumentBadgeComponent[] = []
export const initialDocumentActions: DocumentActionComponent[] = []
export const initialLanguageFilter: DocumentLanguageFilterComponent[] = []
export const schemaTypesReducer: ConfigPropertyReducer<
SchemaTypeDefinition[],
Omit<ConfigContext, 'schema' | 'currentUser' | 'client' | 'getClient' | 'i18n'>
> = (prev, {schema}, context) => {
const schemaTypes = schema?.types
if (!schemaTypes) return prev
if (typeof schemaTypes === 'function') return schemaTypes(prev, context)
if (Array.isArray(schemaTypes)) return [...prev, ...schemaTypes]
throw new Error(
`Expected \`schema.types\` to be an array or a function, but received ${getPrintableType(
schemaTypes,
)}`,
)
}
export const resolveProductionUrlReducer: AsyncConfigPropertyReducer<
string | undefined,
ResolveProductionUrlContext
> = async (prev, {document}, context) => {
const resolveProductionUrl = document?.productionUrl
// the redundant await is useful for error logging because the error is caught
// in this stack vs somewhere down stream
// eslint-disable-next-line no-return-await
if (resolveProductionUrl) return await resolveProductionUrl(prev, context)
return prev
}
export const toolsReducer: ConfigPropertyReducer<Tool[], ConfigContext> = (
prev,
{tools},
context,
) => {
if (!tools) return prev
if (typeof tools === 'function') return tools(prev, context)
if (Array.isArray(tools)) return [...prev, ...tools]
throw new Error(
`Expected \`tools\` to be an array or a function, but received ${getPrintableType(tools)}`,
)
}
// we will need this when we ressurect user config for search
/*export const searchFilterReducer: ConfigPropertyReducer<
SearchFilterDefinition<string>[],
ConfigContext
> = (prev, {search}, context) => {
const filters = search?.filters
if (!filters) return prev
if (typeof filters === 'function') return filters(prev, context)
if (Array.isArray(filters)) return [...prev, ...filters]
throw new Error(
`Expected \`search.filters\` to be an array or a function, but received ${typeof filters}`
)
}
export const searchOperatorsReducer: ConfigPropertyReducer<
SearchOperatorDefinition[],
ConfigContext
> = (prev, {search}, context) => {
const operators = search?.operators
if (!operators) return prev
if (typeof operators === 'function') return operators(prev, context)
if (Array.isArray(operators)) return [...prev, ...operators]
throw new Error(
`Expected \`operators\` to be be an array or a function, but received ${getPrintableType(operators)}`
)
}*/
export const schemaTemplatesReducer: ConfigPropertyReducer<Template[], ConfigContext> = (
prev,
{schema},
context,
) => {
const schemaTemplates = schema?.templates
if (!schemaTemplates) return prev
if (typeof schemaTemplates === 'function') return schemaTemplates(prev, context)
if (Array.isArray(schemaTemplates)) return [...prev, ...schemaTemplates]
throw new Error(
`Expected \`schema.templates\` to be an array or a function, but received ${getPrintableType(
schemaTemplates,
)}`,
)
}
export const localeDefReducer: ConfigPropertyReducer<LocaleDefinition[], LocaleConfigContext> = (
prev,
{i18n},
context,
) => {
const locales = i18n?.locales
if (!locales) return prev
if (typeof locales === 'function') return locales(prev, context)
if (Array.isArray(locales)) return [...prev, ...locales]
throw new Error(
`Expected \`i18n.locales\` to be an array or a function, but received ${getPrintableType(
locales,
)}`,
)
}
export const localeBundlesReducer: ConfigPropertyReducer<
LocaleResourceBundle[],
LocaleConfigContext
> = (prev, {i18n}, context) => {
const bundles = i18n?.bundles
if (!bundles) return prev
if (Array.isArray(bundles)) return [...prev, ...bundles]
if (typeof bundles === 'function') return bundles(prev, context)
throw new Error(
`Expected \`i18n.bundles\` to be an array or a function, but received ${typeof bundles}`,
)
}
export const documentBadgesReducer: ConfigPropertyReducer<
DocumentBadgeComponent[],
DocumentBadgesContext
> = (prev, {document}, context) => {
const documentBadges = document?.badges
if (!documentBadges) return prev
if (typeof documentBadges === 'function') return documentBadges(prev, context)
if (Array.isArray(documentBadges)) return [...prev, ...documentBadges]
throw new Error(
`Expected \`document.badges\` to be an array or a function, but received ${getPrintableType(
documentBadges,
)}`,
)
}
export const documentActionsReducer: ConfigPropertyReducer<
DocumentActionComponent[],
DocumentActionsContext
> = (prev, {document}, context) => {
const documentActions = document?.actions
if (!documentActions) return prev
if (typeof documentActions === 'function') return documentActions(prev, context)
if (Array.isArray(documentActions)) return [...prev, ...documentActions]
throw new Error(
`Expected \`document.actions\` to be an array or a function, but received ${getPrintableType(
documentActions,
)}`,
)
}
export const newDocumentOptionsResolver: ConfigPropertyReducer<
TemplateItem[],
NewDocumentOptionsContext
> = (prev, {document}, context) => {
const resolveNewDocumentOptions = document?.newDocumentOptions
if (!resolveNewDocumentOptions) return prev
if (typeof resolveNewDocumentOptions !== 'function') {
throw new Error(
`Expected \`document.resolveNewDocumentOptions\` to be a function, but received ${getPrintableType(
resolveNewDocumentOptions,
)}`,
)
}
return resolveNewDocumentOptions(prev, context)
}
export const fileAssetSourceResolver: ConfigPropertyReducer<AssetSource[], ConfigContext> = (
prev,
{form},
context,
) => {
const assetSources = form?.file?.assetSources
if (!assetSources) return prev
if (typeof assetSources === 'function') return assetSources(prev, context)
if (Array.isArray(assetSources)) return [...prev, ...assetSources]
throw new Error(
`Expected \`form.file.assetSources\` to be an array or a function, but received ${getPrintableType(
assetSources,
)}`,
)
}
export const imageAssetSourceResolver: ConfigPropertyReducer<AssetSource[], ConfigContext> = (
prev,
{form},
context,
) => {
const assetSources = form?.image?.assetSources
if (!assetSources) return prev
if (typeof assetSources === 'function') return assetSources(prev, context)
if (Array.isArray(assetSources)) return [...prev, ...assetSources]
throw new Error(
`Expected \`form.image.assetSources\` to be an array or a function, but received ${getPrintableType(
assetSources,
)}`,
)
}
/**
* @internal
*/
export const documentLanguageFilterReducer: ConfigPropertyReducer<
DocumentLanguageFilterComponent[],
DocumentLanguageFilterContext
> = (prev, {document}, context) => {
const resolveDocumentLanguageFilter = document?.unstable_languageFilter
if (!resolveDocumentLanguageFilter) return prev
if (typeof resolveDocumentLanguageFilter === 'function')
return resolveDocumentLanguageFilter(prev, context)
if (Array.isArray(resolveDocumentLanguageFilter))
return [...prev, ...resolveDocumentLanguageFilter]
throw new Error(
`Expected \`document.unstable_languageFilter\` to be an array or a function, but received ${getPrintableType(
resolveDocumentLanguageFilter,
)}`,
)
}
export const documentInspectorsReducer: ConfigPropertyReducer<
DocumentInspector[],
DocumentInspectorContext
> = (prev, {document}, context) => {
const resolveInspectorsFilter = document?.inspectors
if (!resolveInspectorsFilter) return prev
if (typeof resolveInspectorsFilter === 'function') return resolveInspectorsFilter(prev, context)
if (Array.isArray(resolveInspectorsFilter)) return [...prev, ...resolveInspectorsFilter]
throw new Error(
`Expected \`document.inspectors\` to be an array or a function, but received ${getPrintableType(
resolveInspectorsFilter,
)}`,
)
}
export const documentCommentsEnabledReducer = (opts: {
config: PluginOptions
context: DocumentCommentsEnabledContext
initialValue: boolean
}): boolean => {
const {config, context, initialValue} = opts
const flattenedConfig = flattenConfig(config, [])
// There is no concept of 'previous value' in this API. We only care about the final value.
// That is, if a plugin returns true, but the next plugin returns false, the result will be false.
// The last plugin 'wins'.
const result = flattenedConfig.reduce((acc, {config: innerConfig}) => {
const resolver = innerConfig.document?.unstable_comments?.enabled
if (!resolver && typeof resolver !== 'boolean') return acc
if (typeof resolver === 'function') return resolver(context)
if (typeof resolver === 'boolean') return resolver
throw new Error(
`Expected \`document.unstable_comments.enabled\` to be a boolean or a function, but received ${getPrintableType(
resolver,
)}`,
)
}, initialValue)
return result
}
export const internalTasksReducer = (opts: {
config: PluginOptions
}): {footerAction: ReactNode} | undefined => {
const {config} = opts
const flattenedConfig = flattenConfig(config, [])
const result = flattenedConfig.reduce(
(acc: {footerAction: ReactNode} | undefined, {config: innerConfig}) => {
const resolver = innerConfig.__internal_tasks
if (!resolver) return acc
if (typeof resolver === 'object' && resolver.footerAction) return resolver
throw new Error(
`Expected \`__internal__tasks\` to be an object with footerAction, but received ${getPrintableType(
resolver,
)}`,
)
},
undefined,
)
return result
}
export const partialIndexingEnabledReducer = (opts: {
config: PluginOptions
initialValue: boolean
}): boolean => {
const {config, initialValue} = opts
const flattenedConfig = flattenConfig(config, [])
const result = flattenedConfig.reduce((acc, {config: innerConfig}) => {
const resolver = innerConfig.search?.unstable_partialIndexing?.enabled
if (!resolver && typeof resolver !== 'boolean') return acc
if (typeof resolver === 'boolean') return resolver
throw new Error(
`Expected \`search.unstable_partialIndexing.enabled\` to be a boolean, but received ${getPrintableType(
resolver,
)}`,
)
}, initialValue)
return result
}
export const newSearchEnabledReducer: ConfigPropertyReducer<boolean, ConfigContext> = (
prev,
{search},
): boolean => {
return prev || search?.unstable_enableNewSearch || false
}