@prismicio/types-internal
Version:
Prismic types for Custom Types and Prismic Data
216 lines (196 loc) • 4.82 kB
text/typescript
import { either } from "fp-ts/"
import { getOrElseW } from "fp-ts/lib/Either"
import { pipe } from "fp-ts/lib/function"
import * as t from "io-ts"
import { StringOrNull } from "../../../validators"
import ImageConstraint from "../shared/ImageConstraint"
export const RichTextFieldType = "StructuredText"
export const DEFAULT_OPTION = "paragraph"
export type RichTextNodeType =
typeof RichTextNodeType[keyof typeof RichTextNodeType]
export const RichTextNodeType = {
heading1: "heading1",
heading2: "heading2",
heading3: "heading3",
heading4: "heading4",
heading5: "heading5",
heading6: "heading6",
paragraph: "paragraph",
strong: "strong",
em: "em",
preformatted: "preformatted",
hyperlink: "hyperlink",
image: "image",
embed: "embed",
list: "list-item",
orderedList: "o-list-item",
rtl: "rtl",
} as const
export const RichTextNodeTypeCodec = t.keyof({
[RichTextNodeType.heading1]: null,
[RichTextNodeType.heading2]: null,
[RichTextNodeType.heading3]: null,
[RichTextNodeType.heading4]: null,
[RichTextNodeType.heading5]: null,
[RichTextNodeType.heading6]: null,
[RichTextNodeType.paragraph]: null,
[RichTextNodeType.strong]: null,
[RichTextNodeType.em]: null,
[RichTextNodeType.preformatted]: null,
[RichTextNodeType.hyperlink]: null,
[RichTextNodeType.image]: null,
[RichTextNodeType.embed]: null,
[RichTextNodeType.list]: null,
[RichTextNodeType.orderedList]: null,
[RichTextNodeType.rtl]: null,
})
const RichTextOptions = new t.Type<string, string, unknown>(
"RichTextOptions",
(u: unknown): u is string => typeof u === "string",
(u: unknown) => {
return pipe(
t.union([t.string, t.null]).decode(u),
either.map((s: string | null) => {
if (!s) return DEFAULT_OPTION
const entries = s.split(",").map((e: string) => e.trim())
const filtered = entries.filter((entry) => {
return getOrElseW(() => undefined)(
RichTextNodeTypeCodec.decode(entry),
)
})
if (!filtered.length) return DEFAULT_OPTION
return filtered.join(",")
}),
)
},
(a) => a,
)
const NoLabels = (
labels:
| string
| ReadonlyArray<string>
| {
[x: string]: ReadonlyArray<{
name: string
}>
}
| null,
) => {
if (!labels) return t.success([])
return
}
const LabelsAsObject = (
labels:
| string
| ReadonlyArray<string>
| {
[x: string]: ReadonlyArray<{
name: string
}>
}
| null,
) => {
if (labels instanceof Object) {
const labelsObj = labels as { [x: string]: { name: string }[] }
// empty labels
if (!Object.entries(labelsObj).length) return t.success([])
// weird case labels with empty key as parent
if (labelsObj[""]) {
return t.success(labelsObj[""].map((l) => l.name))
}
const convertedObjectToArray = Object.entries(labelsObj)
.reduce<ReadonlyArray<string>>((acc, [, labelsEntries]) => {
return acc.concat(labelsEntries.map((l) => l.name))
}, [])
.filter(Boolean)
return t.success(convertedObjectToArray)
}
return
}
const LabelsAsArray = (
labels:
| string
| ReadonlyArray<string>
| {
[x: string]: ReadonlyArray<{
name: string
}>
}
| null,
) => {
if (labels instanceof Array) {
const isValidLabels = labels.reduce(
(acc, l) => acc && typeof l === "string",
true,
)
if (isValidLabels) return t.success(labels)
}
return
}
const LabelsAsString = (
labels:
| string
| ReadonlyArray<string>
| {
[x: string]: ReadonlyArray<{
name: string
}>
}
| null,
) => {
if (typeof labels === "string") {
return t.success([labels])
}
return
}
const RichTextLabels = new t.Type<ReadonlyArray<string>, object, unknown>(
"RichTextLabels",
(u: unknown): u is ReadonlyArray<string> => {
return u instanceof Array
},
(u: unknown, context: t.Context) => {
const legacyValidator = t.record(
t.string,
t.readonlyArray(t.record(t.literal("name"), t.string)),
)
const validator = t.readonlyArray(t.string)
return pipe(
t.union([legacyValidator, validator, t.string, t.null]).decode(u),
either.chain((labels) => {
return (
NoLabels(labels) ||
LabelsAsArray(labels) ||
LabelsAsObject(labels) ||
LabelsAsString(labels) ||
t.failure(u, context)
)
}),
)
},
(res) => res,
)
export const RichTextConfig = t.exact(
t.partial({
label: StringOrNull,
placeholder: t.string,
useAsTitle: t.boolean,
single: RichTextOptions,
multi: RichTextOptions,
imageConstraint: ImageConstraint,
labels: RichTextLabels,
allowTargetBlank: t.boolean,
}),
)
export type RichTextConfig = t.TypeOf<typeof RichTextConfig>
export const RichText = t.exact(
t.intersection([
t.type({
type: t.literal(RichTextFieldType),
}),
t.partial({
fieldset: StringOrNull,
config: RichTextConfig,
}),
]),
)
export type RichText = t.TypeOf<typeof RichText>