UNPKG

@prismicio/types-internal

Version:
281 lines (248 loc) 7.23 kB
import { Either, left, right } from "fp-ts/lib/Either" import * as t from "io-ts" import { withFallback } from "io-ts-types/lib/withFallback" import type { Fields } from "../utils" import { StringOrNull } from "../validators" import { DynamicSection, Sections, StaticSection, traverseSection, } from "./Section" import type { Group, NestableWidget, NestedGroup, UID } from "./widgets" import type { SharedSlice } from "./widgets/slices/SharedSlice" import type { DynamicSlice } from "./widgets/slices/Slice" import type { DynamicSlices } from "./widgets/slices/Slices" import type { DynamicWidget } from "./widgets/Widget" import type { StaticWidget } from "./widgets/Widget" export const CustomTypeFormat = { page: "page", custom: "custom", } class CustomTypeSlicesError extends Error { slices: Array<string> override message: string constructor(slices: Array<string>) { super() this.slices = slices this.message = this._formatError(slices) } _formatError(slicesRefs: Array<string>) { const slicesMsg = slicesRefs.map((ref) => `\t - ${ref}`) return `The following slices doesn't exists in your Prismic repository: ${slicesMsg.join("\n")} ` } } function customTypeReader<T extends StaticSection | DynamicSection>( codec: t.Type<T, unknown>, ) { return t.exact( t.intersection([ t.type({ id: t.string, label: StringOrNull, repeatable: withFallback(t.boolean, true), json: t.record(t.string, codec), //tab name => tab data status: withFallback(t.boolean, true), }), t.partial({ format: withFallback(t.keyof(CustomTypeFormat), "custom"), }), ]), ) } export const StaticCustomType = customTypeReader(StaticSection) export type StaticCustomType = t.TypeOf<typeof StaticCustomType> export const CustomType = customTypeReader(DynamicSection) export type CustomType = t.TypeOf<typeof CustomType> export function flattenWidgets( customType: CustomType, ): Array<[string, DynamicWidget]> { return Object.entries(customType.json).reduce( ( acc: Array<[string, DynamicWidget]>, [, section]: [string, DynamicSection], ) => { const sectionWidgets: Array<[string, DynamicWidget]> = Object.entries(section) return acc.concat(sectionWidgets) }, [], ) } export function flattenSections( customType: StaticCustomType, ): Array<[string, StaticWidget]> { return Object.entries(customType.json).reduce( ( acc: Array<[string, StaticWidget]>, [, section]: [string, StaticSection], ) => { const sectionWidgets: Array<[string, StaticWidget]> = Object.entries(section) return acc.concat(sectionWidgets) }, [], ) } function _retrieveSharedSlicesRef(customType: CustomType): Array<string> { const slicezones = flattenWidgets(customType).filter( ([, widget]: [string, DynamicWidget]) => widget.type === "Slices", ) as Array<[string, DynamicSlices]> const allSharedRefs = slicezones.reduce( (acc: Array<string>, [, slicezone]) => { const sharedRefs = Object.entries( slicezone.config && slicezone.config.choices ? slicezone.config.choices : {}, ) .filter( ([, slice]: [string, DynamicSlice]) => slice.type === "SharedSlice", ) .map(([key]) => key) return acc.concat(sharedRefs) }, [], ) return allSharedRefs } function _mapSharedSlicesRefs<A>( customType: CustomType, ): (mapFn: (ref: string) => A) => Array<A> { const refs = _retrieveSharedSlicesRef(customType) return function (mapFn: (ref: string) => A): Array<A> { return refs.map(mapFn) } } export function toStatic( customType: CustomType, sharedSlices: Map<string, SharedSlice>, ): StaticCustomType { const json = Object.entries(customType.json).reduce( ( acc: { [key: string]: StaticSection }, [sectionKey, dynSection]: [string, DynamicSection], ) => { return { ...acc, [sectionKey]: Sections.toStatic(dynSection, sharedSlices), } }, {}, ) return { ...customType, json } as StaticCustomType } export function validateSlices( customType: CustomType, sharedSlices: Map<string, SharedSlice>, ): Either<CustomTypeSlicesError, CustomType> { const missingSlices = _mapSharedSlicesRefs<string | undefined>(customType)( (ref: string) => { const slice: SharedSlice | undefined = sharedSlices.get(ref) const isMissing = !slice if (isMissing) return ref return }, ).filter(Boolean) as Array<string> if (missingSlices.length > 0) return left(new CustomTypeSlicesError(missingSlices)) else return right(customType) } export function collectWidgets( customType: CustomType, f: (ref: string, widget: DynamicWidget) => DynamicWidget | undefined, ): CustomType { const json = Object.entries(customType.json).reduce( (acc, [sectionId, section]: [string, DynamicSection]) => { const updatedSection = Object.entries(section).reduce( (acc, [ref, widget]) => { const updatedWidget = f(ref, widget) if (updatedWidget) { return { ...acc, [ref]: updatedWidget } } return acc }, {}, ) return { ...acc, [sectionId]: updatedSection } }, {}, ) return { ...customType, json } } export function filterMissingSharedSlices( customType: CustomType, sharedSlices: Map<string, SharedSlice>, ): CustomType { return collectWidgets(customType, (_widgetId, widget) => { if (widget.type === "Slices") { if (!widget.config || !widget.config.choices) return widget const choices = Object.entries(widget.config.choices).reduce( (acc, [sliceId, sliceValue]: [string, DynamicSlice]) => { if (sliceValue.type === "SharedSlice" && !sharedSlices.get(sliceId)) return acc return { ...acc, [sliceId]: sliceValue } }, {}, ) const config = { ...widget.config, choices } return { ...widget, config } } return widget }) } export function flattenCustomTypeFields( customType: StaticCustomType, ): Record<string, StaticWidget> { return Object.values(customType.json).reduce( (acc, tab) => ({ ...acc, ...tab, }), {}, ) } export function collectSharedSlices(customType: { customTypeId: string fields: Record<string, StaticWidget> }): Record<string, SharedSlice> { return Object.entries(customType.fields).reduce((acc, [, w]) => { if (w.type === "Slices" || w.type === "Choice") { return { ...acc, ...Object.entries(w.config?.choices || {}).reduce( (sliceZoneAcc, [sliceKey, sliceModel]) => { return sliceModel.type === "SharedSlice" ? { ...sliceZoneAcc, [sliceKey]: sliceModel } : sliceZoneAcc }, {}, ), } } return acc }, {}) } export function traverseCustomType< T extends CustomType | StaticCustomType, >(args: { customType: T onField: Fields.OnFieldFn<UID | NestableWidget | Group | NestedGroup> }): T { const { customType, onField } = args let json: Record<string, typeof customType.json[string]> | undefined for (const [key, section] of Object.entries(customType.json)) { const newSection = traverseSection({ path: [key], section, onField, }) if (newSection !== section) { if (!json) json = { ...customType.json } json[key] = newSection } } // returns the traversed model instead of a new one if it didn't change return json ? { ...customType, json } : customType }