Skip to content

Commit

Permalink
feat(richtext-lexical): add maxDepth property to various lexical feat…
Browse files Browse the repository at this point in the history
…ures (#6242)
  • Loading branch information
AlessioGr committed May 7, 2024
1 parent d3e27e8 commit 721919f
Show file tree
Hide file tree
Showing 13 changed files with 146 additions and 59 deletions.
44 changes: 22 additions & 22 deletions docs/fields/relationship.mdx
Expand Up @@ -26,28 +26,28 @@ keywords: relationship, fields, config, configuration, documentation, Content Ma

### Config

| Option | Description |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`relationTo`** \* | Provide one or many collection `slug`s to be able to assign relationships to. |
| **`filterOptions`** | A query to filter which options appear in the UI and validate against. [More](#filtering-relationship-options). |
| **`hasMany`** | Boolean when, if set to `true`, allows this field to have many relations instead of only one. |
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. Used with `hasMany`. |
| **`maxRows`** | A number for the most allowed items during validation when a value is present. Used with `hasMany`. |
| **`maxDepth`** | Sets a number limit on iterations of related documents to populate when queried. [Depth](/docs/getting-started/concepts#depth) |
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| Option | Description |
|---------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`relationTo`** \* | Provide one or many collection `slug`s to be able to assign relationships to. |
| **`filterOptions`** | A query to filter which options appear in the UI and validate against. [More](#filtering-relationship-options). |
| **`hasMany`** | Boolean when, if set to `true`, allows this field to have many relations instead of only one. |
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. Used with `hasMany`. |
| **`maxRows`** | A number for the most allowed items during validation when a value is present. Used with `hasMany`. |
| **`maxDepth`** | Sets a maximum population depth for this field, regardless of the remaining depth when this field is reached. [Max Depth](/docs/getting-started/concepts#field-level-max-depth) |
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |

_\* An asterisk denotes that a property is required._

Expand Down
22 changes: 22 additions & 0 deletions docs/getting-started/concepts.mdx
Expand Up @@ -156,6 +156,28 @@ To populate `user.author.department` in it's entirety you could specify `?depth=
}
```

#### Field-level max depth

Fields like relationships or uploads can have a `maxDepth` property that limits the depth of the population for that field. Here are some examples:

Depth: 10
Current depth when field is accessed: 1
`maxDepth`: undefined

In this case, the field would be populated to 9 levels of population.

Depth: 10
Current depth when field is accessed: 0
`maxDepth`: 2

In this case, the field would be populated to 2 levels of population, despite there being a remaining depth of 8.

Depth: 10
Current depth when field is accessed: 2
`maxDepth`: 1

In this case, the field would not be populated, as the current depth (2) has exceeded the `maxDepth` for this field (1).

<Banner type="warning">
<strong>Note:</strong>
<br />
Expand Down
5 changes: 4 additions & 1 deletion packages/graphql/src/schema/buildObjectType.ts
Expand Up @@ -490,9 +490,12 @@ function buildObjectType({
if (editor?.populationPromises) {
const fieldPromises = []
const populationPromises = []
const populateDepth =
field?.maxDepth !== undefined && field?.maxDepth < depth ? field?.maxDepth : depth

editor?.populationPromises({
context,
depth,
depth: populateDepth,
draft: args.draft,
field,
fieldPromises,
Expand Down
1 change: 1 addition & 0 deletions packages/payload/src/fields/config/schema.ts
Expand Up @@ -477,6 +477,7 @@ export const richText = baseField.keys({
validate: joi.func().required(),
})
.unknown(),
maxDepth: joi.number(),
})

export const date = baseField.keys({
Expand Down
16 changes: 16 additions & 0 deletions packages/payload/src/fields/config/types.ts
Expand Up @@ -446,6 +446,11 @@ export type UploadField = FieldBase & {
}
}
filterOptions?: FilterOptions
/**
* Sets a maximum population depth for this field, regardless of the remaining depth when this field is reached.
*
* {@link https://payloadcms.com/docs/getting-started/concepts#field-level-max-depth}
*/
maxDepth?: number
relationTo: string
type: 'upload'
Expand Down Expand Up @@ -506,6 +511,11 @@ export type SelectField = FieldBase & {
type SharedRelationshipProperties = FieldBase & {
filterOptions?: FilterOptions
hasMany?: boolean
/**
* Sets a maximum population depth for this field, regardless of the remaining depth when this field is reached.
*
* {@link https://payloadcms.com/docs/getting-started/concepts#field-level-max-depth}
*/
maxDepth?: number
type: 'relationship'
} & (
Expand Down Expand Up @@ -588,6 +598,12 @@ export type RichTextField<
editor?:
| RichTextAdapter<Value, AdapterProps, AdapterProps>
| RichTextAdapterProvider<Value, AdapterProps, AdapterProps>
/**
* Sets a maximum population depth for this field, regardless of the remaining depth when this field is reached.
*
* {@link https://payloadcms.com/docs/getting-started/concepts#field-level-max-depth}
*/
maxDepth?: number
type: 'richText'
} & ExtraProperties

Expand Down
5 changes: 4 additions & 1 deletion packages/payload/src/fields/hooks/afterRead/promise.ts
Expand Up @@ -150,10 +150,13 @@ export const promise = async ({
const editor: RichTextAdapter = field?.editor
// This is run here AND in the GraphQL Resolver
if (editor?.populationPromises) {
const populateDepth =
field?.maxDepth !== undefined && field?.maxDepth < depth ? field?.maxDepth : depth

editor.populationPromises({
context,
currentDepth,
depth,
depth: populateDepth,
draft,
field,
fieldPromises,
Expand Down
Expand Up @@ -8,6 +8,7 @@ export const getBaseFields = (
config: SanitizedConfig,
enabledCollections: false | string[],
disabledCollections: false | string[],
maxDepth?: number,
): FieldWithRichTextRequiredEditor[] => {
let enabledRelations: string[]

Expand Down Expand Up @@ -97,6 +98,7 @@ export const getBaseFields = (
}
: null,
label: ({ t }) => t('fields:chooseDocumentToLink'),
maxDepth,
relationTo: enabledRelations,
required: true,
})
Expand Down
Expand Up @@ -50,6 +50,13 @@ export type LinkFeatureServerProps = ExclusiveLinkCollectionsProps & {
defaultFields: FieldWithRichTextRequiredEditor[]
}) => FieldWithRichTextRequiredEditor[])
| FieldWithRichTextRequiredEditor[]
/**
* Sets a maximum population depth for the internal doc default field of link, regardless of the remaining depth when the field is reached.
* This behaves exactly like the maxDepth properties of relationship and upload fields.
*
* {@link https://payloadcms.com/docs/getting-started/concepts#field-level-max-depth}
*/
maxDepth?: number
}

export const LinkFeature: FeatureProviderProviderServer<LinkFeatureServerProps, ClientProps> = (
Expand All @@ -67,6 +74,7 @@ export const LinkFeature: FeatureProviderProviderServer<LinkFeatureServerProps,
_config,
props.enabledCollections,
props.disabledCollections,
props.maxDepth,
)

const sanitizedFields = (await sanitizeFields({
Expand Down
Expand Up @@ -16,11 +16,13 @@ export function transformExtraFields(
config: SanitizedConfig,
enabledCollections?: false | string[],
disabledCollections?: false | string[],
maxDepth?: number,
): FieldWithRichTextRequiredEditor[] {
const baseFields: FieldWithRichTextRequiredEditor[] = getBaseFields(
config,
enabledCollections,
disabledCollections,
maxDepth,
)

let fields: FieldWithRichTextRequiredEditor[]
Expand Down
Expand Up @@ -3,9 +3,9 @@ import type { FeatureProviderProviderServer } from '../types.js'
import { createNode } from '../typeUtilities.js'
import { RelationshipFeatureClientComponent } from './feature.client.js'
import { RelationshipNode } from './nodes/RelationshipNode.js'
import { relationshipPopulationPromise } from './populationPromise.js'
import { relationshipPopulationPromiseHOC } from './populationPromise.js'

export type RelationshipFeatureProps =
export type ExclusiveRelationshipFeatureProps =
| {
/**
* The collections that should be disabled. Overrides the `enableRichTextRelationship` property in the collection config.
Expand All @@ -27,6 +27,16 @@ export type RelationshipFeatureProps =
enabledCollections?: string[]
}

export type RelationshipFeatureProps = ExclusiveRelationshipFeatureProps & {
/**
* Sets a maximum population depth for this relationship, regardless of the remaining depth when the respective field is reached.
* This behaves exactly like the maxDepth properties of relationship and upload fields.
*
* {@link https://payloadcms.com/docs/getting-started/concepts#field-level-max-depth}
*/
maxDepth?: number
}

export const RelationshipFeature: FeatureProviderProviderServer<
RelationshipFeatureProps,
RelationshipFeatureProps
Expand All @@ -39,7 +49,7 @@ export const RelationshipFeature: FeatureProviderProviderServer<
nodes: [
createNode({
node: RelationshipNode,
populationPromises: [relationshipPopulationPromise],
populationPromises: [relationshipPopulationPromiseHOC(props)],
// TODO: Add validation similar to upload
}),
],
Expand Down
@@ -1,41 +1,51 @@
import type { PopulationPromise } from '../types.js'
import type { RelationshipFeatureProps } from './feature.server.js'
import type { SerializedRelationshipNode } from './nodes/RelationshipNode.js'

import { populate } from '../../../populate/populate.js'

export const relationshipPopulationPromise: PopulationPromise<SerializedRelationshipNode> = ({
currentDepth,
depth,
draft,
field,
node,
overrideAccess,
populationPromises,
req,
showHiddenFields,
}) => {
if (node?.value) {
// @ts-expect-error
const id = node?.value?.id || node?.value // for backwards-compatibility
export const relationshipPopulationPromiseHOC = (
props: RelationshipFeatureProps,
): PopulationPromise<SerializedRelationshipNode> => {
const relationshipPopulationPromise: PopulationPromise<SerializedRelationshipNode> = ({
currentDepth,
depth,
draft,
field,
node,
overrideAccess,
populationPromises,
req,
showHiddenFields,
}) => {
if (node?.value) {
// @ts-expect-error
const id = node?.value?.id || node?.value // for backwards-compatibility

const collection = req.payload.collections[node?.relationTo]
const collection = req.payload.collections[node?.relationTo]

if (collection) {
populationPromises.push(
populate({
id,
collection,
currentDepth,
data: node,
depth,
draft,
field,
key: 'value',
overrideAccess,
req,
showHiddenFields,
}),
)
if (collection) {
const populateDepth =
props?.maxDepth !== undefined && props?.maxDepth < depth ? props?.maxDepth : depth

populationPromises.push(
populate({
id,
collection,
currentDepth,
data: node,
depth: populateDepth,
draft,
field,
key: 'value',
overrideAccess,
req,
showHiddenFields,
}),
)
}
}
}

return relationshipPopulationPromise
}
Expand Up @@ -26,6 +26,13 @@ export type UploadFeatureProps = {
fields: FieldWithRichTextRequiredEditor[]
}
}
/**
* Sets a maximum population depth for this upload (not the fields for this upload), regardless of the remaining depth when the respective field is reached.
* This behaves exactly like the maxDepth properties of relationship and upload fields.
*
* {@link https://payloadcms.com/docs/getting-started/concepts#field-level-max-depth}
*/
maxDepth?: number
}

/**
Expand Down
Expand Up @@ -31,13 +31,16 @@ export const uploadPopulationPromiseHOC = (
// @ts-expect-error
const id = node?.value?.id || node?.value // for backwards-compatibility

const populateDepth =
props?.maxDepth !== undefined && props?.maxDepth < depth ? props?.maxDepth : depth

populationPromises.push(
populate({
id,
collection,
currentDepth,
data: node,
depth,
depth: populateDepth,
draft,
field,
key: 'value',
Expand Down

0 comments on commit 721919f

Please sign in to comment.