feat: builds beforeDuplicate admin hook, closes #1243
This commit is contained in:
@@ -62,6 +62,7 @@ You can customize the way that the Admin panel behaves on a collection-by-collec
|
|||||||
| Option | Description |
|
| Option | Description |
|
||||||
| ---------------------------- | -------------|
|
| ---------------------------- | -------------|
|
||||||
| `group` | Text used as a label for grouping collection links together in the navigation. |
|
| `group` | Text used as a label for grouping collection links together in the navigation. |
|
||||||
|
| `hooks` | Admin-specific hooks for this collection. [More](#admin-hooks) |
|
||||||
| `useAsTitle` | Specify a top-level field to use for a document title throughout the Admin panel. If no field is defined, the ID of the document is used as the title. |
|
| `useAsTitle` | Specify a top-level field to use for a document title throughout the Admin panel. If no field is defined, the ID of the document is used as the title. |
|
||||||
| `description` | Text or React component to display below the Collection label in the List view to give editors more information. |
|
| `description` | Text or React component to display below the Collection label in the List view to give editors more information. |
|
||||||
| `defaultColumns` | Array of field names that correspond to which columns to show by default in this collection's List view. |
|
| `defaultColumns` | Array of field names that correspond to which columns to show by default in this collection's List view. |
|
||||||
@@ -131,6 +132,50 @@ For example, let's say you have a Posts collection with `title`, `metaDescriptio
|
|||||||
If you are adding <strong>listSearchableFields</strong>, make sure you index each of these fields so your admin queries can remain performant.
|
If you are adding <strong>listSearchableFields</strong>, make sure you index each of these fields so your admin queries can remain performant.
|
||||||
</Banner>
|
</Banner>
|
||||||
|
|
||||||
|
### Admin Hooks
|
||||||
|
|
||||||
|
In addition to collection hooks themselves, Payload provides for admin UI-specific hooks that you can leverage.
|
||||||
|
|
||||||
|
**`beforeDuplicate`**
|
||||||
|
|
||||||
|
The `beforeDuplicate` hook is an async function that accepts an object containing the data to duplicate, as well as the locale of the doc to duplicate. Within this hook, you can modify the data to be duplicated, which is useful in cases where you have unique fields that need to be incremented or similar, as well as if you want to automatically modify a document's `title`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { BeforeDuplicate, CollectionConfig } from 'payload/types';
|
||||||
|
// Your auto-generated Page type
|
||||||
|
import { Page } from '../payload-types.ts';
|
||||||
|
|
||||||
|
const beforeDuplicate: BeforeDuplicate<Page> = ({ data }) => {
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
title: `${data.title} Copy`,
|
||||||
|
uniqueField: data.uniqueField ? `${data.uniqueField}-copy` : '',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Page: CollectionConfig = {
|
||||||
|
slug: 'pages',
|
||||||
|
admin: {
|
||||||
|
hooks: {
|
||||||
|
beforeDuplicate,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'title',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'uniqueField',
|
||||||
|
type: 'text',
|
||||||
|
unique: true,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### TypeScript
|
### TypeScript
|
||||||
|
|
||||||
You can import collection types as follows:
|
You can import collection types as follows:
|
||||||
|
|||||||
@@ -37,7 +37,15 @@ const Duplicate: React.FC<Props> = ({ slug, collection, id }) => {
|
|||||||
locale,
|
locale,
|
||||||
depth: 0,
|
depth: 0,
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
let data = await response.json();
|
||||||
|
|
||||||
|
if (typeof collection.admin.hooks?.beforeDuplicate === 'function') {
|
||||||
|
data = await collection.admin.hooks.beforeDuplicate({
|
||||||
|
data,
|
||||||
|
locale,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const result = await requests.post(`${serverURL}${api}/${slug}`, {
|
const result = await requests.post(`${serverURL}${api}/${slug}`, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -97,7 +105,7 @@ const Duplicate: React.FC<Props> = ({ slug, collection, id }) => {
|
|||||||
pathname: `${admin}/collections/${slug}/${duplicateID}`,
|
pathname: `${admin}/collections/${slug}/${duplicateID}`,
|
||||||
});
|
});
|
||||||
}, 10);
|
}, 10);
|
||||||
}, [modified, localization, collection.labels.singular, setModified, toggleModal, modalSlug, serverURL, api, slug, id, push, admin]);
|
}, [modified, localization, collection, setModified, toggleModal, modalSlug, serverURL, api, slug, id, push, admin]);
|
||||||
|
|
||||||
const confirm = useCallback(async () => {
|
const confirm = useCallback(async () => {
|
||||||
setHasClicked(false);
|
setHasClicked(false);
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ const collectionSchema = joi.object().keys({
|
|||||||
joi.string(),
|
joi.string(),
|
||||||
componentSchema,
|
componentSchema,
|
||||||
),
|
),
|
||||||
|
hooks: joi.object({
|
||||||
|
beforeDuplicate: joi.func(),
|
||||||
|
}),
|
||||||
enableRichTextRelationship: joi.boolean(),
|
enableRichTextRelationship: joi.boolean(),
|
||||||
components: joi.object({
|
components: joi.object({
|
||||||
views: joi.object({
|
views: joi.object({
|
||||||
|
|||||||
@@ -143,6 +143,13 @@ export type AfterForgotPasswordHook = (args: {
|
|||||||
args?: any;
|
args?: any;
|
||||||
}) => any;
|
}) => any;
|
||||||
|
|
||||||
|
type BeforeDuplicateArgs<T> = {
|
||||||
|
data: T
|
||||||
|
locale?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BeforeDuplicate<T = any> = (args: BeforeDuplicateArgs<T>) => T | Promise<T>
|
||||||
|
|
||||||
export type CollectionAdminOptions = {
|
export type CollectionAdminOptions = {
|
||||||
/**
|
/**
|
||||||
* Field to use as title in Edit view and first column in List view
|
* Field to use as title in Edit view and first column in List view
|
||||||
@@ -156,6 +163,12 @@ export type CollectionAdminOptions = {
|
|||||||
* Additional fields to be searched via the full text search
|
* Additional fields to be searched via the full text search
|
||||||
*/
|
*/
|
||||||
listSearchableFields?: string[];
|
listSearchableFields?: string[];
|
||||||
|
hooks?: {
|
||||||
|
/**
|
||||||
|
* Function that allows you to modify a document's data before it is duplicated
|
||||||
|
*/
|
||||||
|
beforeDuplicate?: BeforeDuplicate;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Place collections into a navigational group
|
* Place collections into a navigational group
|
||||||
* */
|
* */
|
||||||
|
|||||||
@@ -1,7 +1,26 @@
|
|||||||
import type { CollectionConfig } from '../../../../src/collections/config/types';
|
import type { BeforeDuplicate, CollectionConfig } from '../../../../src/collections/config/types';
|
||||||
|
import { IndexedField } from '../../payload-types';
|
||||||
|
|
||||||
|
const beforeDuplicate: BeforeDuplicate<IndexedField> = ({ data }) => {
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
uniqueText: data.uniqueText ? `${data.uniqueText}-copy` : '',
|
||||||
|
group: {
|
||||||
|
...data.group || {},
|
||||||
|
localizedUnique: data.group?.localizedUnique ? `${data.group?.localizedUnique}-copy` : '',
|
||||||
|
},
|
||||||
|
collapsibleTextUnique: data.collapsibleTextUnique ? `${data.collapsibleTextUnique}-copy` : '',
|
||||||
|
collapsibleLocalizedUnique: data.collapsibleLocalizedUnique ? `${data.collapsibleLocalizedUnique}-copy` : '',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const IndexedFields: CollectionConfig = {
|
const IndexedFields: CollectionConfig = {
|
||||||
slug: 'indexed-fields',
|
slug: 'indexed-fields',
|
||||||
|
admin: {
|
||||||
|
hooks: {
|
||||||
|
beforeDuplicate,
|
||||||
|
},
|
||||||
|
},
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
name: 'text',
|
name: 'text',
|
||||||
|
|||||||
Reference in New Issue
Block a user