Compare commits
17 Commits
v3.0.0-bet
...
fix/expose
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4283bebc37 | ||
|
|
4082680099 | ||
|
|
9056b9fe9b | ||
|
|
82f278931b | ||
|
|
a7895560a6 | ||
|
|
1f0d8da182 | ||
|
|
c91f21bb78 | ||
|
|
7136515f8d | ||
|
|
73102e97fe | ||
|
|
f37e476758 | ||
|
|
90bca15f52 | ||
|
|
872b205acc | ||
|
|
99b4359e89 | ||
|
|
b269d33278 | ||
|
|
c63b7bc606 | ||
|
|
0fb92d3a0a | ||
|
|
99228b62ce |
@@ -145,7 +145,7 @@ Instead, we utilize component paths to reference React Components. This method e
|
||||
|
||||
When constructing the `ClientConfig`, Payload uses the component paths as keys to fetch the corresponding React Component imports from the Import Map. It then substitutes the `PayloadComponent` with a `MappedComponent`. A `MappedComponent` includes the React Component and additional metadata, such as whether it's a server or a client component and which props it should receive. These components are then rendered through the `<RenderComponent />` component within the Payload Admin Panel.
|
||||
|
||||
Import maps are regenerated whenever you modify any element related to component paths. This regeneration occurs at startup and whenever Hot Module Replacement (HMR) runs. If the import maps fail to regenerate during HMR, you can restart your application and execute the `payload generate:importmap` command to manually create a new import map. If you encounter any errors running this command, see the [Troubleshooting](/docs/beta/local-api/outside-nextjs#troubleshooting) section.
|
||||
Import maps are regenerated whenever you modify any element related to component paths. This regeneration occurs at startup and whenever Hot Module Replacement (HMR) runs. If the import maps fail to regenerate during HMR, you can restart your application and execute the `payload generate:importmap` command to manually create a new import map. If you encounter any errors running this command, see the [Troubleshooting](../local-api/outside-nextjs#troubleshooting) section.
|
||||
|
||||
### Component paths in external packages
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ Cookies can cross subdomains without being considered third party cookies, for e
|
||||
|
||||
##### 2. Configure cookies
|
||||
|
||||
If option 1 isn't possible, then you can get around this limitation by [configuring your cookies](https://payloadcms.com/docs/beta/authentication/overview#config-options) on your authentication collection to achieve the following setup:
|
||||
If option 1 isn't possible, then you can get around this limitation by [configuring your cookies](./overview#config-options) on your authentication collection to achieve the following setup:
|
||||
|
||||
```
|
||||
SameSite: None // allows the cookie to cross domains
|
||||
@@ -122,7 +122,7 @@ Configuration example:
|
||||
},
|
||||
```
|
||||
|
||||
If you're configuring [cors](https://payloadcms.com/docs/beta/production/preventing-abuse#cross-origin-resource-sharing-cors) in your Payload config, you won't be able to use a wildcard anymore, you'll need to specify the list of allowed domains.
|
||||
If you're configuring [cors](../production/preventing-abuse#cross-origin-resource-sharing-cors) in your Payload config, you won't be able to use a wildcard anymore, you'll need to specify the list of allowed domains.
|
||||
|
||||
|
||||
<Banner type="success">
|
||||
|
||||
@@ -84,7 +84,7 @@ export default buildConfig({
|
||||
|
||||
## Email
|
||||
|
||||
Powered by [Resend](https://resend.com), Payload Cloud comes with integrated email support out of the box. No configuration is needed, and you can use `payload.sendEmail()` to send email right from your Payload app. To learn more about sending email with Payload, checkout the [Email Configuration](https://payloadcms.com/docs/email/overview) overview.
|
||||
Powered by [Resend](https://resend.com), Payload Cloud comes with integrated email support out of the box. No configuration is needed, and you can use `payload.sendEmail()` to send email right from your Payload app. To learn more about sending email with Payload, checkout the [Email Configuration](../email/overview) overview.
|
||||
|
||||
If you are on the Pro or Enterprise plan, you can add your own custom Email domain name. From the Email page of your project’s Settings, add the domain you wish to use for email delivery. This will generate a set of DNS records. Add these records to your DNS provider and click verify to check that your records are resolving properly. Once verified, your emails will now be sent from your custom domain name.
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ You can disable this setting and solely use migrations to manage your local deve
|
||||
|
||||
For this reason, we suggest that you leave `push` as its default setting and treat your local dev database as a sandbox.
|
||||
|
||||
For more information about push mode and prototyping in development, [click here](/docs/beta/database/postgres#prototyping-in-dev-mode).
|
||||
For more information about push mode and prototyping in development, [click here](./postgres#prototyping-in-dev-mode).
|
||||
|
||||
The typical workflow in Payload is to build out your Payload configs, install plugins, and make progress in development mode - allowing Drizzle to push your changes to your local database for you. Once you're finished, you can create a migration.
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ Alternatively, you can disable `push` and rely solely on migrations to keep your
|
||||
|
||||
In Postgres, migrations are a fundamental aspect of working with Payload and you should become familiar with how they work.
|
||||
|
||||
For more information about migrations, [click here](/docs/beta/database/migrations#when-to-run-migrations).
|
||||
For more information about migrations, [click here](./migrations#when-to-run-migrations).
|
||||
|
||||
## Drizzle schema hooks
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ Alternatively, you can disable `push` and rely solely on migrations to keep your
|
||||
|
||||
In SQLite, migrations are a fundamental aspect of working with Payload and you should become familiar with how they work.
|
||||
|
||||
For more information about migrations, [click here](/docs/beta/database/migrations#when-to-run-migrations).
|
||||
For more information about migrations, [click here](./migrations#when-to-run-migrations).
|
||||
|
||||
## Drizzle schema hooks
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ import config from '@payload-config'
|
||||
const payload = await getPayload({ config })
|
||||
```
|
||||
|
||||
Both options function in exactly the same way outside of one having HMR support and the other not. For more information about using Payload outside of Next.js, [click here](/docs/beta/local-api/outside-nextjs).
|
||||
Both options function in exactly the same way outside of one having HMR support and the other not. For more information about using Payload outside of Next.js, [click here](./outside-nextjs).
|
||||
|
||||
## Local options available
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ const beforeEmail: BeforeEmail<FormSubmission> = (emailsToSend, beforeChangePara
|
||||
|
||||
### `defaultToEmail`
|
||||
|
||||
Provide a fallback for the email address to send form submissions to. If the email in form configuration does not have a to email set, this email address will be used. If this is not provided then it falls back to the `defaultFromAddress` in your [email configuration](https://payloadcms.com/docs/beta/email/overview).
|
||||
Provide a fallback for the email address to send form submissions to. If the email in form configuration does not have a to email set, this email address will be used. If this is not provided then it falls back to the `defaultFromAddress` in your [email configuration](../email/overview).
|
||||
|
||||
```ts
|
||||
// payload.config.ts
|
||||
@@ -412,7 +412,7 @@ formBuilder({
|
||||
|
||||
## Email
|
||||
|
||||
This plugin relies on the [email configuration](https://payloadcms.com/docs/beta/email/overview) defined in your payload configuration. It will read from your config and attempt to send your emails using the credentials provided.
|
||||
This plugin relies on the [email configuration](../email/overview) defined in your payload configuration. It will read from your config and attempt to send your emails using the credentials provided.
|
||||
|
||||
### Email formatting
|
||||
|
||||
|
||||
@@ -276,6 +276,10 @@ OverviewField({
|
||||
})
|
||||
```
|
||||
|
||||
<Banner type="info">
|
||||
Tip: You can override the length rules by changing the minLength and maxLength props on the fields. In the case of the OverviewField you can use `titleOverrides` and `descriptionOverrides` to override the length rules.
|
||||
</Banner>
|
||||
|
||||
## TypeScript
|
||||
|
||||
All types can be directly imported:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload-monorepo",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -132,7 +132,7 @@
|
||||
"create-payload-app": "workspace:*",
|
||||
"cross-env": "7.0.3",
|
||||
"dotenv": "16.4.5",
|
||||
"drizzle-kit": "0.26.1",
|
||||
"drizzle-kit": "0.26.2",
|
||||
"drizzle-orm": "0.35.1",
|
||||
"escape-html": "^1.0.3",
|
||||
"execa": "5.1.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -246,6 +246,10 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
field.fields.forEach((subField: Field) => {
|
||||
if (fieldIsVirtual(subField)) {
|
||||
return
|
||||
}
|
||||
|
||||
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[subField.type]
|
||||
|
||||
if (addFieldSchema) {
|
||||
@@ -501,6 +505,10 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
field.fields.forEach((subField: Field) => {
|
||||
if (fieldIsVirtual(subField)) {
|
||||
return
|
||||
}
|
||||
|
||||
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[subField.type]
|
||||
|
||||
if (addFieldSchema) {
|
||||
@@ -545,6 +553,9 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
): void => {
|
||||
field.tabs.forEach((tab) => {
|
||||
if (tabHasName(tab)) {
|
||||
if (fieldIsVirtual(tab)) {
|
||||
return
|
||||
}
|
||||
const baseSchema = {
|
||||
type: buildSchema(config, tab.fields, {
|
||||
disableUnique: buildSchemaOptions.disableUnique,
|
||||
@@ -562,6 +573,9 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
})
|
||||
} else {
|
||||
tab.fields.forEach((subField: Field) => {
|
||||
if (fieldIsVirtual(subField)) {
|
||||
return
|
||||
}
|
||||
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[subField.type]
|
||||
|
||||
if (addFieldSchema) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -50,7 +50,7 @@
|
||||
"@payloadcms/drizzle": "workspace:*",
|
||||
"@types/pg": "8.10.2",
|
||||
"console-table-printer": "2.11.2",
|
||||
"drizzle-kit": "0.26.1",
|
||||
"drizzle-kit": "0.26.2",
|
||||
"drizzle-orm": "0.35.1",
|
||||
"pg": "8.11.3",
|
||||
"prompts": "2.4.2",
|
||||
|
||||
@@ -136,6 +136,7 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
|
||||
findGlobalVersions,
|
||||
findOne,
|
||||
findVersions,
|
||||
indexes: new Set<string>(),
|
||||
init,
|
||||
insert,
|
||||
migrate,
|
||||
|
||||
@@ -9,7 +9,7 @@ import type {
|
||||
import type { DrizzleAdapter } from '@payloadcms/drizzle/types'
|
||||
import type { DrizzleConfig } from 'drizzle-orm'
|
||||
import type { PgSchema, PgTableFn, PgTransactionConfig } from 'drizzle-orm/pg-core'
|
||||
import type { Pool, PoolConfig } from 'pg'
|
||||
import type { Client, Pool, PoolConfig } from 'pg'
|
||||
|
||||
export type Args = {
|
||||
/**
|
||||
@@ -62,7 +62,9 @@ declare module 'payload' {
|
||||
afterSchemaInit: PostgresSchemaHook[]
|
||||
beforeSchemaInit: PostgresSchemaHook[]
|
||||
beginTransaction: (options?: PgTransactionConfig) => Promise<null | number | string>
|
||||
drizzle: PostgresDB
|
||||
drizzle: {
|
||||
$client: Client | Pool
|
||||
} & PostgresDB
|
||||
enums: Record<string, GenericEnum>
|
||||
/**
|
||||
* An object keyed on each table, with a key value pair where the constraint name is the key, followed by the dot-notation field name
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-sqlite",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"description": "The officially supported SQLite database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -48,7 +48,7 @@
|
||||
"@libsql/client": "0.14.0",
|
||||
"@payloadcms/drizzle": "workspace:*",
|
||||
"console-table-printer": "2.11.2",
|
||||
"drizzle-kit": "0.26.1",
|
||||
"drizzle-kit": "0.26.2",
|
||||
"drizzle-orm": "0.35.1",
|
||||
"prompts": "2.4.2",
|
||||
"to-snake-case": "1.0.0",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { DrizzleAdapter } from '@payloadcms/drizzle/types'
|
||||
import type { LibSQLDatabase } from 'drizzle-orm/libsql'
|
||||
import type { Connect } from 'payload'
|
||||
|
||||
import { createClient } from '@libsql/client'
|
||||
|
||||
@@ -133,6 +133,7 @@ export function sqliteAdapter(args: Args): DatabaseAdapterObj<SQLiteAdapter> {
|
||||
findGlobalVersions,
|
||||
findOne,
|
||||
findVersions,
|
||||
indexes: new Set<string>(),
|
||||
init,
|
||||
insert,
|
||||
migrate,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { DrizzleAdapter } from '@payloadcms/drizzle/types'
|
||||
import type { Relation } from 'drizzle-orm'
|
||||
import type {
|
||||
AnySQLiteColumn,
|
||||
@@ -9,7 +10,7 @@ import type {
|
||||
} from 'drizzle-orm/sqlite-core'
|
||||
import type { Field, SanitizedJoins } from 'payload'
|
||||
|
||||
import { createTableName } from '@payloadcms/drizzle'
|
||||
import { buildIndexName, createTableName } from '@payloadcms/drizzle'
|
||||
import { relations, sql } from 'drizzle-orm'
|
||||
import {
|
||||
foreignKey,
|
||||
@@ -416,21 +417,25 @@ export const buildTable = ({
|
||||
foreignColumns: [adapter.tables[formattedRelationTo].id],
|
||||
}).onDelete('cascade')
|
||||
|
||||
const indexName = [colName]
|
||||
const indexColumns = [colName]
|
||||
|
||||
const unique = !disableUnique && uniqueRelationships.has(relationTo)
|
||||
|
||||
if (unique) {
|
||||
indexName.push('path')
|
||||
indexColumns.push('path')
|
||||
}
|
||||
if (hasLocalizedRelationshipField) {
|
||||
indexName.push('locale')
|
||||
indexColumns.push('locale')
|
||||
}
|
||||
|
||||
relationExtraConfig[`${relationTo}IdIdx`] = createIndex({
|
||||
name: indexName,
|
||||
columnName: `${formattedRelationTo}_id`,
|
||||
tableName: relationshipsTableName,
|
||||
const indexName = buildIndexName({
|
||||
name: `${relationshipsTableName}_${formattedRelationTo}_id`,
|
||||
adapter: adapter as unknown as DrizzleAdapter,
|
||||
})
|
||||
|
||||
relationExtraConfig[indexName] = createIndex({
|
||||
name: indexColumns,
|
||||
indexName,
|
||||
unique,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,13 +3,12 @@ import type { AnySQLiteColumn } from 'drizzle-orm/sqlite-core'
|
||||
import { index, uniqueIndex } from 'drizzle-orm/sqlite-core'
|
||||
|
||||
type CreateIndexArgs = {
|
||||
columnName: string
|
||||
indexName: string
|
||||
name: string | string[]
|
||||
tableName: string
|
||||
unique?: boolean
|
||||
}
|
||||
|
||||
export const createIndex = ({ name, columnName, tableName, unique }: CreateIndexArgs) => {
|
||||
export const createIndex = ({ name, indexName, unique }: CreateIndexArgs) => {
|
||||
return (table: { [x: string]: AnySQLiteColumn }) => {
|
||||
let columns
|
||||
if (Array.isArray(name)) {
|
||||
@@ -21,8 +20,8 @@ export const createIndex = ({ name, columnName, tableName, unique }: CreateIndex
|
||||
columns = [table[name]]
|
||||
}
|
||||
if (unique) {
|
||||
return uniqueIndex(`${tableName}_${columnName}_idx`).on(columns[0], ...columns.slice(1))
|
||||
return uniqueIndex(indexName).on(columns[0], ...columns.slice(1))
|
||||
}
|
||||
return index(`${tableName}_${columnName}_idx`).on(columns[0], ...columns.slice(1))
|
||||
return index(indexName).on(columns[0], ...columns.slice(1))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import type { DrizzleAdapter } from '@payloadcms/drizzle/types'
|
||||
import type { Relation } from 'drizzle-orm'
|
||||
import type { IndexBuilder, SQLiteColumnBuilder } from 'drizzle-orm/sqlite-core'
|
||||
import type { Field, SanitizedJoins, TabAsField } from 'payload'
|
||||
|
||||
import {
|
||||
buildIndexName,
|
||||
createTableName,
|
||||
hasLocalesTable,
|
||||
validateExistingBlockIsIdentical,
|
||||
@@ -164,10 +166,15 @@ export const traverseFields = ({
|
||||
}
|
||||
adapter.fieldConstraints[rootTableName][`${columnName}_idx`] = constraintValue
|
||||
}
|
||||
targetIndexes[`${newTableName}_${field.name}Idx`] = createIndex({
|
||||
|
||||
const indexName = buildIndexName({
|
||||
name: `${newTableName}_${columnName}`,
|
||||
adapter: adapter as unknown as DrizzleAdapter,
|
||||
})
|
||||
|
||||
targetIndexes[indexName] = createIndex({
|
||||
name: field.localized ? [fieldName, '_locale'] : fieldName,
|
||||
columnName,
|
||||
tableName: newTableName,
|
||||
indexName,
|
||||
unique,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -167,7 +167,9 @@ declare module 'payload' {
|
||||
extends Omit<Args, 'idType' | 'logger' | 'migrationDir' | 'pool'>,
|
||||
DrizzleAdapter {
|
||||
beginTransaction: (options?: SQLiteTransactionConfig) => Promise<null | number | string>
|
||||
drizzle: LibSQLDatabase
|
||||
drizzle: { $client: Client } & LibSQLDatabase<
|
||||
Record<string, GenericRelation | GenericTable> & Record<string, unknown>
|
||||
>
|
||||
/**
|
||||
* An object keyed on each table, with a key value pair where the constraint name is the key, followed by the dot-notation field name
|
||||
* Used for returning properly formed errors from unique fields
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-vercel-postgres",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"description": "Vercel Postgres adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -50,7 +50,7 @@
|
||||
"@payloadcms/drizzle": "workspace:*",
|
||||
"@vercel/postgres": "^0.9.0",
|
||||
"console-table-printer": "2.11.2",
|
||||
"drizzle-kit": "0.26.1",
|
||||
"drizzle-kit": "0.26.2",
|
||||
"drizzle-orm": "0.35.1",
|
||||
"pg": "8.11.3",
|
||||
"prompts": "2.4.2",
|
||||
|
||||
@@ -90,6 +90,7 @@ export function vercelPostgresAdapter(args: Args = {}): DatabaseAdapterObj<Verce
|
||||
fieldConstraints: {},
|
||||
getMigrationTemplate,
|
||||
idType: postgresIDType,
|
||||
indexes: new Set<string>(),
|
||||
initializing,
|
||||
localesSuffix: args.localesSuffix || '_locales',
|
||||
logger: args.logger,
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
PostgresSchemaHook,
|
||||
} from '@payloadcms/drizzle/postgres'
|
||||
import type { DrizzleAdapter } from '@payloadcms/drizzle/types'
|
||||
import type { VercelPool, VercelPostgresPoolConfig } from '@vercel/postgres'
|
||||
import type { VercelClient, VercelPool, VercelPostgresPoolConfig } from '@vercel/postgres'
|
||||
import type { DrizzleConfig } from 'drizzle-orm'
|
||||
import type { PgSchema, PgTableFn, PgTransactionConfig } from 'drizzle-orm/pg-core'
|
||||
|
||||
@@ -56,6 +56,9 @@ export type Args = {
|
||||
}
|
||||
|
||||
export type VercelPostgresAdapter = {
|
||||
drizzle: {
|
||||
$client: VercelClient | VercelPool
|
||||
} & PostgresDB
|
||||
pool?: VercelPool
|
||||
poolOptions?: Args['pool']
|
||||
} & BasePostgresAdapter
|
||||
@@ -67,7 +70,9 @@ declare module 'payload' {
|
||||
afterSchemaInit: PostgresSchemaHook[]
|
||||
beforeSchemaInit: PostgresSchemaHook[]
|
||||
beginTransaction: (options?: PgTransactionConfig) => Promise<null | number | string>
|
||||
drizzle: PostgresDB
|
||||
drizzle: {
|
||||
$client: VercelClient | VercelPool
|
||||
} & PostgresDB
|
||||
enums: Record<string, GenericEnum>
|
||||
/**
|
||||
* An object keyed on each table, with a key value pair where the constraint name is the key, followed by the dot-notation field name
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/drizzle",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"description": "A library of shared functions used by different payload database adapters",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -46,7 +46,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"console-table-printer": "2.11.2",
|
||||
"drizzle-orm": "0.34.1-1f15bfd",
|
||||
"drizzle-orm": "0.35.1",
|
||||
"prompts": "2.4.2",
|
||||
"to-snake-case": "1.0.0",
|
||||
"uuid": "9.0.0"
|
||||
|
||||
@@ -32,6 +32,7 @@ export { updateGlobal } from './updateGlobal.js'
|
||||
export { updateGlobalVersion } from './updateGlobalVersion.js'
|
||||
export { updateVersion } from './updateVersion.js'
|
||||
export { upsertRow } from './upsertRow/index.js'
|
||||
export { buildIndexName } from './utilities/buildIndexName.js'
|
||||
export { executeSchemaHooks } from './utilities/executeSchemaHooks.js'
|
||||
export { extendDrizzleTable } from './utilities/extendDrizzleTable.js'
|
||||
export { hasLocalesTable } from './utilities/hasLocalesTable.js'
|
||||
|
||||
@@ -54,7 +54,7 @@ export const createDatabase = async function (this: BasePostgresAdapter, args: A
|
||||
}
|
||||
|
||||
// import pg only when createDatabase is used
|
||||
const pg = await import('pg')
|
||||
const pg = await import('pg').then((mod) => mod.default)
|
||||
|
||||
const managementClient = new pg.Client(managementClientConfig)
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import type {
|
||||
} from '../types.js'
|
||||
|
||||
import { createTableName } from '../../createTableName.js'
|
||||
import { buildIndexName } from '../../utilities/buildIndexName.js'
|
||||
import { createIndex } from './createIndex.js'
|
||||
import { parentIDColumnMap } from './parentIDColumnMap.js'
|
||||
import { setColumnID } from './setColumnID.js'
|
||||
@@ -389,21 +390,25 @@ export const buildTable = ({
|
||||
foreignColumns: [adapter.tables[formattedRelationTo].id],
|
||||
}).onDelete('cascade')
|
||||
|
||||
const indexName = [colName]
|
||||
const indexColumns = [colName]
|
||||
|
||||
const unique = !disableUnique && uniqueRelationships.has(relationTo)
|
||||
|
||||
if (unique) {
|
||||
indexName.push('path')
|
||||
indexColumns.push('path')
|
||||
}
|
||||
if (hasLocalizedRelationshipField) {
|
||||
indexName.push('locale')
|
||||
indexColumns.push('locale')
|
||||
}
|
||||
|
||||
relationExtraConfig[`${relationTo}IdIdx`] = createIndex({
|
||||
name: indexName,
|
||||
columnName: `${formattedRelationTo}_id`,
|
||||
tableName: relationshipsTableName,
|
||||
const indexName = buildIndexName({
|
||||
name: `${relationshipsTableName}_${formattedRelationTo}_id`,
|
||||
adapter,
|
||||
})
|
||||
|
||||
relationExtraConfig[indexName] = createIndex({
|
||||
name: indexColumns,
|
||||
indexName,
|
||||
unique,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,13 +3,12 @@ import { index, uniqueIndex } from 'drizzle-orm/pg-core'
|
||||
import type { GenericColumn } from '../types.js'
|
||||
|
||||
type CreateIndexArgs = {
|
||||
columnName: string
|
||||
indexName: string
|
||||
name: string | string[]
|
||||
tableName: string
|
||||
unique?: boolean
|
||||
}
|
||||
|
||||
export const createIndex = ({ name, columnName, tableName, unique }: CreateIndexArgs) => {
|
||||
export const createIndex = ({ name, indexName, unique }: CreateIndexArgs) => {
|
||||
return (table: { [x: string]: GenericColumn }) => {
|
||||
let columns
|
||||
if (Array.isArray(name)) {
|
||||
@@ -21,8 +20,8 @@ export const createIndex = ({ name, columnName, tableName, unique }: CreateIndex
|
||||
columns = [table[name]]
|
||||
}
|
||||
if (unique) {
|
||||
return uniqueIndex(`${tableName}_${columnName}_idx`).on(columns[0], ...columns.slice(1))
|
||||
return uniqueIndex(indexName).on(columns[0], ...columns.slice(1))
|
||||
}
|
||||
return index(`${tableName}_${columnName}_idx`).on(columns[0], ...columns.slice(1))
|
||||
return index(indexName).on(columns[0], ...columns.slice(1))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import type {
|
||||
} from '../types.js'
|
||||
|
||||
import { createTableName } from '../../createTableName.js'
|
||||
import { buildIndexName } from '../../utilities/buildIndexName.js'
|
||||
import { hasLocalesTable } from '../../utilities/hasLocalesTable.js'
|
||||
import { validateExistingBlockIsIdentical } from '../../utilities/validateExistingBlockIsIdentical.js'
|
||||
import { buildTable } from './build.js'
|
||||
@@ -169,10 +170,12 @@ export const traverseFields = ({
|
||||
}
|
||||
adapter.fieldConstraints[rootTableName][`${columnName}_idx`] = constraintValue
|
||||
}
|
||||
targetIndexes[`${newTableName}_${field.name}Idx`] = createIndex({
|
||||
|
||||
const indexName = buildIndexName({ name: `${newTableName}_${columnName}`, adapter })
|
||||
|
||||
targetIndexes[indexName] = createIndex({
|
||||
name: field.localized ? [fieldName, '_locale'] : fieldName,
|
||||
columnName,
|
||||
tableName: newTableName,
|
||||
indexName,
|
||||
unique,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import type {
|
||||
} from 'drizzle-orm/pg-core'
|
||||
import type { PgTableFn } from 'drizzle-orm/pg-core/table'
|
||||
import type { Payload, PayloadRequest } from 'payload'
|
||||
import type { ClientConfig, QueryResult } from 'pg'
|
||||
import type { Client, ClientConfig, Pool, QueryResult } from 'pg'
|
||||
|
||||
import type { extendDrizzleTable, Operators } from '../index.js'
|
||||
import type { BuildQueryJoinAliases, DrizzleAdapter, TransactionPg } from '../types.js'
|
||||
@@ -134,7 +134,9 @@ export type BasePostgresAdapter = {
|
||||
defaultDrizzleSnapshot: DrizzleSnapshotJSON
|
||||
deleteWhere: DeleteWhere
|
||||
disableCreateDatabase: boolean
|
||||
drizzle: PostgresDB
|
||||
drizzle: {
|
||||
$client: Client | Pool
|
||||
} & PostgresDB
|
||||
dropDatabase: DropDatabase
|
||||
enums: Record<string, GenericEnum>
|
||||
execute: Execute<unknown>
|
||||
|
||||
@@ -212,6 +212,9 @@ export const traverseFields = ({
|
||||
if (typeof data[field.name] === 'object' && data[field.name] !== null) {
|
||||
if (field.localized) {
|
||||
Object.entries(data[field.name]).forEach(([localeKey, localeData]) => {
|
||||
// preserve array ID if there is
|
||||
localeData._uuid = data.id || data._uuid
|
||||
|
||||
traverseFields({
|
||||
adapter,
|
||||
arrays,
|
||||
@@ -237,6 +240,10 @@ export const traverseFields = ({
|
||||
})
|
||||
})
|
||||
} else {
|
||||
// preserve array ID if there is
|
||||
const groupData = data[field.name] as Record<string, unknown>
|
||||
groupData._uuid = data.id || data._uuid
|
||||
|
||||
traverseFields({
|
||||
adapter,
|
||||
arrays,
|
||||
@@ -244,7 +251,7 @@ export const traverseFields = ({
|
||||
blocks,
|
||||
blocksToDelete,
|
||||
columnPrefix: `${columnName}_`,
|
||||
data: data[field.name] as Record<string, unknown>,
|
||||
data: groupData,
|
||||
existingLocales,
|
||||
fieldPrefix: `${fieldName}_`,
|
||||
fields: field.fields,
|
||||
@@ -275,6 +282,9 @@ export const traverseFields = ({
|
||||
if (typeof data[tab.name] === 'object' && data[tab.name] !== null) {
|
||||
if (tab.localized) {
|
||||
Object.entries(data[tab.name]).forEach(([localeKey, localeData]) => {
|
||||
// preserve array ID if there is
|
||||
localeData._uuid = data.id || data._uuid
|
||||
|
||||
traverseFields({
|
||||
adapter,
|
||||
arrays,
|
||||
@@ -300,6 +310,10 @@ export const traverseFields = ({
|
||||
})
|
||||
})
|
||||
} else {
|
||||
const tabData = data[tab.name] as Record<string, unknown>
|
||||
// preserve array ID if there is
|
||||
tabData._uuid = data.id || data._uuid
|
||||
|
||||
traverseFields({
|
||||
adapter,
|
||||
arrays,
|
||||
@@ -307,7 +321,7 @@ export const traverseFields = ({
|
||||
blocks,
|
||||
blocksToDelete,
|
||||
columnPrefix: `${columnPrefix || ''}${toSnakeCase(tab.name)}_`,
|
||||
data: data[tab.name] as Record<string, unknown>,
|
||||
data: tabData,
|
||||
existingLocales,
|
||||
fieldPrefix: `${fieldPrefix || ''}${tab.name}_`,
|
||||
fields: tab.fields,
|
||||
|
||||
@@ -174,6 +174,7 @@ export interface DrizzleAdapter extends BaseDatabaseAdapter {
|
||||
fieldConstraints: Record<string, Record<string, string>>
|
||||
getMigrationTemplate: (args: MigrationTemplateArgs) => string
|
||||
idType: 'serial' | 'uuid'
|
||||
indexes: Set<string>
|
||||
initializing: Promise<void>
|
||||
insert: Insert
|
||||
localesSuffix?: string
|
||||
|
||||
24
packages/drizzle/src/utilities/buildIndexName.ts
Normal file
24
packages/drizzle/src/utilities/buildIndexName.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { DrizzleAdapter } from '../types.js'
|
||||
|
||||
export const buildIndexName = ({
|
||||
name,
|
||||
adapter,
|
||||
number = 0,
|
||||
}: {
|
||||
adapter: DrizzleAdapter
|
||||
name: string
|
||||
number?: number
|
||||
}): string => {
|
||||
const indexName = `${name}${number ? `_${number}` : ''}_idx`
|
||||
|
||||
if (!adapter.indexes.has(indexName)) {
|
||||
adapter.indexes.add(indexName)
|
||||
return indexName
|
||||
}
|
||||
|
||||
return buildIndexName({
|
||||
name,
|
||||
adapter,
|
||||
number: number + 1,
|
||||
})
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-nodemailer",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"description": "Payload Nodemailer Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-resend",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"description": "Payload Resend Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/graphql",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-react",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"description": "The official React SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-vue",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"description": "The official Vue SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"description": "The official live preview JavaScript SDK for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/next",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"description": "Node, React, Headless CMS and Application Framework built on Next.js",
|
||||
"keywords": [
|
||||
"admin panel",
|
||||
|
||||
@@ -18,4 +18,5 @@ export const migrationsCollection: CollectionConfig = {
|
||||
},
|
||||
],
|
||||
graphQL: false,
|
||||
lockDocuments: false,
|
||||
}
|
||||
|
||||
@@ -29,4 +29,5 @@ export const getLockedDocumentsCollection = (config: Config): CollectionConfig =
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
lockDocuments: false,
|
||||
})
|
||||
|
||||
@@ -70,6 +70,7 @@ const getPreferencesCollection = (config: Config): CollectionConfig => ({
|
||||
type: 'json',
|
||||
},
|
||||
],
|
||||
lockDocuments: false,
|
||||
})
|
||||
|
||||
export default getPreferencesCollection
|
||||
|
||||
@@ -47,8 +47,12 @@ export const checkDocumentLockStatus = async ({
|
||||
throw new Error('Either collectionSlug or globalSlug must be provided.')
|
||||
}
|
||||
|
||||
if (!isLockingEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
// Only perform lock checks if overrideLock is false and locking is enabled
|
||||
if (!overrideLock && isLockingEnabled !== false) {
|
||||
if (!overrideLock) {
|
||||
const defaultLockErrorMessage = collectionSlug
|
||||
? `Document with ID ${id} is currently locked by another user and cannot be modified.`
|
||||
: `Global document with slug "${globalSlug}" is currently locked by another user and cannot be modified.`
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { Field, FieldAccess } from '../fields/config/types.js'
|
||||
import type { SanitizedGlobalConfig } from '../globals/config/types.js'
|
||||
import type { AllOperations, Document, PayloadRequest, Where } from '../types/index.js'
|
||||
|
||||
import { combineQueries } from '../database/combineQueries.js'
|
||||
import { tabHasName } from '../fields/config/types.js'
|
||||
|
||||
type Args = {
|
||||
@@ -63,17 +64,7 @@ export async function getEntityPolicies<T extends Args>(args: T): Promise<Return
|
||||
overrideAccess: true,
|
||||
pagination: false,
|
||||
req,
|
||||
where: {
|
||||
...where,
|
||||
and: [
|
||||
...(where.and || []),
|
||||
{
|
||||
id: {
|
||||
equals: id,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
where: combineQueries(where, { id: { equals: id } }),
|
||||
})
|
||||
|
||||
return paginatedRes?.docs?.[0] || undefined
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Where } from '../../types/index.js'
|
||||
|
||||
export const appendVersionToQueryKey = (query: Where): Where => {
|
||||
export const appendVersionToQueryKey = (query: Where = {}): Where => {
|
||||
return Object.entries(query).reduce((res, [key, val]) => {
|
||||
if (['AND', 'and', 'OR', 'or'].includes(key) && Array.isArray(val)) {
|
||||
return {
|
||||
|
||||
@@ -3,6 +3,9 @@ import type { FindOneArgs } from '../database/types.js'
|
||||
import type { Payload, PayloadRequest } from '../types/index.js'
|
||||
import type { TypeWithVersion } from './types.js'
|
||||
|
||||
import { combineQueries } from '../database/combineQueries.js'
|
||||
import { appendVersionToQueryKey } from './drafts/appendVersionToQueryKey.js'
|
||||
|
||||
type Args = {
|
||||
config: SanitizedCollectionConfig
|
||||
id: number | string
|
||||
@@ -33,7 +36,7 @@ export const getLatestCollectionVersion = async <T extends TypeWithID = any>({
|
||||
pagination: false,
|
||||
req,
|
||||
sort: '-updatedAt',
|
||||
where: whereQuery,
|
||||
where: combineQueries(appendVersionToQueryKey(query.where), whereQuery),
|
||||
})
|
||||
;[latestVersion] = docs
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-cloud-storage",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"description": "The official cloud storage plugin for Payload CMS",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-cloud",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"description": "The official Payload Cloud plugin",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-form-builder",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"description": "Form builder plugin for Payload CMS",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-nested-docs",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"description": "The official Nested Docs plugin for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-redirects",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"description": "Redirects plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-search",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"description": "Search plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-sentry",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"description": "Sentry plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-seo",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"description": "SEO plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -22,7 +22,7 @@ import type { GenerateDescription } from '../../types.js'
|
||||
import { defaults } from '../../defaults.js'
|
||||
import { LengthIndicator } from '../../ui/LengthIndicator.js'
|
||||
|
||||
const { maxLength, minLength } = defaults.description
|
||||
const { maxLength: maxLengthDefault, minLength: minLengthDefault } = defaults.description
|
||||
|
||||
type MetaDescriptionProps = {
|
||||
readonly hasGenerateDescriptionFn: boolean
|
||||
@@ -35,6 +35,8 @@ export const MetaDescriptionComponent: React.FC<MetaDescriptionProps> = (props)
|
||||
components: { Label },
|
||||
},
|
||||
label,
|
||||
maxLength: maxLengthFromProps,
|
||||
minLength: minLengthFromProps,
|
||||
required,
|
||||
},
|
||||
hasGenerateDescriptionFn,
|
||||
@@ -55,6 +57,9 @@ export const MetaDescriptionComponent: React.FC<MetaDescriptionProps> = (props)
|
||||
const { getData } = useForm()
|
||||
const docInfo = useDocumentInfo()
|
||||
|
||||
const maxLength = maxLengthFromProps || maxLengthDefault
|
||||
const minLength = minLengthFromProps || minLengthDefault
|
||||
|
||||
const field: FieldType<string> = useField({
|
||||
path: pathFromContext,
|
||||
} as Options)
|
||||
|
||||
@@ -23,7 +23,7 @@ import { defaults } from '../../defaults.js'
|
||||
import { LengthIndicator } from '../../ui/LengthIndicator.js'
|
||||
import '../index.scss'
|
||||
|
||||
const { maxLength, minLength } = defaults.title
|
||||
const { maxLength: maxLengthDefault, minLength: minLengthDefault } = defaults.title
|
||||
|
||||
type MetaTitleProps = {
|
||||
readonly hasGenerateTitleFn: boolean
|
||||
@@ -36,6 +36,8 @@ export const MetaTitleComponent: React.FC<MetaTitleProps> = (props) => {
|
||||
components: { Label },
|
||||
},
|
||||
label,
|
||||
maxLength: maxLengthFromProps,
|
||||
minLength: minLengthFromProps,
|
||||
required,
|
||||
},
|
||||
field: fieldFromProps,
|
||||
@@ -60,6 +62,9 @@ export const MetaTitleComponent: React.FC<MetaTitleProps> = (props) => {
|
||||
const { getData } = useForm()
|
||||
const docInfo = useDocumentInfo()
|
||||
|
||||
const minLength = minLengthFromProps || minLengthDefault
|
||||
const maxLength = maxLengthFromProps || maxLengthDefault
|
||||
|
||||
const { errorMessage, setValue, showError, value } = field
|
||||
|
||||
const regenerateTitle = useCallback(async () => {
|
||||
|
||||
@@ -10,25 +10,32 @@ import type { PluginSEOTranslationKeys, PluginSEOTranslations } from '../../tran
|
||||
import { defaults } from '../../defaults.js'
|
||||
|
||||
const {
|
||||
description: { maxLength: maxDesc, minLength: minDesc },
|
||||
title: { maxLength: maxTitle, minLength: minTitle },
|
||||
description: { maxLength: maxDescDefault, minLength: minDescDefault },
|
||||
title: { maxLength: maxTitleDefault, minLength: minTitleDefault },
|
||||
} = defaults
|
||||
|
||||
type OverviewProps = {
|
||||
descriptionOverrides?: {
|
||||
maxLength?: number
|
||||
minLength?: number
|
||||
}
|
||||
descriptionPath?: string
|
||||
imagePath?: string
|
||||
titleOverrides?: {
|
||||
maxLength?: number
|
||||
minLength?: number
|
||||
}
|
||||
titlePath?: string
|
||||
} & UIField
|
||||
|
||||
export const OverviewComponent: React.FC<OverviewProps> = ({
|
||||
descriptionOverrides,
|
||||
descriptionPath: descriptionPathFromContext,
|
||||
imagePath: imagePathFromContext,
|
||||
titleOverrides,
|
||||
titlePath: titlePathFromContext,
|
||||
}) => {
|
||||
const {
|
||||
// dispatchFields,
|
||||
getFields,
|
||||
} = useForm()
|
||||
const { getFields } = useForm()
|
||||
|
||||
const descriptionPath = descriptionPathFromContext || 'meta.description'
|
||||
const titlePath = titlePathFromContext || 'meta.title'
|
||||
@@ -47,6 +54,11 @@ export const OverviewComponent: React.FC<OverviewProps> = ({
|
||||
const [descIsValid, setDescIsValid] = useState<boolean | undefined>()
|
||||
const [imageIsValid, setImageIsValid] = useState<boolean | undefined>()
|
||||
|
||||
const minDesc = descriptionOverrides?.minLength || minDescDefault
|
||||
const maxDesc = descriptionOverrides?.maxLength || maxDescDefault
|
||||
const minTitle = titleOverrides?.minLength || minTitleDefault
|
||||
const maxTitle = titleOverrides?.maxLength || maxTitleDefault
|
||||
|
||||
const resetAll = useCallback(() => {
|
||||
const fields = getFields()
|
||||
const fieldsWithoutMeta = fields
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import type { UIField } from 'payload'
|
||||
|
||||
interface FieldFunctionProps {
|
||||
descriptionOverrides?: {
|
||||
maxLength?: number
|
||||
minLength?: number
|
||||
}
|
||||
/**
|
||||
* Path to the description field to use for the preview
|
||||
*
|
||||
@@ -14,6 +18,10 @@ interface FieldFunctionProps {
|
||||
*/
|
||||
imagePath?: string
|
||||
overrides?: Partial<UIField>
|
||||
titleOverrides?: {
|
||||
maxLength?: number
|
||||
minLength?: number
|
||||
}
|
||||
/**
|
||||
* Path to the title field to use for the preview
|
||||
*
|
||||
@@ -25,9 +33,11 @@ interface FieldFunctionProps {
|
||||
type FieldFunction = ({ overrides }: FieldFunctionProps) => UIField
|
||||
|
||||
export const OverviewField: FieldFunction = ({
|
||||
descriptionOverrides,
|
||||
descriptionPath,
|
||||
imagePath,
|
||||
overrides,
|
||||
titleOverrides,
|
||||
titlePath,
|
||||
}) => {
|
||||
return {
|
||||
@@ -37,8 +47,10 @@ export const OverviewField: FieldFunction = ({
|
||||
components: {
|
||||
Field: {
|
||||
clientProps: {
|
||||
descriptionOverrides,
|
||||
descriptionPath,
|
||||
imagePath,
|
||||
titleOverrides,
|
||||
titlePath,
|
||||
},
|
||||
path: '@payloadcms/plugin-seo/client#OverviewComponent',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-stripe",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"description": "Stripe plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/richtext-lexical",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"description": "The officially supported Lexical richtext adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from '@payloadcms/ui'
|
||||
import { dequal } from 'dequal/lite'
|
||||
import { $getNodeByKey } from 'lexical'
|
||||
import React, { useCallback } from 'react'
|
||||
import React, { useCallback, useEffect } from 'react'
|
||||
|
||||
import type { LexicalRichTextFieldProps } from '../../../../types.js'
|
||||
import type { BlockFields } from '../../server/nodes/BlocksNode.js'
|
||||
@@ -70,21 +70,15 @@ export const BlockContent: React.FC<Props> = (props) => {
|
||||
// is important to consider for the data path used in setDocFieldPreferences
|
||||
const { getDocPreferences, setDocFieldPreferences } = useDocumentInfo()
|
||||
|
||||
const [isCollapsed, setIsCollapsed] = React.useState<boolean>(() => {
|
||||
let initialState = false
|
||||
const [isCollapsed, setIsCollapsed] = React.useState<boolean>()
|
||||
|
||||
useEffect(() => {
|
||||
void getDocPreferences().then((currentDocPreferences) => {
|
||||
const currentFieldPreferences = currentDocPreferences?.fields[field.name]
|
||||
|
||||
const collapsedArray = currentFieldPreferences?.collapsed
|
||||
|
||||
if (collapsedArray && collapsedArray.includes(formData.id)) {
|
||||
initialState = true
|
||||
setIsCollapsed(true)
|
||||
}
|
||||
setIsCollapsed(collapsedArray ? collapsedArray.includes(formData.id) : false)
|
||||
})
|
||||
return initialState
|
||||
})
|
||||
}, [field.name, formData.id, getDocPreferences])
|
||||
|
||||
const hasSubmitted = useFormSubmitted()
|
||||
|
||||
@@ -180,6 +174,10 @@ export const BlockContent: React.FC<Props> = (props) => {
|
||||
})
|
||||
}, [editor, nodeKey])
|
||||
|
||||
if (typeof isCollapsed !== 'boolean') {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Collapsible
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/richtext-slate",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"description": "The officially supported Slate richtext adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-azure",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"description": "Payload storage adapter for Azure Blob Storage",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-gcs",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"description": "Payload storage adapter for Google Cloud Storage",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-s3",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"description": "Payload storage adapter for Amazon S3",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-uploadthing",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"description": "Payload storage adapter for uploadthing",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-vercel-blob",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"description": "Payload storage adapter for Vercel Blob Storage",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/translations",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/ui",
|
||||
"version": "3.0.0-beta.115",
|
||||
"version": "3.0.0-beta.116",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.rs__menu-notice {
|
||||
padding: base(0.5) base(0.6);
|
||||
}
|
||||
|
||||
.rs__indicators {
|
||||
gap: calc(var(--base) / 4);
|
||||
}
|
||||
|
||||
26
pnpm-lock.yaml
generated
26
pnpm-lock.yaml
generated
@@ -109,8 +109,8 @@ importers:
|
||||
specifier: 16.4.5
|
||||
version: 16.4.5
|
||||
drizzle-kit:
|
||||
specifier: 0.26.1
|
||||
version: 0.26.1
|
||||
specifier: 0.26.2
|
||||
version: 0.26.2
|
||||
drizzle-orm:
|
||||
specifier: 0.35.1
|
||||
version: 0.35.1(@libsql/client@0.14.0(bufferutil@4.0.8))(@neondatabase/serverless@0.9.4)(@opentelemetry/api@1.9.0)(@types/pg@8.11.6)(@vercel/postgres@0.9.0)(pg@8.11.3)(react@19.0.0-rc-3edc000d-20240926)(types-react@19.0.0-rc.1)
|
||||
@@ -315,8 +315,8 @@ importers:
|
||||
specifier: 2.11.2
|
||||
version: 2.11.2
|
||||
drizzle-kit:
|
||||
specifier: 0.26.1
|
||||
version: 0.26.1
|
||||
specifier: 0.26.2
|
||||
version: 0.26.2
|
||||
drizzle-orm:
|
||||
specifier: 0.35.1
|
||||
version: 0.35.1(@libsql/client@0.14.0(bufferutil@4.0.8)(utf-8-validate@6.0.4))(@neondatabase/serverless@0.9.4)(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(@vercel/postgres@0.9.0)(pg@8.11.3)(react@19.0.0-rc-3edc000d-20240926)(types-react@19.0.0-rc.1)
|
||||
@@ -361,8 +361,8 @@ importers:
|
||||
specifier: 2.11.2
|
||||
version: 2.11.2
|
||||
drizzle-kit:
|
||||
specifier: 0.26.1
|
||||
version: 0.26.1
|
||||
specifier: 0.26.2
|
||||
version: 0.26.2
|
||||
drizzle-orm:
|
||||
specifier: 0.35.1
|
||||
version: 0.35.1(@libsql/client@0.14.0(bufferutil@4.0.8)(utf-8-validate@6.0.4))(@neondatabase/serverless@0.9.4)(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(@vercel/postgres@0.9.0)(pg@8.11.3)(react@19.0.0-rc-3edc000d-20240926)(types-react@19.0.0-rc.1)
|
||||
@@ -401,8 +401,8 @@ importers:
|
||||
specifier: 2.11.2
|
||||
version: 2.11.2
|
||||
drizzle-kit:
|
||||
specifier: 0.26.1
|
||||
version: 0.26.1
|
||||
specifier: 0.26.2
|
||||
version: 0.26.2
|
||||
drizzle-orm:
|
||||
specifier: 0.35.1
|
||||
version: 0.35.1(@libsql/client@0.14.0(bufferutil@4.0.8)(utf-8-validate@6.0.4))(@neondatabase/serverless@0.9.4)(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(@vercel/postgres@0.9.0)(pg@8.11.3)(react@19.0.0-rc-3edc000d-20240926)(types-react@19.0.0-rc.1)
|
||||
@@ -1727,8 +1727,8 @@ importers:
|
||||
specifier: 16.4.5
|
||||
version: 16.4.5
|
||||
drizzle-kit:
|
||||
specifier: 0.26.1
|
||||
version: 0.26.1
|
||||
specifier: 0.26.2
|
||||
version: 0.26.2
|
||||
eslint-plugin-playwright:
|
||||
specifier: 1.6.2
|
||||
version: 1.6.2(eslint-plugin-jest@28.8.1(@typescript-eslint/eslint-plugin@8.3.0(@typescript-eslint/parser@8.3.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.6.2))(eslint@9.9.1(jiti@1.21.6))(typescript@5.6.2))(eslint@9.9.1(jiti@1.21.6))(jest@29.7.0(@types/node@22.5.4)(babel-plugin-macros@3.1.0))(typescript@5.6.2))(eslint@9.9.1(jiti@1.21.6))
|
||||
@@ -5869,8 +5869,8 @@ packages:
|
||||
resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
drizzle-kit@0.26.1:
|
||||
resolution: {integrity: sha512-5/e1tzOPicPDooCm/uJIU9mWK3eD5dhW5EY61TQyVVo29xYxFLmZpXlBdOYlbfDHBsNhVzhb0XjWFmAAj7d7WA==}
|
||||
drizzle-kit@0.26.2:
|
||||
resolution: {integrity: sha512-cMq8omEKywjIy5KcqUo6LvEFxkl8/zYHsgYjFVXjmPWWtuW4blcz+YW9+oIhoaALgs2ebRjzXwsJgN9i6P49Dw==}
|
||||
hasBin: true
|
||||
|
||||
drizzle-orm@0.35.1:
|
||||
@@ -15436,7 +15436,7 @@ snapshots:
|
||||
|
||||
dotenv@16.4.5: {}
|
||||
|
||||
drizzle-kit@0.26.1:
|
||||
drizzle-kit@0.26.2:
|
||||
dependencies:
|
||||
'@drizzle-team/brocli': 0.10.1
|
||||
'@esbuild-kit/esm-loader': 2.6.5
|
||||
|
||||
@@ -318,6 +318,42 @@ export default buildConfigWithDefaults({
|
||||
virtual: true,
|
||||
fields: [],
|
||||
},
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'textWithinRow',
|
||||
virtual: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'collapsible',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'textWithinCollapsible',
|
||||
virtual: true,
|
||||
},
|
||||
],
|
||||
label: 'Colllapsible',
|
||||
},
|
||||
{
|
||||
type: 'tabs',
|
||||
tabs: [
|
||||
{
|
||||
label: 'tab',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'textWithinTabs',
|
||||
virtual: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -810,6 +810,21 @@ describe('database', () => {
|
||||
|
||||
expect(resLocal.textHooked).toBe('hooked')
|
||||
})
|
||||
|
||||
it('should not save a nested field to tabs/row/collapsible with virtual: true to the db', async () => {
|
||||
const res = await payload.create({
|
||||
data: {
|
||||
textWithinCollapsible: '1',
|
||||
textWithinRow: '2',
|
||||
textWithinTabs: '3',
|
||||
},
|
||||
collection: 'fields-persistance',
|
||||
})
|
||||
|
||||
expect(res.textWithinCollapsible).toBeUndefined()
|
||||
expect(res.textWithinRow).toBeUndefined()
|
||||
expect(res.textWithinTabs).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not allow to query by a field with `virtual: true`', async () => {
|
||||
|
||||
@@ -60,6 +60,7 @@ export interface UserAuthOperations {
|
||||
export interface Post {
|
||||
id: string;
|
||||
title: string;
|
||||
hasTransaction?: boolean | null;
|
||||
throwAfterChange?: boolean | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
@@ -225,6 +226,9 @@ export interface FieldsPersistance {
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
textWithinRow?: string | null;
|
||||
textWithinCollapsible?: string | null;
|
||||
textWithinTabs?: string | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
@@ -289,14 +293,10 @@ export interface PayloadLockedDocument {
|
||||
value: string | User;
|
||||
} | null);
|
||||
globalSlug?: string | null;
|
||||
_lastEdited: {
|
||||
user: {
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
};
|
||||
editedAt?: string | null;
|
||||
user: {
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
};
|
||||
isLocked?: boolean | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
@@ -120,6 +120,23 @@ const IndexedFields: CollectionConfig = {
|
||||
],
|
||||
label: 'Collapsible',
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'someText',
|
||||
index: true,
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
name: 'some',
|
||||
index: true,
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'text',
|
||||
index: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
versions: true,
|
||||
}
|
||||
|
||||
@@ -82,6 +82,88 @@ const SelectFields: CollectionConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'array',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'selectHasMany',
|
||||
hasMany: true,
|
||||
type: 'select',
|
||||
admin: {
|
||||
isClearable: true,
|
||||
isSortable: true,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
label: 'Value One',
|
||||
value: 'one',
|
||||
},
|
||||
{
|
||||
label: 'Value Two',
|
||||
value: 'two',
|
||||
},
|
||||
{
|
||||
label: 'Value Three',
|
||||
value: 'three',
|
||||
},
|
||||
{
|
||||
label: 'Value Four',
|
||||
value: 'four',
|
||||
},
|
||||
{
|
||||
label: 'Value Five',
|
||||
value: 'five',
|
||||
},
|
||||
{
|
||||
label: 'Value Six',
|
||||
value: 'six',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'group',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
name: 'selectHasMany',
|
||||
hasMany: true,
|
||||
type: 'select',
|
||||
admin: {
|
||||
isClearable: true,
|
||||
isSortable: true,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
label: 'Value One',
|
||||
value: 'one',
|
||||
},
|
||||
{
|
||||
label: 'Value Two',
|
||||
value: 'two',
|
||||
},
|
||||
{
|
||||
label: 'Value Three',
|
||||
value: 'three',
|
||||
},
|
||||
{
|
||||
label: 'Value Four',
|
||||
value: 'four',
|
||||
},
|
||||
{
|
||||
label: 'Value Five',
|
||||
value: 'five',
|
||||
},
|
||||
{
|
||||
label: 'Value Six',
|
||||
value: 'six',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'selectHasManyLocalized',
|
||||
type: 'select',
|
||||
|
||||
@@ -552,6 +552,54 @@ describe('Fields', () => {
|
||||
expect(hitResult).toBeDefined()
|
||||
expect(missResult).toBeFalsy()
|
||||
})
|
||||
|
||||
it('should CRUD within array hasMany', async () => {
|
||||
const doc = await payload.create({
|
||||
collection: 'select-fields',
|
||||
data: { array: [{ selectHasMany: ['one', 'two'] }] },
|
||||
})
|
||||
|
||||
expect(doc.array[0].selectHasMany).toStrictEqual(['one', 'two'])
|
||||
|
||||
const upd = await payload.update({
|
||||
collection: 'select-fields',
|
||||
id: doc.id,
|
||||
data: {
|
||||
array: [
|
||||
{
|
||||
id: doc.array[0].id,
|
||||
selectHasMany: ['six'],
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
expect(upd.array[0].selectHasMany).toStrictEqual(['six'])
|
||||
})
|
||||
|
||||
it('should CRUD within array + group hasMany', async () => {
|
||||
const doc = await payload.create({
|
||||
collection: 'select-fields',
|
||||
data: { array: [{ group: { selectHasMany: ['one', 'two'] } }] },
|
||||
})
|
||||
|
||||
expect(doc.array[0].group.selectHasMany).toStrictEqual(['one', 'two'])
|
||||
|
||||
const upd = await payload.update({
|
||||
collection: 'select-fields',
|
||||
id: doc.id,
|
||||
data: {
|
||||
array: [
|
||||
{
|
||||
id: doc.array[0].id,
|
||||
group: { selectHasMany: ['six'] },
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
expect(upd.array[0].group.selectHasMany).toStrictEqual(['six'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('number', () => {
|
||||
|
||||
@@ -60,6 +60,7 @@ export interface Config {
|
||||
'uploads-multi': UploadsMulti;
|
||||
'uploads-poly': UploadsPoly;
|
||||
'uploads-multi-poly': UploadsMultiPoly;
|
||||
'uploads-restricted': UploadsRestricted;
|
||||
'ui-fields': UiField;
|
||||
'payload-locked-documents': PayloadLockedDocument;
|
||||
'payload-preferences': PayloadPreference;
|
||||
@@ -1061,6 +1062,13 @@ export interface IndexedField {
|
||||
};
|
||||
collapsibleLocalizedUnique?: string | null;
|
||||
collapsibleTextUnique?: string | null;
|
||||
someText?: string | null;
|
||||
some?:
|
||||
| {
|
||||
text?: string | null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
@@ -1328,6 +1336,15 @@ export interface SelectField {
|
||||
select?: ('one' | 'two' | 'three') | null;
|
||||
selectReadOnly?: ('one' | 'two' | 'three') | null;
|
||||
selectHasMany?: ('one' | 'two' | 'three' | 'four' | 'five' | 'six')[] | null;
|
||||
array?:
|
||||
| {
|
||||
selectHasMany?: ('one' | 'two' | 'three' | 'four' | 'five' | 'six')[] | null;
|
||||
group?: {
|
||||
selectHasMany?: ('one' | 'two' | 'three' | 'four' | 'five' | 'six')[] | null;
|
||||
};
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
selectHasManyLocalized?: ('one' | 'two')[] | null;
|
||||
selectI18n?: ('one' | 'two' | 'three') | null;
|
||||
simple?: ('One' | 'Two' | 'Three') | null;
|
||||
@@ -1619,6 +1636,19 @@ export interface UploadsMultiPoly {
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "uploads-restricted".
|
||||
*/
|
||||
export interface UploadsRestricted {
|
||||
id: string;
|
||||
text?: string | null;
|
||||
uploadWithoutRestriction?: (string | null) | Upload;
|
||||
uploadWithAllowCreateFalse?: (string | null) | Upload;
|
||||
uploadMultipleWithAllowCreateFalse?: (string | Upload)[] | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "ui-fields".
|
||||
@@ -1764,6 +1794,10 @@ export interface PayloadLockedDocument {
|
||||
relationTo: 'uploads-multi-poly';
|
||||
value: string | UploadsMultiPoly;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'uploads-restricted';
|
||||
value: string | UploadsRestricted;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'ui-fields';
|
||||
value: string | UiField;
|
||||
|
||||
@@ -611,4 +611,48 @@ describe('Locked documents', () => {
|
||||
|
||||
expect(docsFromLocksCollection.docs).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('should allow take over on locked doc (simulates take over modal from admin ui)', async () => {
|
||||
const newPost7 = await payload.create({
|
||||
collection: postsSlug,
|
||||
data: {
|
||||
text: 'new post 7',
|
||||
},
|
||||
})
|
||||
|
||||
const lockedDocInstance = await payload.create({
|
||||
collection: lockedDocumentCollection,
|
||||
data: {
|
||||
editedAt: new Date().toISOString(),
|
||||
user: {
|
||||
relationTo: 'users',
|
||||
value: user2.id,
|
||||
},
|
||||
document: {
|
||||
relationTo: 'posts',
|
||||
value: newPost7.id,
|
||||
},
|
||||
globalSlug: undefined,
|
||||
},
|
||||
})
|
||||
|
||||
// This is the take over action - changing the user to the current user
|
||||
await payload.update({
|
||||
collection: 'payload-locked-documents',
|
||||
data: {
|
||||
user: { relationTo: 'users', value: user?.id },
|
||||
},
|
||||
id: lockedDocInstance.id,
|
||||
})
|
||||
|
||||
const docsFromLocksCollection = await payload.find({
|
||||
collection: lockedDocumentCollection,
|
||||
where: {
|
||||
'user.value': { equals: user.id },
|
||||
},
|
||||
})
|
||||
|
||||
expect(docsFromLocksCollection.docs).toHaveLength(1)
|
||||
expect(docsFromLocksCollection.docs[0].user.value?.id).toEqual(user.id)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
"comment-json": "^4.2.3",
|
||||
"create-payload-app": "workspace:*",
|
||||
"dotenv": "16.4.5",
|
||||
"drizzle-kit": "0.26.1",
|
||||
"drizzle-kit": "0.26.2",
|
||||
"eslint-plugin-playwright": "1.6.2",
|
||||
"execa": "5.1.1",
|
||||
"file-type": "19.3.0",
|
||||
|
||||
Reference in New Issue
Block a user