UNPKG

sanity-plugin-internationalized-array

Version:
124 lines (110 loc) 4 kB
/* eslint-disable no-nested-ternary */ import {defineField, type FieldDefinition, type Rule} from 'sanity' import {peek} from '../cache' import {createFieldName} from '../components/createFieldName' import {getSelectedValue} from '../components/getSelectedValue' import InternationalizedArray from '../components/InternationalizedArray' import type {Language, LanguageCallback, Value} from '../types' import {getLanguagesFieldOption} from '../utils/getLanguagesFieldOption' type ArrayFactoryConfig = { apiVersion: string select?: Record<string, string> languages: Language[] | LanguageCallback defaultLanguages?: string[] type: string | FieldDefinition } export type ArrayFieldOptions = Pick< ArrayFactoryConfig, 'apiVersion' | 'select' | 'languages' > export default (config: ArrayFactoryConfig): FieldDefinition<'array'> => { const {apiVersion, select, languages, type} = config const typeName = typeof type === `string` ? type : type.name const arrayName = createFieldName(typeName) const objectName = createFieldName(typeName, true) return defineField({ name: arrayName, title: 'Internationalized array', type: 'array', components: { input: InternationalizedArray, }, options: { // @ts-expect-error - these options are required for validation rules – not the custom input component apiVersion, select, languages, }, of: [ defineField({ ...(typeof type === 'string' ? {} : type), name: objectName, type: objectName, }), ], // @ts-expect-error - fix typings validation: (rule: Rule) => rule.custom<Value[]>(async (value, context) => { if (!value) { return true } const selectedValue = getSelectedValue(select, context.document) const client = context.getClient({apiVersion}) let contextLanguages: Language[] = [] const languagesFieldOption = getLanguagesFieldOption(context?.type) if (Array.isArray(languagesFieldOption)) { contextLanguages = languagesFieldOption } else if (Array.isArray(peek(selectedValue))) { contextLanguages = peek(selectedValue) || [] } else if (typeof languagesFieldOption === 'function') { contextLanguages = await languagesFieldOption(client, selectedValue) } if (value && value.length > contextLanguages.length) { return `Cannot be more than ${ contextLanguages.length === 1 ? `1 item` : `${contextLanguages.length} items` }` } const nonLanguageKeys = value?.length ? value.filter( (item) => !contextLanguages.find((language) => item._key === language.id) ) : [] if (nonLanguageKeys.length) { return { message: `Array item keys must be valid languages registered to the field type`, paths: nonLanguageKeys.map((item) => [{_key: item._key}]), } } // Ensure there's no duplicate `language` fields type KeyedValues = { [key: string]: Value[] } const valuesByLanguage = value?.length ? value .filter((item) => Boolean(item?._key)) .reduce((acc, cur) => { if (acc[cur._key]) { return {...acc, [cur._key]: [...acc[cur._key], cur]} } return { ...acc, [cur._key]: [cur], } }, {} as KeyedValues) : {} const duplicateValues = Object.values(valuesByLanguage) .filter((item) => item?.length > 1) .flat() if (duplicateValues.length) { return { message: 'There can only be one field per language', paths: duplicateValues.map((item) => [{_key: item._key}]), } } return true }), }) }