feat: add siblingFields arg to field hooks (#11117)
This PR adds a new `siblingFields` argument to field hooks. This allows us to dramatically simplify the `lexicalHTML` field, which previously had to use a complex `findFieldPathAndSiblingFields` function that deeply traverses the entire `CollectionConfig` just to find the sibling fields.
This commit is contained in:
@@ -71,6 +71,8 @@ The following arguments are provided to all Field Hooks:
|
||||
| **`schemaPath`** | The path of the [Field](../fields/overview) in the schema. |
|
||||
| **`siblingData`** | The data of sibling fields adjacent to the field that the Hook is running against. |
|
||||
| **`siblingDocWithLocales`** | The sibling data of the Document with all [Locales](../configuration/localization). |
|
||||
| **`siblingFields`** | The sibling fields of the field which the hook is running against.
|
||||
|
|
||||
| **`value`** | The value of the [Field](../fields/overview). |
|
||||
|
||||
<Banner type="success">
|
||||
|
||||
@@ -164,7 +164,8 @@ export type FieldHookArgs<TData extends TypeWithID = any, TValue = any, TSibling
|
||||
/**
|
||||
* Only available in `afterRead` hooks
|
||||
*/
|
||||
currentDepth?: number /**
|
||||
currentDepth?: number
|
||||
/**
|
||||
* Only available in `afterRead` hooks
|
||||
*/
|
||||
/** The data passed to update the document within create and update operations, and the full document itself in the afterRead hook. */
|
||||
@@ -212,6 +213,10 @@ export type FieldHookArgs<TData extends TypeWithID = any, TValue = any, TSibling
|
||||
* The original siblingData with locales (not modified by any hooks). Only available in `beforeChange` and `beforeDuplicate` field hooks.
|
||||
*/
|
||||
siblingDocWithLocales?: Record<string, unknown>
|
||||
/**
|
||||
* The sibling fields of the field which the hook is running against.
|
||||
*/
|
||||
siblingFields: (Field | TabAsField)[]
|
||||
/** The value of the field. */
|
||||
value?: TValue
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ type Args = {
|
||||
req: PayloadRequest
|
||||
siblingData: JsonObject
|
||||
siblingDoc: JsonObject
|
||||
siblingFields?: (Field | TabAsField)[]
|
||||
}
|
||||
|
||||
// This function is responsible for the following actions, in order:
|
||||
@@ -54,6 +55,7 @@ export const promise = async ({
|
||||
req,
|
||||
siblingData,
|
||||
siblingDoc,
|
||||
siblingFields,
|
||||
}: Args): Promise<void> => {
|
||||
const { indexPath, path, schemaPath } = getFieldPaths({
|
||||
field,
|
||||
@@ -90,6 +92,7 @@ export const promise = async ({
|
||||
req,
|
||||
schemaPath: schemaPathSegments,
|
||||
siblingData,
|
||||
siblingFields,
|
||||
value: siblingDoc[field.name],
|
||||
})
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ type Args = {
|
||||
req: PayloadRequest
|
||||
siblingData: JsonObject
|
||||
siblingDoc: JsonObject
|
||||
siblingFields?: (Field | TabAsField)[]
|
||||
}
|
||||
|
||||
export const traverseFields = async ({
|
||||
@@ -45,6 +46,7 @@ export const traverseFields = async ({
|
||||
req,
|
||||
siblingData,
|
||||
siblingDoc,
|
||||
siblingFields,
|
||||
}: Args): Promise<void> => {
|
||||
const promises = []
|
||||
|
||||
@@ -68,6 +70,7 @@ export const traverseFields = async ({
|
||||
req,
|
||||
siblingData,
|
||||
siblingDoc,
|
||||
siblingFields,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -51,6 +51,7 @@ type Args = {
|
||||
selectMode?: SelectMode
|
||||
showHiddenFields: boolean
|
||||
siblingDoc: JsonObject
|
||||
siblingFields?: (Field | TabAsField)[]
|
||||
triggerAccessControl?: boolean
|
||||
triggerHooks?: boolean
|
||||
}
|
||||
@@ -90,6 +91,7 @@ export const promise = async ({
|
||||
selectMode,
|
||||
showHiddenFields,
|
||||
siblingDoc,
|
||||
siblingFields,
|
||||
triggerAccessControl = true,
|
||||
triggerHooks = true,
|
||||
}: Args): Promise<void> => {
|
||||
@@ -260,6 +262,7 @@ export const promise = async ({
|
||||
schemaPath: schemaPathSegments,
|
||||
showHiddenFields,
|
||||
siblingData: siblingDoc,
|
||||
siblingFields,
|
||||
value,
|
||||
})
|
||||
|
||||
@@ -291,6 +294,7 @@ export const promise = async ({
|
||||
schemaPath: schemaPathSegments,
|
||||
showHiddenFields,
|
||||
siblingData: siblingDoc,
|
||||
siblingFields,
|
||||
value: siblingDoc[field.name],
|
||||
})
|
||||
|
||||
|
||||
@@ -106,6 +106,7 @@ export const traverseFields = ({
|
||||
selectMode,
|
||||
showHiddenFields,
|
||||
siblingDoc,
|
||||
siblingFields: fields,
|
||||
triggerAccessControl,
|
||||
triggerHooks,
|
||||
}),
|
||||
|
||||
@@ -39,6 +39,7 @@ type Args = {
|
||||
siblingData: JsonObject
|
||||
siblingDoc: JsonObject
|
||||
siblingDocWithLocales?: JsonObject
|
||||
siblingFields?: (Field | TabAsField)[]
|
||||
skipValidation: boolean
|
||||
}
|
||||
|
||||
@@ -71,6 +72,7 @@ export const promise = async ({
|
||||
siblingData,
|
||||
siblingDoc,
|
||||
siblingDocWithLocales,
|
||||
siblingFields,
|
||||
skipValidation,
|
||||
}: Args): Promise<void> => {
|
||||
const { indexPath, path, schemaPath } = getFieldPaths({
|
||||
@@ -123,6 +125,7 @@ export const promise = async ({
|
||||
schemaPath: schemaPathSegments,
|
||||
siblingData,
|
||||
siblingDocWithLocales,
|
||||
siblingFields,
|
||||
value: siblingData[field.name],
|
||||
})
|
||||
|
||||
|
||||
@@ -100,6 +100,7 @@ export const traverseFields = async ({
|
||||
siblingData,
|
||||
siblingDoc,
|
||||
siblingDocWithLocales,
|
||||
siblingFields: fields,
|
||||
skipValidation,
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -25,6 +25,7 @@ type Args<T> = {
|
||||
parentSchemaPath: string
|
||||
req: PayloadRequest
|
||||
siblingDoc: JsonObject
|
||||
siblingFields?: (Field | TabAsField)[]
|
||||
}
|
||||
|
||||
export const promise = async <T>({
|
||||
@@ -41,6 +42,7 @@ export const promise = async <T>({
|
||||
parentSchemaPath,
|
||||
req,
|
||||
siblingDoc,
|
||||
siblingFields,
|
||||
}: Args<T>): Promise<void> => {
|
||||
const { indexPath, path, schemaPath } = getFieldPaths({
|
||||
field,
|
||||
@@ -82,6 +84,7 @@ export const promise = async <T>({
|
||||
schemaPath: schemaPathSegments,
|
||||
siblingData: siblingDoc,
|
||||
siblingDocWithLocales: siblingDoc,
|
||||
siblingFields,
|
||||
value: siblingDoc[field.name]?.[locale],
|
||||
}
|
||||
|
||||
@@ -116,6 +119,7 @@ export const promise = async <T>({
|
||||
schemaPath: schemaPathSegments,
|
||||
siblingData: siblingDoc,
|
||||
siblingDocWithLocales: siblingDoc,
|
||||
siblingFields,
|
||||
value: siblingDoc[field.name],
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ export const traverseFields = async <T>({
|
||||
parentSchemaPath,
|
||||
req,
|
||||
siblingDoc,
|
||||
siblingFields: fields,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -40,6 +40,7 @@ type Args<T> = {
|
||||
* The original siblingData (not modified by any hooks)
|
||||
*/
|
||||
siblingDoc: JsonObject
|
||||
siblingFields?: (Field | TabAsField)[]
|
||||
}
|
||||
|
||||
// This function is responsible for the following actions, in order:
|
||||
@@ -67,6 +68,7 @@ export const promise = async <T>({
|
||||
req,
|
||||
siblingData,
|
||||
siblingDoc,
|
||||
siblingFields,
|
||||
}: Args<T>): Promise<void> => {
|
||||
const { indexPath, path, schemaPath } = getFieldPaths({
|
||||
field,
|
||||
@@ -291,6 +293,7 @@ export const promise = async <T>({
|
||||
req,
|
||||
schemaPath: schemaPathSegments,
|
||||
siblingData,
|
||||
siblingFields,
|
||||
value: siblingData[field.name],
|
||||
})
|
||||
|
||||
|
||||
@@ -74,6 +74,7 @@ export const traverseFields = async <T>({
|
||||
req,
|
||||
siblingData,
|
||||
siblingDoc,
|
||||
siblingFields: fields,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -83,62 +83,6 @@ export const consolidateHTMLConverters = ({
|
||||
return filteredConverters
|
||||
}
|
||||
|
||||
// find the path of this field, as well as its sibling fields, by looking for this `field` in fields and traversing it recursively
|
||||
function findFieldPathAndSiblingFields(
|
||||
fields: Field[],
|
||||
path: string[],
|
||||
field: FieldAffectingData,
|
||||
): {
|
||||
path: string[]
|
||||
siblingFields: Field[]
|
||||
} | null {
|
||||
for (const curField of fields) {
|
||||
if (curField === field) {
|
||||
return {
|
||||
path: [...path, curField.name],
|
||||
siblingFields: fields,
|
||||
}
|
||||
}
|
||||
|
||||
if ('fields' in curField) {
|
||||
const result = findFieldPathAndSiblingFields(
|
||||
curField.fields,
|
||||
'name' in curField ? [...path, curField.name] : [...path],
|
||||
field,
|
||||
)
|
||||
if (result) {
|
||||
return result
|
||||
}
|
||||
} else if ('tabs' in curField) {
|
||||
for (const tab of curField.tabs) {
|
||||
const result = findFieldPathAndSiblingFields(
|
||||
tab.fields,
|
||||
'name' in tab ? [...path, tab.name] : [...path],
|
||||
field,
|
||||
)
|
||||
if (result) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
} else if ('blocks' in curField) {
|
||||
for (const block of curField.blocks) {
|
||||
if (block?.fields?.length) {
|
||||
const result = findFieldPathAndSiblingFields(
|
||||
block.fields,
|
||||
[...path, curField.name, block.slug],
|
||||
field,
|
||||
)
|
||||
if (result) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export const lexicalHTML: (
|
||||
/**
|
||||
* A string which matches the lexical field name you want to convert to HTML.
|
||||
@@ -161,28 +105,22 @@ export const lexicalHTML: (
|
||||
hooks: {
|
||||
afterRead: [
|
||||
async ({
|
||||
collection,
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
field,
|
||||
global,
|
||||
overrideAccess,
|
||||
req,
|
||||
showHiddenFields,
|
||||
siblingData,
|
||||
siblingFields,
|
||||
}) => {
|
||||
const fields = collection ? collection.fields : global!.fields
|
||||
|
||||
const foundSiblingFields = findFieldPathAndSiblingFields(fields, [], field)
|
||||
|
||||
if (!foundSiblingFields) {
|
||||
if (!siblingFields) {
|
||||
throw new Error(
|
||||
`Could not find sibling fields of current lexicalHTML field with name ${field?.name}`,
|
||||
)
|
||||
}
|
||||
|
||||
const { siblingFields } = foundSiblingFields
|
||||
const lexicalField: RichTextField<SerializedEditorState, AdapterProps> =
|
||||
siblingFields.find(
|
||||
(field) => 'name' in field && field.name === lexicalFieldName,
|
||||
|
||||
Reference in New Issue
Block a user