diff --git a/packages/plugin-nested-docs/src/hooks/populateBreadcrumbsBeforeChange.ts b/packages/plugin-nested-docs/src/hooks/populateBreadcrumbsBeforeChange.ts new file mode 100644 index 0000000000..c62776b0af --- /dev/null +++ b/packages/plugin-nested-docs/src/hooks/populateBreadcrumbsBeforeChange.ts @@ -0,0 +1,19 @@ +import type { CollectionBeforeChangeHook } from 'payload' + +import type { NestedDocsPluginConfig } from '../types.js' + +import { populateBreadcrumbs } from '../utilities/populateBreadcrumbs.js' + +export const populateBreadcrumbsBeforeChange = + (pluginConfig: NestedDocsPluginConfig): CollectionBeforeChangeHook => + async ({ collection, data, originalDoc, req }) => + populateBreadcrumbs({ + breadcrumbsFieldName: pluginConfig.breadcrumbsFieldSlug, + collection, + data, + generateLabel: pluginConfig.generateLabel, + generateURL: pluginConfig.generateURL, + originalDoc, + parentFieldName: pluginConfig.parentFieldSlug, + req, + }) diff --git a/packages/plugin-nested-docs/src/hooks/resaveChildren.ts b/packages/plugin-nested-docs/src/hooks/resaveChildren.ts index 08675106db..7e37ccb251 100644 --- a/packages/plugin-nested-docs/src/hooks/resaveChildren.ts +++ b/packages/plugin-nested-docs/src/hooks/resaveChildren.ts @@ -1,10 +1,4 @@ -import type { - CollectionAfterChangeHook, - CollectionConfig, - JsonObject, - PayloadRequest, - ValidationError, -} from 'payload' +import type { CollectionAfterChangeHook, JsonObject, ValidationError } from 'payload' import { APIError, ValidationErrorName } from 'payload' @@ -12,21 +6,16 @@ import type { NestedDocsPluginConfig } from '../types.js' import { populateBreadcrumbs } from '../utilities/populateBreadcrumbs.js' -type ResaveArgs = { - collection: CollectionConfig - doc: JsonObject - draft: boolean - pluginConfig: NestedDocsPluginConfig - req: PayloadRequest -} +export const resaveChildren = + (pluginConfig: NestedDocsPluginConfig): CollectionAfterChangeHook => + async ({ collection, doc, req }) => { + if (collection.versions.drafts && doc._status !== 'published') { + // If the parent is a draft, don't resave children + return + } -const resave = async ({ collection, doc, draft, pluginConfig, req }: ResaveArgs) => { - const parentSlug = pluginConfig?.parentFieldSlug || 'parent' + const parentSlug = pluginConfig?.parentFieldSlug || 'parent' - if (draft) { - // If the parent is a draft, don't resave children - return - } else { const initialDraftChildren = await req.payload.find({ collection: collection.slug, depth: 0, @@ -74,7 +63,7 @@ const resave = async ({ collection, doc, draft, pluginConfig, req }: ResaveArgs) }) }) - if (sortedChildren) { + if (sortedChildren.length) { try { for (const child of sortedChildren) { const isDraft = child._status !== 'published' @@ -82,7 +71,14 @@ const resave = async ({ collection, doc, draft, pluginConfig, req }: ResaveArgs) await req.payload.update({ id: child.id, collection: collection.slug, - data: populateBreadcrumbs(req, pluginConfig, collection, child), + data: populateBreadcrumbs({ + collection, + data: child, + generateLabel: pluginConfig.generateLabel, + generateURL: pluginConfig.generateURL, + parentFieldName: pluginConfig.parentFieldSlug, + req, + }), depth: 0, draft: isDraft, locale: req.locale, @@ -106,19 +102,6 @@ const resave = async ({ collection, doc, draft, pluginConfig, req }: ResaveArgs) } } } - } -} - -export const resaveChildren = - (pluginConfig: NestedDocsPluginConfig, collection: CollectionConfig): CollectionAfterChangeHook => - async ({ doc, req }) => { - await resave({ - collection, - doc, - draft: doc._status === 'published' ? false : true, - pluginConfig, - req, - }) return undefined } diff --git a/packages/plugin-nested-docs/src/hooks/resaveSelfAfterCreate.ts b/packages/plugin-nested-docs/src/hooks/resaveSelfAfterCreate.ts index facc5464cf..f5bf06d2cb 100644 --- a/packages/plugin-nested-docs/src/hooks/resaveSelfAfterCreate.ts +++ b/packages/plugin-nested-docs/src/hooks/resaveSelfAfterCreate.ts @@ -1,4 +1,4 @@ -import type { CollectionAfterChangeHook, CollectionConfig } from 'payload' +import type { CollectionAfterChangeHook } from 'payload' import type { Breadcrumb, NestedDocsPluginConfig } from '../types.js' @@ -6,8 +6,8 @@ import type { Breadcrumb, NestedDocsPluginConfig } from '../types.js' // so that we can build its breadcrumbs with the newly created document's ID. export const resaveSelfAfterCreate = - (pluginConfig: NestedDocsPluginConfig, collection: CollectionConfig): CollectionAfterChangeHook => - async ({ doc, operation, req }) => { + (pluginConfig: NestedDocsPluginConfig): CollectionAfterChangeHook => + async ({ collection, doc, operation, req }) => { if (operation !== 'create') { return undefined } @@ -16,11 +16,6 @@ export const resaveSelfAfterCreate = const breadcrumbSlug = pluginConfig.breadcrumbsFieldSlug || 'breadcrumbs' const breadcrumbs = doc[breadcrumbSlug] as unknown as Breadcrumb[] - const updateAsDraft = - typeof collection.versions === 'object' && - collection.versions.drafts && - doc._status !== 'published' - try { await payload.update({ id: doc.id, @@ -33,7 +28,7 @@ export const resaveSelfAfterCreate = })) || [], }, depth: 0, - draft: updateAsDraft, + draft: collection.versions.drafts && doc._status !== 'published', locale, req, }) diff --git a/packages/plugin-nested-docs/src/index.ts b/packages/plugin-nested-docs/src/index.ts index c3aad4f199..a87ac39959 100644 --- a/packages/plugin-nested-docs/src/index.ts +++ b/packages/plugin-nested-docs/src/index.ts @@ -5,10 +5,10 @@ import type { NestedDocsPluginConfig } from './types.js' import { createBreadcrumbsField } from './fields/breadcrumbs.js' import { createParentField } from './fields/parent.js' import { parentFilterOptions } from './fields/parentFilterOptions.js' +import { populateBreadcrumbsBeforeChange } from './hooks/populateBreadcrumbsBeforeChange.js' import { resaveChildren } from './hooks/resaveChildren.js' import { resaveSelfAfterCreate } from './hooks/resaveSelfAfterCreate.js' import { getParents } from './utilities/getParents.js' -import { populateBreadcrumbs } from './utilities/populateBreadcrumbs.js' export { createBreadcrumbsField, createParentField, getParents } @@ -53,13 +53,12 @@ export const nestedDocsPlugin = hooks: { ...(collection.hooks || {}), afterChange: [ - resaveChildren(pluginConfig, collection), - resaveSelfAfterCreate(pluginConfig, collection), + resaveChildren(pluginConfig), + resaveSelfAfterCreate(pluginConfig), ...(collection?.hooks?.afterChange || []), ], beforeChange: [ - async ({ data, originalDoc, req }) => - populateBreadcrumbs(req, pluginConfig, collection, data, originalDoc), + populateBreadcrumbsBeforeChange(pluginConfig), ...(collection?.hooks?.beforeChange || []), ], }, diff --git a/packages/plugin-nested-docs/src/utilities/formatBreadcrumb.ts b/packages/plugin-nested-docs/src/utilities/formatBreadcrumb.ts index 2bb92a2365..13702b38c4 100644 --- a/packages/plugin-nested-docs/src/utilities/formatBreadcrumb.ts +++ b/packages/plugin-nested-docs/src/utilities/formatBreadcrumb.ts @@ -1,23 +1,30 @@ -import type { CollectionConfig } from 'payload' +import type { SanitizedCollectionConfig } from 'payload' -import type { Breadcrumb, NestedDocsPluginConfig } from '../types.js' +import type { Breadcrumb, GenerateLabel, GenerateURL } from '../types.js' -export const formatBreadcrumb = ( - pluginConfig: NestedDocsPluginConfig, - collection: CollectionConfig, - docs: Array>, -): Breadcrumb => { +type Args = { + collection: SanitizedCollectionConfig + docs: Record[] + generateLabel?: GenerateLabel + generateURL?: GenerateURL +} +export const formatBreadcrumb = ({ + collection, + docs, + generateLabel, + generateURL, +}: Args): Breadcrumb => { let url: string | undefined = undefined let label: string const lastDoc = docs[docs.length - 1]! - if (typeof pluginConfig?.generateURL === 'function') { - url = pluginConfig.generateURL(docs, lastDoc) + if (typeof generateURL === 'function') { + url = generateURL(docs, lastDoc) } - if (typeof pluginConfig?.generateLabel === 'function') { - label = pluginConfig.generateLabel(docs, lastDoc) + if (typeof generateLabel === 'function') { + label = generateLabel(docs, lastDoc) } else { const title = collection.admin?.useAsTitle ? lastDoc[collection.admin.useAsTitle] : '' diff --git a/packages/plugin-nested-docs/src/utilities/getParents.ts b/packages/plugin-nested-docs/src/utilities/getParents.ts index 68ebacd7fc..a5ebee0a5e 100644 --- a/packages/plugin-nested-docs/src/utilities/getParents.ts +++ b/packages/plugin-nested-docs/src/utilities/getParents.ts @@ -1,14 +1,14 @@ -import type { CollectionConfig, PayloadRequest } from 'payload' +import type { CollectionConfig, Document, PayloadRequest } from 'payload' import type { NestedDocsPluginConfig } from '../types.js' export const getParents = async ( req: PayloadRequest, - pluginConfig: NestedDocsPluginConfig, + pluginConfig: Pick, collection: CollectionConfig, doc: Record, docs: Array> = [], -): Promise>> => { +): Promise => { const parentSlug = pluginConfig?.parentFieldSlug || 'parent' const parent = doc[parentSlug] let retrievedParent: null | Record = null diff --git a/packages/plugin-nested-docs/src/utilities/populateBreadcrumbs.ts b/packages/plugin-nested-docs/src/utilities/populateBreadcrumbs.ts index 27e3eb2881..86b1431269 100644 --- a/packages/plugin-nested-docs/src/utilities/populateBreadcrumbs.ts +++ b/packages/plugin-nested-docs/src/utilities/populateBreadcrumbs.ts @@ -1,42 +1,62 @@ -import type { CollectionConfig } from 'payload' +import type { Data, Document, PayloadRequest, SanitizedCollectionConfig } from 'payload' -import type { NestedDocsPluginConfig } from '../types.js' +import type { GenerateLabel, GenerateURL } from '../types.js' import { formatBreadcrumb } from './formatBreadcrumb.js' -import { getParents } from './getParents.js' +import { getParents as getAllParentDocuments } from './getParents.js' -export const populateBreadcrumbs = async ( - req: any, - pluginConfig: NestedDocsPluginConfig, - collection: CollectionConfig, - data: any, - originalDoc?: any, -): Promise => { +type Args = { + breadcrumbsFieldName?: string + collection: SanitizedCollectionConfig + data: Data + generateLabel?: GenerateLabel + generateURL?: GenerateURL + originalDoc?: Document + parentFieldName?: string + req: PayloadRequest +} +export const populateBreadcrumbs = async ({ + breadcrumbsFieldName = 'breadcrumbs', + collection, + data, + generateLabel, + generateURL, + originalDoc, + parentFieldName, + req, +}: Args): Promise => { const newData = data - const breadcrumbDocs = [ - ...(await getParents(req, pluginConfig, collection, { - ...originalDoc, - ...data, - })), - ] - - const currentDocBreadcrumb = { + const currentDocument = { ...originalDoc, ...data, } - - if (originalDoc?.id) { - currentDocBreadcrumb.id = originalDoc?.id - } - - breadcrumbDocs.push(currentDocBreadcrumb) - - const breadcrumbs = breadcrumbDocs.map((_, i) => - formatBreadcrumb(pluginConfig, collection, breadcrumbDocs.slice(0, i + 1)), + const allParentDocuments: Document[] = await getAllParentDocuments( + req, + { + generateLabel, + generateURL, + parentFieldSlug: parentFieldName, + }, + collection, + currentDocument, ) - return { - ...newData, - [pluginConfig?.breadcrumbsFieldSlug || 'breadcrumbs']: breadcrumbs, + if (originalDoc?.id) { + currentDocument.id = originalDoc?.id } + + allParentDocuments.push(currentDocument) + + const breadcrumbs = allParentDocuments.map((_, i) => + formatBreadcrumb({ + collection, + docs: allParentDocuments.slice(0, i + 1), + generateLabel, + generateURL, + }), + ) + + newData[breadcrumbsFieldName] = breadcrumbs + + return newData } diff --git a/test/plugin-nested-docs/int.spec.ts b/test/plugin-nested-docs/int.spec.ts index caeda59846..3dfb87e02c 100644 --- a/test/plugin-nested-docs/int.spec.ts +++ b/test/plugin-nested-docs/int.spec.ts @@ -179,19 +179,15 @@ describe('@payloadcms/plugin-nested-docs', () => { }) .then(({ docs }) => docs[0]) - if (!updatedChild) { - return - } - // breadcrumbs should be updated - expect(updatedChild.breadcrumbs).toHaveLength(2) + expect(updatedChild!.breadcrumbs).toHaveLength(2) - expect(updatedChild.breadcrumbs?.[0]?.url).toStrictEqual('/parent-updated') - expect(updatedChild.breadcrumbs?.[1]?.url).toStrictEqual('/parent-updated/child') + expect(updatedChild!.breadcrumbs?.[0]?.url).toStrictEqual('/parent-updated') + expect(updatedChild!.breadcrumbs?.[1]?.url).toStrictEqual('/parent-updated/child') // no other data should be affected - expect(updatedChild.title).toEqual('child doc') - expect(updatedChild.slug).toEqual('child') + expect(updatedChild!.title).toEqual('child doc') + expect(updatedChild!.slug).toEqual('child') }) })