Compare commits
18 Commits
templates/
...
fix/window
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f04cf75bfd | ||
|
|
48f183bd42 | ||
|
|
36e152d69d | ||
|
|
1e698c2bdf | ||
|
|
7bb1c9d3c6 | ||
|
|
4410a49132 | ||
|
|
820a6ec55d | ||
|
|
0a1af45549 | ||
|
|
09ca5143eb | ||
|
|
f1b005c4f5 | ||
|
|
dc9e8fa655 | ||
|
|
2477fc6c75 | ||
|
|
37781808eb | ||
|
|
d92c0009ed | ||
|
|
d32608649c | ||
|
|
f9121c1a3a | ||
|
|
f477e0e3c4 | ||
|
|
4224c68002 |
37
.github/CODEOWNERS
vendored
37
.github/CODEOWNERS
vendored
@@ -1,37 +0,0 @@
|
||||
# Order matters. The last matching pattern takes precedence.
|
||||
|
||||
### Package Exports
|
||||
|
||||
**/exports/ @denolfe @jmikrut @DanRibbens
|
||||
|
||||
### Packages
|
||||
|
||||
/packages/plugin-cloud*/src/ @denolfe @jmikrut @DanRibbens
|
||||
/packages/email-*/src/ @denolfe @jmikrut @DanRibbens
|
||||
/packages/live-preview*/src/ @jacobsfletch
|
||||
/packages/plugin-stripe/src/ @jacobsfletch
|
||||
/packages/plugin-multi-tenant/src/ @JarrodMFlesch
|
||||
/packages/richtext-*/src/ @AlessioGr
|
||||
/packages/next/src/ @jmikrut @jacobsfletch @AlessioGr @JarrodMFlesch
|
||||
/packages/ui/src/ @jmikrut @jacobsfletch @AlessioGr @JarrodMFlesch
|
||||
/packages/storage-*/src/ @denolfe @jmikrut @DanRibbens
|
||||
/packages/create-payload-app/src/ @denolfe @jmikrut @DanRibbens
|
||||
/packages/eslint-*/ @denolfe @jmikrut @DanRibbens @AlessioGr
|
||||
|
||||
### Templates
|
||||
|
||||
/templates/_data/ @denolfe @jmikrut @DanRibbens
|
||||
/templates/_template/ @denolfe @jmikrut @DanRibbens
|
||||
|
||||
### Build Files
|
||||
|
||||
**/tsconfig*.json @denolfe @jmikrut @DanRibbens @AlessioGr
|
||||
**/jest.config.js @denolfe @jmikrut @DanRibbens @AlessioGr
|
||||
|
||||
### Root
|
||||
|
||||
/package.json @denolfe @jmikrut @DanRibbens
|
||||
/tools/ @denolfe @jmikrut @DanRibbens
|
||||
/.husky/ @denolfe @jmikrut @DanRibbens
|
||||
/.vscode/ @denolfe @jmikrut @DanRibbens @AlessioGr
|
||||
/.github/ @denolfe @jmikrut @DanRibbens
|
||||
@@ -276,7 +276,7 @@ export default async function MyServerComponent({
|
||||
|
||||
But, the Payload Config is [non-serializable](https://react.dev/reference/rsc/use-client#serializable-types) by design. It is full of custom validation functions and more. This means that the Payload Config, in its entirety, cannot be passed directly to Client Components.
|
||||
|
||||
For this reason, Payload creates a Client Config and passes it into the Config Provider. This is a serializable version of the Payload Config that can be accessed from any Client Component via the [`useConfig`](../admin/hooks#useconfig) hook:
|
||||
For this reason, Payload creates a Client Config and passes it into the Config Provider. This is a serializable version of the Payload Config that can be accessed from any Client Component via the [`useConfig`](../admin/react-hooks#useconfig) hook:
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
@@ -375,7 +375,7 @@ export function MyClientComponent() {
|
||||
```
|
||||
|
||||
<Banner type="success">
|
||||
See the [Hooks](../admin/hooks) documentation for a full list of available hooks.
|
||||
See the [Hooks](../admin/react-hooks) documentation for a full list of available hooks.
|
||||
</Banner>
|
||||
|
||||
### Getting the Current Locale
|
||||
@@ -422,12 +422,12 @@ function Greeting() {
|
||||
```
|
||||
|
||||
<Banner type="success">
|
||||
See the [Hooks](../admin/hooks) documentation for a full list of available hooks.
|
||||
See the [Hooks](../admin/react-hooks) documentation for a full list of available hooks.
|
||||
</Banner>
|
||||
|
||||
### Using Hooks
|
||||
|
||||
To make it easier to [build your Custom Components](#building-custom-components), you can use [Payload's built-in React Hooks](../admin/hooks) in any Client Component. For example, you might want to interact with one of Payload's many React Contexts. To do this, you can use one of the many hooks available depending on your needs.
|
||||
To make it easier to [build your Custom Components](#building-custom-components), you can use [Payload's built-in React Hooks](../admin/react-hooks) in any Client Component. For example, you might want to interact with one of Payload's many React Contexts. To do this, you can use one of the many hooks available depending on your needs.
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
@@ -444,7 +444,7 @@ export function MyClientComponent() {
|
||||
```
|
||||
|
||||
<Banner type="success">
|
||||
See the [Hooks](../admin/hooks) documentation for a full list of available hooks.
|
||||
See the [Hooks](../admin/react-hooks) documentation for a full list of available hooks.
|
||||
</Banner>
|
||||
|
||||
### Adding Styles
|
||||
|
||||
@@ -658,7 +658,7 @@ In addition to the above props, all Server Components will also receive the foll
|
||||
|
||||
When swapping out the `Field` component, you are responsible for sending and receiving the field's `value` from the form itself.
|
||||
|
||||
To do so, import the [`useField`](../admin/hooks#usefield) hook from `@payloadcms/ui` and use it to manage the field's value:
|
||||
To do so, import the [`useField`](../admin/react-hooks#usefield) hook from `@payloadcms/ui` and use it to manage the field's value:
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
@@ -677,7 +677,7 @@ export const CustomTextField: React.FC = () => {
|
||||
```
|
||||
|
||||
<Banner type="success">
|
||||
For a complete list of all available React hooks, see the [Payload React Hooks](../admin/hooks) documentation. For additional help, see [Building Custom Components](../custom-components/overview#building-custom-components).
|
||||
For a complete list of all available React hooks, see the [Payload React Hooks](../admin/react-hooks) documentation. For additional help, see [Building Custom Components](../custom-components/overview#building-custom-components).
|
||||
</Banner>
|
||||
|
||||
##### TypeScript#field-component-types
|
||||
|
||||
@@ -27,7 +27,7 @@ There are four main types of Hooks in Payload:
|
||||
|
||||
<Banner type="warning">
|
||||
**Reminder:**
|
||||
Payload also ships a set of _React_ hooks that you can use in your frontend application. Although they share a common name, these are very different things and should not be confused. [More details](../admin/hooks).
|
||||
Payload also ships a set of _React_ hooks that you can use in your frontend application. Although they share a common name, these are very different things and should not be confused. [More details](../admin/react-hooks).
|
||||
</Banner>
|
||||
|
||||
## Root Hooks
|
||||
|
||||
@@ -414,6 +414,15 @@ For more details, see the [Documentation](https://payloadcms.com/docs/getting-st
|
||||
```
|
||||
1. The `./src/public` directory is now located directly at root level `./public` [see Next.js docs for details](https://nextjs.org/docs/pages/building-your-application/optimizing/static-assets)
|
||||
|
||||
1. Payload now automatically removes `localized: true` property from sub-fields if a parent is localized, as it's redunant and unnecessary. If you have some existing data in this structure and you want to disable that behavior, you need to enable `allowLocalizedWithinLocalized` flag in your payload.config [read more in documentation](https://payloadcms.com/docs/configuration/overview#compatibility-flags), or create a migration script that aligns your data.
|
||||
Mongodb example for a link in a page layout.
|
||||
|
||||
```diff
|
||||
- layout.columns.en.link.en.type.en
|
||||
+ layout.columns.en.link.type
|
||||
```
|
||||
|
||||
|
||||
## Custom Components
|
||||
|
||||
1. All Payload React components have been moved from the `payload` package to `@payloadcms/ui`. If you were previously importing components into your app from the `payload` package, for example to create Custom Components, you will need to change your import paths:
|
||||
|
||||
@@ -278,6 +278,50 @@ async rewrites() {
|
||||
}
|
||||
```
|
||||
|
||||
### React Hooks
|
||||
|
||||
Below are the hooks exported from the plugin that you can import into your own custom components to consume.
|
||||
|
||||
#### useTenantSelection
|
||||
|
||||
You can import this like so:
|
||||
|
||||
```tsx
|
||||
import { useTenantSelection } from '@payloadcms/plugin-multi-tenant/client'
|
||||
|
||||
...
|
||||
|
||||
const tenantContext = useTenantSelection()
|
||||
```
|
||||
|
||||
The hook returns the following context:
|
||||
|
||||
```ts
|
||||
type ContextType = {
|
||||
/**
|
||||
* Array of options to select from
|
||||
*/
|
||||
options: OptionObject[]
|
||||
/**
|
||||
* The currently selected tenant ID
|
||||
*/
|
||||
selectedTenantID: number | string | undefined
|
||||
/**
|
||||
* Prevents a refresh when the tenant is changed
|
||||
*
|
||||
* If not switching tenants while viewing a "global", set to true
|
||||
*/
|
||||
setPreventRefreshOnChange: React.Dispatch<React.SetStateAction<boolean>>
|
||||
/**
|
||||
* Sets the selected tenant ID
|
||||
*
|
||||
* @param args.id - The ID of the tenant to select
|
||||
* @param args.refresh - Whether to refresh the page after changing the tenant
|
||||
*/
|
||||
setTenant: (args: { id: number | string | undefined; refresh?: boolean }) => void
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -153,7 +153,7 @@ export const renderListView = async (
|
||||
const renderedFilters = renderFilters(collectionConfig.fields, req.payload.importMap)
|
||||
|
||||
const resolvedFilterOptions = await resolveAllFilterOptions({
|
||||
collectionConfig,
|
||||
fields: collectionConfig.fields,
|
||||
req,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
import type { CollectionConfig, PayloadRequest, ResolvedFilterOptions } from 'payload'
|
||||
import type { Field, PayloadRequest, ResolvedFilterOptions } from 'payload'
|
||||
|
||||
import { resolveFilterOptions } from '@payloadcms/ui/rsc'
|
||||
import { fieldIsHiddenOrDisabled } from 'payload/shared'
|
||||
import { fieldHasSubFields, fieldIsHiddenOrDisabled } from 'payload/shared'
|
||||
|
||||
export const resolveAllFilterOptions = async ({
|
||||
collectionConfig,
|
||||
fields,
|
||||
req,
|
||||
result,
|
||||
}: {
|
||||
collectionConfig: CollectionConfig
|
||||
fields: Field[]
|
||||
req: PayloadRequest
|
||||
result?: Map<string, ResolvedFilterOptions>
|
||||
}): Promise<Map<string, ResolvedFilterOptions>> => {
|
||||
const resolvedFilterOptions = new Map<string, ResolvedFilterOptions>()
|
||||
const resolvedFilterOptions = !result ? new Map<string, ResolvedFilterOptions>() : result
|
||||
|
||||
await Promise.all(
|
||||
collectionConfig.fields.map(async (field) => {
|
||||
fields.map(async (field) => {
|
||||
if (fieldIsHiddenOrDisabled(field)) {
|
||||
return
|
||||
}
|
||||
@@ -28,8 +30,29 @@ export const resolveAllFilterOptions = async ({
|
||||
siblingData: {}, // use empty object to prevent breaking queries when accessing properties of data
|
||||
user: req.user,
|
||||
})
|
||||
|
||||
resolvedFilterOptions.set(field.name, options)
|
||||
}
|
||||
|
||||
if (fieldHasSubFields(field)) {
|
||||
await resolveAllFilterOptions({
|
||||
fields: field.fields,
|
||||
req,
|
||||
result: resolvedFilterOptions,
|
||||
})
|
||||
}
|
||||
|
||||
if (field.type === 'tabs') {
|
||||
await Promise.all(
|
||||
field.tabs.map((tab) =>
|
||||
resolveAllFilterOptions({
|
||||
fields: tab.fields,
|
||||
req,
|
||||
result: resolvedFilterOptions,
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
import type * as AWS from '@aws-sdk/client-s3'
|
||||
import type { CognitoUserSession } from 'amazon-cognito-identity-js'
|
||||
|
||||
import { CognitoIdentityClient } from '@aws-sdk/client-cognito-identity'
|
||||
import * as AWS from '@aws-sdk/client-s3'
|
||||
import { fromCognitoIdentityPool } from '@aws-sdk/credential-providers'
|
||||
import type { GetStorageClient } from './refreshSession.js'
|
||||
|
||||
import { authAsCognitoUser } from './authAsCognitoUser.js'
|
||||
import { refreshSession } from './refreshSession.js'
|
||||
|
||||
export type GetStorageClient = () => Promise<{
|
||||
identityID: string
|
||||
storageClient: AWS.S3
|
||||
}>
|
||||
|
||||
let storageClient: AWS.S3 | null = null
|
||||
let session: CognitoUserSession | null = null
|
||||
let identityID: string
|
||||
export let storageClient: AWS.S3 | null = null
|
||||
export let session: CognitoUserSession | null = null
|
||||
export let identityID: string
|
||||
|
||||
export const getStorageClient: GetStorageClient = async () => {
|
||||
if (storageClient && session?.isValid()) {
|
||||
@@ -23,6 +17,8 @@ export const getStorageClient: GetStorageClient = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
;({ identityID, session, storageClient } = await refreshSession())
|
||||
|
||||
if (!process.env.PAYLOAD_CLOUD_PROJECT_ID) {
|
||||
throw new Error('PAYLOAD_CLOUD_PROJECT_ID is required')
|
||||
}
|
||||
@@ -33,34 +29,6 @@ export const getStorageClient: GetStorageClient = async () => {
|
||||
throw new Error('PAYLOAD_CLOUD_COGNITO_IDENTITY_POOL_ID is required')
|
||||
}
|
||||
|
||||
session = await authAsCognitoUser(
|
||||
process.env.PAYLOAD_CLOUD_PROJECT_ID,
|
||||
process.env.PAYLOAD_CLOUD_COGNITO_PASSWORD,
|
||||
)
|
||||
|
||||
const cognitoIdentity = new CognitoIdentityClient({
|
||||
credentials: fromCognitoIdentityPool({
|
||||
clientConfig: {
|
||||
region: 'us-east-1',
|
||||
},
|
||||
identityPoolId: process.env.PAYLOAD_CLOUD_COGNITO_IDENTITY_POOL_ID,
|
||||
logins: {
|
||||
[`cognito-idp.us-east-1.amazonaws.com/${process.env.PAYLOAD_CLOUD_COGNITO_USER_POOL_ID}`]:
|
||||
session.getIdToken().getJwtToken(),
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
const credentials = await cognitoIdentity.config.credentials()
|
||||
|
||||
// @ts-expect-error - Incorrect AWS types
|
||||
identityID = credentials.identityId
|
||||
|
||||
storageClient = new AWS.S3({
|
||||
credentials,
|
||||
region: process.env.PAYLOAD_CLOUD_BUCKET_REGION,
|
||||
})
|
||||
|
||||
return {
|
||||
identityID,
|
||||
storageClient,
|
||||
|
||||
46
packages/payload-cloud/src/utilities/refreshSession.ts
Normal file
46
packages/payload-cloud/src/utilities/refreshSession.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { CognitoIdentityClient } from '@aws-sdk/client-cognito-identity'
|
||||
import * as AWS from '@aws-sdk/client-s3'
|
||||
import { fromCognitoIdentityPool } from '@aws-sdk/credential-providers'
|
||||
|
||||
import { authAsCognitoUser } from './authAsCognitoUser.js'
|
||||
|
||||
export type GetStorageClient = () => Promise<{
|
||||
identityID: string
|
||||
storageClient: AWS.S3
|
||||
}>
|
||||
|
||||
export const refreshSession = async () => {
|
||||
const session = await authAsCognitoUser(
|
||||
process.env.PAYLOAD_CLOUD_PROJECT_ID || '',
|
||||
process.env.PAYLOAD_CLOUD_COGNITO_PASSWORD || '',
|
||||
)
|
||||
|
||||
const cognitoIdentity = new CognitoIdentityClient({
|
||||
credentials: fromCognitoIdentityPool({
|
||||
clientConfig: {
|
||||
region: 'us-east-1',
|
||||
},
|
||||
identityPoolId: process.env.PAYLOAD_CLOUD_COGNITO_IDENTITY_POOL_ID || '',
|
||||
logins: {
|
||||
[`cognito-idp.us-east-1.amazonaws.com/${process.env.PAYLOAD_CLOUD_COGNITO_USER_POOL_ID}`]:
|
||||
session.getIdToken().getJwtToken(),
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
const credentials = await cognitoIdentity.config.credentials()
|
||||
|
||||
// @ts-expect-error - Incorrect AWS types
|
||||
const identityID = credentials.identityId
|
||||
|
||||
const storageClient = new AWS.S3({
|
||||
credentials,
|
||||
region: process.env.PAYLOAD_CLOUD_BUCKET_REGION,
|
||||
})
|
||||
|
||||
return {
|
||||
identityID,
|
||||
session,
|
||||
storageClient,
|
||||
}
|
||||
}
|
||||
@@ -88,7 +88,7 @@ export type BeforeChangeRichTextHookArgs<
|
||||
|
||||
errors?: ValidationFieldError[]
|
||||
/** Only available in `beforeChange` field hooks */
|
||||
mergeLocaleActions?: (() => Promise<void>)[]
|
||||
mergeLocaleActions?: (() => Promise<void> | void)[]
|
||||
/** A string relating to which operation the field type is currently executing within. */
|
||||
operation?: 'create' | 'delete' | 'read' | 'update'
|
||||
/** The sibling data of the document before changes being applied. */
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
// @ts-strict-ignore
|
||||
import type { AuthStrategyFunctionArgs, AuthStrategyResult } from './index.js'
|
||||
|
||||
export const executeAuthStrategies = async (
|
||||
args: AuthStrategyFunctionArgs,
|
||||
): Promise<AuthStrategyResult> => {
|
||||
return args.payload.authStrategies.reduce(
|
||||
async (accumulatorPromise, strategy) => {
|
||||
const result: AuthStrategyResult = await accumulatorPromise
|
||||
if (!result.user) {
|
||||
// add the configured AuthStrategy `name` to the strategy function args
|
||||
args.strategyName = strategy.name
|
||||
if (!args.payload.authStrategies?.length) {
|
||||
return { user: null }
|
||||
}
|
||||
|
||||
return strategy.authenticate(args)
|
||||
}
|
||||
for (const strategy of args.payload.authStrategies) {
|
||||
// add the configured AuthStrategy `name` to the strategy function args
|
||||
args.strategyName = strategy.name
|
||||
|
||||
const result = await strategy.authenticate(args)
|
||||
if (result.user) {
|
||||
return result
|
||||
},
|
||||
Promise.resolve({ user: null }),
|
||||
)
|
||||
}
|
||||
}
|
||||
return { user: null }
|
||||
}
|
||||
|
||||
@@ -64,18 +64,18 @@ export const forgotPasswordOperation = async <TSlug extends CollectionSlug>(
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection?.config,
|
||||
context: args.req.context,
|
||||
operation: 'forgotPassword',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}, Promise.resolve())
|
||||
if (args.collection.config.hooks?.beforeOperation?.length) {
|
||||
for (const hook of args.collection.config.hooks.beforeOperation) {
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection?.config,
|
||||
context: args.req.context,
|
||||
operation: 'forgotPassword',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
collection: { config: collectionConfig },
|
||||
@@ -190,10 +190,11 @@ export const forgotPasswordOperation = async <TSlug extends CollectionSlug>(
|
||||
// afterForgotPassword - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterForgotPassword.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
await hook({ args, collection: args.collection?.config, context: req.context })
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.afterForgotPassword?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterForgotPassword) {
|
||||
await hook({ args, collection: args.collection?.config, context: req.context })
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterOperation - Collection
|
||||
|
||||
@@ -51,18 +51,18 @@ export const loginOperation = async <TSlug extends CollectionSlug>(
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection?.config,
|
||||
context: args.req.context,
|
||||
operation: 'login',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}, Promise.resolve())
|
||||
if (args.collection.config.hooks?.beforeOperation?.length) {
|
||||
for (const hook of args.collection.config.hooks.beforeOperation) {
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection?.config,
|
||||
context: args.req.context,
|
||||
operation: 'login',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
collection: { config: collectionConfig },
|
||||
@@ -227,17 +227,17 @@ export const loginOperation = async <TSlug extends CollectionSlug>(
|
||||
// beforeLogin - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.beforeLogin.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
user =
|
||||
(await hook({
|
||||
collection: args.collection?.config,
|
||||
context: args.req.context,
|
||||
req: args.req,
|
||||
user,
|
||||
})) || user
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.beforeLogin?.length) {
|
||||
for (const hook of collectionConfig.hooks.beforeLogin) {
|
||||
user =
|
||||
(await hook({
|
||||
collection: args.collection?.config,
|
||||
context: args.req.context,
|
||||
req: args.req,
|
||||
user,
|
||||
})) || user
|
||||
}
|
||||
}
|
||||
|
||||
const { exp, token } = await jwtSign({
|
||||
fieldsToSign,
|
||||
@@ -251,18 +251,18 @@ export const loginOperation = async <TSlug extends CollectionSlug>(
|
||||
// afterLogin - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterLogin.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
user =
|
||||
(await hook({
|
||||
collection: args.collection?.config,
|
||||
context: args.req.context,
|
||||
req: args.req,
|
||||
token,
|
||||
user,
|
||||
})) || user
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.afterLogin?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterLogin) {
|
||||
user =
|
||||
(await hook({
|
||||
collection: args.collection?.config,
|
||||
context: args.req.context,
|
||||
req: args.req,
|
||||
token,
|
||||
user,
|
||||
})) || user
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterRead - Fields
|
||||
@@ -286,17 +286,17 @@ export const loginOperation = async <TSlug extends CollectionSlug>(
|
||||
// afterRead - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
user =
|
||||
(await hook({
|
||||
collection: args.collection?.config,
|
||||
context: req.context,
|
||||
doc: user,
|
||||
req,
|
||||
})) || user
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.afterRead?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterRead) {
|
||||
user =
|
||||
(await hook({
|
||||
collection: args.collection?.config,
|
||||
context: req.context,
|
||||
doc: user,
|
||||
req,
|
||||
})) || user
|
||||
}
|
||||
}
|
||||
|
||||
let result: { user: DataFromCollectionSlug<TSlug> } & Result = {
|
||||
exp,
|
||||
|
||||
@@ -25,16 +25,16 @@ export const logoutOperation = async (incomingArgs: Arguments): Promise<boolean>
|
||||
throw new APIError('Incorrect collection', httpStatus.FORBIDDEN)
|
||||
}
|
||||
|
||||
await collectionConfig.hooks.afterLogout.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
args =
|
||||
(await hook({
|
||||
collection: args.collection?.config,
|
||||
context: req.context,
|
||||
req,
|
||||
})) || args
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.afterLogout?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterLogout) {
|
||||
args =
|
||||
(await hook({
|
||||
collection: args.collection?.config,
|
||||
context: req.context,
|
||||
req,
|
||||
})) || args
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -86,17 +86,17 @@ export const meOperation = async (args: Arguments): Promise<MeOperationResult> =
|
||||
// After Me - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collection.config.hooks.afterMe.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: collection?.config,
|
||||
context: req.context,
|
||||
req,
|
||||
response: result,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (collection.config.hooks?.afterMe?.length) {
|
||||
for (const hook of collection.config.hooks.afterMe) {
|
||||
result =
|
||||
(await hook({
|
||||
collection: collection?.config,
|
||||
context: req.context,
|
||||
req,
|
||||
response: result,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -35,10 +35,8 @@ export const refreshOperation = async (incomingArgs: Arguments): Promise<Result>
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(
|
||||
async (priorHook: BeforeOperationHook | Promise<void>, hook: BeforeOperationHook) => {
|
||||
await priorHook
|
||||
|
||||
if (args.collection.config.hooks?.beforeOperation?.length) {
|
||||
for (const hook of args.collection.config.hooks.beforeOperation) {
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
@@ -47,9 +45,8 @@ export const refreshOperation = async (incomingArgs: Arguments): Promise<Result>
|
||||
operation: 'refresh',
|
||||
req: args.req,
|
||||
})) || args
|
||||
},
|
||||
Promise.resolve(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Refresh
|
||||
@@ -122,18 +119,18 @@ export const refreshOperation = async (incomingArgs: Arguments): Promise<Result>
|
||||
// After Refresh - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterRefresh.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: args.collection?.config,
|
||||
context: args.req.context,
|
||||
exp: result.exp,
|
||||
req: args.req,
|
||||
token: result.refreshedToken,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.afterRefresh?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterRefresh) {
|
||||
result =
|
||||
(await hook({
|
||||
collection: args.collection?.config,
|
||||
context: args.req.context,
|
||||
exp: result.exp,
|
||||
req: args.req,
|
||||
token: result.refreshedToken,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterOperation - Collection
|
||||
|
||||
@@ -91,17 +91,17 @@ export const resetPasswordOperation = async (args: Arguments): Promise<Result> =
|
||||
// beforeValidate - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.beforeValidate.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
await hook({
|
||||
collection: args.collection?.config,
|
||||
context: req.context,
|
||||
data: user,
|
||||
operation: 'update',
|
||||
req,
|
||||
})
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.beforeValidate?.length) {
|
||||
for (const hook of collectionConfig.hooks.beforeValidate) {
|
||||
await hook({
|
||||
collection: args.collection?.config,
|
||||
context: req.context,
|
||||
data: user,
|
||||
operation: 'update',
|
||||
req,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Update new password
|
||||
|
||||
@@ -44,7 +44,9 @@ const batchAndLoadDocs =
|
||||
*
|
||||
**/
|
||||
|
||||
const batchByFindArgs = keys.reduce((batches, key) => {
|
||||
const batchByFindArgs = {}
|
||||
|
||||
for (const key of keys) {
|
||||
const [
|
||||
transactionID,
|
||||
collection,
|
||||
@@ -77,27 +79,16 @@ const batchAndLoadDocs =
|
||||
const batchKey = JSON.stringify(batchKeyArray)
|
||||
|
||||
const idType = payload.collections?.[collection].customIDType || payload.db.defaultIDType
|
||||
|
||||
let sanitizedID: number | string = id
|
||||
|
||||
if (idType === 'number') {
|
||||
sanitizedID = parseFloat(id)
|
||||
}
|
||||
const sanitizedID = idType === 'number' ? parseFloat(id) : id
|
||||
|
||||
if (isValidID(sanitizedID, idType)) {
|
||||
return {
|
||||
...batches,
|
||||
[batchKey]: [...(batches[batchKey] || []), sanitizedID],
|
||||
}
|
||||
batchByFindArgs[batchKey] = [...(batchByFindArgs[batchKey] || []), sanitizedID]
|
||||
}
|
||||
return batches
|
||||
}, {})
|
||||
}
|
||||
|
||||
// Run find requests one after another, so as to not hang transactions
|
||||
|
||||
await Object.entries(batchByFindArgs).reduce(async (priorFind, [batchKey, ids]) => {
|
||||
await priorFind
|
||||
|
||||
for (const [batchKey, ids] of Object.entries(batchByFindArgs)) {
|
||||
const [
|
||||
transactionID,
|
||||
collection,
|
||||
@@ -137,8 +128,7 @@ const batchAndLoadDocs =
|
||||
|
||||
// For each returned doc, find index in original keys
|
||||
// Inject doc within docs array if index exists
|
||||
|
||||
result.docs.forEach((doc) => {
|
||||
for (const doc of result.docs) {
|
||||
const docKey = createDataloaderCacheKey({
|
||||
collectionSlug: collection,
|
||||
currentDepth,
|
||||
@@ -158,8 +148,8 @@ const batchAndLoadDocs =
|
||||
if (docsIndex > -1) {
|
||||
docs[docsIndex] = doc
|
||||
}
|
||||
})
|
||||
}, Promise.resolve())
|
||||
}
|
||||
}
|
||||
|
||||
// Return docs array,
|
||||
// which has now been injected with all fetched docs
|
||||
|
||||
@@ -28,18 +28,18 @@ export const countOperation = async <TSlug extends CollectionSlug>(
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'count',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}, Promise.resolve())
|
||||
if (args.collection.config.hooks?.beforeOperation?.length) {
|
||||
for (const hook of args.collection.config.hooks.beforeOperation) {
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'count',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
collection: { config: collectionConfig },
|
||||
|
||||
@@ -28,18 +28,18 @@ export const countVersionsOperation = async <TSlug extends CollectionSlug>(
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'countVersions',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}, Promise.resolve())
|
||||
if (args.collection.config.hooks.beforeOperation?.length) {
|
||||
for (const hook of args.collection.config.hooks.beforeOperation) {
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'countVersions',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
collection: { config: collectionConfig },
|
||||
|
||||
@@ -78,10 +78,8 @@ export const createOperation = async <
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(
|
||||
async (priorHook: BeforeOperationHook | Promise<void>, hook: BeforeOperationHook) => {
|
||||
await priorHook
|
||||
|
||||
if (args.collection.config.hooks.beforeOperation?.length) {
|
||||
for (const hook of args.collection.config.hooks.beforeOperation) {
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
@@ -90,9 +88,8 @@ export const createOperation = async <
|
||||
operation: 'create',
|
||||
req: args.req,
|
||||
})) || args
|
||||
},
|
||||
Promise.resolve(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
autosave = false,
|
||||
@@ -183,10 +180,8 @@ export const createOperation = async <
|
||||
// beforeValidate - Collections
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.beforeValidate.reduce(
|
||||
async (priorHook: BeforeValidateHook | Promise<void>, hook: BeforeValidateHook) => {
|
||||
await priorHook
|
||||
|
||||
if (collectionConfig.hooks.beforeValidate?.length) {
|
||||
for (const hook of collectionConfig.hooks.beforeValidate) {
|
||||
data =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
@@ -196,27 +191,26 @@ export const createOperation = async <
|
||||
originalDoc: duplicatedFromDoc,
|
||||
req,
|
||||
})) || data
|
||||
},
|
||||
Promise.resolve(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// beforeChange - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.beforeChange.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
data =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data,
|
||||
operation: 'create',
|
||||
originalDoc: duplicatedFromDoc,
|
||||
req,
|
||||
})) || data
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.beforeChange?.length) {
|
||||
for (const hook of collectionConfig.hooks.beforeChange) {
|
||||
data =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data,
|
||||
operation: 'create',
|
||||
originalDoc: duplicatedFromDoc,
|
||||
req,
|
||||
})) || data
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// beforeChange - Fields
|
||||
@@ -332,17 +326,17 @@ export const createOperation = async <
|
||||
// afterRead - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
req,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.afterRead?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterRead) {
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterChange - Fields
|
||||
@@ -363,10 +357,8 @@ export const createOperation = async <
|
||||
// afterChange - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterChange.reduce(
|
||||
async (priorHook: AfterChangeHook | Promise<void>, hook: AfterChangeHook) => {
|
||||
await priorHook
|
||||
|
||||
if (collectionConfig.hooks?.afterChange?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterChange) {
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
@@ -376,9 +368,8 @@ export const createOperation = async <
|
||||
previousDoc: {},
|
||||
req: args.req,
|
||||
})) || result
|
||||
},
|
||||
Promise.resolve(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterOperation - Collection
|
||||
|
||||
@@ -54,10 +54,8 @@ export const deleteOperation = async <
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(
|
||||
async (priorHook: BeforeOperationHook | Promise<void>, hook: BeforeOperationHook) => {
|
||||
await priorHook
|
||||
|
||||
if (args.collection.config.hooks?.beforeOperation?.length) {
|
||||
for (const hook of args.collection.config.hooks.beforeOperation) {
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
@@ -66,9 +64,8 @@ export const deleteOperation = async <
|
||||
operation: 'delete',
|
||||
req: args.req,
|
||||
})) || args
|
||||
},
|
||||
Promise.resolve(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
collection: { config: collectionConfig },
|
||||
@@ -147,16 +144,16 @@ export const deleteOperation = async <
|
||||
// beforeDelete - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.beforeDelete.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
return hook({
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
req,
|
||||
})
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.beforeDelete?.length) {
|
||||
for (const hook of collectionConfig.hooks.beforeDelete) {
|
||||
await hook({
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
req,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
await deleteAssociatedFiles({
|
||||
collectionConfig,
|
||||
@@ -229,34 +226,34 @@ export const deleteOperation = async <
|
||||
// afterRead - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result || doc,
|
||||
req,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.afterRead?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterRead) {
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result || doc,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterDelete - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterDelete.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
req,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.afterDelete?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterDelete) {
|
||||
result =
|
||||
(await hook({
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 8. Return results
|
||||
|
||||
@@ -48,10 +48,8 @@ export const deleteByIDOperation = async <TSlug extends CollectionSlug, TSelect
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(
|
||||
async (priorHook: BeforeOperationHook | Promise<void>, hook: BeforeOperationHook) => {
|
||||
await priorHook
|
||||
|
||||
if (args.collection.config.hooks?.beforeOperation?.length) {
|
||||
for (const hook of args.collection.config.hooks.beforeOperation) {
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
@@ -60,9 +58,8 @@ export const deleteByIDOperation = async <TSlug extends CollectionSlug, TSelect
|
||||
operation: 'delete',
|
||||
req: args.req,
|
||||
})) || args
|
||||
},
|
||||
Promise.resolve(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
id,
|
||||
@@ -95,16 +92,16 @@ export const deleteByIDOperation = async <TSlug extends CollectionSlug, TSelect
|
||||
// beforeDelete - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.beforeDelete.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
return hook({
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
req,
|
||||
})
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.beforeDelete?.length) {
|
||||
for (const hook of collectionConfig.hooks.beforeDelete) {
|
||||
await hook({
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
req,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Retrieve document
|
||||
@@ -215,34 +212,34 @@ export const deleteByIDOperation = async <TSlug extends CollectionSlug, TSelect
|
||||
// afterRead - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
req,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.afterRead?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterRead) {
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterDelete - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterDelete.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
req,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.afterDelete?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterDelete) {
|
||||
result =
|
||||
(await hook({
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterOperation - Collection
|
||||
|
||||
@@ -63,18 +63,18 @@ export const findOperation = async <
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'read',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}, Promise.resolve())
|
||||
if (args.collection.config.hooks?.beforeOperation?.length) {
|
||||
for (const hook of args.collection.config.hooks.beforeOperation) {
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'read',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
collection: { config: collectionConfig },
|
||||
@@ -257,9 +257,7 @@ export const findOperation = async <
|
||||
result.docs.map(async (doc) => {
|
||||
let docRef = doc
|
||||
|
||||
await collectionConfig.hooks.beforeRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
for (const hook of collectionConfig.hooks.beforeRead) {
|
||||
docRef =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
@@ -268,7 +266,7 @@ export const findOperation = async <
|
||||
query: fullWhere,
|
||||
req,
|
||||
})) || docRef
|
||||
}, Promise.resolve())
|
||||
}
|
||||
|
||||
return docRef
|
||||
}),
|
||||
@@ -310,9 +308,7 @@ export const findOperation = async <
|
||||
result.docs.map(async (doc) => {
|
||||
let docRef = doc
|
||||
|
||||
await collectionConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
for (const hook of collectionConfig.hooks.afterRead) {
|
||||
docRef =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
@@ -322,7 +318,7 @@ export const findOperation = async <
|
||||
query: fullWhere,
|
||||
req,
|
||||
})) || doc
|
||||
}, Promise.resolve())
|
||||
}
|
||||
|
||||
return docRef
|
||||
}),
|
||||
|
||||
@@ -54,18 +54,18 @@ export const findByIDOperation = async <
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'read',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}, Promise.resolve())
|
||||
if (args.collection.config.hooks?.beforeOperation?.length) {
|
||||
for (const hook of args.collection.config.hooks.beforeOperation) {
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'read',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
id,
|
||||
@@ -221,18 +221,18 @@ export const findByIDOperation = async <
|
||||
// beforeRead - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.beforeRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
query: findOneArgs.where,
|
||||
req,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.beforeRead?.length) {
|
||||
for (const hook of collectionConfig.hooks.beforeRead) {
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
query: findOneArgs.where,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterRead - Fields
|
||||
@@ -259,18 +259,18 @@ export const findByIDOperation = async <
|
||||
// afterRead - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
query: findOneArgs.where,
|
||||
req,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.afterRead?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterRead) {
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
query: findOneArgs.where,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterOperation - Collection
|
||||
|
||||
@@ -101,18 +101,18 @@ export const findVersionByIDOperation = async <TData extends TypeWithID = any>(
|
||||
// beforeRead - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.beforeRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result.version =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result.version,
|
||||
query: fullWhere,
|
||||
req,
|
||||
})) || result.version
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.beforeRead?.length) {
|
||||
for (const hook of collectionConfig.hooks.beforeRead) {
|
||||
result.version =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result.version,
|
||||
query: fullWhere,
|
||||
req,
|
||||
})) || result.version
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterRead - Fields
|
||||
@@ -139,18 +139,18 @@ export const findVersionByIDOperation = async <TData extends TypeWithID = any>(
|
||||
// afterRead - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result.version =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result.version,
|
||||
query: fullWhere,
|
||||
req,
|
||||
})) || result.version
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.afterRead?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterRead) {
|
||||
result.version =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result.version,
|
||||
query: fullWhere,
|
||||
req,
|
||||
})) || result.version
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Return results
|
||||
|
||||
@@ -96,18 +96,19 @@ export const findVersionsOperation = async <TData extends TypeWithVersion<TData>
|
||||
if (!docRef.version) {
|
||||
;(docRef as any).version = {}
|
||||
}
|
||||
await collectionConfig.hooks.beforeRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
docRef.version =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: docRef.version,
|
||||
query: fullWhere,
|
||||
req,
|
||||
})) || docRef.version
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.beforeRead?.length) {
|
||||
for (const hook of collectionConfig.hooks.beforeRead) {
|
||||
docRef.version =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: docRef.version,
|
||||
query: fullWhere,
|
||||
req,
|
||||
})) || docRef.version
|
||||
}
|
||||
}
|
||||
|
||||
return docRef
|
||||
}),
|
||||
@@ -147,9 +148,7 @@ export const findVersionsOperation = async <TData extends TypeWithVersion<TData>
|
||||
result.docs.map(async (doc) => {
|
||||
const docRef = doc
|
||||
|
||||
await collectionConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
for (const hook of collectionConfig.hooks.afterRead) {
|
||||
docRef.version =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
@@ -159,7 +158,7 @@ export const findVersionsOperation = async <TData extends TypeWithVersion<TData>
|
||||
query: fullWhere,
|
||||
req,
|
||||
})) || doc.version
|
||||
}, Promise.resolve())
|
||||
}
|
||||
|
||||
return docRef
|
||||
}),
|
||||
|
||||
@@ -165,17 +165,17 @@ export const restoreVersionOperation = async <TData extends TypeWithID = any>(
|
||||
// afterRead - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
req,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.afterRead?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterRead) {
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterChange - Fields
|
||||
@@ -196,19 +196,19 @@ export const restoreVersionOperation = async <TData extends TypeWithID = any>(
|
||||
// afterChange - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterChange.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
operation: 'update',
|
||||
previousDoc: prevDocWithLocales,
|
||||
req,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.afterChange?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterChange) {
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
operation: 'update',
|
||||
previousDoc: prevDocWithLocales,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (error: unknown) {
|
||||
|
||||
@@ -62,18 +62,18 @@ export const updateOperation = async <
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'update',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}, Promise.resolve())
|
||||
if (args.collection.config.hooks?.beforeOperation?.length) {
|
||||
for (const hook of args.collection.config.hooks.beforeOperation) {
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'update',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
collection: { config: collectionConfig },
|
||||
|
||||
@@ -64,18 +64,18 @@ export const updateByIDOperation = async <
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'update',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}, Promise.resolve())
|
||||
if (args.collection.config.hooks?.beforeOperation?.length) {
|
||||
for (const hook of args.collection.config.hooks.beforeOperation) {
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'update',
|
||||
req: args.req,
|
||||
})) || args
|
||||
}
|
||||
}
|
||||
|
||||
if (args.publishSpecificLocale) {
|
||||
args.req.locale = args.publishSpecificLocale
|
||||
|
||||
@@ -171,19 +171,19 @@ export const updateDocument = async <
|
||||
// beforeValidate - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.beforeValidate.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
data =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data,
|
||||
operation: 'update',
|
||||
originalDoc,
|
||||
req,
|
||||
})) || data
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.beforeValidate?.length) {
|
||||
for (const hook of collectionConfig.hooks.beforeValidate) {
|
||||
data =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data,
|
||||
operation: 'update',
|
||||
originalDoc,
|
||||
req,
|
||||
})) || data
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Write files to local storage
|
||||
@@ -197,19 +197,19 @@ export const updateDocument = async <
|
||||
// beforeChange - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.beforeChange.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
data =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data,
|
||||
operation: 'update',
|
||||
originalDoc,
|
||||
req,
|
||||
})) || data
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.beforeChange?.length) {
|
||||
for (const hook of collectionConfig.hooks.beforeChange) {
|
||||
data =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data,
|
||||
operation: 'update',
|
||||
originalDoc,
|
||||
req,
|
||||
})) || data
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// beforeChange - Fields
|
||||
@@ -338,17 +338,17 @@ export const updateDocument = async <
|
||||
// afterRead - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
req,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.afterRead?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterRead) {
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterChange - Fields
|
||||
@@ -369,19 +369,19 @@ export const updateDocument = async <
|
||||
// afterChange - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.afterChange.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
operation: 'update',
|
||||
previousDoc: originalDoc,
|
||||
req,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (collectionConfig.hooks?.afterChange?.length) {
|
||||
for (const hook of collectionConfig.hooks.afterChange) {
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
operation: 'update',
|
||||
previousDoc: originalDoc,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
return result as TransformCollectionWithSelect<TSlug, TSelect>
|
||||
}
|
||||
|
||||
@@ -125,10 +125,8 @@ export const buildAfterOperation = async <
|
||||
|
||||
let newResult = result as OperationResult<TOperationGeneric, O>
|
||||
|
||||
await args.collection.config.hooks.afterOperation.reduce(
|
||||
async (priorHook, hook: AfterOperationHook<TOperationGeneric>) => {
|
||||
await priorHook
|
||||
|
||||
if (args.collection.config.hooks?.afterOperation?.length) {
|
||||
for (const hook of args.collection.config.hooks.afterOperation) {
|
||||
const hookResult = await hook({
|
||||
args,
|
||||
collection,
|
||||
@@ -140,9 +138,8 @@ export const buildAfterOperation = async <
|
||||
if (hookResult !== undefined) {
|
||||
newResult = hookResult as OperationResult<TOperationGeneric, O>
|
||||
}
|
||||
},
|
||||
Promise.resolve(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return newResult
|
||||
}
|
||||
|
||||
@@ -9,11 +9,10 @@ import { sanitizeConfig } from './sanitize.js'
|
||||
*/
|
||||
export async function buildConfig(config: Config): Promise<SanitizedConfig> {
|
||||
if (Array.isArray(config.plugins)) {
|
||||
const configAfterPlugins = await config.plugins.reduce(async (acc, plugin) => {
|
||||
const configAfterPlugin = await acc
|
||||
return plugin(configAfterPlugin)
|
||||
}, Promise.resolve(config))
|
||||
|
||||
let configAfterPlugins = config
|
||||
for (const plugin of config.plugins) {
|
||||
configAfterPlugins = await plugin(configAfterPlugins)
|
||||
}
|
||||
return await sanitizeConfig(configAfterPlugins)
|
||||
}
|
||||
|
||||
|
||||
@@ -179,10 +179,7 @@ export const sanitizeConfig = async (incomingConfig: Config): Promise<SanitizedC
|
||||
}))
|
||||
} else {
|
||||
// is Locale[], so convert to string[] for localeCodes
|
||||
config.localization.localeCodes = config.localization.locales.reduce((locales, locale) => {
|
||||
locales.push(locale.code)
|
||||
return locales
|
||||
}, [] as string[])
|
||||
config.localization.localeCodes = config.localization.locales.map((locale) => locale.code)
|
||||
|
||||
config.localization.locales = (
|
||||
config.localization as LocalizationConfigWithLabels
|
||||
|
||||
@@ -75,10 +75,8 @@ export const promise = async ({
|
||||
if (fieldAffectsData(field)) {
|
||||
// Execute hooks
|
||||
if (field.hooks?.afterChange) {
|
||||
await field.hooks.afterChange.reduce(async (priorHook, currentHook) => {
|
||||
await priorHook
|
||||
|
||||
const hookedValue = await currentHook({
|
||||
for (const hook of field.hooks.afterChange) {
|
||||
const hookedValue = await hook({
|
||||
blockData,
|
||||
collection,
|
||||
context,
|
||||
@@ -102,7 +100,7 @@ export const promise = async ({
|
||||
if (hookedValue !== undefined) {
|
||||
siblingDoc[field.name] = hookedValue
|
||||
}
|
||||
}, Promise.resolve())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,17 +240,15 @@ export const promise = async ({
|
||||
throw new MissingEditorProp(field) // while we allow disabling editor functionality, you should not have any richText fields defined if you do not have an editor
|
||||
}
|
||||
|
||||
if (typeof field?.editor === 'function') {
|
||||
if (typeof field.editor === 'function') {
|
||||
throw new Error('Attempted to access unsanitized rich text editor.')
|
||||
}
|
||||
|
||||
const editor: RichTextAdapter = field?.editor
|
||||
const editor: RichTextAdapter = field.editor
|
||||
|
||||
if (editor?.hooks?.afterChange?.length) {
|
||||
await editor.hooks.afterChange.reduce(async (priorHook, currentHook) => {
|
||||
await priorHook
|
||||
|
||||
const hookedValue = await currentHook({
|
||||
for (const hook of editor.hooks.afterChange) {
|
||||
const hookedValue = await hook({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
@@ -275,7 +271,7 @@ export const promise = async ({
|
||||
if (hookedValue !== undefined) {
|
||||
siblingDoc[field.name] = hookedValue
|
||||
}
|
||||
}, Promise.resolve())
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
@@ -237,18 +237,17 @@ export const promise = async ({
|
||||
if (fieldAffectsData(field)) {
|
||||
// Execute hooks
|
||||
if (triggerHooks && field.hooks?.afterRead) {
|
||||
await field.hooks.afterRead.reduce(async (priorHook, currentHook) => {
|
||||
await priorHook
|
||||
|
||||
for (const hook of field.hooks.afterRead) {
|
||||
const shouldRunHookOnAllLocales =
|
||||
fieldShouldBeLocalized({ field, parentIsLocalized }) &&
|
||||
(locale === 'all' || !flattenLocales) &&
|
||||
typeof siblingDoc[field.name] === 'object'
|
||||
|
||||
if (shouldRunHookOnAllLocales) {
|
||||
const hookPromises = Object.entries(siblingDoc[field.name]).map(([locale, value]) =>
|
||||
(async () => {
|
||||
const hookedValue = await currentHook({
|
||||
const localesAndValues = Object.entries(siblingDoc[field.name])
|
||||
await Promise.all(
|
||||
localesAndValues.map(async ([localeKey, value]) => {
|
||||
const hookedValue = await hook({
|
||||
blockData,
|
||||
collection,
|
||||
context,
|
||||
@@ -273,14 +272,12 @@ export const promise = async ({
|
||||
})
|
||||
|
||||
if (hookedValue !== undefined) {
|
||||
siblingDoc[field.name][locale] = hookedValue
|
||||
siblingDoc[field.name][localeKey] = hookedValue
|
||||
}
|
||||
})(),
|
||||
}),
|
||||
)
|
||||
|
||||
await Promise.all(hookPromises)
|
||||
} else {
|
||||
const hookedValue = await currentHook({
|
||||
const hookedValue = await hook({
|
||||
blockData,
|
||||
collection,
|
||||
context,
|
||||
@@ -308,7 +305,7 @@ export const promise = async ({
|
||||
siblingDoc[field.name] = hookedValue
|
||||
}
|
||||
}
|
||||
}, Promise.resolve())
|
||||
}
|
||||
}
|
||||
|
||||
// Execute access control
|
||||
@@ -677,18 +674,18 @@ export const promise = async ({
|
||||
const editor: RichTextAdapter = field?.editor
|
||||
|
||||
if (editor?.hooks?.afterRead?.length) {
|
||||
await editor.hooks.afterRead.reduce(async (priorHook, currentHook) => {
|
||||
await priorHook
|
||||
|
||||
for (const hook of editor.hooks.afterRead) {
|
||||
const shouldRunHookOnAllLocales =
|
||||
fieldShouldBeLocalized({ field, parentIsLocalized }) &&
|
||||
(locale === 'all' || !flattenLocales) &&
|
||||
typeof siblingDoc[field.name] === 'object'
|
||||
|
||||
if (shouldRunHookOnAllLocales) {
|
||||
const hookPromises = Object.entries(siblingDoc[field.name]).map(([locale, value]) =>
|
||||
(async () => {
|
||||
const hookedValue = await currentHook({
|
||||
const localesAndValues = Object.entries(siblingDoc[field.name])
|
||||
|
||||
await Promise.all(
|
||||
localesAndValues.map(async ([locale, value]) => {
|
||||
const hookedValue = await hook({
|
||||
collection,
|
||||
context,
|
||||
currentDepth,
|
||||
@@ -722,12 +719,10 @@ export const promise = async ({
|
||||
if (hookedValue !== undefined) {
|
||||
siblingDoc[field.name][locale] = hookedValue
|
||||
}
|
||||
})(),
|
||||
}),
|
||||
)
|
||||
|
||||
await Promise.all(hookPromises)
|
||||
} else {
|
||||
const hookedValue = await currentHook({
|
||||
const hookedValue = await hook({
|
||||
collection,
|
||||
context,
|
||||
currentDepth,
|
||||
@@ -762,7 +757,7 @@ export const promise = async ({
|
||||
siblingDoc[field.name] = hookedValue
|
||||
}
|
||||
}
|
||||
}, Promise.resolve())
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
@@ -81,10 +81,9 @@ export const beforeChange = async <T extends JsonObject>({
|
||||
)
|
||||
}
|
||||
|
||||
await mergeLocaleActions.reduce(async (priorAction, action) => {
|
||||
await priorAction
|
||||
for (const action of mergeLocaleActions) {
|
||||
await action()
|
||||
}, Promise.resolve())
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ type Args = {
|
||||
fieldIndex: number
|
||||
global: null | SanitizedGlobalConfig
|
||||
id?: number | string
|
||||
mergeLocaleActions: (() => Promise<void>)[]
|
||||
mergeLocaleActions: (() => Promise<void> | void)[]
|
||||
operation: Operation
|
||||
parentIndexPath: string
|
||||
parentIsLocalized: boolean
|
||||
@@ -108,10 +108,8 @@ export const promise = async ({
|
||||
|
||||
// Execute hooks
|
||||
if (field.hooks?.beforeChange) {
|
||||
await field.hooks.beforeChange.reduce(async (priorHook, currentHook) => {
|
||||
await priorHook
|
||||
|
||||
const hookedValue = await currentHook({
|
||||
for (const hook of field.hooks.beforeChange) {
|
||||
const hookedValue = await hook({
|
||||
blockData,
|
||||
collection,
|
||||
context,
|
||||
@@ -135,7 +133,7 @@ export const promise = async ({
|
||||
if (hookedValue !== undefined) {
|
||||
siblingData[field.name] = hookedValue
|
||||
}
|
||||
}, Promise.resolve())
|
||||
}
|
||||
}
|
||||
|
||||
// Validate
|
||||
@@ -192,28 +190,20 @@ export const promise = async ({
|
||||
|
||||
// Push merge locale action if applicable
|
||||
if (localization && fieldShouldBeLocalized({ field, parentIsLocalized })) {
|
||||
mergeLocaleActions.push(async () => {
|
||||
const localeData = await localization.localeCodes.reduce(
|
||||
async (localizedValuesPromise: Promise<JsonObject>, locale) => {
|
||||
const localizedValues = await localizedValuesPromise
|
||||
const fieldValue =
|
||||
locale === req.locale
|
||||
? siblingData[field.name]
|
||||
: siblingDocWithLocales?.[field.name]?.[locale]
|
||||
mergeLocaleActions.push(() => {
|
||||
const localeData = {}
|
||||
|
||||
// const result = await localizedValues
|
||||
// update locale value if it's not undefined
|
||||
if (typeof fieldValue !== 'undefined') {
|
||||
return {
|
||||
...localizedValues,
|
||||
[locale]: fieldValue,
|
||||
}
|
||||
}
|
||||
for (const locale of localization.localeCodes) {
|
||||
const fieldValue =
|
||||
locale === req.locale
|
||||
? siblingData[field.name]
|
||||
: siblingDocWithLocales?.[field.name]?.[locale]
|
||||
|
||||
return localizedValuesPromise
|
||||
},
|
||||
Promise.resolve({}),
|
||||
)
|
||||
// update locale value if it's not undefined
|
||||
if (typeof fieldValue !== 'undefined') {
|
||||
localeData[locale] = fieldValue
|
||||
}
|
||||
}
|
||||
|
||||
// If there are locales with data, set the data
|
||||
if (Object.keys(localeData).length > 0) {
|
||||
@@ -423,10 +413,8 @@ export const promise = async ({
|
||||
const editor: RichTextAdapter = field?.editor
|
||||
|
||||
if (editor?.hooks?.beforeChange?.length) {
|
||||
await editor.hooks.beforeChange.reduce(async (priorHook, currentHook) => {
|
||||
await priorHook
|
||||
|
||||
const hookedValue = await currentHook({
|
||||
for (const hook of editor.hooks.beforeChange) {
|
||||
const hookedValue = await hook({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
@@ -453,7 +441,7 @@ export const promise = async ({
|
||||
if (hookedValue !== undefined) {
|
||||
siblingData[field.name] = hookedValue
|
||||
}
|
||||
}, Promise.resolve())
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
|
||||
@@ -28,7 +28,7 @@ type Args = {
|
||||
fields: (Field | TabAsField)[]
|
||||
global: null | SanitizedGlobalConfig
|
||||
id?: number | string
|
||||
mergeLocaleActions: (() => Promise<void>)[]
|
||||
mergeLocaleActions: (() => Promise<void> | void)[]
|
||||
operation: Operation
|
||||
parentIndexPath: string
|
||||
/**
|
||||
|
||||
@@ -6,7 +6,6 @@ import type { Block, Field, FieldHookArgs, TabAsField } from '../../config/types
|
||||
|
||||
import { fieldAffectsData, fieldShouldBeLocalized } from '../../config/types.js'
|
||||
import { getFieldPathsModified as getFieldPaths } from '../../getFieldPaths.js'
|
||||
import { runBeforeDuplicateHooks } from './runHook.js'
|
||||
import { traverseFields } from './traverseFields.js'
|
||||
|
||||
type Args<T> = {
|
||||
@@ -68,42 +67,37 @@ export const promise = async <T>({
|
||||
// Run field beforeDuplicate hooks
|
||||
if (Array.isArray(field.hooks?.beforeDuplicate)) {
|
||||
if (fieldIsLocalized) {
|
||||
const localeData = await localization.localeCodes.reduce(
|
||||
async (localizedValuesPromise: Promise<JsonObject>, locale) => {
|
||||
const localizedValues = await localizedValuesPromise
|
||||
const localeData: JsonObject = {}
|
||||
|
||||
const beforeDuplicateArgs: FieldHookArgs = {
|
||||
blockData,
|
||||
collection,
|
||||
context,
|
||||
data: doc,
|
||||
field,
|
||||
global: undefined,
|
||||
indexPath: indexPathSegments,
|
||||
path: pathSegments,
|
||||
previousSiblingDoc: siblingDoc,
|
||||
previousValue: siblingDoc[field.name]?.[locale],
|
||||
req,
|
||||
schemaPath: schemaPathSegments,
|
||||
siblingData: siblingDoc,
|
||||
siblingDocWithLocales: siblingDoc,
|
||||
siblingFields,
|
||||
value: siblingDoc[field.name]?.[locale],
|
||||
}
|
||||
for (const locale of localization.localeCodes) {
|
||||
const beforeDuplicateArgs: FieldHookArgs = {
|
||||
blockData,
|
||||
collection,
|
||||
context,
|
||||
data: doc,
|
||||
field,
|
||||
global: undefined,
|
||||
indexPath: indexPathSegments,
|
||||
path: pathSegments,
|
||||
previousSiblingDoc: siblingDoc,
|
||||
previousValue: siblingDoc[field.name]?.[locale],
|
||||
req,
|
||||
schemaPath: schemaPathSegments,
|
||||
siblingData: siblingDoc,
|
||||
siblingDocWithLocales: siblingDoc,
|
||||
siblingFields,
|
||||
value: siblingDoc[field.name]?.[locale],
|
||||
}
|
||||
|
||||
const hookResult = await runBeforeDuplicateHooks(beforeDuplicateArgs)
|
||||
let hookResult
|
||||
for (const hook of field.hooks.beforeDuplicate) {
|
||||
hookResult = await hook(beforeDuplicateArgs)
|
||||
}
|
||||
|
||||
if (typeof hookResult !== 'undefined') {
|
||||
return {
|
||||
...localizedValues,
|
||||
[locale]: hookResult,
|
||||
}
|
||||
}
|
||||
|
||||
return localizedValuesPromise
|
||||
},
|
||||
Promise.resolve({}),
|
||||
)
|
||||
if (typeof hookResult !== 'undefined') {
|
||||
localeData[locale] = hookResult
|
||||
}
|
||||
}
|
||||
|
||||
siblingDoc[field.name] = localeData
|
||||
} else {
|
||||
@@ -126,7 +120,11 @@ export const promise = async <T>({
|
||||
value: siblingDoc[field.name],
|
||||
}
|
||||
|
||||
const hookResult = await runBeforeDuplicateHooks(beforeDuplicateArgs)
|
||||
let hookResult
|
||||
for (const hook of field.hooks.beforeDuplicate) {
|
||||
hookResult = await hook(beforeDuplicateArgs)
|
||||
}
|
||||
|
||||
if (typeof hookResult !== 'undefined') {
|
||||
siblingDoc[field.name] = hookResult
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
// @ts-strict-ignore
|
||||
import type { FieldHookArgs } from '../../config/types.js'
|
||||
|
||||
export const runBeforeDuplicateHooks = async (args: FieldHookArgs) =>
|
||||
await args.field.hooks.beforeDuplicate.reduce(async (priorHook, currentHook) => {
|
||||
await priorHook
|
||||
return await currentHook(args)
|
||||
}, Promise.resolve())
|
||||
@@ -276,10 +276,8 @@ export const promise = async <T>({
|
||||
|
||||
// Execute hooks
|
||||
if (field.hooks?.beforeValidate) {
|
||||
await field.hooks.beforeValidate.reduce(async (priorHook, currentHook) => {
|
||||
await priorHook
|
||||
|
||||
const hookedValue = await currentHook({
|
||||
for (const hook of field.hooks.beforeValidate) {
|
||||
const hookedValue = await hook({
|
||||
blockData,
|
||||
collection,
|
||||
context,
|
||||
@@ -303,7 +301,7 @@ export const promise = async <T>({
|
||||
if (hookedValue !== undefined) {
|
||||
siblingData[field.name] = hookedValue
|
||||
}
|
||||
}, Promise.resolve())
|
||||
}
|
||||
}
|
||||
|
||||
// Execute access control
|
||||
@@ -493,10 +491,8 @@ export const promise = async <T>({
|
||||
const editor: RichTextAdapter = field?.editor
|
||||
|
||||
if (editor?.hooks?.beforeValidate?.length) {
|
||||
await editor.hooks.beforeValidate.reduce(async (priorHook, currentHook) => {
|
||||
await priorHook
|
||||
|
||||
const hookedValue = await currentHook({
|
||||
for (const hook of editor.hooks.beforeValidate) {
|
||||
const hookedValue = await hook({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
@@ -519,7 +515,7 @@ export const promise = async <T>({
|
||||
if (hookedValue !== undefined) {
|
||||
siblingData[field.name] = hookedValue
|
||||
}
|
||||
}, Promise.resolve())
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
@@ -133,17 +133,17 @@ export const findOneOperation = async <T extends Record<string, unknown>>(
|
||||
// Execute before global hook
|
||||
// /////////////////////////////////////
|
||||
|
||||
await globalConfig.hooks.beforeRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
doc =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc,
|
||||
global: globalConfig,
|
||||
req,
|
||||
})) || doc
|
||||
}, Promise.resolve())
|
||||
if (globalConfig.hooks?.beforeRead?.length) {
|
||||
for (const hook of globalConfig.hooks.beforeRead) {
|
||||
doc =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc,
|
||||
global: globalConfig,
|
||||
req,
|
||||
})) || doc
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Execute globalType field if not selected
|
||||
@@ -182,17 +182,17 @@ export const findOneOperation = async <T extends Record<string, unknown>>(
|
||||
// Execute after global hook
|
||||
// /////////////////////////////////////
|
||||
|
||||
await globalConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
doc =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc,
|
||||
global: globalConfig,
|
||||
req,
|
||||
})) || doc
|
||||
}, Promise.resolve())
|
||||
if (globalConfig.hooks?.afterRead?.length) {
|
||||
for (const hook of globalConfig.hooks.afterRead) {
|
||||
doc =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc,
|
||||
global: globalConfig,
|
||||
req,
|
||||
})) || doc
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Return results
|
||||
|
||||
@@ -102,17 +102,17 @@ export const findVersionByIDOperation = async <T extends TypeWithVersion<T> = an
|
||||
// beforeRead - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await globalConfig.hooks.beforeRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result.version,
|
||||
global: globalConfig,
|
||||
req,
|
||||
})) || result.version
|
||||
}, Promise.resolve())
|
||||
if (globalConfig.hooks?.beforeRead?.length) {
|
||||
for (const hook of globalConfig.hooks.beforeRead) {
|
||||
result =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result.version,
|
||||
global: globalConfig,
|
||||
req,
|
||||
})) || result.version
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterRead - Fields
|
||||
@@ -139,18 +139,18 @@ export const findVersionByIDOperation = async <T extends TypeWithVersion<T> = an
|
||||
// afterRead - Global
|
||||
// /////////////////////////////////////
|
||||
|
||||
await globalConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result.version =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result.version,
|
||||
global: globalConfig,
|
||||
query: findGlobalVersionsArgs.where,
|
||||
req,
|
||||
})) || result.version
|
||||
}, Promise.resolve())
|
||||
if (globalConfig.hooks?.afterRead?.length) {
|
||||
for (const hook of globalConfig.hooks.afterRead) {
|
||||
result.version =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result.version,
|
||||
global: globalConfig,
|
||||
query: findGlobalVersionsArgs.where,
|
||||
req,
|
||||
})) || result.version
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (error: unknown) {
|
||||
|
||||
@@ -126,15 +126,12 @@ export const findVersionsOperation = async <T extends TypeWithVersion<T>>(
|
||||
// afterRead - Global
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = {
|
||||
...result,
|
||||
docs: await Promise.all(
|
||||
if (globalConfig.hooks?.afterRead?.length) {
|
||||
result.docs = await Promise.all(
|
||||
result.docs.map(async (doc) => {
|
||||
const docRef = doc
|
||||
|
||||
await globalConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
for (const hook of globalConfig.hooks.afterRead) {
|
||||
docRef.version =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
@@ -144,11 +141,11 @@ export const findVersionsOperation = async <T extends TypeWithVersion<T>>(
|
||||
query: fullWhere,
|
||||
req,
|
||||
})) || doc.version
|
||||
}, Promise.resolve())
|
||||
}
|
||||
|
||||
return docRef
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -143,17 +143,17 @@ export const restoreVersionOperation = async <T extends TypeWithVersion<T> = any
|
||||
// afterRead - Global
|
||||
// /////////////////////////////////////
|
||||
|
||||
await globalConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result,
|
||||
global: globalConfig,
|
||||
req,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (globalConfig.hooks?.afterRead?.length) {
|
||||
for (const hook of globalConfig.hooks.afterRead) {
|
||||
result =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result,
|
||||
global: globalConfig,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterChange - Fields
|
||||
@@ -174,18 +174,18 @@ export const restoreVersionOperation = async <T extends TypeWithVersion<T> = any
|
||||
// afterChange - Global
|
||||
// /////////////////////////////////////
|
||||
|
||||
await globalConfig.hooks.afterChange.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result,
|
||||
global: globalConfig,
|
||||
previousDoc,
|
||||
req,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (globalConfig.hooks?.afterChange?.length) {
|
||||
for (const hook of globalConfig.hooks.afterChange) {
|
||||
result =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result,
|
||||
global: globalConfig,
|
||||
previousDoc,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldCommit) {
|
||||
await commitTransaction(req)
|
||||
|
||||
@@ -168,35 +168,35 @@ export const updateOperation = async <
|
||||
// beforeValidate - Global
|
||||
// /////////////////////////////////////
|
||||
|
||||
await globalConfig.hooks.beforeValidate.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
data =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
data,
|
||||
global: globalConfig,
|
||||
originalDoc,
|
||||
req,
|
||||
})) || data
|
||||
}, Promise.resolve())
|
||||
if (globalConfig.hooks?.beforeValidate?.length) {
|
||||
for (const hook of globalConfig.hooks.beforeValidate) {
|
||||
data =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
data,
|
||||
global: globalConfig,
|
||||
originalDoc,
|
||||
req,
|
||||
})) || data
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// beforeChange - Global
|
||||
// /////////////////////////////////////
|
||||
|
||||
await globalConfig.hooks.beforeChange.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
data =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
data,
|
||||
global: globalConfig,
|
||||
originalDoc,
|
||||
req,
|
||||
})) || data
|
||||
}, Promise.resolve())
|
||||
if (globalConfig.hooks?.beforeChange?.length) {
|
||||
for (const hook of globalConfig.hooks.beforeChange) {
|
||||
data =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
data,
|
||||
global: globalConfig,
|
||||
originalDoc,
|
||||
req,
|
||||
})) || data
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// beforeChange - Fields
|
||||
@@ -326,17 +326,17 @@ export const updateOperation = async <
|
||||
// afterRead - Global
|
||||
// /////////////////////////////////////
|
||||
|
||||
await globalConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result,
|
||||
global: globalConfig,
|
||||
req,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (globalConfig.hooks?.afterRead?.length) {
|
||||
for (const hook of globalConfig.hooks.afterRead) {
|
||||
result =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result,
|
||||
global: globalConfig,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterChange - Fields
|
||||
@@ -357,18 +357,18 @@ export const updateOperation = async <
|
||||
// afterChange - Global
|
||||
// /////////////////////////////////////
|
||||
|
||||
await globalConfig.hooks.afterChange.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result,
|
||||
global: globalConfig,
|
||||
previousDoc: originalDoc,
|
||||
req,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
if (globalConfig.hooks?.afterChange?.length) {
|
||||
for (const hook of globalConfig.hooks.afterChange) {
|
||||
result =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result,
|
||||
global: globalConfig,
|
||||
previousDoc: originalDoc,
|
||||
req,
|
||||
})) || result
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Return results
|
||||
|
||||
@@ -22,8 +22,12 @@ type Handler = (
|
||||
}
|
||||
|
||||
export const tempFileHandler: Handler = (options, fieldname, filename) => {
|
||||
const dir = path.normalize(options.tempFileDir)
|
||||
const tempFilePath = path.join(process.cwd(), dir, getTempFilename())
|
||||
const tempFilePath = path.join(
|
||||
process.cwd(),
|
||||
// Remove drive letter prefix on Windows
|
||||
path.normalize(options.tempFileDir).replace(/^[A-Z]:\\/i, ''),
|
||||
getTempFilename(),
|
||||
)
|
||||
checkAndMakeDir({ createParentPath: true }, tempFilePath)
|
||||
|
||||
debugLog(options, `Temporary file path is ${tempFilePath}`)
|
||||
|
||||
@@ -119,7 +119,7 @@ export const checkAndMakeDir: CheckAndMakeDir = (fileUploadOptions, filePath) =>
|
||||
return false
|
||||
}
|
||||
// Check whether folder for the file exists.
|
||||
const parentPath = path.dirname(filePath)
|
||||
const parentPath = path.resolve(path.dirname(filePath))
|
||||
// Create folder if it doesn't exist.
|
||||
if (!fs.existsSync(parentPath)) {
|
||||
fs.mkdirSync(parentPath, { recursive: true })
|
||||
|
||||
@@ -1156,14 +1156,13 @@ export function configToJSONSchema(
|
||||
)
|
||||
: {}
|
||||
|
||||
let blocksDefinition: JSONSchema4 | undefined = undefined
|
||||
const blocksDefinition: JSONSchema4 | undefined = {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {},
|
||||
required: [],
|
||||
}
|
||||
if (config?.blocks?.length) {
|
||||
blocksDefinition = {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {},
|
||||
required: [],
|
||||
}
|
||||
for (const block of config.blocks) {
|
||||
const blockFieldSchemas = fieldsToJSONSchema(
|
||||
collectionIDFieldTypes,
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export { TenantField } from '../components/TenantField/index.client.js'
|
||||
export { TenantSelector } from '../components/TenantSelector/index.js'
|
||||
export { useTenantSelection } from '../providers/TenantSelectionProvider/index.client.js'
|
||||
|
||||
@@ -115,7 +115,13 @@ export const afterTenantDelete =
|
||||
id: user.id,
|
||||
collection: usersSlug,
|
||||
data: {
|
||||
tenants: (user.tenants || []).filter(({ tenant: tenantID }) => tenantID !== id),
|
||||
[usersTenantsArrayFieldName]: (user[usersTenantsArrayFieldName] || []).filter(
|
||||
(row: Record<string, string>) => {
|
||||
if (row[usersTenantsArrayTenantFieldName]) {
|
||||
return row[usersTenantsArrayTenantFieldName] !== id
|
||||
}
|
||||
},
|
||||
),
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -92,6 +92,8 @@ export const multiTenantPlugin =
|
||||
addCollectionAccess({
|
||||
collection: adminUsersCollection,
|
||||
fieldName: `${tenantsArrayFieldName}.${tenantsArrayTenantFieldName}`,
|
||||
tenantsArrayFieldName,
|
||||
tenantsArrayTenantFieldName,
|
||||
userHasAccessToAllTenants,
|
||||
})
|
||||
|
||||
@@ -130,6 +132,8 @@ export const multiTenantPlugin =
|
||||
addCollectionAccess({
|
||||
collection,
|
||||
fieldName: 'id',
|
||||
tenantsArrayFieldName,
|
||||
tenantsArrayTenantFieldName,
|
||||
userHasAccessToAllTenants,
|
||||
})
|
||||
}
|
||||
@@ -205,6 +209,8 @@ export const multiTenantPlugin =
|
||||
addCollectionAccess({
|
||||
collection,
|
||||
fieldName: tenantFieldName,
|
||||
tenantsArrayFieldName,
|
||||
tenantsArrayTenantFieldName,
|
||||
userHasAccessToAllTenants,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9,9 +9,26 @@ import React, { createContext } from 'react'
|
||||
import { SELECT_ALL } from '../../constants.js'
|
||||
|
||||
type ContextType = {
|
||||
/**
|
||||
* Array of options to select from
|
||||
*/
|
||||
options: OptionObject[]
|
||||
/**
|
||||
* The currently selected tenant ID
|
||||
*/
|
||||
selectedTenantID: number | string | undefined
|
||||
/**
|
||||
* Prevents a refresh when the tenant is changed
|
||||
*
|
||||
* If not switching tenants while viewing a "global", set to true
|
||||
*/
|
||||
setPreventRefreshOnChange: React.Dispatch<React.SetStateAction<boolean>>
|
||||
/**
|
||||
* Sets the selected tenant ID
|
||||
*
|
||||
* @param args.id - The ID of the tenant to select
|
||||
* @param args.refresh - Whether to refresh the page after changing the tenant
|
||||
*/
|
||||
setTenant: (args: { id: number | string | undefined; refresh?: boolean }) => void
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ const collectionAccessKeys: AllAccessKeys<
|
||||
type Args<ConfigType> = {
|
||||
collection: CollectionConfig
|
||||
fieldName: string
|
||||
tenantsArrayFieldName?: string
|
||||
tenantsArrayTenantFieldName?: string
|
||||
userHasAccessToAllTenants: Required<
|
||||
MultiTenantPluginConfig<ConfigType>
|
||||
>['userHasAccessToAllTenants']
|
||||
@@ -32,6 +34,8 @@ type Args<ConfigType> = {
|
||||
export const addCollectionAccess = <ConfigType>({
|
||||
collection,
|
||||
fieldName,
|
||||
tenantsArrayFieldName,
|
||||
tenantsArrayTenantFieldName,
|
||||
userHasAccessToAllTenants,
|
||||
}: Args<ConfigType>): void => {
|
||||
collectionAccessKeys.forEach((key) => {
|
||||
@@ -40,7 +44,11 @@ export const addCollectionAccess = <ConfigType>({
|
||||
}
|
||||
collection.access[key] = withTenantAccess<ConfigType>({
|
||||
accessFunction: collection.access?.[key],
|
||||
collection,
|
||||
fieldName: key === 'readVersions' ? `version.${fieldName}` : fieldName,
|
||||
operation: key,
|
||||
tenantsArrayFieldName,
|
||||
tenantsArrayTenantFieldName,
|
||||
userHasAccessToAllTenants,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,14 +2,25 @@ import type { Where } from 'payload'
|
||||
|
||||
import type { UserWithTenantsField } from '../types.js'
|
||||
|
||||
import { defaults } from '../defaults.js'
|
||||
import { getUserTenantIDs } from './getUserTenantIDs.js'
|
||||
|
||||
type Args = {
|
||||
fieldName: string
|
||||
tenantsArrayFieldName?: string
|
||||
tenantsArrayTenantFieldName?: string
|
||||
user: UserWithTenantsField
|
||||
}
|
||||
export function getTenantAccess({ fieldName, user }: Args): Where {
|
||||
const userAssignedTenantIDs = getUserTenantIDs(user)
|
||||
export function getTenantAccess({
|
||||
fieldName,
|
||||
tenantsArrayFieldName = defaults.tenantsArrayFieldName,
|
||||
tenantsArrayTenantFieldName = defaults.tenantsArrayTenantFieldName,
|
||||
user,
|
||||
}: Args): Where {
|
||||
const userAssignedTenantIDs = getUserTenantIDs(user, {
|
||||
tenantsArrayFieldName,
|
||||
tenantsArrayTenantFieldName,
|
||||
})
|
||||
|
||||
return {
|
||||
[fieldName]: {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Tenant, UserWithTenantsField } from '../types.js'
|
||||
|
||||
import { defaults } from '../defaults.js'
|
||||
import { extractID } from './extractID.js'
|
||||
|
||||
/**
|
||||
@@ -9,15 +10,26 @@ import { extractID } from './extractID.js'
|
||||
*/
|
||||
export const getUserTenantIDs = <IDType extends number | string>(
|
||||
user: null | UserWithTenantsField,
|
||||
options?: {
|
||||
tenantsArrayFieldName?: string
|
||||
tenantsArrayTenantFieldName?: string
|
||||
},
|
||||
): IDType[] => {
|
||||
if (!user) {
|
||||
return []
|
||||
}
|
||||
|
||||
const {
|
||||
tenantsArrayFieldName = defaults.tenantsArrayFieldName,
|
||||
tenantsArrayTenantFieldName = defaults.tenantsArrayTenantFieldName,
|
||||
} = options || {}
|
||||
|
||||
return (
|
||||
user?.tenants?.reduce<IDType[]>((acc, { tenant }) => {
|
||||
if (tenant) {
|
||||
acc.push(extractID<IDType>(tenant as Tenant<IDType>))
|
||||
(Array.isArray(user[tenantsArrayFieldName]) ? user[tenantsArrayFieldName] : [])?.reduce<
|
||||
IDType[]
|
||||
>((acc, row) => {
|
||||
if (row[tenantsArrayTenantFieldName]) {
|
||||
acc.push(extractID<IDType>(row[tenantsArrayTenantFieldName] as Tenant<IDType>))
|
||||
}
|
||||
|
||||
return acc
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import type { Access, AccessArgs, AccessResult, User } from 'payload'
|
||||
import type {
|
||||
Access,
|
||||
AccessArgs,
|
||||
AccessResult,
|
||||
AllOperations,
|
||||
CollectionConfig,
|
||||
User,
|
||||
Where,
|
||||
} from 'payload'
|
||||
|
||||
import type { MultiTenantPluginConfig, UserWithTenantsField } from '../types.js'
|
||||
|
||||
@@ -7,15 +15,26 @@ import { getTenantAccess } from './getTenantAccess.js'
|
||||
|
||||
type Args<ConfigType> = {
|
||||
accessFunction?: Access
|
||||
collection: CollectionConfig
|
||||
fieldName: string
|
||||
operation: AllOperations
|
||||
tenantsArrayFieldName?: string
|
||||
tenantsArrayTenantFieldName?: string
|
||||
userHasAccessToAllTenants: Required<
|
||||
MultiTenantPluginConfig<ConfigType>
|
||||
>['userHasAccessToAllTenants']
|
||||
}
|
||||
export const withTenantAccess =
|
||||
<ConfigType>({ accessFunction, fieldName, userHasAccessToAllTenants }: Args<ConfigType>) =>
|
||||
<ConfigType>({
|
||||
accessFunction,
|
||||
collection,
|
||||
fieldName,
|
||||
tenantsArrayFieldName,
|
||||
tenantsArrayTenantFieldName,
|
||||
userHasAccessToAllTenants,
|
||||
}: Args<ConfigType>) =>
|
||||
async (args: AccessArgs): Promise<AccessResult> => {
|
||||
const constraints = []
|
||||
const constraints: Where[] = []
|
||||
const accessFn =
|
||||
typeof accessFunction === 'function'
|
||||
? accessFunction
|
||||
@@ -34,12 +53,26 @@ export const withTenantAccess =
|
||||
args.req.user as ConfigType extends { user: unknown } ? ConfigType['user'] : User,
|
||||
)
|
||||
) {
|
||||
constraints.push(
|
||||
getTenantAccess({
|
||||
fieldName,
|
||||
user: args.req.user as UserWithTenantsField,
|
||||
}),
|
||||
)
|
||||
const tenantConstraint = getTenantAccess({
|
||||
fieldName,
|
||||
tenantsArrayFieldName,
|
||||
tenantsArrayTenantFieldName,
|
||||
user: args.req.user as UserWithTenantsField,
|
||||
})
|
||||
if (collection.slug === args.req.user.collection) {
|
||||
constraints.push({
|
||||
or: [
|
||||
{
|
||||
id: {
|
||||
equals: args.req.user.id,
|
||||
},
|
||||
},
|
||||
tenantConstraint,
|
||||
],
|
||||
})
|
||||
} else {
|
||||
constraints.push(tenantConstraint)
|
||||
}
|
||||
return combineWhereConstraints(constraints)
|
||||
}
|
||||
|
||||
|
||||
@@ -81,10 +81,7 @@ const resave = async ({ collection, doc, draft, pluginConfig, req }: ResaveArgs)
|
||||
await req.payload.update({
|
||||
id: child.id,
|
||||
collection: collection.slug,
|
||||
data: {
|
||||
...child,
|
||||
[breadcrumbSlug]: await populateBreadcrumbs(req, pluginConfig, collection, child),
|
||||
},
|
||||
data: populateBreadcrumbs(req, pluginConfig, collection, child),
|
||||
depth: 0,
|
||||
draft: isDraft,
|
||||
locale: req.locale,
|
||||
|
||||
@@ -19,8 +19,9 @@ export const formatBreadcrumb = (
|
||||
if (typeof pluginConfig?.generateLabel === 'function') {
|
||||
label = pluginConfig.generateLabel(docs, lastDoc)
|
||||
} else {
|
||||
const useAsTitle = collection?.admin?.useAsTitle || 'id'
|
||||
label = lastDoc[useAsTitle] as string
|
||||
const title = lastDoc[collection.admin.useAsTitle]
|
||||
|
||||
label = typeof title === 'string' || typeof title === 'number' ? String(title) : ''
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -176,7 +176,7 @@ export type BeforeChangeNodeHookArgs<T extends SerializedLexicalNode> = {
|
||||
* Only available in `beforeChange` hooks.
|
||||
*/
|
||||
errors: ValidationFieldError[]
|
||||
mergeLocaleActions: (() => Promise<void>)[]
|
||||
mergeLocaleActions: (() => Promise<void> | void)[]
|
||||
/** A string relating to which operation the field type is currently executing within. Useful within beforeValidate, beforeChange, and afterChange hooks to differentiate between create and update operations. */
|
||||
operation: 'create' | 'delete' | 'read' | 'update'
|
||||
/** The value of the node before any changes. Not available in afterRead hooks */
|
||||
|
||||
@@ -4,12 +4,9 @@ import { useModal } from '@faceless-ui/modal'
|
||||
import React from 'react'
|
||||
|
||||
import { useTranslation } from '../../../providers/Translation/index.js'
|
||||
import { Button } from '../../Button/index.js'
|
||||
import { FullscreenModal } from '../../FullscreenModal/index.js'
|
||||
import { ConfirmationModal } from '../../ConfirmationModal/index.js'
|
||||
import { useBulkUpload } from '../index.js'
|
||||
|
||||
export const discardBulkUploadModalSlug = 'bulk-upload--discard-without-saving'
|
||||
const baseClass = 'leave-without-saving'
|
||||
|
||||
export function DiscardWithoutSaving() {
|
||||
const { t } = useTranslation()
|
||||
@@ -26,21 +23,14 @@ export function DiscardWithoutSaving() {
|
||||
}, [closeModal, drawerSlug])
|
||||
|
||||
return (
|
||||
<FullscreenModal className={baseClass} slug={discardBulkUploadModalSlug}>
|
||||
<div className={`${baseClass}__wrapper`}>
|
||||
<div className={`${baseClass}__content`}>
|
||||
<h1>{t('general:leaveWithoutSaving')}</h1>
|
||||
<p>{t('general:changesNotSaved')}</p>
|
||||
</div>
|
||||
<div className={`${baseClass}__controls`}>
|
||||
<Button buttonStyle="secondary" onClick={onCancel} size="large">
|
||||
{t('general:stayOnThisPage')}
|
||||
</Button>
|
||||
<Button onClick={onConfirm} size="large">
|
||||
{t('general:leaveAnyway')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</FullscreenModal>
|
||||
<ConfirmationModal
|
||||
body={t('general:changesNotSaved')}
|
||||
cancelLabel={t('general:stayOnThisPage')}
|
||||
confirmLabel={t('general:leaveAnyway')}
|
||||
heading={t('general:leaveWithoutSaving')}
|
||||
modalSlug={discardBulkUploadModalSlug}
|
||||
onCancel={onCancel}
|
||||
onConfirm={onConfirm}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -23,10 +23,20 @@ function isModifiedEvent(event: React.MouseEvent): boolean {
|
||||
)
|
||||
}
|
||||
|
||||
export const Link: React.FC<Parameters<typeof NextLink>[0]> = ({
|
||||
type Props = {
|
||||
/**
|
||||
* Disable the e.preventDefault() call on click if you want to handle it yourself via onClick
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
preventDefault?: boolean
|
||||
} & Parameters<typeof NextLink>[0]
|
||||
|
||||
export const Link: React.FC<Props> = ({
|
||||
children,
|
||||
href,
|
||||
onClick,
|
||||
preventDefault = true,
|
||||
ref,
|
||||
replace,
|
||||
scroll,
|
||||
@@ -47,6 +57,12 @@ export const Link: React.FC<Parameters<typeof NextLink>[0]> = ({
|
||||
onClick(e)
|
||||
}
|
||||
|
||||
// We need a preventDefault here so that a clicked link doesn't trigger twice,
|
||||
// once for default browser navigation and once for startRouteTransition
|
||||
if (preventDefault) {
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
startRouteTransition(() => {
|
||||
const url = typeof href === 'string' ? href : formatUrl(href)
|
||||
|
||||
|
||||
@@ -73,7 +73,8 @@ export const ListControls: React.FC<ListControlsProps> = (props) => {
|
||||
const searchLabel =
|
||||
(titleField &&
|
||||
getTranslation(
|
||||
'label' in titleField && typeof titleField.label === 'string'
|
||||
'label' in titleField &&
|
||||
(typeof titleField.label === 'string' || typeof titleField.label === 'object')
|
||||
? titleField.label
|
||||
: 'name' in titleField
|
||||
? titleField.name
|
||||
|
||||
@@ -10,10 +10,10 @@ import { useField } from '../../forms/useField/index.js'
|
||||
import { withCondition } from '../../forms/withCondition/index.js'
|
||||
import { FieldDescription } from '../FieldDescription/index.js'
|
||||
import { FieldError } from '../FieldError/index.js'
|
||||
import './index.scss'
|
||||
import { FieldLabel } from '../FieldLabel/index.js'
|
||||
import { mergeFieldStyles } from '../mergeFieldStyles.js'
|
||||
import { fieldBaseClass } from '../shared/index.js'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'json-field'
|
||||
|
||||
@@ -31,10 +31,9 @@ const JSONFieldComponent: JSONFieldClientComponent = (props) => {
|
||||
readOnly,
|
||||
validate,
|
||||
} = props
|
||||
|
||||
const [stringValue, setStringValue] = useState<string>()
|
||||
const [jsonError, setJsonError] = useState<string>()
|
||||
const [hasLoadedValue, setHasLoadedValue] = useState(false)
|
||||
const inputChangeFromRef = React.useRef<'system' | 'user'>('system')
|
||||
const [editorKey, setEditorKey] = useState<string>('')
|
||||
|
||||
const memoizedValidate = useCallback(
|
||||
(value, options) => {
|
||||
@@ -56,6 +55,12 @@ const JSONFieldComponent: JSONFieldClientComponent = (props) => {
|
||||
validate: memoizedValidate,
|
||||
})
|
||||
|
||||
const [initialStringValue, setInitialStringValue] = useState<string | undefined>(() =>
|
||||
(value || initialValue) !== undefined
|
||||
? JSON.stringify(value ?? initialValue, null, 2)
|
||||
: undefined,
|
||||
)
|
||||
|
||||
const handleMount = useCallback<OnMount>(
|
||||
(editor, monaco) => {
|
||||
if (!jsonSchema) {
|
||||
@@ -88,7 +93,7 @@ const JSONFieldComponent: JSONFieldClientComponent = (props) => {
|
||||
if (readOnly) {
|
||||
return
|
||||
}
|
||||
setStringValue(val)
|
||||
inputChangeFromRef.current = 'user'
|
||||
|
||||
try {
|
||||
setValue(val ? JSON.parse(val) : null)
|
||||
@@ -98,20 +103,21 @@ const JSONFieldComponent: JSONFieldClientComponent = (props) => {
|
||||
setJsonError(e)
|
||||
}
|
||||
},
|
||||
[readOnly, setValue, setStringValue],
|
||||
[readOnly, setValue],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (hasLoadedValue || value === undefined) {
|
||||
return
|
||||
if (inputChangeFromRef.current === 'system') {
|
||||
setInitialStringValue(
|
||||
(value || initialValue) !== undefined
|
||||
? JSON.stringify(value ?? initialValue, null, 2)
|
||||
: undefined,
|
||||
)
|
||||
setEditorKey(new Date().toString())
|
||||
}
|
||||
|
||||
setStringValue(
|
||||
value || initialValue ? JSON.stringify(value ? value : initialValue, null, 2) : '',
|
||||
)
|
||||
|
||||
setHasLoadedValue(true)
|
||||
}, [initialValue, value, hasLoadedValue])
|
||||
inputChangeFromRef.current = 'system'
|
||||
}, [initialValue, value])
|
||||
|
||||
const styles = useMemo(() => mergeFieldStyles(field), [field])
|
||||
|
||||
@@ -142,12 +148,16 @@ const JSONFieldComponent: JSONFieldClientComponent = (props) => {
|
||||
{BeforeInput}
|
||||
<CodeEditor
|
||||
defaultLanguage="json"
|
||||
key={editorKey}
|
||||
maxHeight={maxHeight}
|
||||
onChange={handleChange}
|
||||
onMount={handleMount}
|
||||
options={editorOptions}
|
||||
readOnly={readOnly}
|
||||
value={stringValue}
|
||||
value={initialStringValue}
|
||||
wrapperProps={{
|
||||
id: `field-${path?.replace(/\./g, '__')}`,
|
||||
}}
|
||||
/>
|
||||
{AfterInput}
|
||||
</div>
|
||||
|
||||
@@ -24,7 +24,7 @@ export type RenderedFieldSlots = Map<string, RenderedField>
|
||||
/**
|
||||
* Get the state of the form, can be used to submit & validate the form.
|
||||
*
|
||||
* @see https://payloadcms.com/docs/admin/hooks#useform
|
||||
* @see https://payloadcms.com/docs/admin/react-hooks#useform
|
||||
*/
|
||||
const useForm = (): Context => useContext(FormContext)
|
||||
/**
|
||||
@@ -42,7 +42,7 @@ const useFormInitializing = (): boolean => useContext(InitializingContext)
|
||||
/**
|
||||
* Get and set the value of a form field based on a selector
|
||||
*
|
||||
* @see https://payloadcms.com/docs/admin/hooks#useformfields
|
||||
* @see https://payloadcms.com/docs/admin/react-hooks#useformfields
|
||||
*/
|
||||
const useFormFields = <Value = unknown>(
|
||||
selector: (context: FormFieldsContextType) => Value,
|
||||
@@ -51,7 +51,7 @@ const useFormFields = <Value = unknown>(
|
||||
/**
|
||||
* Get the state of all form fields.
|
||||
*
|
||||
* @see https://payloadcms.com/docs/admin/hooks#useallformfields
|
||||
* @see https://payloadcms.com/docs/admin/react-hooks#useallformfields
|
||||
*/
|
||||
const useAllFormFields = (): FormFieldsContextType => useFullContext(FormFieldsContext)
|
||||
|
||||
|
||||
@@ -180,31 +180,52 @@ export function fieldReducer(state: FormState, action: FieldAction): FormState {
|
||||
|
||||
case 'MOVE_ROW': {
|
||||
const { moveFromIndex, moveToIndex, path } = action
|
||||
const { remainingFields, rows } = separateRows(path, state)
|
||||
|
||||
// copy the row to move
|
||||
const copyOfMovingRow = rows[moveFromIndex]
|
||||
// delete the row by index
|
||||
rows.splice(moveFromIndex, 1)
|
||||
// insert row copyOfMovingRow back in
|
||||
rows.splice(moveToIndex, 0, copyOfMovingRow)
|
||||
// Handle moving rows on the top-level, i.e. `array.0.text` -> `array.1.text`
|
||||
const { remainingFields, rows: topLevelRows } = separateRows(path, state)
|
||||
const copyOfMovingRow = topLevelRows[moveFromIndex]
|
||||
topLevelRows.splice(moveFromIndex, 1)
|
||||
topLevelRows.splice(moveToIndex, 0, copyOfMovingRow)
|
||||
|
||||
// modify array/block internal row state (i.e. collapsed, blockType)
|
||||
const rowStateCopy = [...(state[path]?.rows || [])]
|
||||
const movingRowState = { ...rowStateCopy[moveFromIndex] }
|
||||
rowStateCopy.splice(moveFromIndex, 1)
|
||||
rowStateCopy.splice(moveToIndex, 0, movingRowState)
|
||||
const rowsWithinField = [...(state[path]?.rows || [])]
|
||||
const copyOfMovingRow2 = { ...rowsWithinField[moveFromIndex] }
|
||||
rowsWithinField.splice(moveFromIndex, 1)
|
||||
rowsWithinField.splice(moveToIndex, 0, copyOfMovingRow2)
|
||||
|
||||
const newState = {
|
||||
...remainingFields,
|
||||
...flattenRows(path, rows),
|
||||
...flattenRows(path, topLevelRows),
|
||||
[path]: {
|
||||
...state[path],
|
||||
requiresRender: true,
|
||||
rows: rowStateCopy,
|
||||
rows: rowsWithinField,
|
||||
},
|
||||
}
|
||||
|
||||
// Do the same for custom components, i.e. `array.customComponents.RowLabels[0]` -> `array.customComponents.RowLabels[1]`
|
||||
// Do this _after_ initializing `newState` to avoid adding the `customComponents` key to the state if it doesn't exist
|
||||
if (newState[path]?.customComponents?.RowLabels) {
|
||||
const customComponents = {
|
||||
...newState[path].customComponents,
|
||||
RowLabels: [...newState[path].customComponents.RowLabels],
|
||||
}
|
||||
|
||||
// Ensure the array grows if necessary
|
||||
if (moveToIndex >= customComponents.RowLabels.length) {
|
||||
customComponents.RowLabels.length = moveToIndex + 1
|
||||
}
|
||||
|
||||
const copyOfMovingLabel = customComponents.RowLabels[moveFromIndex]
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
customComponents.RowLabels.splice(moveFromIndex, 1)
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
customComponents.RowLabels.splice(moveToIndex, 0, copyOfMovingLabel)
|
||||
|
||||
newState[path].customComponents = customComponents
|
||||
}
|
||||
|
||||
return newState
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
/**
|
||||
* Get and set the value of a form field.
|
||||
*
|
||||
* @see https://payloadcms.com/docs/admin/hooks#usefield
|
||||
* @see https://payloadcms.com/docs/admin/react-hooks#usefield
|
||||
*/
|
||||
export const useField = <TValue,>(options: Options): FieldType<TValue> => {
|
||||
const { disableFormData = false, hasRows, path, validate } = options
|
||||
|
||||
@@ -88,6 +88,28 @@ export const Relationship: CollectionConfig = {
|
||||
relationTo: slug,
|
||||
type: 'relationship',
|
||||
},
|
||||
{
|
||||
type: 'collapsible',
|
||||
label: 'Collapsible',
|
||||
fields: [
|
||||
{
|
||||
name: 'nestedRelationshipFilteredByField',
|
||||
filterOptions: () => {
|
||||
return {
|
||||
filter: {
|
||||
equals: 'Include me',
|
||||
},
|
||||
}
|
||||
},
|
||||
admin: {
|
||||
description:
|
||||
'This will filter the relationship options if the filter field in this document is set to "Include me"',
|
||||
},
|
||||
relationTo: slug,
|
||||
type: 'relationship',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'relationshipFilteredAsync',
|
||||
filterOptions: (args: FilterOptionsProps<FieldsRelationship>) => {
|
||||
|
||||
@@ -351,6 +351,41 @@ describe('Relationship Field', () => {
|
||||
await expect(valueOptions.locator(`text=${idToInclude}`)).toBeVisible()
|
||||
})
|
||||
|
||||
test('should apply filter options of nested fields to list view filter controls', async () => {
|
||||
const { id: idToInclude } = await payload.create({
|
||||
collection: slug,
|
||||
data: {
|
||||
filter: 'Include me',
|
||||
},
|
||||
})
|
||||
|
||||
// first ensure that filter options are applied in the edit view
|
||||
await page.goto(url.edit(idToInclude))
|
||||
const field = page.locator('#field-nestedRelationshipFilteredByField')
|
||||
await field.click({ delay: 100 })
|
||||
const options = field.locator('.rs__option')
|
||||
await expect(options).toHaveCount(1)
|
||||
await expect(options).toContainText(idToInclude)
|
||||
|
||||
// now ensure that the same filter options are applied in the list view
|
||||
await page.goto(url.list)
|
||||
|
||||
const whereBuilder = await addListFilter({
|
||||
page,
|
||||
fieldLabel: 'Collapsible > Nested Relationship Filtered By Field',
|
||||
operatorLabel: 'equals',
|
||||
skipValueInput: true,
|
||||
})
|
||||
|
||||
const valueInput = page.locator('.condition__value input')
|
||||
await valueInput.click()
|
||||
const valueOptions = whereBuilder.locator('.condition__value .rs__option')
|
||||
|
||||
await expect(valueOptions).toHaveCount(2)
|
||||
await expect(valueOptions.locator(`text=None`)).toBeVisible()
|
||||
await expect(valueOptions.locator(`text=${idToInclude}`)).toBeVisible()
|
||||
})
|
||||
|
||||
test('should allow usage of relationTo in filterOptions', async () => {
|
||||
const { id: include } = (await payload.create({
|
||||
collection: relationOneSlug,
|
||||
|
||||
@@ -177,6 +177,10 @@ export interface FieldsRelationship {
|
||||
* This will filter the relationship options if the filter field in this document is set to "Include me"
|
||||
*/
|
||||
relationshipFilteredByField?: (string | null) | FieldsRelationship;
|
||||
/**
|
||||
* This will filter the relationship options if the filter field in this document is set to "Include me"
|
||||
*/
|
||||
nestedRelationshipFilteredByField?: (string | null) | FieldsRelationship;
|
||||
relationshipFilteredAsync?: (string | null) | RelationOne;
|
||||
relationshipManyFiltered?:
|
||||
| (
|
||||
@@ -506,6 +510,7 @@ export interface FieldsRelationshipSelect<T extends boolean = true> {
|
||||
relationshipWithTitle?: T;
|
||||
relationshipFilteredByID?: T;
|
||||
relationshipFilteredByField?: T;
|
||||
nestedRelationshipFilteredByField?: T;
|
||||
relationshipFilteredAsync?: T;
|
||||
relationshipManyFiltered?: T;
|
||||
filter?: T;
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { BrowserContext, Page } from '@playwright/test'
|
||||
import { expect, test } from '@playwright/test'
|
||||
import { addBlock } from 'helpers/e2e/addBlock.js'
|
||||
import { openBlocksDrawer } from 'helpers/e2e/openBlocksDrawer.js'
|
||||
import { reorderBlocks } from 'helpers/e2e/reorderBlocks.js'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
@@ -289,6 +290,39 @@ describe('Block fields', () => {
|
||||
})
|
||||
|
||||
describe('row manipulation', () => {
|
||||
test('moving rows should immediately move custom row labels', async () => {
|
||||
await page.goto(url.create)
|
||||
|
||||
// first ensure that the first block has the custom header, and that the second block doesn't
|
||||
|
||||
await expect(
|
||||
page.locator('#field-blocks #blocks-row-0 .blocks-field__block-header'),
|
||||
).toHaveText('Custom Block Label: Content 01')
|
||||
|
||||
const secondBlockHeader = page.locator(
|
||||
'#field-blocks #blocks-row-1 .blocks-field__block-header',
|
||||
)
|
||||
|
||||
await expect(secondBlockHeader.locator('.blocks-field__block-pill')).toHaveText('Number')
|
||||
|
||||
await expect(secondBlockHeader.locator('input[id="blocks.1.blockName"]')).toHaveValue(
|
||||
'Second block',
|
||||
)
|
||||
|
||||
await reorderBlocks({
|
||||
page,
|
||||
fieldName: 'blocks',
|
||||
fromBlockIndex: 0,
|
||||
toBlockIndex: 1,
|
||||
})
|
||||
|
||||
// Important: do _not_ poll here, use `textContent()` instead of `toHaveText()`
|
||||
// This will prevent Playwright from polling for the change to the DOM
|
||||
expect(
|
||||
await page.locator('#field-blocks #blocks-row-1 .blocks-field__block-header').textContent(),
|
||||
).toMatch(/^Custom Block Label: Content/)
|
||||
})
|
||||
|
||||
describe('react hooks', () => {
|
||||
test('should add 2 new block rows', async () => {
|
||||
await page.goto(url.create)
|
||||
|
||||
38
test/fields/collections/JSON/AfterField.tsx
Normal file
38
test/fields/collections/JSON/AfterField.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
'use client'
|
||||
|
||||
import { useField } from '@payloadcms/ui'
|
||||
|
||||
export function AfterField() {
|
||||
const { setValue } = useField({ path: 'customJSON' })
|
||||
|
||||
return (
|
||||
<button
|
||||
id="set-custom-json"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
setValue({
|
||||
users: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'John Doe',
|
||||
email: 'john.doe@example.com',
|
||||
isActive: true,
|
||||
roles: ['admin', 'editor'],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Jane Smith',
|
||||
email: 'jane.smith@example.com',
|
||||
isActive: false,
|
||||
roles: ['viewer'],
|
||||
},
|
||||
],
|
||||
})
|
||||
}}
|
||||
style={{ marginTop: '5px', padding: '5px 10px' }}
|
||||
type="button"
|
||||
>
|
||||
Set Custom JSON
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@@ -103,4 +103,24 @@ describe('JSON', () => {
|
||||
'"foo.with.periods": "bar"',
|
||||
)
|
||||
})
|
||||
|
||||
test('should update', async () => {
|
||||
const createdDoc = await payload.create({
|
||||
collection: 'json-fields',
|
||||
data: {
|
||||
customJSON: {
|
||||
default: 'value',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await page.goto(url.edit(createdDoc.id))
|
||||
const jsonField = page.locator('.json-field #field-customJSON')
|
||||
await expect(jsonField).toContainText('"default": "value"')
|
||||
|
||||
const originalHeight = (await page.locator('#field-customJSON').boundingBox())?.height || 0
|
||||
await page.locator('#set-custom-json').click()
|
||||
const newHeight = (await page.locator('#field-customJSON').boundingBox())?.height || 0
|
||||
expect(newHeight).toBeGreaterThan(originalHeight)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -67,6 +67,16 @@ const JSON: CollectionConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'customJSON',
|
||||
type: 'json',
|
||||
admin: {
|
||||
components: {
|
||||
afterInput: ['./collections/JSON/AfterField#AfterField'],
|
||||
},
|
||||
},
|
||||
label: 'Custom Json',
|
||||
},
|
||||
],
|
||||
versions: {
|
||||
maxPerDoc: 1,
|
||||
|
||||
@@ -1474,6 +1474,15 @@ export interface JsonField {
|
||||
| boolean
|
||||
| null;
|
||||
};
|
||||
customJSON?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
@@ -3165,6 +3174,7 @@ export interface JsonFieldsSelect<T extends boolean = true> {
|
||||
| {
|
||||
jsonWithinGroup?: T;
|
||||
};
|
||||
customJSON?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
|
||||
@@ -64,8 +64,16 @@ export default buildConfigWithDefaults({
|
||||
NestedArray,
|
||||
NestedFields,
|
||||
{
|
||||
admin: {
|
||||
listSearchableFields: 'name',
|
||||
},
|
||||
auth: true,
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
label: { en: 'Full name' },
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'relation',
|
||||
relationTo: localizedPostsSlug,
|
||||
@@ -83,6 +91,7 @@ export default buildConfigWithDefaults({
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
label: { en: 'Full title' },
|
||||
index: true,
|
||||
localized: true,
|
||||
type: 'text',
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import type { BrowserContext, Page } from '@playwright/test'
|
||||
import type { GeneratedTypes } from 'helpers/sdk/types.js'
|
||||
|
||||
import { expect, test } from '@playwright/test'
|
||||
import { navigateToDoc } from 'helpers/e2e/navigateToDoc.js'
|
||||
import { openDocControls } from 'helpers/e2e/openDocControls.js'
|
||||
import { upsertPrefs } from 'helpers/e2e/upsertPrefs.js'
|
||||
import { RESTClient } from 'helpers/rest.js'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
@@ -31,11 +35,6 @@ import {
|
||||
spanishLocale,
|
||||
withRequiredLocalizedFields,
|
||||
} from './shared.js'
|
||||
import { navigateToDoc } from 'helpers/e2e/navigateToDoc.js'
|
||||
|
||||
import { upsertPrefs } from 'helpers/e2e/upsertPrefs.js'
|
||||
import { RESTClient } from 'helpers/rest.js'
|
||||
import { GeneratedTypes } from 'helpers/sdk/types.js'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
@@ -119,16 +118,16 @@ describe('Localization', () => {
|
||||
|
||||
await expect(page.locator('.localizer .popup')).toHaveClass(/popup--active/)
|
||||
|
||||
const activeOption = await page.locator(
|
||||
const activeOption = page.locator(
|
||||
`.localizer .popup.popup--active .popup-button-list__button--selected`,
|
||||
)
|
||||
|
||||
await expect(activeOption).toBeVisible()
|
||||
const tagName = await activeOption.evaluate((node) => node.tagName)
|
||||
await expect(tagName).not.toBe('A')
|
||||
expect(tagName).not.toBe('A')
|
||||
await expect(activeOption).not.toHaveAttribute('href')
|
||||
await expect(tagName).not.toBe('BUTTON')
|
||||
await expect(tagName).toBe('DIV')
|
||||
expect(tagName).not.toBe('BUTTON')
|
||||
expect(tagName).toBe('DIV')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -140,7 +139,7 @@ describe('Localization', () => {
|
||||
const createNewButtonLocator =
|
||||
'.collection-list a[href="/admin/collections/cannot-create-default-locale/create"]'
|
||||
|
||||
await expect(page.locator(createNewButtonLocator)).not.toBeVisible()
|
||||
await expect(page.locator(createNewButtonLocator)).toBeHidden()
|
||||
await changeLocale(page, spanishLocale)
|
||||
await expect(page.locator(createNewButtonLocator).first()).toBeVisible()
|
||||
await page.goto(urlCannotCreateDefaultLocale.create)
|
||||
@@ -330,11 +329,11 @@ describe('Localization', () => {
|
||||
|
||||
await page.goto(url.list)
|
||||
|
||||
const localeLabel = await page
|
||||
const localeLabel = page
|
||||
.locator('.localizer.app-header__localizer .localizer-button__current-label')
|
||||
.innerText()
|
||||
|
||||
|
||||
expect(localeLabel).not.toEqual('English')
|
||||
await expect(localeLabel).not.toHaveText('English')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -351,7 +350,7 @@ describe('Localization', () => {
|
||||
await navigateToDoc(page, urlRelationshipLocalized)
|
||||
const drawerToggler =
|
||||
'#field-relationMultiRelationTo .relationship--single-value__drawer-toggler'
|
||||
expect(page.locator(drawerToggler)).toBeEnabled()
|
||||
await expect(page.locator(drawerToggler)).toBeEnabled()
|
||||
await openDocDrawer(page, drawerToggler)
|
||||
await expect(page.locator('.doc-drawer__header-text')).toContainText('spanish-relation2')
|
||||
await page.locator('.doc-drawer__header-close').click()
|
||||
@@ -518,7 +517,7 @@ describe('Localization', () => {
|
||||
|
||||
// only throttle test after initial load to avoid timeouts
|
||||
const cdpSession = await throttleTest({
|
||||
page: page,
|
||||
page,
|
||||
context,
|
||||
delay: 'Fast 4G',
|
||||
})
|
||||
@@ -541,6 +540,13 @@ describe('Localization', () => {
|
||||
await cdpSession.detach()
|
||||
})
|
||||
})
|
||||
|
||||
test('should use label in search filter when string or object', async () => {
|
||||
await page.goto(url.list)
|
||||
const searchInput = page.locator('.search-filter__input')
|
||||
await expect(searchInput).toBeVisible()
|
||||
await expect(searchInput).toHaveAttribute('placeholder', 'Search by Full title')
|
||||
})
|
||||
})
|
||||
|
||||
async function fillValues(data: Partial<LocalizedPost>) {
|
||||
|
||||
@@ -64,7 +64,6 @@ export interface Config {
|
||||
auth: {
|
||||
users: UserAuthOperations;
|
||||
};
|
||||
blocks: {};
|
||||
collections: {
|
||||
richText: RichText;
|
||||
'blocks-fields': BlocksField;
|
||||
@@ -322,6 +321,7 @@ export interface NestedFieldTable {
|
||||
*/
|
||||
export interface User {
|
||||
id: string;
|
||||
name?: string | null;
|
||||
relation?: (string | null) | LocalizedPost;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
@@ -928,6 +928,7 @@ export interface NestedFieldTablesSelect<T extends boolean = true> {
|
||||
* via the `definition` "users_select".
|
||||
*/
|
||||
export interface UsersSelect<T extends boolean = true> {
|
||||
name?: T;
|
||||
relation?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { NextRESTClient } from '../helpers/NextRESTClient.js'
|
||||
|
||||
import { devUser } from '../credentials.js'
|
||||
import { initPayloadInt } from '../helpers/initPayloadInt.js'
|
||||
import { tenantsSlug } from './shared.js'
|
||||
|
||||
let payload: Payload
|
||||
let restClient: NextRESTClient
|
||||
@@ -40,7 +41,7 @@ describe('@payloadcms/plugin-multi-tenant', () => {
|
||||
describe('tenants', () => {
|
||||
it('should create a tenant', async () => {
|
||||
const tenant1 = await payload.create({
|
||||
collection: 'tenants',
|
||||
collection: tenantsSlug,
|
||||
data: {
|
||||
name: 'tenant1',
|
||||
domain: 'tenant1.com',
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import type { Config } from 'payload'
|
||||
|
||||
import { devUser } from '../../credentials.js'
|
||||
import { menuItemsSlug, menuSlug, usersSlug } from '../shared.js'
|
||||
import { menuItemsSlug, menuSlug, tenantsSlug, usersSlug } from '../shared.js'
|
||||
|
||||
export const seed: Config['onInit'] = async (payload) => {
|
||||
// create tenants
|
||||
const blueDogTenant = await payload.create({
|
||||
collection: 'tenants',
|
||||
collection: tenantsSlug,
|
||||
data: {
|
||||
name: 'Blue Dog',
|
||||
domain: 'bluedog.com',
|
||||
},
|
||||
})
|
||||
const steelCatTenant = await payload.create({
|
||||
collection: 'tenants',
|
||||
collection: tenantsSlug,
|
||||
data: {
|
||||
name: 'Steel Cat',
|
||||
domain: 'steelcat.com',
|
||||
|
||||
@@ -76,7 +76,6 @@ describe('@payloadcms/plugin-nested-docs', () => {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// update parent doc
|
||||
await payload.update({
|
||||
collection: 'pages',
|
||||
@@ -110,6 +109,91 @@ describe('@payloadcms/plugin-nested-docs', () => {
|
||||
// @ts-ignore
|
||||
expect(lastUpdatedChildBreadcrumbs[0].url).toStrictEqual('/11-children-updated')
|
||||
})
|
||||
|
||||
it('should return breadcrumbs as an array of objects', async () => {
|
||||
const parentDoc = await payload.create({
|
||||
collection: 'pages',
|
||||
data: {
|
||||
title: 'parent doc',
|
||||
slug: 'parent-doc',
|
||||
_status: 'published',
|
||||
},
|
||||
})
|
||||
|
||||
const childDoc = await payload.create({
|
||||
collection: 'pages',
|
||||
data: {
|
||||
title: 'child doc',
|
||||
slug: 'child-doc',
|
||||
parent: parentDoc.id,
|
||||
_status: 'published',
|
||||
},
|
||||
})
|
||||
|
||||
// expect breadcrumbs to be an array
|
||||
expect(childDoc.breadcrumbs).toBeInstanceOf(Array)
|
||||
expect(childDoc.breadcrumbs).toBeDefined()
|
||||
|
||||
// expect each to be objects
|
||||
childDoc.breadcrumbs?.map((breadcrumb) => {
|
||||
expect(breadcrumb).toBeInstanceOf(Object)
|
||||
})
|
||||
})
|
||||
|
||||
it('should update child doc breadcrumb without affecting any other data', async () => {
|
||||
const parentDoc = await payload.create({
|
||||
collection: 'pages',
|
||||
data: {
|
||||
title: 'parent doc',
|
||||
slug: 'parent',
|
||||
},
|
||||
})
|
||||
|
||||
const childDoc = await payload.create({
|
||||
collection: 'pages',
|
||||
data: {
|
||||
title: 'child doc',
|
||||
slug: 'child',
|
||||
parent: parentDoc.id,
|
||||
_status: 'published',
|
||||
},
|
||||
})
|
||||
|
||||
await payload.update({
|
||||
collection: 'pages',
|
||||
id: parentDoc.id,
|
||||
data: {
|
||||
title: 'parent updated',
|
||||
slug: 'parent-updated',
|
||||
_status: 'published',
|
||||
},
|
||||
})
|
||||
|
||||
const updatedChild = await payload
|
||||
.find({
|
||||
collection: 'pages',
|
||||
where: {
|
||||
id: {
|
||||
equals: childDoc.id,
|
||||
},
|
||||
},
|
||||
})
|
||||
.then(({ docs }) => docs[0])
|
||||
|
||||
if (!updatedChild) {
|
||||
return
|
||||
}
|
||||
|
||||
// breadcrumbs should be updated
|
||||
expect(updatedChild.breadcrumbs).toHaveLength(2)
|
||||
|
||||
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')
|
||||
})
|
||||
})
|
||||
|
||||
describe('overrides', () => {
|
||||
|
||||
@@ -438,6 +438,47 @@ describe('Versions', () => {
|
||||
await expect(drawer.locator('.id-label')).toBeVisible()
|
||||
})
|
||||
|
||||
test('collection - autosave - should not create duplicates when clicking Create new', async () => {
|
||||
// This test checks that when we click "Create new" in the list view, it only creates 1 extra document and not more
|
||||
const { totalDocs: initialDocsCount } = await payload.find({
|
||||
collection: autosaveCollectionSlug,
|
||||
draft: true,
|
||||
})
|
||||
|
||||
await page.goto(autosaveURL.create)
|
||||
await page.locator('#field-title').fill('autosave title')
|
||||
await waitForAutoSaveToRunAndComplete(page)
|
||||
await expect(page.locator('#field-title')).toHaveValue('autosave title')
|
||||
|
||||
const { totalDocs: updatedDocsCount } = await payload.find({
|
||||
collection: autosaveCollectionSlug,
|
||||
draft: true,
|
||||
})
|
||||
|
||||
await expect(() => {
|
||||
expect(updatedDocsCount).toBe(initialDocsCount + 1)
|
||||
}).toPass({ timeout: POLL_TOPASS_TIMEOUT, intervals: [100] })
|
||||
|
||||
await page.goto(autosaveURL.list)
|
||||
const createNewButton = page.locator('.list-header .btn:has-text("Create New")')
|
||||
await createNewButton.click()
|
||||
|
||||
await page.waitForURL(`**/${autosaveCollectionSlug}/**`)
|
||||
|
||||
await page.locator('#field-title').fill('autosave title')
|
||||
await waitForAutoSaveToRunAndComplete(page)
|
||||
await expect(page.locator('#field-title')).toHaveValue('autosave title')
|
||||
|
||||
const { totalDocs: latestDocsCount } = await payload.find({
|
||||
collection: autosaveCollectionSlug,
|
||||
draft: true,
|
||||
})
|
||||
|
||||
await expect(() => {
|
||||
expect(latestDocsCount).toBe(updatedDocsCount + 1)
|
||||
}).toPass({ timeout: POLL_TOPASS_TIMEOUT, intervals: [100] })
|
||||
})
|
||||
|
||||
test('collection - should update updatedAt', async () => {
|
||||
await page.goto(url.create)
|
||||
await page.waitForURL(`**/${url.create}`)
|
||||
@@ -757,7 +798,7 @@ describe('Versions', () => {
|
||||
|
||||
// schedule publish should not be available before document has been saved
|
||||
await page.locator('#action-save-popup').click()
|
||||
await expect(page.locator('#schedule-publish')).not.toBeVisible()
|
||||
await expect(page.locator('#schedule-publish')).toBeHidden()
|
||||
|
||||
// save draft then try to schedule publish
|
||||
await saveDocAndAssert(page)
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@payload-config": ["./test/admin/config.ts"],
|
||||
"@payload-config": ["./test/fields-relationship/config.ts"],
|
||||
"@payloadcms/live-preview": ["./packages/live-preview/src"],
|
||||
"@payloadcms/live-preview-react": ["./packages/live-preview-react/src/index.ts"],
|
||||
"@payloadcms/live-preview-vue": ["./packages/live-preview-vue/src/index.ts"],
|
||||
|
||||
Reference in New Issue
Block a user