@prismicio/types-internal
Version:
Prismic types for Custom Types and Prismic Data
290 lines (260 loc) • 7.03 kB
text/typescript
import { either } from "fp-ts"
import { pipe } from "fp-ts/lib/function"
import * as t from "io-ts"
import {
ContentPath,
SliceModel,
TraverseSliceContentFn,
TraverseWidgetContentFn,
} from "../../../_internal/utils"
import {
type StaticSlices,
CompositeSlice,
Group,
isCompositeSlice,
isLegacySlice,
isStaticSharedSlice,
NestableWidget,
SharedSlice,
VariationFields,
} from "../../../customtypes"
import type {
FieldOrSliceType,
LegacyContentCtx,
WithTypes,
} from "../../LegacyContentCtx"
import { hasContentType } from "../../utils"
import { traverseCompositeSliceContent } from "./Slice/CompositeSliceContent"
import { traverseSharedSliceContent } from "./Slice/SharedSliceContent"
import { traverseSimpleSliceContent } from "./Slice/SimpleSliceContent"
import {
isCompositeSliceItemContent,
isSharedSliceItemContent,
isSimpleSliceItemContent,
SliceItemContent,
sliceItemContentWithDefaultValues,
SlicesItemLegacy,
} from "./SliceItem"
export const SlicesContentType = "SliceContentType"
export const isSlicesContent = (u: unknown): u is SlicesContent =>
hasContentType(u) && u.__TYPE__ === SlicesContentType
type SlicesLegacy = Array<unknown>
export const SlicesLegacy = (ctx: LegacyContentCtx) => {
const codec = t.array(SlicesItemLegacy(ctx))
return new t.Type<SlicesContent, WithTypes<SlicesLegacy>, unknown>(
"SlicesLegacy",
isSlicesContent,
(items) => {
return pipe(
codec.decode(items),
either.map((parsedSlices) => {
return {
__TYPE__: SlicesContentType,
value: parsedSlices,
}
}),
)
},
(s: SlicesContent) => {
const result = codec.encode(s.value)
return {
content: result.map((s) => s.content),
types: result.reduce<Record<string, FieldOrSliceType>>(
(acc, s) => ({ ...acc, ...s.types }),
{ [ctx.keyOfType]: "Slices" },
),
keys: result.reduce<Record<string, string>>(
(acc, s) => ({ ...acc, ...s.keys }),
{},
),
}
},
)
}
export const SlicesContent = t.type({
__TYPE__: t.literal(SlicesContentType),
value: t.array(SliceItemContent),
})
export type SlicesContent = t.TypeOf<typeof SlicesContent>
export function slicesContentWithDefaultValues(
codec: StaticSlices,
content: SlicesContent,
): SlicesContent {
const choices = codec?.config?.choices
if (choices === undefined) return content
const updateSlicesValue = content.value.map((slice) => {
const sliceConfig = choices[slice.name]
if (sliceConfig) {
const updatedSliceWidget = sliceItemContentWithDefaultValues(
sliceConfig,
slice.widget,
)
return {
...slice,
widget: updatedSliceWidget,
}
}
return slice
})
return {
...content,
value: updateSlicesValue,
}
}
function findSliceModel(
slicesPath: ContentPath,
model: StaticSlices,
content: SliceItemContent,
): SliceModel | undefined {
const defaultModel = model?.config?.choices?.[content.name]
// regular case for shared slices
const sharedSliceModel = (): VariationFields | undefined => {
if (isSharedSliceItemContent(content)) {
if (defaultModel && isStaticSharedSlice(defaultModel)) {
const variationDef = defaultModel.variations.find(
(v) => v.id === content.widget.variation,
)
return variationDef
? {
type: "SharedSlice",
sliceName: defaultModel.id,
variationId: variationDef.id,
fields: {
primary: variationDef.primary || {},
items: variationDef.items || {},
},
}
: undefined
}
}
return
}
const migratedSliceModel = (): VariationFields | undefined => {
const legacyContentPath = ContentPath.append(
ContentPath.serialize(slicesPath),
content.name,
)
const migratedSliceModel = Object.values(model?.config?.choices || {}).find(
(sliceModel): sliceModel is SharedSlice => {
if (isStaticSharedSlice(sliceModel)) {
return !!sliceModel.legacyPaths?.[legacyContentPath]
}
return false
},
)
if (!migratedSliceModel) return
const migratedVariation = migratedSliceModel?.variations.find(
(v) => v.id === migratedSliceModel.legacyPaths?.[legacyContentPath],
)
if (!migratedVariation) return
return {
type: "SharedSlice",
sliceName: migratedSliceModel.id,
variationId: migratedVariation.id,
fields: {
primary: migratedVariation.primary || {},
items: migratedVariation.items || {},
},
}
}
const legacySliceModel = ():
| NestableWidget
| CompositeSlice
| Group
| undefined => {
if (!defaultModel) return
if (isCompositeSlice(defaultModel)) {
return {
type: "Slice",
"non-repeat": defaultModel["non-repeat"] || {},
repeat: defaultModel.repeat || {},
}
}
if (isLegacySlice(defaultModel)) {
return defaultModel
}
return
}
return sharedSliceModel() || migratedSliceModel() || legacySliceModel()
}
export function traverseSlices({
path,
key,
model,
content,
}: {
path: ContentPath
key: string
content: SlicesContent
model?: StaticSlices | undefined
}) {
return ({
transformWidget = ({ content }) => content,
transformSlice = ({ content }) => content,
}: {
transformWidget?: TraverseWidgetContentFn
transformSlice?: TraverseSliceContentFn
}): SlicesContent | undefined => {
const value = content.value.reduce<SlicesContent["value"]>(
(acc, sliceContent) => {
const sliceModel = model && findSliceModel(path, model, sliceContent)
const convertedSlice: SliceItemContent | undefined = (() => {
if (isSharedSliceItemContent(sliceContent))
return traverseSharedSliceContent({
path: path.concat({
key: sliceContent.key,
type: "SharedSlice",
}),
sliceKey: sliceContent.key,
sliceName: sliceContent.name,
model:
sliceModel?.type === "SharedSlice" ? sliceModel : undefined,
content: sliceContent,
})(transformWidget, transformSlice)
if (isCompositeSliceItemContent(sliceContent))
return traverseCompositeSliceContent({
path: path.concat({
key: sliceContent.key,
type: "Slice",
}),
sliceKey: sliceContent.key,
sliceName: sliceContent.name,
model:
sliceModel?.type === "Slice" ||
sliceModel?.type === "SharedSlice"
? sliceModel
: undefined,
content: sliceContent,
})(transformWidget, transformSlice)
if (isSimpleSliceItemContent(sliceContent))
return traverseSimpleSliceContent({
path: path.concat({
key: sliceContent.key,
type: "LegacySlice",
}),
sliceKey: sliceContent.key,
sliceName: sliceContent.name,
model:
sliceModel && sliceModel?.type !== "Slice"
? sliceModel
: undefined,
content: sliceContent,
})(transformWidget, transformSlice)
return
})()
return convertedSlice ? acc.concat(convertedSlice) : acc
},
[],
)
return transformWidget({
path,
key,
apiId: key,
model,
content: {
__TYPE__: content.__TYPE__,
value,
},
})
}
}