Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support link variants #368

Merged
merged 15 commits into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions src/Migration.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as is from "./lib/isValue"
import { getOptionalLinkProperties } from "./lib/getOptionalLinkProperties"
import { validateAssetMetadata } from "./lib/validateAssetMetadata"

import type { Asset } from "./types/api/asset/asset"
Expand Down Expand Up @@ -404,28 +405,32 @@ export class Migration<TDocuments extends PrismicDocument = PrismicDocument> {
*/
#migratePrismicDocumentData(input: unknown): unknown {
if (is.filledContentRelationship(input)) {
const optionalLinkProperties = getOptionalLinkProperties(input)

if (input.isBroken) {
return {
...optionalLinkProperties,
link_type: LinkType.Document,
// ID needs to be 16 characters long to be considered valid by the API
id: "_____broken_____",
isBroken: true,
text: input.text,
}
}

return {
...optionalLinkProperties,
link_type: LinkType.Document,
id: () => this._getByOriginalID(input.id),
text: input.text,
}
}

if (is.filledLinkToMedia(input)) {
const optionalLinkProperties = getOptionalLinkProperties(input)

return {
...optionalLinkProperties,
link_type: LinkType.Media,
id: this.createAsset(input),
text: input.text,
}
}

Expand Down
27 changes: 27 additions & 0 deletions src/lib/getOptionalLinkProperties.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { OptionalLinkProperties } from "../types/value/link"

/**
* Returns optional properties only available to link fields. Link fields can
* have the same shape as content relationship and link to media fields,
* requiring special treatment to extract link-specific properties.
*
* @param input - The content relationship or link to media field from which the
* link properties will be extracted.
*
* @returns Optional link properties that `input` might have.
*/
export const getOptionalLinkProperties = (
input: OptionalLinkProperties,
): OptionalLinkProperties => {
const res: OptionalLinkProperties = {}

if ("variant" in input) {
res.variant = input.variant
}

if ("text" in input) {
res.text = input.text
}

return res
}
7 changes: 6 additions & 1 deletion src/lib/isMigrationValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import type { PrismicDocument } from "../types/value/document"
import type { GroupField } from "../types/value/group"
import { LinkType } from "../types/value/link"
import type { OptionalLinkProperties } from "../types/value/link"
import { RichTextNodeType } from "../types/value/richText"
import type { SliceZone } from "../types/value/sliceZone"
import type { AnyRegularField } from "../types/value/types"
Expand All @@ -32,6 +33,10 @@ type UnknownValue =
/**
* Checks if a value is a migration content relationship field.
*
* @remarks
* `OptionalLinkProperties` is included because `MigrationContentRelationship`
* may be a link field, not strictly a content relationship field.
*
* @param value - Value to check.
*
* @returns `true` if `value` is a migration content relationship field, `false`
Expand All @@ -42,7 +47,7 @@ type UnknownValue =
*/
export const contentRelationship = (
value: UnknownValue,
): value is MigrationContentRelationship => {
): value is MigrationContentRelationship & OptionalLinkProperties => {
return (
value instanceof PrismicMigrationDocument ||
is.prismicDocument(value) ||
Expand Down
15 changes: 13 additions & 2 deletions src/lib/isValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { PrismicDocument } from "../types/value/document"
import type { GroupField } from "../types/value/group"
import type { ImageField } from "../types/value/image"
import { LinkType } from "../types/value/link"
import type { OptionalLinkProperties } from "../types/value/link"
import type { FilledLinkToMediaField } from "../types/value/linkToMedia"
import { type RTImageNode, RichTextNodeType } from "../types/value/richText"
import type { SliceZone } from "../types/value/sliceZone"
Expand All @@ -23,6 +24,11 @@ type UnknownValue =
/**
* Checks if a value is a link to media field.
*
* @remarks
* The return value includes `OptionalLinkProperties` because
* `FilledLinkToMediaField` may be a link field, not strictly a content
* relationship field.
*
* @param value - Value to check.
*
* @returns `true` if `value` is a link to media field, `false` otherwise.
Expand All @@ -32,7 +38,7 @@ type UnknownValue =
*/
export const filledLinkToMedia = (
value: UnknownValue,
): value is FilledLinkToMediaField => {
): value is FilledLinkToMediaField & OptionalLinkProperties => {
if (value && typeof value === "object" && !("version" in value)) {
if (
"link_type" in value &&
Expand Down Expand Up @@ -138,6 +144,11 @@ export const rtImageNode = (value: UnknownValue): value is RTImageNode => {
/**
* Checks if a value is a content relationship field.
*
* @remarks
* The return value includes `OptionalLinkProperties` because
* `FilledContentRelationshipField` may be a link field, not strictly a content
* relationship field.
*
* @param value - Value to check.
*
* @returns `true` if `value` is a content relationship, `false` otherwise.
Expand All @@ -147,7 +158,7 @@ export const rtImageNode = (value: UnknownValue): value is RTImageNode => {
*/
export const filledContentRelationship = (
value: UnknownValue,
): value is FilledContentRelationshipField => {
): value is FilledContentRelationshipField & OptionalLinkProperties => {
if (value && typeof value === "object" && !("version" in value)) {
if (
"link_type" in value &&
Expand Down
45 changes: 32 additions & 13 deletions src/lib/resolveMigrationDocumentData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import type {
MigrationContentRelationshipField,
} from "../types/migration/ContentRelationship"
import { PrismicMigrationDocument } from "../types/migration/Document"
import type { MaybeLink } from "../types/migration/Link"
import type { FilledImageFieldImage } from "../types/value/image"
import type { LinkField } from "../types/value/link"
import type { LinkField, OptionalLinkProperties } from "../types/value/link"
import { LinkType } from "../types/value/link"
import type { RTImageNode } from "../types/value/richText"
import { RichTextNodeType } from "../types/value/richText"
Expand All @@ -20,6 +21,7 @@ import * as isFilled from "../helpers/isFilled"
import type { Migration } from "../Migration"

import * as isMigration from "./isMigrationValue"
import { getOptionalLinkProperties } from "./getOptionalLinkProperties"

/**
* Resolves a migration content relationship to a content relationship field.
Expand All @@ -29,35 +31,48 @@ import * as isMigration from "./isMigrationValue"
* @returns Resolved content relationship field.
*/
export async function resolveMigrationContentRelationship(
relation: MigrationContentRelationship,
): Promise<MigrationContentRelationshipField> {
relation: MaybeLink<MigrationContentRelationship>,
): Promise<MigrationContentRelationshipField & OptionalLinkProperties> {
if (typeof relation === "function") {
return resolveMigrationContentRelationship(await relation())
}

if (relation instanceof PrismicMigrationDocument) {
return relation.document.id
? { link_type: LinkType.Document, id: relation.document.id }
: { link_type: LinkType.Document }
: { link_type: LinkType.Any }
}

const optionalLinkProperties =
relation && "link_type" in relation
? getOptionalLinkProperties(relation)
: undefined

if (relation) {
if (
isMigration.contentRelationship(relation.id) ||
typeof relation.id === "function"
typeof relation.id !== "string"
) {
return {
...optionalLinkProperties,
...(await resolveMigrationContentRelationship(relation.id)),
// TODO: Remove when link text PR is merged
// @ts-expect-error - Future-proofing for link text
text: relation.text,
}
}

return { link_type: LinkType.Document, id: relation.id }
// This is only called when resolveMigrationContentRelationship recursively
// calls itself from the statement above and the resolved content relation
// is a Prismic document value.
return {
...optionalLinkProperties,
link_type: LinkType.Document,
id: relation.id,
}
}

return { link_type: LinkType.Document }
return {
...optionalLinkProperties,
link_type: LinkType.Any,
}
}

/**
Expand Down Expand Up @@ -154,20 +169,24 @@ export const resolveMigrationRTImageNode = async (
* @returns Resolved link to media field.
*/
export const resolveMigrationLinkToMedia = (
linkToMedia: MigrationLinkToMedia,
linkToMedia: MaybeLink<MigrationLinkToMedia>,
migration: Migration,
): MigrationLinkToMediaField => {
const asset = migration._assets.get(linkToMedia.id.config.id)?.asset
const optionalLinkProperties = getOptionalLinkProperties(linkToMedia)

if (asset) {
return {
...optionalLinkProperties,
id: asset.id,
link_type: LinkType.Media,
text: linkToMedia.text,
}
}

return { link_type: LinkType.Media }
return {
...optionalLinkProperties,
link_type: LinkType.Any,
}
}

/**
Expand Down
18 changes: 8 additions & 10 deletions src/types/migration/Asset.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { Asset } from "../api/asset/asset"
import type { FilledImageFieldImage } from "../value/image"
import type { EmptyLinkField } from "../value/link"
import type { LinkToMediaField } from "../value/linkToMedia"
import { type RTImageNode } from "../value/richText"

Expand Down Expand Up @@ -74,22 +73,21 @@ export type MigrationImage =
export type MigrationLinkToMedia = Pick<
LinkToMediaField<"filled">,
"link_type"
> &
Partial<Pick<LinkToMediaField<"filled">, "text">> & {
/**
* A reference to the migration asset used to resolve the link to media
* field's value.
*/
id: PrismicMigrationAsset
}
> & {
/**
* A reference to the migration asset used to resolve the link to media
* field's value.
*/
id: PrismicMigrationAsset
}

/**
* The minimum amount of information needed to represent a link to media field
* with the migration API.
*/
export type MigrationLinkToMediaField =
| Pick<LinkToMediaField<"filled">, "link_type" | "id" | "text">
| EmptyLinkField<"Media">
| LinkToMediaField<"empty">

/**
* A rich text image node in a migration.
Expand Down
20 changes: 11 additions & 9 deletions src/types/migration/ContentRelationship.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import type { FilledContentRelationshipField } from "../value/contentRelationship"
import type {
ContentRelationshipField,
EmptyContentRelationshipField,
FilledContentRelationshipField,
} from "../value/contentRelationship"
import type { PrismicDocument } from "../value/document"
import type { EmptyLinkField } from "../value/link"

import type { PrismicMigrationDocument } from "./Document"

Expand All @@ -13,17 +16,16 @@ export type MigrationContentRelationship<
TDocuments extends PrismicDocument = PrismicDocument,
> =
| ValueOrThunk<TDocuments | PrismicMigrationDocument<TDocuments> | undefined>
| (Pick<FilledContentRelationshipField, "link_type"> &
Partial<Pick<FilledContentRelationshipField, "text">> & {
id: ValueOrThunk<
TDocuments | PrismicMigrationDocument<TDocuments> | undefined
>
})
| (Pick<ContentRelationshipField, "link_type"> & {
id: ValueOrThunk<
TDocuments | PrismicMigrationDocument<TDocuments> | undefined
>
})

/**
* The minimum amount of information needed to represent a content relationship
* field with the migration API.
*/
export type MigrationContentRelationshipField =
| Pick<FilledContentRelationshipField, "link_type" | "id">
| EmptyLinkField<"Document">
| EmptyContentRelationshipField
19 changes: 19 additions & 0 deletions src/types/migration/Link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { LinkType, OptionalLinkProperties } from "../value/link"

/**
* Adds `OptionalLinkProperties` to any type that looks like a link object (one
* that includes a valid `link_type` property).
*
* @example
*
* ```ts
* type Example = MaybeLink<PrismicDocument | LinkField>
* // PrismicDocument | (LinkField & OptionalLinkProperties)
* ```
*
* @typeParam T - The type to augment.
*/
export type MaybeLink<T> =
| Exclude<T, { link_type: (typeof LinkType)[keyof typeof LinkType] }>
| (Extract<T, { link_type: (typeof LinkType)[keyof typeof LinkType] }> &
OptionalLinkProperties)
1 change: 0 additions & 1 deletion src/types/model/contentRelationship.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,5 @@ export interface CustomTypeModelContentRelationshipField<
select: typeof CustomTypeModelLinkSelectType.Document
customtypes?: readonly CustomTypeIDs[]
tags?: readonly Tags[]
allowText?: boolean
}
}
1 change: 1 addition & 0 deletions src/types/model/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface CustomTypeModelLinkField {
allowText?: boolean
allowTargetBlank?: boolean
repeat?: boolean
variants?: string[]
}
}

Expand Down
10 changes: 6 additions & 4 deletions src/types/value/contentRelationship.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { AnyRegularField, FieldState } from "./types"

import type { GroupField } from "./group"
import type { EmptyLinkField, LinkType } from "./link"
import type { SliceZone } from "./sliceZone"

/**
Expand All @@ -21,9 +20,13 @@ export type ContentRelationshipField<
| unknown = unknown,
State extends FieldState = FieldState,
> = State extends "empty"
? EmptyLinkField<typeof LinkType.Document>
? EmptyContentRelationshipField
: FilledContentRelationshipField<TypeEnum, LangEnum, DataInterface>

export type EmptyContentRelationshipField = {
link_type: "Any"
}

/**
* Links that refer to documents
*/
Expand All @@ -34,7 +37,7 @@ export interface FilledContentRelationshipField<
| Record<string, AnyRegularField | GroupField | SliceZone>
| unknown = unknown,
> {
link_type: typeof LinkType.Document
link_type: "Document"
id: string
uid?: string
type: TypeEnum
Expand All @@ -44,5 +47,4 @@ export interface FilledContentRelationshipField<
slug?: string
isBroken?: boolean
data?: DataInterface
text?: string
}
Loading
Loading