Compare commits
42 Commits
bundler-we
...
feat/resto
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b0308fa92 | ||
|
|
f0446558d2 | ||
|
|
b40e9f85a2 | ||
|
|
e5a7907a72 | ||
|
|
3f25d1ca84 | ||
|
|
d5720bea7b | ||
|
|
8ce15c8b07 | ||
|
|
9f5efef78f | ||
|
|
dfba5222f3 | ||
|
|
b99d24fcfa | ||
|
|
836ed77568 | ||
|
|
1c5d5b07c8 | ||
|
|
da5f1f2240 | ||
|
|
c84c58c7b4 | ||
|
|
1c1b8f3cec | ||
|
|
3f69f83180 | ||
|
|
371353f153 | ||
|
|
a92c6334b6 | ||
|
|
eb9e771a9c | ||
|
|
ee5390aaca | ||
|
|
a861311c5a | ||
|
|
74c3fe1bb2 | ||
|
|
a2be50279e | ||
|
|
403eb06acf | ||
|
|
f5c2cd74cc | ||
|
|
a6a1963ec6 | ||
|
|
0647c870f1 | ||
|
|
3b88adc7d0 | ||
|
|
82383a5b5f | ||
|
|
f9dda628b2 | ||
|
|
93eb0e4a31 | ||
|
|
2e362f44f4 | ||
|
|
775502b161 | ||
|
|
84d75ce6ca | ||
|
|
175cf229c0 | ||
|
|
bb40bd3efb | ||
|
|
3d74c133aa | ||
|
|
2b731c1088 | ||
|
|
6affa1c304 | ||
|
|
57dc93da5d | ||
|
|
28d3f73c2a | ||
|
|
7eae86bcb3 |
2
.github/ISSUE_TEMPLATE/1.bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/1.bug_report.yml
vendored
@@ -1,6 +1,6 @@
|
||||
name: Bug Report
|
||||
description: Create a bug report for Payload
|
||||
labels: ['possible-bug']
|
||||
labels: ['[possible-bug]']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
||||
26
CHANGELOG.md
26
CHANGELOG.md
@@ -1,3 +1,29 @@
|
||||
## [2.8.2](https://github.com/payloadcms/payload/compare/v2.8.1...v2.8.2) (2024-01-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **db-postgres:** support drizzle logging config ([#4809](https://github.com/payloadcms/payload/issues/4809)) ([371353f](https://github.com/payloadcms/payload/commit/371353f1535fbab4ebd9f56fc14fd10a30eec289))
|
||||
* **plugin-form-builder:** add validation for form ID when creating a submission ([#4730](https://github.com/payloadcms/payload/pull/4730))
|
||||
* **plugin-seo:** add support for interfaceName and fieldOverrides ([#4695](https://github.com/payloadcms/payload/pull/4695))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **db-mongodb:** mongodb versions creating duplicates ([#4825](https://github.com/payloadcms/payload/issues/4825)) ([a861311](https://github.com/payloadcms/payload/commit/a861311c5a98126700f98f9a2ab380782e754717))
|
||||
* **db-mongodb:** transactionOptions=false typeErrors ([82383a5](https://github.com/payloadcms/payload/commit/82383a5b5f52785115c0feb970da70e91971b7ca))
|
||||
* **db-postgres:** Remove duplicate keys from response ([#4747](https://github.com/payloadcms/payload/issues/4747)) ([eb9e771](https://github.com/payloadcms/payload/commit/eb9e771a9ca03636486d36654f215b73435574cb))
|
||||
* **db-postgres:** validateExistingBlockIsIdentical with arrays ([3b88adc](https://github.com/payloadcms/payload/commit/3b88adc7d0594af63ce190c40c9ee3905df67a31))
|
||||
* **db-postgres:** validateExistingBlockIsIdentical with other tables ([0647c87](https://github.com/payloadcms/payload/commit/0647c870f15dc1b122734b678c2abeb6f56377d4))
|
||||
* **plugin-seo:** fix missing spread operator in URL generator function ([#4723](https://github.com/payloadcms/payload/pull/4723))
|
||||
* removes max-width from field-types class & correctly sets it on uploads ([#4829](https://github.com/payloadcms/payload/issues/4829)) ([ee5390a](https://github.com/payloadcms/payload/commit/ee5390aaca37a4154cde8392b60f091ec3e5175c))
|
||||
|
||||
## [2.8.1](https://github.com/payloadcms/payload/compare/v2.8.0...v2.8.1) (2024-01-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* corrects config usage in build bin script ([#4796](https://github.com/payloadcms/payload/issues/4796)) ([775502b](https://github.com/payloadcms/payload/commit/775502b1616c1bd35a3044438e253a0e84219f99))
|
||||
|
||||
## [2.8.0](https://github.com/payloadcms/payload/compare/v2.7.0...v2.8.0) (2024-01-12)
|
||||
|
||||
|
||||
|
||||
@@ -159,6 +159,39 @@ A function called by the search preview component to display the actual URL of y
|
||||
}
|
||||
```
|
||||
|
||||
#### `interfaceName`
|
||||
|
||||
Rename the meta group interface name that is generated for TypeScript and GraphQL.
|
||||
|
||||
```ts
|
||||
// payload.config.ts
|
||||
{
|
||||
// ...
|
||||
seoPlugin({
|
||||
interfaceName: 'customInterfaceNameSEO'
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
#### `fieldOverrides`
|
||||
|
||||
Pass any valid field props to the base fields: Title, Description or Image.
|
||||
|
||||
```ts
|
||||
// payload.config.ts
|
||||
seoPlugin({
|
||||
// ...
|
||||
fieldOverrides: {
|
||||
title: {
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
localized: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## TypeScript
|
||||
|
||||
All types can be directly imported:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "1.3.0",
|
||||
"version": "1.3.2",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"repository": "https://github.com/payloadcms/payload",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -29,15 +29,18 @@ export const connect: Connect = async function connect(this: MongooseAdapter, pa
|
||||
urlToConnect = process.env.PAYLOAD_TEST_MONGO_URL
|
||||
} else {
|
||||
connectionOptions.dbName = 'payloadmemory'
|
||||
const { MongoMemoryServer } = require('mongodb-memory-server')
|
||||
const { MongoMemoryReplSet } = require('mongodb-memory-server')
|
||||
const getPort = require('get-port')
|
||||
|
||||
const port = await getPort()
|
||||
this.mongoMemoryServer = await MongoMemoryServer.create({
|
||||
this.mongoMemoryServer = await MongoMemoryReplSet.create({
|
||||
instance: {
|
||||
dbName: 'payloadmemory',
|
||||
port,
|
||||
},
|
||||
replSet: {
|
||||
count: 3,
|
||||
},
|
||||
})
|
||||
|
||||
urlToConnect = this.mongoMemoryServer.getUri()
|
||||
@@ -50,7 +53,7 @@ export const connect: Connect = async function connect(this: MongooseAdapter, pa
|
||||
|
||||
const client = this.connection.getClient()
|
||||
|
||||
if (!client.options.replicaSet || this.transactionOptions === false) {
|
||||
if (!client.options.replicaSet) {
|
||||
this.transactionOptions = false
|
||||
this.beginTransaction = undefined
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ export const createGlobalVersion: CreateGlobalVersion = async function createGlo
|
||||
],
|
||||
},
|
||||
{ $unset: { latest: 1 } },
|
||||
options,
|
||||
)
|
||||
|
||||
const result: Document = JSON.parse(JSON.stringify(doc))
|
||||
|
||||
@@ -57,6 +57,7 @@ export const createVersion: CreateVersion = async function createVersion(
|
||||
],
|
||||
},
|
||||
{ $unset: { latest: 1 } },
|
||||
options,
|
||||
)
|
||||
|
||||
const result: Document = JSON.parse(JSON.stringify(doc))
|
||||
|
||||
@@ -63,6 +63,7 @@ export const find: Find = async function find(
|
||||
paginationOptions.useCustomCountFn = () => {
|
||||
return Promise.resolve(
|
||||
Model.countDocuments(query, {
|
||||
...options,
|
||||
hint: { _id: 1 },
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -82,6 +82,7 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
|
||||
paginationOptions.useCustomCountFn = () => {
|
||||
return Promise.resolve(
|
||||
Model.countDocuments(query, {
|
||||
...options,
|
||||
hint: { _id: 1 },
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -79,6 +79,7 @@ export const findVersions: FindVersions = async function findVersions(
|
||||
paginationOptions.useCustomCountFn = () => {
|
||||
return Promise.resolve(
|
||||
Model.countDocuments(query, {
|
||||
...options,
|
||||
hint: { _id: 1 },
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -93,18 +93,13 @@ export function mongooseAdapter({
|
||||
connectOptions,
|
||||
disableIndexHints = false,
|
||||
migrationDir: migrationDirArg,
|
||||
transactionOptions,
|
||||
transactionOptions = {},
|
||||
url,
|
||||
}: Args): MongooseAdapterResult {
|
||||
function adapter({ payload }: { payload: Payload }) {
|
||||
const migrationDir = findMigrationDir(migrationDirArg)
|
||||
let beginTransactionFunction = beginTransaction
|
||||
mongoose.set('strictQuery', false)
|
||||
|
||||
if (transactionOptions === false) {
|
||||
beginTransactionFunction = () => null
|
||||
}
|
||||
|
||||
return createDatabaseAdapter<MongooseAdapter>({
|
||||
name: 'mongoose',
|
||||
|
||||
@@ -122,7 +117,7 @@ export function mongooseAdapter({
|
||||
versions: {},
|
||||
|
||||
// DatabaseAdapter
|
||||
beginTransaction: beginTransactionFunction,
|
||||
beginTransaction: transactionOptions ? beginTransaction : undefined,
|
||||
commitTransaction,
|
||||
connect,
|
||||
create,
|
||||
|
||||
@@ -77,6 +77,7 @@ export const sanitizeQueryValue = ({
|
||||
// Object equality requires the value to be the first key in the object that is being queried.
|
||||
if (
|
||||
operator === 'equals' &&
|
||||
formattedValue &&
|
||||
typeof formattedValue === 'object' &&
|
||||
formattedValue.value &&
|
||||
formattedValue.relationTo
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.0",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"repository": "https://github.com/payloadcms/payload",
|
||||
"license": "MIT",
|
||||
@@ -22,8 +22,8 @@
|
||||
"dependencies": {
|
||||
"@libsql/client": "^0.3.1",
|
||||
"console-table-printer": "2.11.2",
|
||||
"drizzle-kit": "0.19.13-e99bac1",
|
||||
"drizzle-orm": "0.28.5",
|
||||
"drizzle-kit": "0.20.5-608ae62",
|
||||
"drizzle-orm": "0.29.3",
|
||||
"pg": "8.11.3",
|
||||
"prompts": "2.4.2",
|
||||
"to-snake-case": "1.0.0",
|
||||
|
||||
@@ -18,8 +18,9 @@ export const connect: Connect = async function connect(this: PostgresAdapter, pa
|
||||
try {
|
||||
this.pool = new Pool(this.poolOptions)
|
||||
await this.pool.connect()
|
||||
const logger = this.logger || false
|
||||
|
||||
this.drizzle = drizzle(this.pool, { schema: this.schema })
|
||||
this.drizzle = drizzle(this.pool, { schema: this.schema, logger })
|
||||
if (process.env.PAYLOAD_DROP_DATABASE === 'true') {
|
||||
this.payload.logger.info('---- DROPPING TABLES ----')
|
||||
await this.drizzle.execute(sql`drop schema public cascade;
|
||||
@@ -39,7 +40,7 @@ export const connect: Connect = async function connect(this: PostgresAdapter, pa
|
||||
)
|
||||
return
|
||||
|
||||
const { pushSchema } = require('drizzle-kit/utils')
|
||||
const { pushSchema } = require('drizzle-kit/payload')
|
||||
|
||||
// This will prompt if clarifications are needed for Drizzle to push new schema
|
||||
const { apply, hasDataLoss, statementsToExecute, warnings } = await pushSchema(
|
||||
@@ -59,9 +60,9 @@ export const connect: Connect = async function connect(this: PostgresAdapter, pa
|
||||
const { confirm: acceptWarnings } = await prompts(
|
||||
{
|
||||
name: 'confirm',
|
||||
type: 'confirm',
|
||||
initial: false,
|
||||
message,
|
||||
type: 'confirm',
|
||||
},
|
||||
{
|
||||
onCancel: () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-restricted-syntax, no-await-in-loop */
|
||||
import type { DrizzleSnapshotJSON } from 'drizzle-kit/utils'
|
||||
import type { DrizzleSnapshotJSON } from 'drizzle-kit/payload'
|
||||
import type { CreateMigration } from 'payload/database'
|
||||
|
||||
import fs from 'fs'
|
||||
@@ -60,7 +60,7 @@ export const createMigration: CreateMigration = async function createMigration(
|
||||
fs.mkdirSync(dir)
|
||||
}
|
||||
|
||||
const { generateDrizzleJson, generateMigration } = require('drizzle-kit/utils')
|
||||
const { generateDrizzleJson, generateMigration } = require('drizzle-kit/payload')
|
||||
|
||||
const [yyymmdd, hhmmss] = new Date().toISOString().split('T')
|
||||
const formattedDate = yyymmdd.replace(/\D/g, '')
|
||||
@@ -99,9 +99,9 @@ export const createMigration: CreateMigration = async function createMigration(
|
||||
const { confirm: shouldCreateBlankMigration } = await prompts(
|
||||
{
|
||||
name: 'confirm',
|
||||
type: 'confirm',
|
||||
initial: false,
|
||||
message: 'No schema changes detected. Would you like to create a blank migration file?',
|
||||
type: 'confirm',
|
||||
},
|
||||
{
|
||||
onCancel: () => {
|
||||
|
||||
@@ -50,6 +50,7 @@ export function postgresAdapter(args: Args): PostgresAdapterResult {
|
||||
drizzle: undefined,
|
||||
enums: {},
|
||||
fieldConstraints: {},
|
||||
logger: args.logger,
|
||||
pool: undefined,
|
||||
poolOptions: args.pool,
|
||||
push: args.push,
|
||||
|
||||
@@ -80,7 +80,7 @@ export async function migrate(this: PostgresAdapter): Promise<void> {
|
||||
}
|
||||
|
||||
async function runMigrationFile(payload: Payload, migration: Migration, batch: number) {
|
||||
const { generateDrizzleJson } = require('drizzle-kit/utils')
|
||||
const { generateDrizzleJson } = require('drizzle-kit/payload')
|
||||
|
||||
const start = Date.now()
|
||||
const req = { payload } as PayloadRequest
|
||||
|
||||
@@ -37,7 +37,7 @@ export async function migrateDown(this: PostgresAdapter): Promise<void> {
|
||||
}
|
||||
|
||||
const start = Date.now()
|
||||
const req = {} as PayloadRequest
|
||||
const req = { payload } as PayloadRequest
|
||||
|
||||
try {
|
||||
payload.logger.info({ msg: `Migrating down: ${migrationFile.name}` })
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { SQL } from 'drizzle-orm'
|
||||
import type { Field, FieldAffectingData, TabAsField } from 'payload/types'
|
||||
|
||||
import { and, eq, sql } from 'drizzle-orm'
|
||||
import { and, eq, like, sql } from 'drizzle-orm'
|
||||
import { alias } from 'drizzle-orm/pg-core'
|
||||
import { APIError } from 'payload/errors'
|
||||
import { fieldAffectsData, tabHasName } from 'payload/types'
|
||||
@@ -317,21 +317,15 @@ export const getTableColumnFromPath = ({
|
||||
|
||||
// Join in the relationships table
|
||||
joinAliases.push({
|
||||
condition: eq(
|
||||
(aliasTable || adapter.tables[rootTableName]).id,
|
||||
aliasRelationshipTable.parent,
|
||||
condition: and(
|
||||
eq((aliasTable || adapter.tables[rootTableName]).id, aliasRelationshipTable.parent),
|
||||
like(aliasRelationshipTable.path, `${constraintPath}${field.name}`),
|
||||
),
|
||||
table: aliasRelationshipTable,
|
||||
})
|
||||
|
||||
selectFields[`${relationTableName}.path`] = aliasRelationshipTable.path
|
||||
|
||||
constraints.push({
|
||||
columnName: 'path',
|
||||
table: aliasRelationshipTable,
|
||||
value: `${constraintPath}${field.name}`,
|
||||
})
|
||||
|
||||
let newAliasTable
|
||||
|
||||
if (typeof field.relationTo === 'string') {
|
||||
@@ -428,7 +422,7 @@ export const getTableColumnFromPath = ({
|
||||
columnName: `${columnPrefix}${field.name}`,
|
||||
constraints,
|
||||
field,
|
||||
pathSegments: pathSegments,
|
||||
pathSegments,
|
||||
table: targetTable,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,6 +207,16 @@ export async function parseParams({
|
||||
break
|
||||
}
|
||||
|
||||
if (operator === 'equals' && queryValue === null) {
|
||||
constraints.push(isNull(rawColumn || table[columnName]))
|
||||
break
|
||||
}
|
||||
|
||||
if (operator === 'not_equals' && queryValue === null) {
|
||||
constraints.push(isNotNull(rawColumn || table[columnName]))
|
||||
break
|
||||
}
|
||||
|
||||
constraints.push(
|
||||
operatorMap[queryOperator](rawColumn || table[columnName], queryValue),
|
||||
)
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
// type GenerateMigration = (before: DrizzleSnapshotJSON, after: DrizzleSnapshotJSON) => string[]
|
||||
|
||||
// type GenerateDrizzleJSON = (schema: DrizzleSchemaExports) => DrizzleSnapshotJSON
|
||||
|
||||
// type PushDiff = (schema: DrizzleSchemaExports) => Promise<{ warnings: string[], apply: () => Promise<void> }>
|
||||
|
||||
// drizzle-kit@utils
|
||||
|
||||
import { drizzle } from 'drizzle-orm/node-postgres'
|
||||
import { Pool } from 'pg'
|
||||
|
||||
async function generateUsage() {
|
||||
const { generateDrizzleJson, generateMigration } = require('drizzle-kit/utils')
|
||||
|
||||
// @ts-expect-error Just TypeScript being broken // TODO: Open TypeScript issue
|
||||
const schema = await import('./data/users')
|
||||
// @ts-expect-error Just TypeScript being broken // TODO: Open TypeScript issue
|
||||
const schemaAfter = await import('./data/users-after')
|
||||
|
||||
const drizzleJsonBefore = generateDrizzleJson(schema)
|
||||
const drizzleJsonAfter = generateDrizzleJson(schemaAfter)
|
||||
|
||||
const sqlStatements = await generateMigration(drizzleJsonBefore, drizzleJsonAfter)
|
||||
|
||||
console.log(sqlStatements)
|
||||
}
|
||||
|
||||
async function pushUsage() {
|
||||
const { pushSchema } = require('drizzle-kit/utils')
|
||||
|
||||
// @ts-expect-error Just TypeScript being broken // TODO: Open TypeScript issue
|
||||
const schemaAfter = await import('./data/users-after')
|
||||
|
||||
const db = drizzle(new Pool({ connectionString: '' }))
|
||||
|
||||
const response = await pushSchema(schemaAfter, db)
|
||||
|
||||
console.log('\n')
|
||||
console.log('hasDataLoss: ', response.hasDataLoss)
|
||||
console.log('warnings: ', response.warnings)
|
||||
console.log('statements: ', response.statementsToExecute)
|
||||
|
||||
await response.apply()
|
||||
|
||||
process.exit(0)
|
||||
}
|
||||
@@ -16,7 +16,10 @@ const getFlattenedFieldNames = (fields: Field[], prefix: string = ''): string[]
|
||||
return fields.reduce((fieldsToUse, field) => {
|
||||
let fieldPrefix = prefix
|
||||
|
||||
if (field.type === 'blocks') {
|
||||
if (
|
||||
['array', 'blocks', 'relationship', 'upload'].includes(field.type) ||
|
||||
('hasMany' in field && field.hasMany === true)
|
||||
) {
|
||||
return fieldsToUse
|
||||
}
|
||||
|
||||
@@ -54,29 +57,27 @@ export const validateExistingBlockIsIdentical = ({
|
||||
rootTableName,
|
||||
table,
|
||||
}: Args): void => {
|
||||
if (table) {
|
||||
const fieldNames = getFlattenedFieldNames(block.fields)
|
||||
const fieldNames = getFlattenedFieldNames(block.fields)
|
||||
|
||||
const missingField =
|
||||
// ensure every field from the config is in the matching table
|
||||
fieldNames.find((name) => Object.keys(table).indexOf(name) === -1) ||
|
||||
// ensure every table column is matched for every field from the config
|
||||
Object.keys(table).find((fieldName) => {
|
||||
if (!['_locale', '_order', '_parentID', '_path', '_uuid'].includes(fieldName)) {
|
||||
return fieldNames.indexOf(fieldName) === -1
|
||||
}
|
||||
})
|
||||
const missingField =
|
||||
// ensure every field from the config is in the matching table
|
||||
fieldNames.find((name) => Object.keys(table).indexOf(name) === -1) ||
|
||||
// ensure every table column is matched for every field from the config
|
||||
Object.keys(table).find((fieldName) => {
|
||||
if (!['_locale', '_order', '_parentID', '_path', '_uuid'].includes(fieldName)) {
|
||||
return fieldNames.indexOf(fieldName) === -1
|
||||
}
|
||||
})
|
||||
|
||||
if (missingField) {
|
||||
throw new InvalidConfiguration(
|
||||
`The table ${rootTableName} has multiple blocks with slug ${block.slug}, but the schemas do not match. One block includes the field ${missingField}, while the other block does not.`,
|
||||
)
|
||||
}
|
||||
if (missingField) {
|
||||
throw new InvalidConfiguration(
|
||||
`The table ${rootTableName} has multiple blocks with slug ${block.slug}, but the schemas do not match. One block includes the field ${missingField}, while the other block does not.`,
|
||||
)
|
||||
}
|
||||
|
||||
if (Boolean(localized) !== Boolean(table._locale)) {
|
||||
throw new InvalidConfiguration(
|
||||
`The table ${rootTableName} has multiple blocks with slug ${block.slug}, but the schemas do not match. One is localized, but another is not. Block schemas of the same name must match exactly.`,
|
||||
)
|
||||
}
|
||||
if (Boolean(localized) !== Boolean(table._locale)) {
|
||||
throw new InvalidConfiguration(
|
||||
`The table ${rootTableName} has multiple blocks with slug ${block.slug}, but the schemas do not match. One is localized, but another is not. Block schemas of the same name must match exactly.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import { fieldAffectsData } from 'payload/types'
|
||||
import type { BlocksMap } from '../../utilities/createBlocksMap'
|
||||
|
||||
import { transformHasManyNumber } from './hasManyNumber'
|
||||
import { transformRelationship } from './relationship'
|
||||
import { transformHasManyText } from './hasManyText'
|
||||
import { transformRelationship } from './relationship'
|
||||
|
||||
type TraverseFieldsArgs = {
|
||||
/**
|
||||
@@ -35,10 +35,6 @@ type TraverseFieldsArgs = {
|
||||
* An array of Payload fields to traverse
|
||||
*/
|
||||
fields: (Field | TabAsField)[]
|
||||
/**
|
||||
* All hasMany text fields, as returned by Drizzle, keyed on an object by field path
|
||||
*/
|
||||
texts: Record<string, Record<string, unknown>[]>
|
||||
/**
|
||||
* All hasMany number fields, as returned by Drizzle, keyed on an object by field path
|
||||
*/
|
||||
@@ -55,6 +51,10 @@ type TraverseFieldsArgs = {
|
||||
* Data structure representing the nearest table from db
|
||||
*/
|
||||
table: Record<string, unknown>
|
||||
/**
|
||||
* All hasMany text fields, as returned by Drizzle, keyed on an object by field path
|
||||
*/
|
||||
texts: Record<string, Record<string, unknown>[]>
|
||||
}
|
||||
|
||||
// Traverse fields recursively, transforming data
|
||||
@@ -66,11 +66,11 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
deletions,
|
||||
fieldPrefix,
|
||||
fields,
|
||||
texts,
|
||||
numbers,
|
||||
path,
|
||||
relationships,
|
||||
table,
|
||||
texts,
|
||||
}: TraverseFieldsArgs): T => {
|
||||
const sanitizedPath = path ? `${path}.` : path
|
||||
|
||||
@@ -83,11 +83,11 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
deletions,
|
||||
fieldPrefix,
|
||||
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
||||
texts,
|
||||
numbers,
|
||||
path,
|
||||
relationships,
|
||||
table,
|
||||
texts,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -103,17 +103,22 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
deletions,
|
||||
fieldPrefix,
|
||||
fields: field.fields,
|
||||
texts,
|
||||
numbers,
|
||||
path,
|
||||
relationships,
|
||||
table,
|
||||
texts,
|
||||
})
|
||||
}
|
||||
|
||||
if (fieldAffectsData(field)) {
|
||||
const fieldName = `${fieldPrefix || ''}${field.name}`
|
||||
const fieldData = table[fieldName]
|
||||
|
||||
if (fieldPrefix) {
|
||||
deletions.push(() => delete table[fieldName])
|
||||
}
|
||||
|
||||
if (field.type === 'array') {
|
||||
if (Array.isArray(fieldData)) {
|
||||
if (field.localized) {
|
||||
@@ -135,13 +140,17 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
deletions,
|
||||
fieldPrefix: '',
|
||||
fields: field.fields,
|
||||
texts,
|
||||
numbers,
|
||||
path: `${sanitizedPath}${field.name}.${row._order - 1}`,
|
||||
relationships,
|
||||
table: row,
|
||||
texts,
|
||||
})
|
||||
|
||||
if ('_order' in rowResult) {
|
||||
delete rowResult._order
|
||||
}
|
||||
|
||||
arrayResult[locale].push(rowResult)
|
||||
}
|
||||
|
||||
@@ -153,6 +162,11 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
row.id = row._uuid
|
||||
delete row._uuid
|
||||
}
|
||||
|
||||
if ('_order' in row) {
|
||||
delete row._order
|
||||
}
|
||||
|
||||
return traverseFields<T>({
|
||||
blocks,
|
||||
config,
|
||||
@@ -160,11 +174,11 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
deletions,
|
||||
fieldPrefix: '',
|
||||
fields: field.fields,
|
||||
texts,
|
||||
numbers,
|
||||
path: `${sanitizedPath}${field.name}.${i}`,
|
||||
relationships,
|
||||
table: row,
|
||||
texts,
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -204,11 +218,11 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
deletions,
|
||||
fieldPrefix: '',
|
||||
fields: block.fields,
|
||||
texts,
|
||||
numbers,
|
||||
path: `${blockFieldPath}.${row._order - 1}`,
|
||||
relationships,
|
||||
table: row,
|
||||
texts,
|
||||
})
|
||||
|
||||
delete blockResult._order
|
||||
@@ -235,11 +249,11 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
deletions,
|
||||
fieldPrefix: '',
|
||||
fields: block.fields,
|
||||
texts,
|
||||
numbers,
|
||||
path: `${blockFieldPath}.${i}`,
|
||||
relationships,
|
||||
table: row,
|
||||
texts,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -316,15 +330,15 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
transformHasManyText({
|
||||
field,
|
||||
locale,
|
||||
textRows: texts,
|
||||
ref: result,
|
||||
textRows: texts,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
transformHasManyText({
|
||||
field,
|
||||
textRows: textPathMatch,
|
||||
ref: result,
|
||||
textRows: textPathMatch,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -420,13 +434,16 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
deletions,
|
||||
fieldPrefix: groupFieldPrefix,
|
||||
fields: field.fields,
|
||||
texts,
|
||||
numbers,
|
||||
path: `${sanitizedPath}${field.name}`,
|
||||
relationships,
|
||||
table,
|
||||
texts,
|
||||
})
|
||||
})
|
||||
if ('_order' in ref) {
|
||||
delete ref._order
|
||||
}
|
||||
} else {
|
||||
const groupData = {}
|
||||
|
||||
@@ -437,12 +454,15 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
deletions,
|
||||
fieldPrefix: groupFieldPrefix,
|
||||
fields: field.fields,
|
||||
texts,
|
||||
numbers,
|
||||
path: `${sanitizedPath}${field.name}`,
|
||||
relationships,
|
||||
table,
|
||||
texts,
|
||||
})
|
||||
if ('_order' in ref) {
|
||||
delete ref._order
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type {
|
||||
ColumnBaseConfig,
|
||||
ColumnDataType,
|
||||
DrizzleConfig,
|
||||
ExtractTablesWithRelations,
|
||||
Relation,
|
||||
Relations,
|
||||
@@ -16,6 +17,7 @@ export type DrizzleDB = NodePgDatabase<Record<string, unknown>>
|
||||
export type Args = {
|
||||
migrationDir?: string
|
||||
pool: PoolConfig
|
||||
logger?: DrizzleConfig['logger']
|
||||
push?: boolean
|
||||
}
|
||||
|
||||
@@ -47,6 +49,7 @@ export type DrizzleTransaction = PgTransaction<
|
||||
|
||||
export type PostgresAdapter = BaseDatabaseAdapter & {
|
||||
drizzle: DrizzleDB
|
||||
logger: DrizzleConfig['logger']
|
||||
enums: Record<string, GenericEnum>
|
||||
pool: Pool
|
||||
poolOptions: Args['pool']
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload",
|
||||
"version": "2.8.0",
|
||||
"version": "2.8.2",
|
||||
"description": "Node, React and MongoDB Headless CMS and Application Framework",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
|
||||
& > .field-type {
|
||||
margin-bottom: var(--spacing-field);
|
||||
max-width: 100%;
|
||||
|
||||
&[type='hidden'] {
|
||||
margin-bottom: 0;
|
||||
|
||||
@@ -151,7 +151,7 @@ const NumberField: React.FC<Props> = (props) => {
|
||||
if (isOverHasMany) {
|
||||
return t('validation:limitReached', { max: maxRows, value: value.length + 1 })
|
||||
}
|
||||
return t('general:noOptions')
|
||||
return null
|
||||
}}
|
||||
numberOnly
|
||||
onChange={handleHasManyChange}
|
||||
@@ -170,7 +170,7 @@ const NumberField: React.FC<Props> = (props) => {
|
||||
onChange={handleChange}
|
||||
onWheel={(e) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
e.target.blur()
|
||||
}}
|
||||
placeholder={getTranslation(placeholder, i18n)}
|
||||
|
||||
@@ -110,7 +110,7 @@ const TextInput: React.FC<TextInputProps> = (props) => {
|
||||
if (isOverHasMany) {
|
||||
return t('validation:limitReached', { max: maxRows, value: value.length + 1 })
|
||||
}
|
||||
return t('general:noOptions')
|
||||
return null
|
||||
}}
|
||||
onChange={onChange}
|
||||
options={[]}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
.upload {
|
||||
position: relative;
|
||||
max-width: 100%;
|
||||
|
||||
&__wrap {
|
||||
background: var(--theme-elevation-50);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import payload from '..'
|
||||
import loadConfig from '../config/load'
|
||||
|
||||
export const build = async (): Promise<void> => {
|
||||
const config = await loadConfig() // Will throw its own error if it fails
|
||||
|
||||
await payload.config.admin.bundler.build(config)
|
||||
await config.admin.bundler.build(config)
|
||||
}
|
||||
|
||||
// when build.js is launched directly
|
||||
|
||||
@@ -3,7 +3,7 @@ import httpStatus from 'http-status'
|
||||
|
||||
import type { FindOneArgs } from '../../database/types'
|
||||
import type { PayloadRequest } from '../../express/types'
|
||||
import type { Collection, TypeWithID } from '../config/types'
|
||||
import type { Collection, TypeWithID, BeforeOperationHook } from '../config/types'
|
||||
|
||||
import executeAccess from '../../auth/executeAccess'
|
||||
import { hasWhereAccessResult } from '../../auth/types'
|
||||
@@ -45,6 +45,26 @@ async function restoreVersion<T extends TypeWithID = any>(args: Arguments): Prom
|
||||
throw new APIError('Missing ID of version to restore.', httpStatus.BAD_REQUEST)
|
||||
}
|
||||
|
||||
// WIP: https://github.com/payloadcms/payload/discussions/4901
|
||||
// /////////////////////////////////////
|
||||
// beforeOperation - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
await args.collection.config.hooks.beforeOperation.reduce(
|
||||
async (priorHook: BeforeOperationHook | Promise<void>, hook: BeforeOperationHook) => {
|
||||
await priorHook
|
||||
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'update',
|
||||
})) || args
|
||||
},
|
||||
Promise.resolve(),
|
||||
)
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Retrieve original raw version
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -25,7 +25,9 @@ export async function migrateDown(this: BaseDatabaseAdapter): Promise<void> {
|
||||
msg: `Rolling back batch ${latestBatch} consisting of ${existingMigrations.length} migration(s).`,
|
||||
})
|
||||
|
||||
for (const migration of existingMigrations) {
|
||||
const latestBatchMigrations = existingMigrations.filter(({ batch }) => batch === latestBatch)
|
||||
|
||||
for (const migration of latestBatchMigrations) {
|
||||
const migrationFile = migrationFiles.find((m) => m.name === migration.name)
|
||||
if (!migrationFile) {
|
||||
throw new Error(`Migration ${migration.name} not found locally.`)
|
||||
|
||||
@@ -109,6 +109,7 @@ export {
|
||||
fieldHasSubFields,
|
||||
fieldIsArrayType,
|
||||
fieldIsBlockType,
|
||||
fieldIsGroupType,
|
||||
fieldIsLocalized,
|
||||
fieldIsPresentationalOnly,
|
||||
fieldSupportsMany,
|
||||
|
||||
@@ -577,7 +577,7 @@ export type Block = {
|
||||
*/
|
||||
interfaceName?: string
|
||||
labels?: Labels
|
||||
slug: string,
|
||||
slug: string
|
||||
/** Extension point to add your custom data. */
|
||||
custom?: Record<string, any>
|
||||
}
|
||||
@@ -692,6 +692,10 @@ export function fieldIsBlockType(field: Field): field is BlockField {
|
||||
return field.type === 'blocks'
|
||||
}
|
||||
|
||||
export function fieldIsGroupType(field: Field): field is GroupField {
|
||||
return field.type === 'group'
|
||||
}
|
||||
|
||||
export function optionIsObject(option: Option): option is OptionObject {
|
||||
return typeof option === 'object'
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@ import translations from './index'
|
||||
export const defaultOptions: InitOptions = {
|
||||
debug: false,
|
||||
detection: {
|
||||
caches: ['header', 'cookie', 'localStorage'],
|
||||
caches: ['cookie', 'localStorage', 'header'],
|
||||
lookupCookie: 'lng',
|
||||
lookupLocalStorage: 'lng',
|
||||
order: ['header', 'cookie', 'localStorage'],
|
||||
order: ['cookie', 'localStorage', 'header'],
|
||||
},
|
||||
fallbackLng: 'en',
|
||||
interpolation: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-form-builder",
|
||||
"description": "Form builder plugin for Payload CMS",
|
||||
"version": "1.1.1",
|
||||
"version": "1.2.0",
|
||||
"homepage:": "https://payloadcms.com",
|
||||
"repository": "git@github.com:payloadcms/plugin-form-builder.git",
|
||||
"main": "dist/index.js",
|
||||
|
||||
@@ -7,6 +7,8 @@ import sendEmail from './hooks/sendEmail'
|
||||
|
||||
// all settings can be overridden by the config
|
||||
export const generateSubmissionCollection = (formConfig: PluginConfig): CollectionConfig => {
|
||||
const formSlug = formConfig?.formOverrides?.slug || 'forms'
|
||||
|
||||
const newConfig: CollectionConfig = {
|
||||
...(formConfig?.formSubmissionOverrides || {}),
|
||||
access: {
|
||||
@@ -25,9 +27,28 @@ export const generateSubmissionCollection = (formConfig: PluginConfig): Collecti
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
relationTo: formConfig?.formOverrides?.slug || 'forms',
|
||||
relationTo: formSlug,
|
||||
required: true,
|
||||
type: 'relationship',
|
||||
validate: async (value, { payload }) => {
|
||||
/* Don't run in the client side */
|
||||
if (!payload) return true
|
||||
|
||||
if (payload) {
|
||||
let existingForm
|
||||
|
||||
try {
|
||||
existingForm = await payload.findByID({
|
||||
id: value,
|
||||
collection: formSlug,
|
||||
})
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
return 'Cannot create this submission because this form does not exist.'
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'submissionData',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-seo",
|
||||
"version": "2.1.0",
|
||||
"version": "2.2.0",
|
||||
"homepage:": "https://payloadcms.com",
|
||||
"repository": "git@github.com:payloadcms/plugin-seo.git",
|
||||
"description": "SEO plugin for Payload",
|
||||
|
||||
@@ -22,7 +22,7 @@ type MetaDescriptionProps = TextareaField & {
|
||||
}
|
||||
|
||||
export const MetaDescription: React.FC<MetaDescriptionProps> = (props) => {
|
||||
const { name, label, path, pluginConfig } = props
|
||||
const { name, label, path, pluginConfig, required } = props
|
||||
|
||||
const { t } = useTranslation('plugin-seo')
|
||||
|
||||
@@ -36,7 +36,7 @@ export const MetaDescription: React.FC<MetaDescriptionProps> = (props) => {
|
||||
path,
|
||||
} as Options)
|
||||
|
||||
const { setValue, showError, value } = field
|
||||
const { setValue, showError, value, errorMessage } = field
|
||||
|
||||
const regenerateDescription = useCallback(async () => {
|
||||
const { generateDescription } = pluginConfig
|
||||
@@ -67,6 +67,18 @@ export const MetaDescription: React.FC<MetaDescriptionProps> = (props) => {
|
||||
>
|
||||
<div>
|
||||
{label && typeof label === 'string' && label}
|
||||
|
||||
{required && (
|
||||
<span
|
||||
style={{
|
||||
marginLeft: '5px',
|
||||
color: 'var(--theme-error-500)',
|
||||
}}
|
||||
>
|
||||
*
|
||||
</span>
|
||||
)}
|
||||
|
||||
{typeof pluginConfig.generateDescription === 'function' && (
|
||||
<React.Fragment>
|
||||
—
|
||||
@@ -117,6 +129,8 @@ export const MetaDescription: React.FC<MetaDescriptionProps> = (props) => {
|
||||
style={{
|
||||
marginBottom: 0,
|
||||
}}
|
||||
required={required}
|
||||
errorMessage={errorMessage}
|
||||
value={value}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -19,7 +19,7 @@ type MetaImageProps = UploadInputProps & {
|
||||
}
|
||||
|
||||
export const MetaImage: React.FC<MetaImageProps> = (props) => {
|
||||
const { name, fieldTypes, label, pluginConfig, relationTo } = props || {}
|
||||
const { name, fieldTypes, label, pluginConfig, relationTo, required } = props || {}
|
||||
|
||||
const field: FieldType<string> = useField(props as Options)
|
||||
|
||||
@@ -29,7 +29,7 @@ export const MetaImage: React.FC<MetaImageProps> = (props) => {
|
||||
const [fields] = useAllFormFields()
|
||||
const docInfo = useDocumentInfo()
|
||||
|
||||
const { setValue, showError, value } = field
|
||||
const { setValue, showError, value, errorMessage } = field
|
||||
|
||||
const regenerateImage = useCallback(async () => {
|
||||
const { generateImage } = pluginConfig
|
||||
@@ -68,6 +68,18 @@ export const MetaImage: React.FC<MetaImageProps> = (props) => {
|
||||
>
|
||||
<div>
|
||||
{label && typeof label === 'string' && label}
|
||||
|
||||
{required && (
|
||||
<span
|
||||
style={{
|
||||
marginLeft: '5px',
|
||||
color: 'var(--theme-error-500)',
|
||||
}}
|
||||
>
|
||||
*
|
||||
</span>
|
||||
)}
|
||||
|
||||
{typeof pluginConfig.generateImage === 'function' && (
|
||||
<React.Fragment>
|
||||
—
|
||||
@@ -110,6 +122,8 @@ export const MetaImage: React.FC<MetaImageProps> = (props) => {
|
||||
collection={collection}
|
||||
fieldTypes={fieldTypes}
|
||||
filterOptions={{}}
|
||||
errorMessage={errorMessage}
|
||||
required={required}
|
||||
label={undefined}
|
||||
name={name}
|
||||
onChange={(incomingImage) => {
|
||||
|
||||
@@ -25,7 +25,7 @@ type MetaTitleProps = TextFieldType & {
|
||||
}
|
||||
|
||||
export const MetaTitle: React.FC<MetaTitleProps> = (props) => {
|
||||
const { name, label, path, pluginConfig } = props || {}
|
||||
const { name, label, path, pluginConfig, required } = props || {}
|
||||
|
||||
const { t } = useTranslation('plugin-seo')
|
||||
|
||||
@@ -39,7 +39,7 @@ export const MetaTitle: React.FC<MetaTitleProps> = (props) => {
|
||||
const [fields] = useAllFormFields()
|
||||
const docInfo = useDocumentInfo()
|
||||
|
||||
const { setValue, showError, value } = field
|
||||
const { setValue, showError, value, errorMessage } = field
|
||||
|
||||
const regenerateTitle = useCallback(async () => {
|
||||
const { generateTitle } = pluginConfig
|
||||
@@ -70,6 +70,18 @@ export const MetaTitle: React.FC<MetaTitleProps> = (props) => {
|
||||
>
|
||||
<div>
|
||||
{label && typeof label === 'string' && label}
|
||||
|
||||
{required && (
|
||||
<span
|
||||
style={{
|
||||
marginLeft: '5px',
|
||||
color: 'var(--theme-error-500)',
|
||||
}}
|
||||
>
|
||||
*
|
||||
</span>
|
||||
)}
|
||||
|
||||
{typeof pluginConfig.generateTitle === 'function' && (
|
||||
<React.Fragment>
|
||||
—
|
||||
@@ -121,6 +133,8 @@ export const MetaTitle: React.FC<MetaTitleProps> = (props) => {
|
||||
style={{
|
||||
marginBottom: 0,
|
||||
}}
|
||||
errorMessage={errorMessage}
|
||||
required={required}
|
||||
value={value}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -30,6 +30,7 @@ const seo =
|
||||
},
|
||||
label: 'Overview',
|
||||
},
|
||||
// @ts-expect-error
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
@@ -39,6 +40,7 @@ const seo =
|
||||
},
|
||||
},
|
||||
localized: true,
|
||||
...(pluginConfig?.fieldOverrides?.title ?? {}),
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
@@ -49,6 +51,7 @@ const seo =
|
||||
},
|
||||
},
|
||||
localized: true,
|
||||
...(pluginConfig?.fieldOverrides?.description ?? {}),
|
||||
},
|
||||
...(pluginConfig?.uploadsCollection
|
||||
? [
|
||||
@@ -66,6 +69,7 @@ const seo =
|
||||
label: 'Meta Image',
|
||||
localized: true,
|
||||
relationTo: pluginConfig?.uploadsCollection,
|
||||
...(pluginConfig?.fieldOverrides?.image ?? {}),
|
||||
} as Field,
|
||||
]
|
||||
: []),
|
||||
@@ -81,6 +85,7 @@ const seo =
|
||||
label: 'Preview',
|
||||
},
|
||||
],
|
||||
interfaceName: pluginConfig.interfaceName,
|
||||
label: 'SEO',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ContextType } from 'payload/dist/admin/components/utilities/DocumentInfo/types'
|
||||
import type { Field } from 'payload/dist/fields/config/types'
|
||||
import type { Field, TextareaField, TextField, UploadField } from 'payload/dist/fields/config/types'
|
||||
|
||||
export type GenerateTitle = <T = any>(
|
||||
args: ContextType & { doc: T; locale?: string },
|
||||
@@ -29,6 +29,12 @@ export interface PluginConfig {
|
||||
generateURL?: GenerateURL
|
||||
globals?: string[]
|
||||
tabbedUI?: boolean
|
||||
fieldOverrides?: {
|
||||
title?: Partial<TextField>
|
||||
description?: Partial<TextareaField>
|
||||
image?: Partial<UploadField>
|
||||
}
|
||||
interfaceName?: string
|
||||
uploadsCollection?: string
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ export const Preview: React.FC<PreviewProps> = (props) => {
|
||||
if (typeof generateURL === 'function' && !href) {
|
||||
const newHref = await generateURL({
|
||||
...docInfo,
|
||||
doc: { fields },
|
||||
doc: { ...fields },
|
||||
locale: typeof locale === 'object' ? locale?.code : locale,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/richtext-lexical",
|
||||
"version": "0.5.1",
|
||||
"version": "0.5.2",
|
||||
"description": "The officially supported Lexical richtext adapter for Payload",
|
||||
"repository": "https://github.com/payloadcms/payload",
|
||||
"license": "MIT",
|
||||
|
||||
632
pnpm-lock.yaml
generated
632
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -41,7 +41,7 @@ export default buildConfigWithDefaults({
|
||||
{
|
||||
name: 'blockTwoField',
|
||||
type: 'text',
|
||||
}
|
||||
},
|
||||
],
|
||||
custom: { description: 'The blockOne of this page' },
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { BlockField } from 'payload/types'
|
||||
|
||||
import payload from '../../packages/payload/src'
|
||||
import { initPayloadTest } from '../helpers/configHelpers'
|
||||
import { BlockField } from "payload/dist/fields/config/types";
|
||||
|
||||
require('isomorphic-fetch')
|
||||
|
||||
@@ -39,7 +40,7 @@ describe('Config', () => {
|
||||
|
||||
it('allows a custom field in collection fields', () => {
|
||||
const [collection] = payload.config.collections
|
||||
const [field,] = collection.fields
|
||||
const [field] = collection.fields
|
||||
|
||||
expect(field.custom).toEqual({ description: 'The title of this page' })
|
||||
})
|
||||
@@ -48,7 +49,9 @@ describe('Config', () => {
|
||||
const [collection] = payload.config.collections
|
||||
const [, blocksField] = collection.fields
|
||||
|
||||
expect((blocksField as BlockField).blocks[0].custom).toEqual({ description: 'The blockOne of this page' })
|
||||
expect((blocksField as BlockField).blocks[0].custom).toEqual({
|
||||
description: 'The blockOne of this page',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -8,77 +8,76 @@
|
||||
|
||||
export interface Config {
|
||||
collections: {
|
||||
pages: Page;
|
||||
users: User;
|
||||
'payload-preferences': PayloadPreference;
|
||||
'payload-migrations': PayloadMigration;
|
||||
};
|
||||
pages: Page
|
||||
users: User
|
||||
'payload-preferences': PayloadPreference
|
||||
'payload-migrations': PayloadMigration
|
||||
}
|
||||
globals: {
|
||||
'my-global': MyGlobal;
|
||||
};
|
||||
'my-global': MyGlobal
|
||||
}
|
||||
}
|
||||
export interface Page {
|
||||
id: string;
|
||||
title?: string | null;
|
||||
id: string
|
||||
title?: string | null
|
||||
myBlocks?:
|
||||
| {
|
||||
blockOneField?: string | null;
|
||||
blockTwoField?: string | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'blockOne';
|
||||
blockOneField?: string | null
|
||||
blockTwoField?: string | null
|
||||
id?: string | null
|
||||
blockName?: string | null
|
||||
blockType: 'blockOne'
|
||||
}[]
|
||||
| null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
| null
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
}
|
||||
export interface User {
|
||||
id: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
email: string;
|
||||
resetPasswordToken?: string | null;
|
||||
resetPasswordExpiration?: string | null;
|
||||
salt?: string | null;
|
||||
hash?: string | null;
|
||||
loginAttempts?: number | null;
|
||||
lockUntil?: string | null;
|
||||
password: string | null;
|
||||
id: string
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
email: string
|
||||
resetPasswordToken?: string | null
|
||||
resetPasswordExpiration?: string | null
|
||||
salt?: string | null
|
||||
hash?: string | null
|
||||
loginAttempts?: number | null
|
||||
lockUntil?: string | null
|
||||
password: string | null
|
||||
}
|
||||
export interface PayloadPreference {
|
||||
id: string;
|
||||
id: string
|
||||
user: {
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
};
|
||||
key?: string | null;
|
||||
relationTo: 'users'
|
||||
value: string | User
|
||||
}
|
||||
key?: string | null
|
||||
value?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
[k: string]: unknown
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
| null
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
}
|
||||
export interface PayloadMigration {
|
||||
id: string;
|
||||
name?: string | null;
|
||||
batch?: number | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
id: string
|
||||
name?: string | null
|
||||
batch?: number | null
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
}
|
||||
export interface MyGlobal {
|
||||
id: string;
|
||||
title?: string | null;
|
||||
updatedAt?: string | null;
|
||||
createdAt?: string | null;
|
||||
id: string
|
||||
title?: string | null
|
||||
updatedAt?: string | null
|
||||
createdAt?: string | null
|
||||
}
|
||||
|
||||
|
||||
declare module 'payload' {
|
||||
export interface GeneratedTypes extends Config {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,16 +16,12 @@ describe('database', () => {
|
||||
const collection = 'posts'
|
||||
const title = 'title'
|
||||
let user: TypeWithID & Record<string, unknown>
|
||||
let useTransactions = true
|
||||
|
||||
beforeAll(async () => {
|
||||
const init = await initPayloadTest({ __dirname, init: { local: false } })
|
||||
serverURL = init.serverURL
|
||||
const url = `${serverURL}/api/graphql`
|
||||
client = new GraphQLClient(url)
|
||||
if (payload.db.name === 'mongoose') {
|
||||
useTransactions = false
|
||||
}
|
||||
|
||||
const loginResult = await payload.login({
|
||||
collection: 'users',
|
||||
@@ -57,15 +53,13 @@ describe('database', () => {
|
||||
req,
|
||||
})
|
||||
|
||||
if (useTransactions) {
|
||||
await expect(() =>
|
||||
payload.findByID({
|
||||
id: first.id,
|
||||
collection,
|
||||
// omitting req for isolation
|
||||
}),
|
||||
).rejects.toThrow('The requested resource was not found.')
|
||||
}
|
||||
await expect(() =>
|
||||
payload.findByID({
|
||||
id: first.id,
|
||||
collection,
|
||||
// omitting req for isolation
|
||||
}),
|
||||
).rejects.toThrow('The requested resource was not found.')
|
||||
|
||||
const second = await payload.create({
|
||||
collection,
|
||||
@@ -180,15 +174,13 @@ describe('database', () => {
|
||||
// this should not do anything but is needed to be certain about the next assertion
|
||||
await commitTransaction(req)
|
||||
|
||||
if (useTransactions) {
|
||||
await expect(() =>
|
||||
payload.findByID({
|
||||
id: first.id,
|
||||
collection,
|
||||
req,
|
||||
}),
|
||||
).rejects.toThrow('The requested resource was not found.')
|
||||
}
|
||||
await expect(() =>
|
||||
payload.findByID({
|
||||
id: first.id,
|
||||
collection,
|
||||
req,
|
||||
}),
|
||||
).rejects.toThrow('The requested resource was not found.')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -11,7 +11,6 @@ import type {
|
||||
} from './payload-types'
|
||||
|
||||
import payload from '../../packages/payload/src'
|
||||
import { mapAsync } from '../../packages/payload/src/utilities/mapAsync'
|
||||
import wait from '../../packages/payload/src/utilities/wait'
|
||||
import { initPageConsoleErrorCatch, openDocControls, saveDocAndAssert } from '../helpers'
|
||||
import { AdminUrlUtil } from '../helpers/adminUrlUtil'
|
||||
@@ -525,10 +524,10 @@ async function clearAllDocs(): Promise<void> {
|
||||
}
|
||||
|
||||
async function clearCollectionDocs(collectionSlug: string): Promise<void> {
|
||||
const ids = (await payload.find({ collection: collectionSlug, limit: 100 })).docs.map(
|
||||
(doc) => doc.id,
|
||||
)
|
||||
await mapAsync(ids, async (id) => {
|
||||
await payload.delete({ id, collection: collectionSlug })
|
||||
await payload.delete({
|
||||
collection: collectionSlug,
|
||||
where: {
|
||||
id: { exists: true },
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -246,6 +246,22 @@ const BlockFields: CollectionConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: 'block-b',
|
||||
fields: [
|
||||
{
|
||||
name: 'items',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'title2',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -58,6 +58,24 @@ const GroupFields: CollectionConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'arrayOfGroups',
|
||||
type: 'array',
|
||||
defaultValue: [
|
||||
{
|
||||
groupItem: {
|
||||
text: 'Hello world',
|
||||
},
|
||||
},
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
name: 'groupItem',
|
||||
type: 'group',
|
||||
fields: [{ name: 'text', type: 'text' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'potentiallyEmptyGroup',
|
||||
type: 'group',
|
||||
|
||||
@@ -6,7 +6,6 @@ import path from 'path'
|
||||
import type { RelationshipField, TextField } from './payload-types'
|
||||
|
||||
import payload from '../../packages/payload/src'
|
||||
import { mapAsync } from '../../packages/payload/src/utilities/mapAsync'
|
||||
import wait from '../../packages/payload/src/utilities/wait'
|
||||
import {
|
||||
exactText,
|
||||
@@ -900,6 +899,7 @@ describe('fields', () => {
|
||||
await page.goto(url.list)
|
||||
await page.locator('.row-1 .cell-title a').click()
|
||||
}
|
||||
|
||||
describe('cell', () => {
|
||||
test('ensure cells are smaller than 300px in height', async () => {
|
||||
const url: AdminUrlUtil = new AdminUrlUtil(serverURL, 'rich-text-fields')
|
||||
@@ -1387,13 +1387,9 @@ describe('fields', () => {
|
||||
|
||||
afterEach(async () => {
|
||||
// delete all existing relationship documents
|
||||
const allRelationshipDocs = await payload.find({
|
||||
await payload.delete({
|
||||
collection: relationshipFieldsSlug,
|
||||
limit: 100,
|
||||
})
|
||||
const relationshipIDs = allRelationshipDocs.docs.map((doc) => doc.id)
|
||||
await mapAsync(relationshipIDs, async (id) => {
|
||||
await payload.delete({ id, collection: relationshipFieldsSlug })
|
||||
where: { id: { exists: true } },
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ describe('Fields', () => {
|
||||
})
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
expect(localizedDoc.localizedHasMany.en).toEqual(localizedHasMany)
|
||||
})
|
||||
})
|
||||
@@ -413,7 +413,7 @@ describe('Fields', () => {
|
||||
})
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
expect(localizedDoc.localizedHasMany.en).toEqual(localizedHasMany)
|
||||
})
|
||||
})
|
||||
@@ -643,6 +643,14 @@ describe('Fields', () => {
|
||||
collection,
|
||||
})
|
||||
|
||||
expect(result.items[0]).toMatchObject({
|
||||
subArray: [
|
||||
{
|
||||
text: subArrayText,
|
||||
},
|
||||
],
|
||||
text: 'test',
|
||||
})
|
||||
expect(result.items[0].subArray[0].text).toStrictEqual(subArrayText)
|
||||
})
|
||||
|
||||
@@ -707,6 +715,15 @@ describe('Fields', () => {
|
||||
expect(document.group.defaultParent).toStrictEqual(groupDefaultValue)
|
||||
expect(document.group.defaultChild).toStrictEqual(groupDefaultChild)
|
||||
})
|
||||
|
||||
it('should not have duplicate keys', async () => {
|
||||
expect(document.arrayOfGroups[0]).toMatchObject({
|
||||
id: expect.any(String),
|
||||
groupItem: {
|
||||
text: 'Hello world',
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('tabs', () => {
|
||||
|
||||
@@ -216,7 +216,8 @@ describe('Localization', () => {
|
||||
await page.fill('#field-layout__0__text', 'test')
|
||||
await saveDocAndAssert(page)
|
||||
|
||||
const originalDocURL = page.url()
|
||||
const originalID = await page.locator('.id-label').innerText()
|
||||
|
||||
// duplicate
|
||||
await openDocControls(page)
|
||||
await page.locator('#action-duplicate').click()
|
||||
@@ -229,7 +230,7 @@ describe('Localization', () => {
|
||||
await expect(page.locator('.Toastify')).toContainText('successfully duplicated')
|
||||
|
||||
// expect that the document has a new id
|
||||
expect(page.url()).not.toStrictEqual(originalDocURL)
|
||||
await expect(page.locator('.id-label')).not.toContainText(originalID)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -5,6 +5,7 @@ import { serializeLexical } from '../../packages/plugin-form-builder/src/utiliti
|
||||
import { serializeSlate } from '../../packages/plugin-form-builder/src/utilities/slate/serializeSlate'
|
||||
import { initPayloadTest } from '../helpers/configHelpers'
|
||||
import { formSubmissionsSlug, formsSlug } from './shared'
|
||||
import { ValidationError } from '../../packages/payload/src/errors'
|
||||
|
||||
describe('Form Builder Plugin', () => {
|
||||
let form: Form
|
||||
@@ -42,7 +43,7 @@ describe('Form Builder Plugin', () => {
|
||||
|
||||
it('adds form submissions collection', async () => {
|
||||
const { docs: formSubmissions } = await payload.find({ collection: formSubmissionsSlug })
|
||||
expect(formSubmissions).toHaveLength(0)
|
||||
expect(formSubmissions).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -118,6 +119,25 @@ describe('Form Builder Plugin', () => {
|
||||
expect(formSubmission.submissionData[0]).toHaveProperty('value', 'Test Submission')
|
||||
})
|
||||
|
||||
it('does not create a form submission for a non-existing form', async () => {
|
||||
const req = async () =>
|
||||
payload.create({
|
||||
collection: formSubmissionsSlug,
|
||||
data: {
|
||||
form: '659c7c2f98ffb5d83df9dadb',
|
||||
submissionData: [
|
||||
{
|
||||
field: 'name',
|
||||
value: 'Test Submission',
|
||||
},
|
||||
],
|
||||
},
|
||||
depth: 0,
|
||||
})
|
||||
|
||||
await expect(req).rejects.toThrow(ValidationError)
|
||||
})
|
||||
|
||||
it('replaces curly braces with data when using slate serializer', async () => {
|
||||
const mockName = 'Test Submission'
|
||||
const mockEmail = 'dev@payloadcms.com'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Payload } from '../../../packages/payload/src'
|
||||
import type { PayloadRequest } from '../../../packages/payload/src/express/types'
|
||||
|
||||
import { formsSlug, pagesSlug } from '../shared'
|
||||
import { formSubmissionsSlug, formsSlug, pagesSlug } from '../shared'
|
||||
|
||||
export const seed = async (payload: Payload): Promise<boolean> => {
|
||||
payload.logger.info('Seeding data...')
|
||||
@@ -53,6 +53,23 @@ export const seed = async (payload: Payload): Promise<boolean> => {
|
||||
},
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: formSubmissionsSlug,
|
||||
data: {
|
||||
form: formID,
|
||||
submissionData: [
|
||||
{
|
||||
field: 'name',
|
||||
value: 'Test Submission',
|
||||
},
|
||||
{
|
||||
field: 'email',
|
||||
value: 'tester@example.com',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: pagesSlug,
|
||||
data: {
|
||||
|
||||
@@ -51,6 +51,11 @@ export default buildConfigWithDefaults({
|
||||
label: 'og:title',
|
||||
},
|
||||
],
|
||||
fieldOverrides: {
|
||||
title: {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
generateTitle: (data: any) => `Website.com — ${data?.doc?.title?.value}`,
|
||||
generateDescription: ({ doc }: any) => doc?.excerpt?.value,
|
||||
generateURL: ({ doc, locale }: any) =>
|
||||
|
||||
@@ -78,6 +78,15 @@ describe('SEO Plugin', () => {
|
||||
await expect(metaTitle).toHaveValue('Website.com — Test Page')
|
||||
})
|
||||
|
||||
// todo: Re-enable this test once required attributes are fixed
|
||||
/* test('Title should be required as per custom override', async () => {
|
||||
const metaTitleClass = '#field-title'
|
||||
|
||||
const metaTitle = page.locator(metaTitleClass).nth(0)
|
||||
|
||||
await expect(metaTitle).toHaveAttribute('required', '')
|
||||
}) */
|
||||
|
||||
test('Indicator should be orangered and characters counted', async () => {
|
||||
const indicatorClass =
|
||||
'#field-meta > div > div.render-fields.render-fields--margins-small > div:nth-child(2) > div:nth-child(3) > div > div:nth-child(3) > div'
|
||||
|
||||
@@ -31,6 +31,9 @@ describe('SEO Plugin', () => {
|
||||
data: {
|
||||
title: 'Test page',
|
||||
slug: 'test-page',
|
||||
meta: {
|
||||
title: 'Test page',
|
||||
},
|
||||
},
|
||||
depth: 0,
|
||||
})
|
||||
|
||||
@@ -43,6 +43,7 @@ export const chainedRelSlug = 'chained'
|
||||
export const customIdSlug = 'custom-id'
|
||||
export const customIdNumberSlug = 'custom-id-number'
|
||||
export const polymorphicRelationshipsSlug = 'polymorphic-relationships'
|
||||
export const treeSlug = 'tree'
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
collections: [
|
||||
@@ -244,6 +245,20 @@ export default buildConfigWithDefaults({
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: treeSlug,
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'parent',
|
||||
type: 'relationship',
|
||||
relationTo: 'tree',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
onInit: async (payload) => {
|
||||
await payload.create({
|
||||
@@ -337,5 +352,20 @@ export default buildConfigWithDefaults({
|
||||
filteredRelation: filteredRelation.id,
|
||||
},
|
||||
})
|
||||
|
||||
const root = await payload.create({
|
||||
collection: 'tree',
|
||||
data: {
|
||||
text: 'root',
|
||||
},
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: 'tree',
|
||||
data: {
|
||||
text: 'sub',
|
||||
parent: root.id,
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -11,7 +11,6 @@ import type {
|
||||
} from './payload-types'
|
||||
|
||||
import payload from '../../packages/payload/src'
|
||||
import { mapAsync } from '../../packages/payload/src/utilities/mapAsync'
|
||||
import { devUser } from '../credentials'
|
||||
import { initPayloadTest } from '../helpers/configHelpers'
|
||||
import { RESTClient } from '../helpers/rest'
|
||||
@@ -22,6 +21,7 @@ import config, {
|
||||
defaultAccessRelSlug,
|
||||
relationSlug,
|
||||
slug,
|
||||
treeSlug,
|
||||
} from './config'
|
||||
|
||||
let apiUrl
|
||||
@@ -39,7 +39,7 @@ describe('Relationships', () => {
|
||||
beforeAll(async () => {
|
||||
const { serverURL } = await initPayloadTest({ __dirname, init: { local: false } })
|
||||
apiUrl = `${serverURL}/api`
|
||||
client = new RESTClient(config, { serverURL, defaultSlug: slug })
|
||||
client = new RESTClient(config, { defaultSlug: slug, serverURL })
|
||||
await client.login()
|
||||
|
||||
const response = await fetch(`${apiUrl}/users/login`, {
|
||||
@@ -127,8 +127,8 @@ describe('Relationships', () => {
|
||||
})
|
||||
|
||||
chained3 = await payload.update<ChainedRelation>({
|
||||
collection: chainedRelSlug,
|
||||
id: chained3.id,
|
||||
collection: chainedRelSlug,
|
||||
data: {
|
||||
name: 'chain3',
|
||||
relation: chained.id,
|
||||
@@ -154,13 +154,13 @@ describe('Relationships', () => {
|
||||
})
|
||||
|
||||
post = await createPost({
|
||||
relationField: relation.id,
|
||||
defaultAccessRelation: defaultAccessRelation.id,
|
||||
chainedRelation: chained.id,
|
||||
maxDepthRelation: relation.id,
|
||||
customIdRelation: customIdRelation.id,
|
||||
customIdNumberRelation: customIdNumberRelation.id,
|
||||
customIdRelation: customIdRelation.id,
|
||||
defaultAccessRelation: defaultAccessRelation.id,
|
||||
filteredRelation: filteredRelation.id,
|
||||
maxDepthRelation: relation.id,
|
||||
relationField: relation.id,
|
||||
})
|
||||
|
||||
await createPost() // Extra post to allow asserting totalDoc count
|
||||
@@ -193,15 +193,15 @@ describe('Relationships', () => {
|
||||
expect(docAfterUpdatingRel.filteredRelation).toMatchObject({ id: filteredRelation.id })
|
||||
|
||||
// Attempt to update post with a now filtered relation
|
||||
const { status, errors } = await client.update<Post>({
|
||||
const { errors, status } = await client.update<Post>({
|
||||
id: post.id,
|
||||
data: { filteredRelation: filteredRelation.id },
|
||||
})
|
||||
|
||||
expect(errors?.[0]).toMatchObject({
|
||||
name: 'ValidationError',
|
||||
message: expect.any(String),
|
||||
data: expect.anything(),
|
||||
message: expect.any(String),
|
||||
})
|
||||
expect(status).toEqual(400)
|
||||
})
|
||||
@@ -306,8 +306,8 @@ describe('Relationships', () => {
|
||||
|
||||
it('should allow update removing a relationship', async () => {
|
||||
const result = await client.update<Post>({
|
||||
slug,
|
||||
id: post.id,
|
||||
slug,
|
||||
data: {
|
||||
relationField: null,
|
||||
},
|
||||
@@ -445,8 +445,8 @@ describe('Relationships', () => {
|
||||
await payload.create({
|
||||
collection: 'screenings',
|
||||
data: {
|
||||
movie: movie.id,
|
||||
name: 'Pulp Fiction Screening',
|
||||
movie: movie.id,
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -485,8 +485,8 @@ describe('Relationships', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
await Promise.all(
|
||||
movieList.map((movie) => {
|
||||
return payload.create({
|
||||
movieList.map(async (movie) => {
|
||||
return await payload.create({
|
||||
collection: 'movies',
|
||||
data: {
|
||||
name: movie,
|
||||
@@ -527,8 +527,8 @@ describe('Relationships', () => {
|
||||
it('should allow clearing hasMany relationships', async () => {
|
||||
const fiveMovies = await payload.find({
|
||||
collection: 'movies',
|
||||
limit: 5,
|
||||
depth: 0,
|
||||
limit: 5,
|
||||
})
|
||||
|
||||
const movieIDs = fiveMovies.docs.map((doc) => doc.id)
|
||||
@@ -544,8 +544,8 @@ describe('Relationships', () => {
|
||||
expect(stanley.movies).toHaveLength(5)
|
||||
|
||||
const stanleyNeverMadeMovies = await payload.update({
|
||||
collection: 'directors',
|
||||
id: stanley.id,
|
||||
collection: 'directors',
|
||||
data: {
|
||||
movies: null,
|
||||
},
|
||||
@@ -554,6 +554,64 @@ describe('Relationships', () => {
|
||||
expect(stanleyNeverMadeMovies.movies).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Hierarchy', () => {
|
||||
it('finds 1 root item with equals', async () => {
|
||||
const {
|
||||
docs: [item],
|
||||
totalDocs: count,
|
||||
} = await payload.find({
|
||||
collection: treeSlug,
|
||||
where: {
|
||||
parent: { equals: null },
|
||||
},
|
||||
})
|
||||
expect(count).toBe(1)
|
||||
expect(item.text).toBe('root')
|
||||
})
|
||||
|
||||
it('finds 1 root item with exists', async () => {
|
||||
const {
|
||||
docs: [item],
|
||||
totalDocs: count,
|
||||
} = await payload.find({
|
||||
collection: treeSlug,
|
||||
where: {
|
||||
parent: { exists: false },
|
||||
},
|
||||
})
|
||||
expect(count).toBe(1)
|
||||
expect(item.text).toBe('root')
|
||||
})
|
||||
|
||||
it('finds 1 sub item with equals', async () => {
|
||||
const {
|
||||
docs: [item],
|
||||
totalDocs: count,
|
||||
} = await payload.find({
|
||||
collection: treeSlug,
|
||||
where: {
|
||||
parent: { not_equals: null },
|
||||
},
|
||||
})
|
||||
expect(count).toBe(1)
|
||||
expect(item.text).toBe('sub')
|
||||
})
|
||||
|
||||
it('finds 1 sub item with exists', async () => {
|
||||
const {
|
||||
docs: [item],
|
||||
totalDocs: count,
|
||||
} = await payload.find({
|
||||
collection: treeSlug,
|
||||
where: {
|
||||
parent: { exists: true },
|
||||
},
|
||||
})
|
||||
expect(count).toBe(1)
|
||||
expect(item.text).toBe('sub')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Creating', () => {
|
||||
@@ -562,18 +620,18 @@ describe('Relationships', () => {
|
||||
const req = {} as PayloadRequest
|
||||
req.transactionID = await payload.db.beginTransaction?.()
|
||||
const related = await payload.create({
|
||||
req,
|
||||
collection: relationSlug,
|
||||
data: {
|
||||
name: 'parent',
|
||||
},
|
||||
req,
|
||||
})
|
||||
const withRelation = await payload.create({
|
||||
req,
|
||||
collection: slug,
|
||||
data: {
|
||||
filteredRelation: related.id,
|
||||
},
|
||||
req,
|
||||
})
|
||||
|
||||
if (req.transactionID) {
|
||||
@@ -597,8 +655,8 @@ describe('Relationships', () => {
|
||||
collection: 'polymorphic-relationships',
|
||||
data: {
|
||||
polymorphic: {
|
||||
value: movie.id,
|
||||
relationTo: 'movies',
|
||||
value: movie.id,
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -650,9 +708,8 @@ async function createPost(overrides?: Partial<Post>) {
|
||||
}
|
||||
|
||||
async function clearDocs(): Promise<void> {
|
||||
const allDocs = await payload.find({ collection: slug, limit: 100 })
|
||||
const ids = allDocs.docs.map((doc) => doc.id)
|
||||
await mapAsync(ids, async (id) => {
|
||||
await payload.delete({ collection: slug, id })
|
||||
await payload.delete({
|
||||
collection: slug,
|
||||
where: { id: { exists: true } },
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user