Compare commits
1 Commits
fix/postgr
...
fix/disabl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9839655b07 |
@@ -94,7 +94,6 @@ The Relationship Field inherits all of the default options from the base [Field
|
||||
| **`allowCreate`** | Set to `false` if you'd like to disable the ability to create new documents from within the relationship field. |
|
||||
| **`allowEdit`** | Set to `false` if you'd like to disable the ability to edit documents from within the relationship field. |
|
||||
| **`sortOptions`** | Define a default sorting order for the options within a Relationship field's dropdown. [More](#sort-options) |
|
||||
| **`placeholder`** | Define a custom text or function to replace the generic default placeholder |
|
||||
| **`appearance`** | Set to `drawer` or `select` to change the behavior of the field. Defaults to `select`. |
|
||||
|
||||
### Sort Options
|
||||
@@ -150,7 +149,7 @@ The `filterOptions` property can either be a `Where` query, or a function return
|
||||
| `id` | The `id` of the current document being edited. Will be `undefined` during the `create` operation or when called on a `Filter` component within the list view. |
|
||||
| `relationTo` | The collection `slug` to filter against, limited to this field's `relationTo` property. |
|
||||
| `req` | The Payload Request, which contains references to `payload`, `user`, `locale`, and more. |
|
||||
| `siblingData` | An object containing document data that is scoped to only fields within the same parent of this field. Will be an empty object when called on a `Filter` component within the list view. |
|
||||
| `siblingData` | An object containing document data that is scoped to only fields within the same parent of this field. Will be an emprt object when called on a `Filter` component within the list view. |
|
||||
| `user` | An object containing the currently authenticated user. |
|
||||
|
||||
## Example
|
||||
|
||||
@@ -89,7 +89,6 @@ The Select Field inherits all of the default options from the base [Field Admin
|
||||
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`isClearable`** | Set to `true` if you'd like this field to be clearable within the Admin UI. |
|
||||
| **`isSortable`** | Set to `true` if you'd like this field to be sortable within the Admin UI using drag and drop. (Only works when `hasMany` is set to `true`) |
|
||||
| **`placeholder`** | Define a custom text or function to replace the generic default placeholder |
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ To install a Database Adapter, you can run **one** of the following commands:
|
||||
|
||||
#### 2. Copy Payload files into your Next.js app folder
|
||||
|
||||
Payload installs directly in your Next.js `/app` folder, and you'll need to place some files into that folder for Payload to run. You can copy these files from the [Blank Template](https://github.com/payloadcms/payload/tree/main/templates/blank/src/app/(payload)) on GitHub. Once you have the required Payload files in place in your `/app` folder, you should have something like this:
|
||||
Payload installs directly in your Next.js `/app` folder, and you'll need to place some files into that folder for Payload to run. You can copy these files from the [Blank Template](<https://github.com/payloadcms/payload/tree/main/templates/blank/src/app/(payload)>) on GitHub. Once you have the required Payload files in place in your `/app` folder, you should have something like this:
|
||||
|
||||
```plaintext
|
||||
app/
|
||||
|
||||
@@ -55,11 +55,10 @@ All collection `find` queries are paginated automatically. Responses are returne
|
||||
|
||||
All Payload APIs support the pagination controls below. With them, you can create paginated lists of documents within your application:
|
||||
|
||||
| Control | Default | Description |
|
||||
| ------------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `limit` | `10` | Limits the number of documents returned per page - set to `0` to show all documents, we automatically disabled pagination for you when `limit` is `0` for optimisation |
|
||||
| `pagination` | `true` | Set to `false` to disable pagination and return all documents |
|
||||
| `page` | `1` | Get a specific page number |
|
||||
| Control | Description |
|
||||
| ------- | --------------------------------------- |
|
||||
| `limit` | Limits the number of documents returned |
|
||||
| `page` | Get a specific page number |
|
||||
|
||||
### Disabling pagination within Local API
|
||||
|
||||
|
||||
@@ -84,7 +84,6 @@ pnpm add @payloadcms/storage-s3
|
||||
- The `config` object can be any [`S3ClientConfig`](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/s3) object (from [`@aws-sdk/client-s3`](https://github.com/aws/aws-sdk-js-v3)). _This is highly dependent on your AWS setup_. Check the AWS documentation for more information.
|
||||
- When enabled, this package will automatically set `disableLocalStorage` to `true` for each collection.
|
||||
- When deploying to Vercel, server uploads are limited with 4.5MB. Set `clientUploads` to `true` to do uploads directly on the client. You must allow CORS PUT method for the bucket to your website.
|
||||
- Configure `signedDownloads` (either globally of per-collection in `collections`) to use [presigned URLs](https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html) for files downloading. This can improve performance for large files (like videos) while still respecting your access control.
|
||||
|
||||
```ts
|
||||
import { s3Storage } from '@payloadcms/storage-s3'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload-monorepo",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/admin-bar",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "An admin bar for React apps using Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-sqlite",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "The officially supported SQLite database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-vercel-postgres",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "Vercel Postgres adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/drizzle",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "A library of shared functions used by different payload database adapters",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -42,36 +42,33 @@ export const migrate: DrizzleAdapter['migrate'] = async function migrate(
|
||||
limit: 0,
|
||||
sort: '-name',
|
||||
}))
|
||||
|
||||
if (migrationsInDB.find((m) => m.batch === -1)) {
|
||||
const { confirm: runMigrations } = await prompts(
|
||||
{
|
||||
name: 'confirm',
|
||||
type: 'confirm',
|
||||
initial: false,
|
||||
message:
|
||||
"It looks like you've run Payload in dev mode, meaning you've dynamically pushed changes to your database.\n\n" +
|
||||
"If you'd like to run migrations, data loss will occur. Would you like to proceed?",
|
||||
},
|
||||
{
|
||||
onCancel: () => {
|
||||
process.exit(0)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if (!runMigrations) {
|
||||
process.exit(0)
|
||||
}
|
||||
// ignore the dev migration so that the latest batch number increments correctly
|
||||
migrationsInDB = migrationsInDB.filter((m) => m.batch !== -1)
|
||||
}
|
||||
|
||||
if (Number(migrationsInDB?.[0]?.batch) > 0) {
|
||||
latestBatch = Number(migrationsInDB[0]?.batch)
|
||||
}
|
||||
}
|
||||
|
||||
if (migrationsInDB.find((m) => m.batch === -1)) {
|
||||
const { confirm: runMigrations } = await prompts(
|
||||
{
|
||||
name: 'confirm',
|
||||
type: 'confirm',
|
||||
initial: false,
|
||||
message:
|
||||
"It looks like you've run Payload in dev mode, meaning you've dynamically pushed changes to your database.\n\n" +
|
||||
"If you'd like to run migrations, data loss will occur. Would you like to proceed?",
|
||||
},
|
||||
{
|
||||
onCancel: () => {
|
||||
process.exit(0)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if (!runMigrations) {
|
||||
process.exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
const newBatch = latestBatch + 1
|
||||
|
||||
// Execute 'up' function for each migration sequentially
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
export type Groups =
|
||||
| 'addColumn'
|
||||
| 'addConstraint'
|
||||
| 'alterType'
|
||||
| 'createIndex'
|
||||
| 'createTable'
|
||||
| 'createType'
|
||||
| 'disableRowSecurity'
|
||||
| 'dropColumn'
|
||||
| 'dropConstraint'
|
||||
| 'dropIndex'
|
||||
| 'dropTable'
|
||||
| 'dropType'
|
||||
| 'notNull'
|
||||
| 'setDefault'
|
||||
|
||||
/**
|
||||
* Convert an "ADD COLUMN" statement to an "ALTER COLUMN" statement
|
||||
@@ -52,35 +44,6 @@ export const groupUpSQLStatements = (list: string[]): Record<Groups, string[]> =
|
||||
|
||||
notNull: 'NOT NULL',
|
||||
// example: ALTER TABLE "pages_blocks_my_block" ALTER COLUMN "person_id" SET NOT NULL;
|
||||
|
||||
createType: 'CREATE TYPE',
|
||||
// example: CREATE TYPE "public"."enum__pages_v_published_locale" AS ENUM('en', 'es');
|
||||
|
||||
alterType: 'ALTER TYPE',
|
||||
// example: ALTER TYPE "public"."enum_pages_blocks_cta" ADD VALUE 'copy';
|
||||
|
||||
createTable: 'CREATE TABLE',
|
||||
// example: CREATE TABLE IF NOT EXISTS "payload_locked_documents" (
|
||||
// "id" serial PRIMARY KEY NOT NULL,
|
||||
// "global_slug" varchar,
|
||||
// "updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
// "created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||
// );
|
||||
|
||||
disableRowSecurity: 'DISABLE ROW LEVEL SECURITY;',
|
||||
// example: ALTER TABLE "categories_rels" DISABLE ROW LEVEL SECURITY;
|
||||
|
||||
dropIndex: 'DROP INDEX IF EXISTS',
|
||||
// example: DROP INDEX IF EXISTS "pages_title_idx";
|
||||
|
||||
setDefault: 'SET DEFAULT',
|
||||
// example: ALTER TABLE "pages" ALTER COLUMN "_status" SET DEFAULT 'draft';
|
||||
|
||||
createIndex: 'INDEX IF NOT EXISTS',
|
||||
// example: CREATE INDEX IF NOT EXISTS "payload_locked_documents_global_slug_idx" ON "payload_locked_documents" USING btree ("global_slug");
|
||||
|
||||
dropType: 'DROP TYPE',
|
||||
// example: DROP TYPE "public"."enum__pages_v_published_locale";
|
||||
}
|
||||
|
||||
const result = Object.keys(groups).reduce((result, group: Groups) => {
|
||||
@@ -88,17 +51,7 @@ export const groupUpSQLStatements = (list: string[]): Record<Groups, string[]> =
|
||||
return result
|
||||
}, {}) as Record<Groups, string[]>
|
||||
|
||||
// push multi-line changes to a single grouping
|
||||
let isCreateTable = false
|
||||
|
||||
for (const line of list) {
|
||||
if (isCreateTable) {
|
||||
result.createTable.push(line)
|
||||
if (line.includes(');')) {
|
||||
isCreateTable = false
|
||||
}
|
||||
continue
|
||||
}
|
||||
Object.entries(groups).some(([key, value]) => {
|
||||
if (line.endsWith('NOT NULL;')) {
|
||||
// split up the ADD COLUMN and ALTER COLUMN NOT NULL statements
|
||||
|
||||
@@ -20,17 +20,6 @@ type Args = {
|
||||
req?: Partial<PayloadRequest>
|
||||
}
|
||||
|
||||
const runStatementGroup = async ({ adapter, db, debug, statements }) => {
|
||||
const addColumnsStatement = statements.join('\n')
|
||||
|
||||
if (debug) {
|
||||
adapter.payload.logger.info(debug)
|
||||
adapter.payload.logger.info(addColumnsStatement)
|
||||
}
|
||||
|
||||
await db.execute(sql.raw(addColumnsStatement))
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves upload and relationship columns from the join table and into the tables while moving data
|
||||
* This is done in the following order:
|
||||
@@ -47,6 +36,7 @@ const runStatementGroup = async ({ adapter, db, debug, statements }) => {
|
||||
*/
|
||||
export const migratePostgresV2toV3 = async ({ debug, payload, req }: Args) => {
|
||||
const adapter = payload.db as unknown as BasePostgresAdapter
|
||||
const db = await getTransaction(adapter, req)
|
||||
const dir = payload.db.migrationDir
|
||||
|
||||
// get the drizzle migrateUpSQL from drizzle using the last schema
|
||||
@@ -92,57 +82,14 @@ export const migratePostgresV2toV3 = async ({ debug, payload, req }: Args) => {
|
||||
|
||||
const sqlUpStatements = groupUpSQLStatements(generatedSQL)
|
||||
|
||||
const db = await getTransaction(adapter, req)
|
||||
const addColumnsStatement = sqlUpStatements.addColumn.join('\n')
|
||||
|
||||
await runStatementGroup({
|
||||
adapter,
|
||||
db,
|
||||
debug: debug ? 'CREATING TYPES' : null,
|
||||
statements: sqlUpStatements.createType,
|
||||
})
|
||||
if (debug) {
|
||||
payload.logger.info('CREATING NEW RELATIONSHIP COLUMNS')
|
||||
payload.logger.info(addColumnsStatement)
|
||||
}
|
||||
|
||||
await runStatementGroup({
|
||||
adapter,
|
||||
db,
|
||||
debug: debug ? 'ALTERING TYPES' : null,
|
||||
statements: sqlUpStatements.alterType,
|
||||
})
|
||||
|
||||
await runStatementGroup({
|
||||
adapter,
|
||||
db,
|
||||
debug: debug ? 'CREATING TABLES' : null,
|
||||
statements: sqlUpStatements.createTable,
|
||||
})
|
||||
|
||||
await runStatementGroup({
|
||||
adapter,
|
||||
db,
|
||||
debug: debug ? 'DISABLING ROW LEVEL SECURITY' : null,
|
||||
statements: sqlUpStatements.disableRowSecurity,
|
||||
})
|
||||
|
||||
await runStatementGroup({
|
||||
adapter,
|
||||
db,
|
||||
debug: debug ? 'CREATING NEW RELATIONSHIP COLUMNS' : null,
|
||||
statements: sqlUpStatements.addColumn,
|
||||
})
|
||||
|
||||
// SET DEFAULTS
|
||||
await runStatementGroup({
|
||||
adapter,
|
||||
db,
|
||||
debug: debug ? 'SETTING DEFAULTS' : null,
|
||||
statements: sqlUpStatements.setDefault,
|
||||
})
|
||||
|
||||
await runStatementGroup({
|
||||
adapter,
|
||||
db,
|
||||
debug: debug ? 'CREATING INDEXES' : null,
|
||||
statements: sqlUpStatements.createIndex,
|
||||
})
|
||||
await db.execute(sql.raw(addColumnsStatement))
|
||||
|
||||
for (const collection of payload.config.collections) {
|
||||
const tableName = adapter.tableNameMap.get(toSnakeCase(collection.slug))
|
||||
@@ -290,58 +237,52 @@ export const migratePostgresV2toV3 = async ({ debug, payload, req }: Args) => {
|
||||
}
|
||||
|
||||
// ADD CONSTRAINT
|
||||
await runStatementGroup({
|
||||
adapter,
|
||||
db,
|
||||
debug: debug ? 'ADDING CONSTRAINTS' : null,
|
||||
statements: sqlUpStatements.addConstraint,
|
||||
})
|
||||
const addConstraintsStatement = sqlUpStatements.addConstraint.join('\n')
|
||||
|
||||
if (debug) {
|
||||
payload.logger.info('ADDING CONSTRAINTS')
|
||||
payload.logger.info(addConstraintsStatement)
|
||||
}
|
||||
|
||||
await db.execute(sql.raw(addConstraintsStatement))
|
||||
|
||||
// NOT NULL
|
||||
await runStatementGroup({
|
||||
adapter,
|
||||
db,
|
||||
debug: debug ? 'NOT NULL CONSTRAINTS' : null,
|
||||
statements: sqlUpStatements.notNull,
|
||||
})
|
||||
const notNullStatements = sqlUpStatements.notNull.join('\n')
|
||||
|
||||
if (debug) {
|
||||
payload.logger.info('NOT NULL CONSTRAINTS')
|
||||
payload.logger.info(notNullStatements)
|
||||
}
|
||||
|
||||
await db.execute(sql.raw(notNullStatements))
|
||||
|
||||
// DROP TABLE
|
||||
await runStatementGroup({
|
||||
adapter,
|
||||
db,
|
||||
debug: debug ? 'DROPPING TABLES' : null,
|
||||
statements: sqlUpStatements.dropTable,
|
||||
})
|
||||
const dropTablesStatement = sqlUpStatements.dropTable.join('\n')
|
||||
|
||||
// DROP INDEX
|
||||
await runStatementGroup({
|
||||
adapter,
|
||||
db,
|
||||
debug: debug ? 'DROPPING INDEXES' : null,
|
||||
statements: sqlUpStatements.dropIndex,
|
||||
})
|
||||
if (debug) {
|
||||
payload.logger.info('DROPPING TABLES')
|
||||
payload.logger.info(dropTablesStatement)
|
||||
}
|
||||
|
||||
await db.execute(sql.raw(dropTablesStatement))
|
||||
|
||||
// DROP CONSTRAINT
|
||||
await runStatementGroup({
|
||||
adapter,
|
||||
db,
|
||||
debug: debug ? 'DROPPING CONSTRAINTS' : null,
|
||||
statements: sqlUpStatements.dropConstraint,
|
||||
})
|
||||
const dropConstraintsStatement = sqlUpStatements.dropConstraint.join('\n')
|
||||
|
||||
if (debug) {
|
||||
payload.logger.info('DROPPING CONSTRAINTS')
|
||||
payload.logger.info(dropConstraintsStatement)
|
||||
}
|
||||
|
||||
await db.execute(sql.raw(dropConstraintsStatement))
|
||||
|
||||
// DROP COLUMN
|
||||
await runStatementGroup({
|
||||
adapter,
|
||||
db,
|
||||
debug: debug ? 'DROPPING COLUMNS' : null,
|
||||
statements: sqlUpStatements.dropColumn,
|
||||
})
|
||||
const dropColumnsStatement = sqlUpStatements.dropColumn.join('\n')
|
||||
|
||||
// DROP TYPES
|
||||
await runStatementGroup({
|
||||
adapter,
|
||||
db,
|
||||
debug: debug ? 'DROPPING TYPES' : null,
|
||||
statements: sqlUpStatements.dropType,
|
||||
})
|
||||
if (debug) {
|
||||
payload.logger.info('DROPPING COLUMNS')
|
||||
payload.logger.info(dropColumnsStatement)
|
||||
}
|
||||
|
||||
await db.execute(sql.raw(dropColumnsStatement))
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ export const migrateRelationships = async ({
|
||||
${where} ORDER BY parent_id LIMIT 500 OFFSET ${offset * 500};
|
||||
`
|
||||
|
||||
paginationResult = await db.execute(sql.raw(`${paginationStatement}`))
|
||||
paginationResult = await adapter.drizzle.execute(sql.raw(`${paginationStatement}`))
|
||||
|
||||
if (paginationResult.rows.length === 0) {
|
||||
return
|
||||
@@ -72,7 +72,7 @@ export const migrateRelationships = async ({
|
||||
payload.logger.info(statement)
|
||||
}
|
||||
|
||||
const result = await db.execute(sql.raw(`${statement}`))
|
||||
const result = await adapter.drizzle.execute(sql.raw(`${statement}`))
|
||||
|
||||
const docsToResave: DocsToResave = {}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-nodemailer",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "Payload Nodemailer Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-resend",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "Payload Resend Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/graphql",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -10,11 +10,11 @@ export const buildPaginatedListType = (name, docType) =>
|
||||
hasNextPage: { type: new GraphQLNonNull(GraphQLBoolean) },
|
||||
hasPrevPage: { type: new GraphQLNonNull(GraphQLBoolean) },
|
||||
limit: { type: new GraphQLNonNull(GraphQLInt) },
|
||||
nextPage: { type: GraphQLInt },
|
||||
nextPage: { type: new GraphQLNonNull(GraphQLInt) },
|
||||
offset: { type: GraphQLInt },
|
||||
page: { type: new GraphQLNonNull(GraphQLInt) },
|
||||
pagingCounter: { type: new GraphQLNonNull(GraphQLInt) },
|
||||
prevPage: { type: GraphQLInt },
|
||||
prevPage: { type: new GraphQLNonNull(GraphQLInt) },
|
||||
totalDocs: { type: new GraphQLNonNull(GraphQLInt) },
|
||||
totalPages: { type: new GraphQLNonNull(GraphQLInt) },
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-react",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "The official React SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
// To prevent the flicker of stale data while the post message is being sent,
|
||||
// you can conditionally render loading UI based on the `isLoading` state
|
||||
|
||||
export const useLivePreview = <T extends Record<string, unknown>>(props: {
|
||||
export const useLivePreview = <T extends any>(props: {
|
||||
apiRoute?: string
|
||||
depth?: number
|
||||
initialData: T
|
||||
@@ -21,7 +21,7 @@ export const useLivePreview = <T extends Record<string, unknown>>(props: {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true)
|
||||
const hasSentReadyMessage = useRef<boolean>(false)
|
||||
|
||||
const onChange = useCallback((mergedData: T) => {
|
||||
const onChange = useCallback((mergedData) => {
|
||||
setData(mergedData)
|
||||
setIsLoading(false)
|
||||
}, [])
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
/* TODO: remove the following lines */
|
||||
"strict": false,
|
||||
"noUncheckedIndexedAccess": false,
|
||||
},
|
||||
"references": [{ "path": "../payload" }]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-vue",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "The official Vue SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { onMounted, onUnmounted, ref } from 'vue'
|
||||
*
|
||||
* {@link https://payloadcms.com/docs/live-preview/frontend View the documentation}
|
||||
*/
|
||||
export const useLivePreview = <T extends Record<string, unknown>>(props: {
|
||||
export const useLivePreview = <T>(props: {
|
||||
apiRoute?: string
|
||||
depth?: number
|
||||
initialData: T
|
||||
@@ -27,7 +27,7 @@ export const useLivePreview = <T extends Record<string, unknown>>(props: {
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
let subscription: (event: MessageEvent) => Promise<void> | void
|
||||
let subscription: (event: MessageEvent) => void
|
||||
|
||||
onMounted(() => {
|
||||
subscription = subscribe({
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
/* TODO: remove the following lines */
|
||||
"strict": false,
|
||||
"noUncheckedIndexedAccess": false,
|
||||
},
|
||||
"references": [{ "path": "../payload" }] // db-mongodb depends on payload
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "The official live preview JavaScript SDK for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/next",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -2,14 +2,7 @@
|
||||
|
||||
import type { PaginatedDocs, Where } from 'payload'
|
||||
|
||||
import {
|
||||
fieldBaseClass,
|
||||
Pill,
|
||||
ReactSelect,
|
||||
useConfig,
|
||||
useDocumentInfo,
|
||||
useTranslation,
|
||||
} from '@payloadcms/ui'
|
||||
import { fieldBaseClass, Pill, ReactSelect, useConfig, useTranslation } from '@payloadcms/ui'
|
||||
import { formatDate } from '@payloadcms/ui/shared'
|
||||
import { stringify } from 'qs-esm'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
@@ -44,8 +37,6 @@ export const SelectComparison: React.FC<Props> = (props) => {
|
||||
},
|
||||
} = useConfig()
|
||||
|
||||
const { hasPublishedDoc } = useDocumentInfo()
|
||||
|
||||
const [options, setOptions] = useState<
|
||||
{
|
||||
label: React.ReactNode | string
|
||||
@@ -118,10 +109,7 @@ export const SelectComparison: React.FC<Props> = (props) => {
|
||||
},
|
||||
published: {
|
||||
currentLabel: t('version:currentPublishedVersion'),
|
||||
// The latest published version does not necessarily equal the current published version,
|
||||
// because the latest published version might have been unpublished in the meantime.
|
||||
// Hence, we should only use the latest published version if there is a published document.
|
||||
latestVersion: hasPublishedDoc ? latestPublishedVersion : undefined,
|
||||
latestVersion: latestPublishedVersion,
|
||||
pillStyle: 'success',
|
||||
previousLabel: t('version:previouslyPublished'),
|
||||
},
|
||||
|
||||
@@ -85,34 +85,13 @@ export async function VersionsView(props: DocumentViewServerProps) {
|
||||
payload,
|
||||
status: 'draft',
|
||||
})
|
||||
const publishedDoc = await payload.count({
|
||||
collection: collectionSlug,
|
||||
depth: 0,
|
||||
overrideAccess: true,
|
||||
req,
|
||||
where: {
|
||||
id: {
|
||||
equals: id,
|
||||
},
|
||||
_status: {
|
||||
equals: 'published',
|
||||
},
|
||||
},
|
||||
latestPublishedVersion = await getLatestVersion({
|
||||
slug: collectionSlug,
|
||||
type: 'collection',
|
||||
parentID: id,
|
||||
payload,
|
||||
status: 'published',
|
||||
})
|
||||
|
||||
// If we pass a latestPublishedVersion to buildVersionColumns,
|
||||
// this will be used to display it as the "current published version".
|
||||
// However, the latest published version might have been unpublished in the meantime.
|
||||
// Hence, we should only pass the latest published version if there is a published document.
|
||||
latestPublishedVersion =
|
||||
publishedDoc.totalDocs > 0 &&
|
||||
(await getLatestVersion({
|
||||
slug: collectionSlug,
|
||||
type: 'collection',
|
||||
parentID: id,
|
||||
payload,
|
||||
status: 'published',
|
||||
}))
|
||||
}
|
||||
} catch (err) {
|
||||
logError({ err, payload })
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/payload-cloud",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "The official Payload Cloud plugin",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "Node, React, Headless CMS and Application Framework built on Next.js",
|
||||
"keywords": [
|
||||
"admin panel",
|
||||
|
||||
7
packages/payload/src/auth/isLocked.ts
Normal file
7
packages/payload/src/auth/isLocked.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
const isLocked = (date: number): boolean => {
|
||||
if (!date) {
|
||||
return false
|
||||
}
|
||||
return date > Date.now()
|
||||
}
|
||||
export default isLocked
|
||||
@@ -1,6 +0,0 @@
|
||||
export const isUserLocked = (date: number): boolean => {
|
||||
if (!date) {
|
||||
return false
|
||||
}
|
||||
return date > Date.now()
|
||||
}
|
||||
@@ -3,7 +3,6 @@ import type {
|
||||
AuthOperationsFromCollectionSlug,
|
||||
Collection,
|
||||
DataFromCollectionSlug,
|
||||
SanitizedCollectionConfig,
|
||||
} from '../../collections/config/types.js'
|
||||
import type { CollectionSlug } from '../../index.js'
|
||||
import type { PayloadRequest, Where } from '../../types/index.js'
|
||||
@@ -22,7 +21,7 @@ import { killTransaction } from '../../utilities/killTransaction.js'
|
||||
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields.js'
|
||||
import { getFieldsToSign } from '../getFieldsToSign.js'
|
||||
import { getLoginOptions } from '../getLoginOptions.js'
|
||||
import { isUserLocked } from '../isUserLocked.js'
|
||||
import isLocked from '../isLocked.js'
|
||||
import { jwtSign } from '../jwt.js'
|
||||
import { authenticateLocalStrategy } from '../strategies/local/authenticate.js'
|
||||
import { incrementLoginAttempts } from '../strategies/local/incrementLoginAttempts.js'
|
||||
@@ -43,32 +42,6 @@ export type Arguments<TSlug extends CollectionSlug> = {
|
||||
showHiddenFields?: boolean
|
||||
}
|
||||
|
||||
type CheckLoginPermissionArgs = {
|
||||
collection: SanitizedCollectionConfig
|
||||
loggingInWithUsername?: boolean
|
||||
req: PayloadRequest
|
||||
user: any
|
||||
}
|
||||
|
||||
export const checkLoginPermission = ({
|
||||
collection,
|
||||
loggingInWithUsername,
|
||||
req,
|
||||
user,
|
||||
}: CheckLoginPermissionArgs) => {
|
||||
if (!user) {
|
||||
throw new AuthenticationError(req.t, Boolean(loggingInWithUsername))
|
||||
}
|
||||
|
||||
if (collection.auth.verify && user._verified === false) {
|
||||
throw new UnverifiedEmail({ t: req.t })
|
||||
}
|
||||
|
||||
if (isUserLocked(new Date(user.lockUntil).getTime())) {
|
||||
throw new LockedAuth(req.t)
|
||||
}
|
||||
}
|
||||
|
||||
export const loginOperation = async <TSlug extends CollectionSlug>(
|
||||
incomingArgs: Arguments<TSlug>,
|
||||
): Promise<{ user: DataFromCollectionSlug<TSlug> } & Result> => {
|
||||
@@ -211,16 +184,21 @@ export const loginOperation = async <TSlug extends CollectionSlug>(
|
||||
where: whereConstraint,
|
||||
})
|
||||
|
||||
checkLoginPermission({
|
||||
collection: collectionConfig,
|
||||
loggingInWithUsername: Boolean(canLoginWithUsername && sanitizedUsername),
|
||||
req,
|
||||
user,
|
||||
})
|
||||
if (!user) {
|
||||
throw new AuthenticationError(req.t, Boolean(canLoginWithUsername && sanitizedUsername))
|
||||
}
|
||||
|
||||
if (args.collection.config.auth.verify && user._verified === false) {
|
||||
throw new UnverifiedEmail({ t: req.t })
|
||||
}
|
||||
|
||||
user.collection = collectionConfig.slug
|
||||
user._strategy = 'local-jwt'
|
||||
|
||||
if (isLocked(new Date(user.lockUntil).getTime())) {
|
||||
throw new LockedAuth(req.t)
|
||||
}
|
||||
|
||||
const authResult = await authenticateLocalStrategy({ doc: user, password })
|
||||
|
||||
user = sanitizeInternalFields(user)
|
||||
|
||||
@@ -1061,7 +1061,6 @@ export type SelectField = {
|
||||
} & Admin['components']
|
||||
isClearable?: boolean
|
||||
isSortable?: boolean
|
||||
placeholder?: LabelFunction | string
|
||||
} & Admin
|
||||
/**
|
||||
* Customize the SQL table name
|
||||
@@ -1094,7 +1093,7 @@ export type SelectField = {
|
||||
Omit<FieldBase, 'validate'>
|
||||
|
||||
export type SelectFieldClient = {
|
||||
admin?: AdminClient & Pick<SelectField['admin'], 'isClearable' | 'isSortable' | 'placeholder'>
|
||||
admin?: AdminClient & Pick<SelectField['admin'], 'isClearable' | 'isSortable'>
|
||||
} & FieldBaseClient &
|
||||
Pick<SelectField, 'hasMany' | 'interfaceName' | 'options' | 'type'>
|
||||
|
||||
@@ -1161,11 +1160,10 @@ type RelationshipAdmin = {
|
||||
>
|
||||
} & Admin['components']
|
||||
isSortable?: boolean
|
||||
placeholder?: LabelFunction | string
|
||||
} & Admin
|
||||
|
||||
type RelationshipAdminClient = AdminClient &
|
||||
Pick<RelationshipAdmin, 'allowCreate' | 'allowEdit' | 'appearance' | 'isSortable' | 'placeholder'>
|
||||
Pick<RelationshipAdmin, 'allowCreate' | 'allowEdit' | 'appearance' | 'isSortable'>
|
||||
|
||||
export type PolymorphicRelationshipField = {
|
||||
admin?: {
|
||||
|
||||
@@ -89,10 +89,6 @@ import { traverseFields } from './utilities/traverseFields.js'
|
||||
|
||||
export { default as executeAccess } from './auth/executeAccess.js'
|
||||
export { executeAuthStrategies } from './auth/executeAuthStrategies.js'
|
||||
export { extractAccessFromPermission } from './auth/extractAccessFromPermission.js'
|
||||
export { getAccessResults } from './auth/getAccessResults.js'
|
||||
export { getFieldsToSign } from './auth/getFieldsToSign.js'
|
||||
export { getLoginOptions } from './auth/getLoginOptions.js'
|
||||
|
||||
export interface GeneratedTypes {
|
||||
authUntyped: {
|
||||
@@ -981,12 +977,13 @@ interface RequestContext {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
export interface DatabaseAdapter extends BaseDatabaseAdapter {}
|
||||
export type { Payload, RequestContext }
|
||||
export { extractAccessFromPermission } from './auth/extractAccessFromPermission.js'
|
||||
export { getAccessResults } from './auth/getAccessResults.js'
|
||||
export { getFieldsToSign } from './auth/getFieldsToSign.js'
|
||||
export * from './auth/index.js'
|
||||
export { jwtSign } from './auth/jwt.js'
|
||||
export { accessOperation } from './auth/operations/access.js'
|
||||
export { forgotPasswordOperation } from './auth/operations/forgotPassword.js'
|
||||
export { initOperation } from './auth/operations/init.js'
|
||||
export { checkLoginPermission } from './auth/operations/login.js'
|
||||
export { loginOperation } from './auth/operations/login.js'
|
||||
export { logoutOperation } from './auth/operations/logout.js'
|
||||
export type { MeOperationResult } from './auth/operations/me.js'
|
||||
@@ -997,8 +994,6 @@ export { resetPasswordOperation } from './auth/operations/resetPassword.js'
|
||||
export { unlockOperation } from './auth/operations/unlock.js'
|
||||
export { verifyEmailOperation } from './auth/operations/verifyEmail.js'
|
||||
export { JWTAuthentication } from './auth/strategies/jwt.js'
|
||||
export { incrementLoginAttempts } from './auth/strategies/local/incrementLoginAttempts.js'
|
||||
export { resetLoginAttempts } from './auth/strategies/local/resetLoginAttempts.js'
|
||||
export type {
|
||||
AuthStrategyFunction,
|
||||
AuthStrategyFunctionArgs,
|
||||
@@ -1206,7 +1201,6 @@ export {
|
||||
MissingFile,
|
||||
NotFound,
|
||||
QueryError,
|
||||
UnverifiedEmail,
|
||||
ValidationError,
|
||||
ValidationErrorName,
|
||||
} from './errors/index.js'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-cloud-storage",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "The official cloud storage plugin for Payload CMS",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -26,7 +26,6 @@ export async function getFilePrefix({
|
||||
const files = await req.payload.find({
|
||||
collection: collection.slug,
|
||||
depth: 0,
|
||||
draft: true,
|
||||
limit: 1,
|
||||
pagination: false,
|
||||
where: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-form-builder",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "Form builder plugin for Payload CMS",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-import-export",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "Import-Export plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-multi-tenant",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "Multi Tenant plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -14,7 +14,6 @@ export const findTenantOptions = async ({
|
||||
useAsTitle,
|
||||
user,
|
||||
}: Args): Promise<PaginatedDocs> => {
|
||||
const isOrderable = payload.collections[tenantsCollectionSlug]?.config?.orderable || false
|
||||
return payload.find({
|
||||
collection: tenantsCollectionSlug,
|
||||
depth: 0,
|
||||
@@ -22,9 +21,8 @@ export const findTenantOptions = async ({
|
||||
overrideAccess: false,
|
||||
select: {
|
||||
[useAsTitle]: true,
|
||||
...(isOrderable ? { _order: true } : {}),
|
||||
},
|
||||
sort: isOrderable ? '_order' : useAsTitle,
|
||||
sort: useAsTitle,
|
||||
user,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-nested-docs",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "The official Nested Docs plugin for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-redirects",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "Redirects plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-search",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "Search plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-sentry",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "Sentry plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-seo",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "SEO plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-stripe",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "Stripe plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/richtext-lexical",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "The officially supported Lexical richtext adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -5,12 +5,6 @@ import type {
|
||||
SerializedParagraphNode,
|
||||
SerializedTextNode,
|
||||
SerializedLineBreakNode,
|
||||
SerializedHeadingNode,
|
||||
SerializedListItemNode,
|
||||
SerializedListNode,
|
||||
SerializedTableRowNode,
|
||||
SerializedTableNode,
|
||||
SerializedTableCellNode,
|
||||
} from '../../../nodeTypes.js'
|
||||
import { convertLexicalToPlaintext } from './sync/index.js'
|
||||
|
||||
@@ -57,83 +51,7 @@ function paragraphNode(children: DefaultNodeTypes[]): SerializedParagraphNode {
|
||||
}
|
||||
}
|
||||
|
||||
function headingNode(children: DefaultNodeTypes[]): SerializedHeadingNode {
|
||||
return {
|
||||
type: 'heading',
|
||||
children,
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
textFormat: 0,
|
||||
tag: 'h1',
|
||||
version: 1,
|
||||
}
|
||||
}
|
||||
|
||||
function listItemNode(children: DefaultNodeTypes[]): SerializedListItemNode {
|
||||
return {
|
||||
type: 'listitem',
|
||||
children,
|
||||
checked: false,
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
value: 0,
|
||||
version: 1,
|
||||
}
|
||||
}
|
||||
|
||||
function listNode(children: DefaultNodeTypes[]): SerializedListNode {
|
||||
return {
|
||||
type: 'list',
|
||||
children,
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
listType: 'bullet',
|
||||
start: 0,
|
||||
tag: 'ul',
|
||||
version: 1,
|
||||
}
|
||||
}
|
||||
|
||||
function tableNode(children: (DefaultNodeTypes | SerializedTableRowNode)[]): SerializedTableNode {
|
||||
return {
|
||||
type: 'table',
|
||||
children,
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
}
|
||||
}
|
||||
|
||||
function tableRowNode(
|
||||
children: (DefaultNodeTypes | SerializedTableCellNode)[],
|
||||
): SerializedTableRowNode {
|
||||
return {
|
||||
type: 'tablerow',
|
||||
children,
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
}
|
||||
}
|
||||
|
||||
function tableCellNode(children: DefaultNodeTypes[]): SerializedTableCellNode {
|
||||
return {
|
||||
type: 'tablecell',
|
||||
children,
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
headerState: 0,
|
||||
version: 1,
|
||||
}
|
||||
}
|
||||
|
||||
function rootNode(nodes: (DefaultNodeTypes | SerializedTableNode)[]): DefaultTypedEditorState {
|
||||
function rootNode(nodes: DefaultNodeTypes[]): DefaultTypedEditorState {
|
||||
return {
|
||||
root: {
|
||||
type: 'root',
|
||||
@@ -154,6 +72,7 @@ describe('convertLexicalToPlaintext', () => {
|
||||
data,
|
||||
})
|
||||
|
||||
console.log('plaintext', plaintext)
|
||||
expect(plaintext).toBe('Basic Text')
|
||||
})
|
||||
|
||||
@@ -192,67 +111,4 @@ describe('convertLexicalToPlaintext', () => {
|
||||
|
||||
expect(plaintext).toBe('Basic Text\tNext Line')
|
||||
})
|
||||
|
||||
it('ensure new lines are added between paragraphs', () => {
|
||||
const data: DefaultTypedEditorState = rootNode([
|
||||
paragraphNode([textNode('Basic text')]),
|
||||
paragraphNode([textNode('Next block-node')]),
|
||||
])
|
||||
|
||||
const plaintext = convertLexicalToPlaintext({
|
||||
data,
|
||||
})
|
||||
|
||||
expect(plaintext).toBe('Basic text\n\nNext block-node')
|
||||
})
|
||||
|
||||
it('ensure new lines are added between heading nodes', () => {
|
||||
const data: DefaultTypedEditorState = rootNode([
|
||||
headingNode([textNode('Basic text')]),
|
||||
headingNode([textNode('Next block-node')]),
|
||||
])
|
||||
|
||||
const plaintext = convertLexicalToPlaintext({
|
||||
data,
|
||||
})
|
||||
|
||||
expect(plaintext).toBe('Basic text\n\nNext block-node')
|
||||
})
|
||||
|
||||
it('ensure new lines are added between list items and lists', () => {
|
||||
const data: DefaultTypedEditorState = rootNode([
|
||||
listNode([listItemNode([textNode('First item')]), listItemNode([textNode('Second item')])]),
|
||||
listNode([listItemNode([textNode('Next list')])]),
|
||||
])
|
||||
|
||||
const plaintext = convertLexicalToPlaintext({
|
||||
data,
|
||||
})
|
||||
|
||||
expect(plaintext).toBe('First item\nSecond item\n\nNext list')
|
||||
})
|
||||
|
||||
it('ensure new lines are added between tables, table rows, and table cells', () => {
|
||||
const data: DefaultTypedEditorState = rootNode([
|
||||
tableNode([
|
||||
tableRowNode([
|
||||
tableCellNode([textNode('Cell 1, Row 1')]),
|
||||
tableCellNode([textNode('Cell 2, Row 1')]),
|
||||
]),
|
||||
tableRowNode([
|
||||
tableCellNode([textNode('Cell 1, Row 2')]),
|
||||
tableCellNode([textNode('Cell 2, Row 2')]),
|
||||
]),
|
||||
]),
|
||||
tableNode([tableRowNode([tableCellNode([textNode('Cell in Table 2')])])]),
|
||||
])
|
||||
|
||||
const plaintext = convertLexicalToPlaintext({
|
||||
data,
|
||||
})
|
||||
|
||||
expect(plaintext).toBe(
|
||||
'Cell 1, Row 1 | Cell 2, Row 1\nCell 1, Row 2 | Cell 2, Row 2\n\nCell in Table 2',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -86,25 +86,11 @@ export function convertLexicalNodesToPlaintext({
|
||||
}
|
||||
} else {
|
||||
// Default plaintext converter heuristic
|
||||
if (
|
||||
node.type === 'paragraph' ||
|
||||
node.type === 'heading' ||
|
||||
node.type === 'list' ||
|
||||
node.type === 'table'
|
||||
) {
|
||||
if (node.type === 'paragraph') {
|
||||
if (plainTextArray?.length) {
|
||||
// Only add a new line if there is already text in the array
|
||||
plainTextArray.push('\n\n')
|
||||
}
|
||||
} else if (node.type === 'listitem' || node.type === 'tablerow') {
|
||||
if (plainTextArray?.length) {
|
||||
// Only add a new line if there is already text in the array
|
||||
plainTextArray.push('\n')
|
||||
}
|
||||
} else if (node.type === 'tablecell') {
|
||||
if (plainTextArray?.length) {
|
||||
plainTextArray.push(' | ')
|
||||
}
|
||||
} else if (node.type === 'linebreak') {
|
||||
plainTextArray.push('\n')
|
||||
} else if (node.type === 'tab') {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/richtext-slate",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "The officially supported Slate richtext adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-azure",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "Payload storage adapter for Azure Blob Storage",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-gcs",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "Payload storage adapter for Google Cloud Storage",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-s3",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "Payload storage adapter for Amazon S3",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -12,8 +12,6 @@ import * as AWS from '@aws-sdk/client-s3'
|
||||
import { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'
|
||||
import { initClientUploads } from '@payloadcms/plugin-cloud-storage/utilities'
|
||||
|
||||
import type { SignedDownloadsConfig } from './staticHandler.js'
|
||||
|
||||
import { getGenerateSignedURLHandler } from './generateSignedURL.js'
|
||||
import { getGenerateURL } from './generateURL.js'
|
||||
import { getHandleDelete } from './handleDelete.js'
|
||||
@@ -26,7 +24,6 @@ export type S3StorageOptions = {
|
||||
*/
|
||||
|
||||
acl?: 'private' | 'public-read'
|
||||
|
||||
/**
|
||||
* Bucket name to upload files to.
|
||||
*
|
||||
@@ -42,15 +39,8 @@ export type S3StorageOptions = {
|
||||
/**
|
||||
* Collection options to apply the S3 adapter to.
|
||||
*/
|
||||
collections: Partial<
|
||||
Record<
|
||||
UploadCollectionSlug,
|
||||
| ({
|
||||
signedDownloads?: SignedDownloadsConfig
|
||||
} & Omit<CollectionOptions, 'adapter'>)
|
||||
| true
|
||||
>
|
||||
>
|
||||
collections: Partial<Record<UploadCollectionSlug, Omit<CollectionOptions, 'adapter'> | true>>
|
||||
|
||||
/**
|
||||
* AWS S3 client configuration. Highly dependent on your AWS setup.
|
||||
*
|
||||
@@ -71,10 +61,6 @@ export type S3StorageOptions = {
|
||||
* Default: true
|
||||
*/
|
||||
enabled?: boolean
|
||||
/**
|
||||
* Use pre-signed URLs for files downloading. Can be overriden per-collection.
|
||||
*/
|
||||
signedDownloads?: SignedDownloadsConfig
|
||||
}
|
||||
|
||||
type S3StoragePlugin = (storageS3Args: S3StorageOptions) => Plugin
|
||||
@@ -172,27 +158,9 @@ export const s3Storage: S3StoragePlugin =
|
||||
|
||||
function s3StorageInternal(
|
||||
getStorageClient: () => AWS.S3,
|
||||
{
|
||||
acl,
|
||||
bucket,
|
||||
clientUploads,
|
||||
collections,
|
||||
config = {},
|
||||
signedDownloads: topLevelSignedDownloads,
|
||||
}: S3StorageOptions,
|
||||
{ acl, bucket, clientUploads, config = {} }: S3StorageOptions,
|
||||
): Adapter {
|
||||
return ({ collection, prefix }): GeneratedAdapter => {
|
||||
const collectionStorageConfig = collections[collection.slug]
|
||||
|
||||
let signedDownloads: null | SignedDownloadsConfig =
|
||||
typeof collectionStorageConfig === 'object'
|
||||
? (collectionStorageConfig.signedDownloads ?? false)
|
||||
: null
|
||||
|
||||
if (signedDownloads === null) {
|
||||
signedDownloads = topLevelSignedDownloads ?? null
|
||||
}
|
||||
|
||||
return {
|
||||
name: 's3',
|
||||
clientUploads,
|
||||
@@ -205,12 +173,7 @@ function s3StorageInternal(
|
||||
getStorageClient,
|
||||
prefix,
|
||||
}),
|
||||
staticHandler: getHandler({
|
||||
bucket,
|
||||
collection,
|
||||
getStorageClient,
|
||||
signedDownloads: signedDownloads ?? false,
|
||||
}),
|
||||
staticHandler: getHandler({ bucket, collection, getStorageClient }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,23 +3,13 @@ import type { StaticHandler } from '@payloadcms/plugin-cloud-storage/types'
|
||||
import type { CollectionConfig } from 'payload'
|
||||
import type { Readable } from 'stream'
|
||||
|
||||
import { GetObjectCommand } from '@aws-sdk/client-s3'
|
||||
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
|
||||
import { getFilePrefix } from '@payloadcms/plugin-cloud-storage/utilities'
|
||||
import path from 'path'
|
||||
|
||||
export type SignedDownloadsConfig =
|
||||
| {
|
||||
/** @default 7200 */
|
||||
expiresIn?: number
|
||||
}
|
||||
| boolean
|
||||
|
||||
interface Args {
|
||||
bucket: string
|
||||
collection: CollectionConfig
|
||||
getStorageClient: () => AWS.S3
|
||||
signedDownloads?: SignedDownloadsConfig
|
||||
}
|
||||
|
||||
// Type guard for NodeJS.Readable streams
|
||||
@@ -50,12 +40,7 @@ const streamToBuffer = async (readableStream: any) => {
|
||||
return Buffer.concat(chunks)
|
||||
}
|
||||
|
||||
export const getHandler = ({
|
||||
bucket,
|
||||
collection,
|
||||
getStorageClient,
|
||||
signedDownloads,
|
||||
}: Args): StaticHandler => {
|
||||
export const getHandler = ({ bucket, collection, getStorageClient }: Args): StaticHandler => {
|
||||
return async (req, { params: { clientUploadContext, filename } }) => {
|
||||
let object: AWS.GetObjectOutput | undefined = undefined
|
||||
try {
|
||||
@@ -63,17 +48,6 @@ export const getHandler = ({
|
||||
|
||||
const key = path.posix.join(prefix, filename)
|
||||
|
||||
if (signedDownloads && !clientUploadContext) {
|
||||
const command = new GetObjectCommand({ Bucket: bucket, Key: key })
|
||||
const signedUrl = await getSignedUrl(
|
||||
// @ts-expect-error mismatch versions
|
||||
getStorageClient(),
|
||||
command,
|
||||
typeof signedDownloads === 'object' ? signedDownloads : { expiresIn: 7200 },
|
||||
)
|
||||
return Response.redirect(signedUrl)
|
||||
}
|
||||
|
||||
object = await getStorageClient().getObject({
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-uploadthing",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "Payload storage adapter for uploadthing",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -13,6 +13,6 @@ export const UploadthingClientUploadHandler = createClientUploadHandler({
|
||||
files: [file],
|
||||
})
|
||||
|
||||
return { key: res[0]?.key }
|
||||
return { key: res[0].key }
|
||||
},
|
||||
})
|
||||
|
||||
@@ -33,7 +33,7 @@ export const getHandler = ({ utApi }: Args): StaticHandler => {
|
||||
},
|
||||
]
|
||||
|
||||
if (collectionConfig?.upload.imageSizes) {
|
||||
if (collectionConfig.upload.imageSizes) {
|
||||
collectionConfig.upload.imageSizes.forEach(({ name }) => {
|
||||
or.push({
|
||||
[`sizes.${name}.filename`]: {
|
||||
@@ -58,7 +58,7 @@ export const getHandler = ({ utApi }: Args): StaticHandler => {
|
||||
return new Response(null, { status: 404, statusText: 'Not Found' })
|
||||
}
|
||||
|
||||
key = getKeyFromFilename(retrievedDoc, filename)!
|
||||
key = getKeyFromFilename(retrievedDoc, filename)
|
||||
}
|
||||
|
||||
if (!key) {
|
||||
@@ -97,7 +97,7 @@ export const getHandler = ({ utApi }: Args): StaticHandler => {
|
||||
headers: new Headers({
|
||||
'Content-Length': String(blob.size),
|
||||
'Content-Type': blob.type,
|
||||
ETag: objectEtag!,
|
||||
ETag: objectEtag,
|
||||
}),
|
||||
status: 200,
|
||||
})
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
/* TODO: remove the following lines */
|
||||
"strict": false,
|
||||
},
|
||||
"references": [{ "path": "../payload" }, { "path": "../plugin-cloud-storage" }]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-vercel-blob",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"description": "Payload storage adapter for Vercel Blob Storage",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/translations",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/ui",
|
||||
"version": "3.37.0",
|
||||
"version": "3.36.1",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
import React from 'react'
|
||||
|
||||
import './index.scss'
|
||||
import { Link } from '../../elements/Link/index.js'
|
||||
import { useConfig } from '../../providers/Config/index.js'
|
||||
import { useDocumentInfo } from '../../providers/DocumentInfo/index.js'
|
||||
import { formatAdminURL } from '../../utilities/formatAdminURL.js'
|
||||
import { sanitizeID } from '../../utilities/sanitizeID.js'
|
||||
import { useDrawerDepth } from '../Drawer/index.js'
|
||||
|
||||
const baseClass = 'id-label'
|
||||
|
||||
@@ -15,26 +10,10 @@ export const IDLabel: React.FC<{ className?: string; id: string; prefix?: string
|
||||
id,
|
||||
className,
|
||||
prefix = 'ID:',
|
||||
}) => {
|
||||
const {
|
||||
config: {
|
||||
routes: { admin: adminRoute },
|
||||
},
|
||||
} = useConfig()
|
||||
|
||||
const { collectionSlug, globalSlug } = useDocumentInfo()
|
||||
const drawerDepth = useDrawerDepth()
|
||||
|
||||
const docPath = formatAdminURL({
|
||||
adminRoute,
|
||||
path: `/${collectionSlug ? `collections/${collectionSlug}` : `globals/${globalSlug}`}/${id}`,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={[baseClass, className].filter(Boolean).join(' ')} title={id}>
|
||||
{prefix}
|
||||
|
||||
{drawerDepth > 1 ? <Link href={docPath}>{sanitizeID(id)}</Link> : sanitizeID(id)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}) => (
|
||||
<div className={[baseClass, className].filter(Boolean).join(' ')} title={id}>
|
||||
{prefix}
|
||||
|
||||
{sanitizeID(id)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -84,6 +84,7 @@ const SelectAdapter: React.FC<ReactSelectAdapterProps> = (props) => {
|
||||
captureMenuScroll
|
||||
customProps={customProps}
|
||||
isLoading={isLoading}
|
||||
placeholder={getTranslation(placeholder, i18n)}
|
||||
{...props}
|
||||
className={classes}
|
||||
classNamePrefix="rs"
|
||||
@@ -112,7 +113,6 @@ const SelectAdapter: React.FC<ReactSelectAdapterProps> = (props) => {
|
||||
onMenuClose={onMenuClose}
|
||||
onMenuOpen={onMenuOpen}
|
||||
options={options}
|
||||
placeholder={getTranslation(placeholder, i18n)}
|
||||
styles={styles}
|
||||
unstyled={true}
|
||||
value={value}
|
||||
@@ -160,6 +160,7 @@ const SelectAdapter: React.FC<ReactSelectAdapterProps> = (props) => {
|
||||
<CreatableSelect
|
||||
captureMenuScroll
|
||||
isLoading={isLoading}
|
||||
placeholder={getTranslation(placeholder, i18n)}
|
||||
{...props}
|
||||
className={classes}
|
||||
classNamePrefix="rs"
|
||||
@@ -190,7 +191,6 @@ const SelectAdapter: React.FC<ReactSelectAdapterProps> = (props) => {
|
||||
onMenuClose={onMenuClose}
|
||||
onMenuOpen={onMenuOpen}
|
||||
options={options}
|
||||
placeholder={getTranslation(placeholder, i18n)}
|
||||
styles={styles}
|
||||
unstyled={true}
|
||||
value={value}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { LabelFunction } from 'payload'
|
||||
import type { CommonProps, GroupBase, Props as ReactSelectStateManagerProps } from 'react-select'
|
||||
|
||||
import type { DocumentDrawerProps } from '../DocumentDrawer/types.js'
|
||||
import type { DocumentDrawerProps, UseDocumentDrawer } from '../DocumentDrawer/types.js'
|
||||
|
||||
type CustomSelectProps = {
|
||||
disableKeyDown?: boolean
|
||||
disableMouseDown?: boolean
|
||||
DocumentDrawerToggler?: ReturnType<UseDocumentDrawer>[1]
|
||||
draggableProps?: any
|
||||
droppableRef?: React.RefObject<HTMLDivElement | null>
|
||||
editableProps?: (
|
||||
@@ -14,11 +14,10 @@ type CustomSelectProps = {
|
||||
selectProps: ReactSelectStateManagerProps,
|
||||
) => any
|
||||
onDelete?: DocumentDrawerProps['onDelete']
|
||||
onDocumentOpen?: (args: {
|
||||
onDocumentDrawerOpen?: (args: {
|
||||
collectionSlug: string
|
||||
hasReadPermission: boolean
|
||||
id: number | string
|
||||
openInNewTab?: boolean
|
||||
}) => void
|
||||
onDuplicate?: DocumentDrawerProps['onSave']
|
||||
onSave?: DocumentDrawerProps['onSave']
|
||||
@@ -102,7 +101,7 @@ export type ReactSelectAdapterProps = {
|
||||
onMenuOpen?: () => void
|
||||
onMenuScrollToBottom?: () => void
|
||||
options: Option[] | OptionGroup[]
|
||||
placeholder?: LabelFunction | string
|
||||
placeholder?: string
|
||||
showError?: boolean
|
||||
value?: Option | Option[]
|
||||
}
|
||||
|
||||
@@ -117,19 +117,7 @@ export const Status: React.FC = () => {
|
||||
setUnpublishedVersionCount(0)
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
const json = await res.json()
|
||||
if (json.errors?.[0]?.message) {
|
||||
toast.error(json.errors[0].message)
|
||||
} else if (json.error) {
|
||||
toast.error(json.error)
|
||||
} else {
|
||||
toast.error(t('error:unPublishingDocument'))
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
} catch (err) {
|
||||
toast.error(t('error:unPublishingDocument'))
|
||||
}
|
||||
toast.error(t('error:unPublishingDocument'))
|
||||
}
|
||||
},
|
||||
[
|
||||
@@ -166,7 +154,6 @@ export const Status: React.FC = () => {
|
||||
<Button
|
||||
buttonStyle="none"
|
||||
className={`${baseClass}__action`}
|
||||
id={`action-unpublish`}
|
||||
onClick={() => toggleModal(unPublishModalSlug)}
|
||||
>
|
||||
{t('version:unpublish')}
|
||||
|
||||
@@ -23,7 +23,7 @@ const maxResultsPerRequest = 10
|
||||
export const RelationshipFilter: React.FC<Props> = (props) => {
|
||||
const {
|
||||
disabled,
|
||||
field: { admin: { isSortable, placeholder } = {}, hasMany, relationTo },
|
||||
field: { admin: { isSortable } = {}, hasMany, relationTo },
|
||||
filterOptions,
|
||||
onChange,
|
||||
value,
|
||||
@@ -324,7 +324,7 @@ export const RelationshipFilter: React.FC<Props> = (props) => {
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [i18n, relationTo, debouncedSearch, filterOptions])
|
||||
}, [i18n, relationTo, debouncedSearch])
|
||||
|
||||
/**
|
||||
* Load any other options that might exist in the value that were not loaded already
|
||||
@@ -412,7 +412,7 @@ export const RelationshipFilter: React.FC<Props> = (props) => {
|
||||
onInputChange={handleInputChange}
|
||||
onMenuScrollToBottom={handleScrollToBottom}
|
||||
options={options}
|
||||
placeholder={placeholder}
|
||||
placeholder={t('general:selectValue')}
|
||||
value={valueToRender}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -11,9 +11,6 @@ import { formatOptions } from './formatOptions.js'
|
||||
|
||||
export const Select: React.FC<Props> = ({
|
||||
disabled,
|
||||
field: {
|
||||
admin: { placeholder },
|
||||
},
|
||||
isClearable,
|
||||
onChange,
|
||||
operator,
|
||||
@@ -80,7 +77,6 @@ export const Select: React.FC<Props> = ({
|
||||
isMulti={isMulti}
|
||||
onChange={onSelect}
|
||||
options={options.map((option) => ({ ...option, label: getTranslation(option.label, i18n) }))}
|
||||
placeholder={placeholder}
|
||||
value={valueToRender}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { LabelFunction, Option, SelectFieldClient } from 'payload'
|
||||
import type { Option, SelectFieldClient } from 'payload'
|
||||
|
||||
import type { DefaultFilterProps } from '../types.js'
|
||||
|
||||
@@ -7,6 +7,5 @@ export type SelectFilterProps = {
|
||||
readonly isClearable?: boolean
|
||||
readonly onChange: (val: string) => void
|
||||
readonly options: Option[]
|
||||
readonly placeholder?: LabelFunction | string
|
||||
readonly value: string
|
||||
} & DefaultFilterProps
|
||||
|
||||
@@ -141,11 +141,6 @@ export const Condition: React.FC<Props> = (props) => {
|
||||
<div className={`${baseClass}__field`}>
|
||||
<ReactSelect
|
||||
disabled={disabled}
|
||||
filterOption={(option, inputValue) =>
|
||||
((option?.data?.plainTextLabel as string) || option.label)
|
||||
.toLowerCase()
|
||||
.includes(inputValue.toLowerCase())
|
||||
}
|
||||
isClearable={false}
|
||||
onChange={handleFieldChange}
|
||||
options={reducedFields.filter((field) => !field.field.admin.disableListFilter)}
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { ClientField } from 'payload'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { fieldIsHiddenOrDisabled, fieldIsID, tabHasName } from 'payload/shared'
|
||||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
|
||||
import type { ReducedField } from './types.js'
|
||||
|
||||
@@ -100,7 +99,11 @@ export const reduceFields = ({
|
||||
return reduced
|
||||
}
|
||||
|
||||
if ((field.type === 'group' || field.type === 'array') && 'fields' in field) {
|
||||
if (
|
||||
(field.type === 'group' || field.type === 'array') &&
|
||||
'fields' in field &&
|
||||
!field.admin.disableListFilter
|
||||
) {
|
||||
const translatedLabel = getTranslation(field.label || '', i18n)
|
||||
|
||||
const labelWithPrefix = labelPrefix
|
||||
@@ -153,15 +156,10 @@ export const reduceFields = ({
|
||||
})
|
||||
: localizedLabel
|
||||
|
||||
// React elements in filter options are not searchable in React Select
|
||||
// Extract plain text to make them filterable in dropdowns
|
||||
const textFromLabel = extractTextFromReactNode(formattedLabel)
|
||||
|
||||
const fieldPath = pathPrefix ? createNestedClientFieldPath(pathPrefix, field) : field.name
|
||||
|
||||
const formattedField: ReducedField = {
|
||||
label: formattedLabel,
|
||||
plainTextLabel: textFromLabel,
|
||||
value: fieldPath,
|
||||
...fieldTypes[field.type],
|
||||
field,
|
||||
@@ -174,29 +172,3 @@ export const reduceFields = ({
|
||||
return reduced
|
||||
}, [])
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts plain text content from a React node by removing HTML tags.
|
||||
* Used to make React elements searchable in filter dropdowns.
|
||||
*/
|
||||
const extractTextFromReactNode = (reactNode: React.ReactNode): string => {
|
||||
if (!reactNode) {
|
||||
return ''
|
||||
}
|
||||
if (typeof reactNode === 'string') {
|
||||
return reactNode
|
||||
}
|
||||
|
||||
const html = renderToStaticMarkup(reactNode)
|
||||
|
||||
// Handle different environments (server vs browser)
|
||||
if (typeof document !== 'undefined') {
|
||||
// Browser environment - use actual DOM
|
||||
const div = document.createElement('div')
|
||||
div.innerHTML = html
|
||||
return div.textContent || ''
|
||||
} else {
|
||||
// Server environment - use regex to strip HTML tags
|
||||
return html.replace(/<[^>]*>/g, '')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ export type ReducedField = {
|
||||
label: string
|
||||
value: Operator
|
||||
}[]
|
||||
plainTextLabel?: string
|
||||
value: Value
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ import type {
|
||||
} from 'payload'
|
||||
|
||||
import { dequal } from 'dequal/lite'
|
||||
import { formatAdminURL, wordBoundariesRegex } from 'payload/shared'
|
||||
import { wordBoundariesRegex } from 'payload/shared'
|
||||
import * as qs from 'qs-esm'
|
||||
import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'
|
||||
import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'
|
||||
|
||||
import type { DocumentDrawerProps } from '../../elements/DocumentDrawer/types.js'
|
||||
import type { ListDrawerProps } from '../../elements/ListDrawer/types.js'
|
||||
@@ -56,7 +56,6 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
|
||||
className,
|
||||
description,
|
||||
isSortable = true,
|
||||
placeholder,
|
||||
sortOptions,
|
||||
} = {},
|
||||
hasMany,
|
||||
@@ -83,7 +82,7 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
|
||||
const hasMultipleRelations = Array.isArray(relationTo)
|
||||
|
||||
const [currentlyOpenRelationship, setCurrentlyOpenRelationship] = useState<
|
||||
Parameters<ReactSelectAdapterProps['customProps']['onDocumentOpen']>[0]
|
||||
Parameters<ReactSelectAdapterProps['customProps']['onDocumentDrawerOpen']>[0]
|
||||
>({
|
||||
id: undefined,
|
||||
collectionSlug: undefined,
|
||||
@@ -631,29 +630,16 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
|
||||
return r.test(labelString.slice(-breakApartThreshold))
|
||||
}, [])
|
||||
|
||||
const onDocumentOpen = useCallback<ReactSelectAdapterProps['customProps']['onDocumentOpen']>(
|
||||
({ id, collectionSlug, hasReadPermission, openInNewTab }) => {
|
||||
if (openInNewTab) {
|
||||
if (hasReadPermission && id && collectionSlug) {
|
||||
const docUrl = formatAdminURL({
|
||||
adminRoute: config.routes.admin,
|
||||
path: `/collections/${collectionSlug}/${id}`,
|
||||
})
|
||||
|
||||
window.open(docUrl, '_blank')
|
||||
}
|
||||
} else {
|
||||
openDrawerWhenRelationChanges.current = true
|
||||
|
||||
setCurrentlyOpenRelationship({
|
||||
id,
|
||||
collectionSlug,
|
||||
hasReadPermission,
|
||||
})
|
||||
}
|
||||
},
|
||||
[setCurrentlyOpenRelationship, config.routes.admin],
|
||||
)
|
||||
const onDocumentDrawerOpen = useCallback<
|
||||
ReactSelectAdapterProps['customProps']['onDocumentDrawerOpen']
|
||||
>(({ id, collectionSlug, hasReadPermission }) => {
|
||||
openDrawerWhenRelationChanges.current = true
|
||||
setCurrentlyOpenRelationship({
|
||||
id,
|
||||
collectionSlug,
|
||||
hasReadPermission,
|
||||
})
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (openDrawerWhenRelationChanges.current) {
|
||||
@@ -710,7 +696,7 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
|
||||
customProps={{
|
||||
disableKeyDown: isDrawerOpen || isListDrawerOpen,
|
||||
disableMouseDown: isDrawerOpen || isListDrawerOpen,
|
||||
onDocumentOpen,
|
||||
onDocumentDrawerOpen,
|
||||
onSave,
|
||||
}}
|
||||
disabled={readOnly || disabled || isDrawerOpen || isListDrawerOpen}
|
||||
@@ -793,7 +779,6 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
|
||||
})
|
||||
}}
|
||||
options={options}
|
||||
placeholder={placeholder}
|
||||
showError={showError}
|
||||
value={valueToRender ?? null}
|
||||
/>
|
||||
|
||||
@@ -25,7 +25,7 @@ export const MultiValueLabel: React.FC<
|
||||
> = (props) => {
|
||||
const {
|
||||
data: { allowEdit, label, relationTo, value },
|
||||
selectProps: { customProps: { draggableProps, onDocumentOpen } = {} } = {},
|
||||
selectProps: { customProps: { draggableProps, onDocumentDrawerOpen } = {} } = {},
|
||||
} = props
|
||||
|
||||
const { permissions } = useAuth()
|
||||
@@ -49,13 +49,12 @@ export const MultiValueLabel: React.FC<
|
||||
<button
|
||||
aria-label={`Edit ${label}`}
|
||||
className={`${baseClass}__drawer-toggler`}
|
||||
onClick={(event) => {
|
||||
onClick={() => {
|
||||
setShowTooltip(false)
|
||||
onDocumentOpen({
|
||||
onDocumentDrawerOpen({
|
||||
id: value,
|
||||
collectionSlug: relationTo,
|
||||
hasReadPermission,
|
||||
openInNewTab: event.metaKey || event.ctrlKey,
|
||||
})
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
|
||||
@@ -26,7 +26,7 @@ export const SingleValue: React.FC<
|
||||
const {
|
||||
children,
|
||||
data: { allowEdit, label, relationTo, value },
|
||||
selectProps: { customProps: { onDocumentOpen } = {} } = {},
|
||||
selectProps: { customProps: { onDocumentDrawerOpen } = {} } = {},
|
||||
} = props
|
||||
|
||||
const [showTooltip, setShowTooltip] = useState(false)
|
||||
@@ -44,13 +44,12 @@ export const SingleValue: React.FC<
|
||||
<button
|
||||
aria-label={t('general:editLabel', { label })}
|
||||
className={`${baseClass}__drawer-toggler`}
|
||||
onClick={(event) => {
|
||||
onClick={() => {
|
||||
setShowTooltip(false)
|
||||
onDocumentOpen({
|
||||
onDocumentDrawerOpen({
|
||||
id: value,
|
||||
collectionSlug: relationTo,
|
||||
hasReadPermission,
|
||||
openInNewTab: event.metaKey || event.ctrlKey,
|
||||
})
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import type { LabelFunction, OptionObject, StaticDescription, StaticLabel } from 'payload'
|
||||
import type { OptionObject, StaticDescription, StaticLabel } from 'payload'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import React from 'react'
|
||||
@@ -33,7 +33,6 @@ export type SelectInputProps = {
|
||||
readonly onInputChange?: ReactSelectAdapterProps['onInputChange']
|
||||
readonly options?: OptionObject[]
|
||||
readonly path: string
|
||||
readonly placeholder?: LabelFunction | string
|
||||
readonly readOnly?: boolean
|
||||
readonly required?: boolean
|
||||
readonly showError?: boolean
|
||||
@@ -59,7 +58,6 @@ export const SelectInput: React.FC<SelectInputProps> = (props) => {
|
||||
onInputChange,
|
||||
options,
|
||||
path,
|
||||
placeholder,
|
||||
readOnly,
|
||||
required,
|
||||
showError,
|
||||
@@ -127,7 +125,6 @@ export const SelectInput: React.FC<SelectInputProps> = (props) => {
|
||||
...option,
|
||||
label: getTranslation(option.label, i18n),
|
||||
}))}
|
||||
placeholder={placeholder}
|
||||
showError={showError}
|
||||
value={valueToRender as OptionObject}
|
||||
/>
|
||||
|
||||
@@ -38,7 +38,6 @@ const SelectFieldComponent: SelectFieldClientComponent = (props) => {
|
||||
description,
|
||||
isClearable = true,
|
||||
isSortable = true,
|
||||
placeholder,
|
||||
} = {} as SelectFieldClientProps['field']['admin'],
|
||||
hasMany = false,
|
||||
label,
|
||||
@@ -119,7 +118,6 @@ const SelectFieldComponent: SelectFieldClientComponent = (props) => {
|
||||
onChange={onChange}
|
||||
options={options}
|
||||
path={path}
|
||||
placeholder={placeholder}
|
||||
readOnly={readOnly || disabled}
|
||||
required={required}
|
||||
showError={showError}
|
||||
|
||||
@@ -456,10 +456,6 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
||||
value: row.blockType,
|
||||
}
|
||||
|
||||
if (addedByServer) {
|
||||
state[fieldKey].addedByServer = addedByServer
|
||||
}
|
||||
|
||||
if (includeSchema) {
|
||||
state[fieldKey].fieldSchema = block.fields.find(
|
||||
(blockField) => 'name' in blockField && blockField.name === 'blockType',
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import { placeholderCollectionSlug } from '../slugs.js'
|
||||
|
||||
export const Placeholder: CollectionConfig = {
|
||||
slug: placeholderCollectionSlug,
|
||||
fields: [
|
||||
{
|
||||
name: 'defaultSelect',
|
||||
type: 'select',
|
||||
options: [
|
||||
{
|
||||
label: 'Option 1',
|
||||
value: 'option1',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'placeholderSelect',
|
||||
type: 'select',
|
||||
options: [{ label: 'Option 1', value: 'option1' }],
|
||||
admin: {
|
||||
placeholder: 'Custom placeholder',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'defaultRelationship',
|
||||
type: 'relationship',
|
||||
relationTo: 'posts',
|
||||
},
|
||||
{
|
||||
name: 'placeholderRelationship',
|
||||
type: 'relationship',
|
||||
relationTo: 'posts',
|
||||
admin: {
|
||||
placeholder: 'Custom placeholder',
|
||||
},
|
||||
},
|
||||
],
|
||||
versions: true,
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import path from 'path'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
||||
import { Array } from './collections/Array.js'
|
||||
import { BaseListFilter } from './collections/BaseListFilter.js'
|
||||
@@ -18,7 +19,6 @@ import { CollectionHidden } from './collections/Hidden.js'
|
||||
import { ListDrawer } from './collections/ListDrawer.js'
|
||||
import { CollectionNoApiView } from './collections/NoApiView.js'
|
||||
import { CollectionNotInView } from './collections/NotInView.js'
|
||||
import { Placeholder } from './collections/Placeholder.js'
|
||||
import { Posts } from './collections/Posts.js'
|
||||
import { UploadCollection } from './collections/Upload.js'
|
||||
import { UploadTwoCollection } from './collections/UploadTwo.js'
|
||||
@@ -43,8 +43,7 @@ import {
|
||||
protectedCustomNestedViewPath,
|
||||
publicCustomViewPath,
|
||||
} from './shared.js'
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
admin: {
|
||||
importMap: {
|
||||
@@ -166,7 +165,6 @@ export default buildConfigWithDefaults({
|
||||
BaseListFilter,
|
||||
with300Documents,
|
||||
ListDrawer,
|
||||
Placeholder,
|
||||
],
|
||||
globals: [
|
||||
GlobalHidden,
|
||||
|
||||
@@ -360,65 +360,6 @@ describe('Document View', () => {
|
||||
|
||||
await expect.poll(() => drawer2Left > drawerLeft).toBe(true)
|
||||
})
|
||||
|
||||
test('document drawer displays a link to document', async () => {
|
||||
await navigateToDoc(page, postsUrl)
|
||||
|
||||
// change the relationship to a document which is a different one than the current one
|
||||
await page.locator('#field-relationship').click()
|
||||
await page.locator('#field-relationship .rs__option').nth(2).click()
|
||||
await saveDocAndAssert(page)
|
||||
|
||||
// open relationship drawer
|
||||
await page
|
||||
.locator('.field-type.relationship .relationship--single-value__drawer-toggler')
|
||||
.click()
|
||||
|
||||
const drawer1Content = page.locator('[id^=doc-drawer_posts_1_] .drawer__content')
|
||||
await expect(drawer1Content).toBeVisible()
|
||||
|
||||
// modify the title to trigger the leave page modal
|
||||
await page.locator('.drawer__content #field-title').fill('New Title')
|
||||
|
||||
// Open link in a new tab by holding down the Meta or Control key
|
||||
const documentLink = page.locator('.id-label a')
|
||||
const documentId = String(await documentLink.textContent())
|
||||
await documentLink.click()
|
||||
|
||||
const leavePageModal = page.locator('#leave-without-saving #confirm-action').last()
|
||||
await expect(leavePageModal).toBeVisible()
|
||||
|
||||
await leavePageModal.click()
|
||||
await page.waitForURL(postsUrl.edit(documentId))
|
||||
})
|
||||
|
||||
test('document can be opened in a new tab from within the drawer', async () => {
|
||||
await navigateToDoc(page, postsUrl)
|
||||
await page
|
||||
.locator('.field-type.relationship .relationship--single-value__drawer-toggler')
|
||||
.click()
|
||||
await wait(500)
|
||||
const drawer1Content = page.locator('[id^=doc-drawer_posts_1_] .drawer__content')
|
||||
await expect(drawer1Content).toBeVisible()
|
||||
|
||||
const currentUrl = page.url()
|
||||
|
||||
// Open link in a new tab by holding down the Meta or Control key
|
||||
const documentLink = page.locator('.id-label a')
|
||||
const documentId = String(await documentLink.textContent())
|
||||
const [newPage] = await Promise.all([
|
||||
page.context().waitForEvent('page'),
|
||||
documentLink.click({ modifiers: ['ControlOrMeta'] }),
|
||||
])
|
||||
|
||||
// Wait for navigation to complete in the new tab and ensure correct URL
|
||||
await expect(newPage.locator('.doc-header')).toBeVisible()
|
||||
// using contain here, because after load the lists view will add query params like "?limit=10"
|
||||
expect(newPage.url()).toContain(postsUrl.edit(documentId))
|
||||
|
||||
// Ensure the original page did not change
|
||||
expect(page.url()).toBe(currentUrl)
|
||||
})
|
||||
})
|
||||
|
||||
describe('descriptions', () => {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
import type { User as PayloadUser } from 'payload'
|
||||
|
||||
import { expect, test } from '@playwright/test'
|
||||
import { mapAsync } from 'payload'
|
||||
import * as qs from 'qs-esm'
|
||||
|
||||
import type { Config, Geo, Post } from '../../payload-types.js'
|
||||
import type { Config, Geo, Post, User } from '../../payload-types.js'
|
||||
|
||||
import {
|
||||
ensureCompilationIsDone,
|
||||
@@ -20,7 +21,6 @@ import {
|
||||
customViews1CollectionSlug,
|
||||
geoCollectionSlug,
|
||||
listDrawerSlug,
|
||||
placeholderCollectionSlug,
|
||||
postsCollectionSlug,
|
||||
with300DocumentsSlug,
|
||||
} from '../../slugs.js'
|
||||
@@ -64,7 +64,6 @@ describe('List View', () => {
|
||||
let customViewsUrl: AdminUrlUtil
|
||||
let with300DocumentsUrl: AdminUrlUtil
|
||||
let withListViewUrl: AdminUrlUtil
|
||||
let placeholderUrl: AdminUrlUtil
|
||||
let user: any
|
||||
|
||||
let serverURL: string
|
||||
@@ -88,7 +87,7 @@ describe('List View', () => {
|
||||
baseListFiltersUrl = new AdminUrlUtil(serverURL, 'base-list-filters')
|
||||
customViewsUrl = new AdminUrlUtil(serverURL, customViews1CollectionSlug)
|
||||
withListViewUrl = new AdminUrlUtil(serverURL, listDrawerSlug)
|
||||
placeholderUrl = new AdminUrlUtil(serverURL, placeholderCollectionSlug)
|
||||
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
initPageConsoleErrorCatch(page)
|
||||
@@ -393,24 +392,6 @@ describe('List View', () => {
|
||||
await expect(page.locator(tableRowLocator)).toHaveCount(2)
|
||||
})
|
||||
|
||||
test('should search for nested fields in field dropdown', async () => {
|
||||
await page.goto(postsUrl.list)
|
||||
|
||||
await openListFilters(page, {})
|
||||
|
||||
const whereBuilder = page.locator('.where-builder')
|
||||
await whereBuilder.locator('.where-builder__add-first-filter').click()
|
||||
const conditionField = whereBuilder.locator('.condition__field')
|
||||
await conditionField.click()
|
||||
await conditionField.locator('input.rs__input').fill('Tab 1 > Title')
|
||||
|
||||
await expect(
|
||||
conditionField.locator('.rs__menu-list').locator('div', {
|
||||
hasText: exactText('Tab 1 > Title'),
|
||||
}),
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('should allow to filter in array field', async () => {
|
||||
await createArray()
|
||||
|
||||
@@ -1427,66 +1408,6 @@ describe('List View', () => {
|
||||
).toHaveText('Title')
|
||||
})
|
||||
})
|
||||
|
||||
describe('placeholder', () => {
|
||||
test('should display placeholder in filter options', async () => {
|
||||
await page.goto(
|
||||
`${placeholderUrl.list}${qs.stringify(
|
||||
{
|
||||
where: {
|
||||
or: [
|
||||
{
|
||||
and: [
|
||||
{
|
||||
defaultSelect: {
|
||||
equals: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
placeholderSelect: {
|
||||
equals: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
defaultRelationship: {
|
||||
equals: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
placeholderRelationship: {
|
||||
equals: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{ addQueryPrefix: true },
|
||||
)}`,
|
||||
)
|
||||
|
||||
const conditionValueSelects = page.locator('#list-controls-where .condition__value')
|
||||
await expect(conditionValueSelects.nth(0)).toHaveText('Select a value')
|
||||
await expect(conditionValueSelects.nth(1)).toHaveText('Custom placeholder')
|
||||
await expect(conditionValueSelects.nth(2)).toHaveText('Select a value')
|
||||
await expect(conditionValueSelects.nth(3)).toHaveText('Custom placeholder')
|
||||
})
|
||||
})
|
||||
test('should display placeholder in edit view', async () => {
|
||||
await page.goto(placeholderUrl.create)
|
||||
|
||||
await expect(page.locator('#field-defaultSelect .rs__placeholder')).toHaveText('Select a value')
|
||||
await expect(page.locator('#field-placeholderSelect .rs__placeholder')).toHaveText(
|
||||
'Custom placeholder',
|
||||
)
|
||||
await expect(page.locator('#field-defaultRelationship .rs__placeholder')).toHaveText(
|
||||
'Select a value',
|
||||
)
|
||||
await expect(page.locator('#field-placeholderRelationship .rs__placeholder')).toHaveText(
|
||||
'Custom placeholder',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
async function createPost(overrides?: Partial<Post>): Promise<Post> {
|
||||
|
||||
@@ -14,7 +14,6 @@ export const noApiViewCollectionSlug = 'collection-no-api-view'
|
||||
export const disableDuplicateSlug = 'disable-duplicate'
|
||||
export const disableCopyToLocale = 'disable-copy-to-locale'
|
||||
export const uploadCollectionSlug = 'uploads'
|
||||
export const placeholderCollectionSlug = 'placeholder'
|
||||
|
||||
export const uploadTwoCollectionSlug = 'uploads-two'
|
||||
export const customFieldsSlug = 'custom-fields'
|
||||
|
||||
@@ -358,46 +358,6 @@ describe('relationship', () => {
|
||||
).toHaveText(`${value}123456`)
|
||||
})
|
||||
|
||||
test('should open related document in a new tab when meta key is applied', async () => {
|
||||
await page.goto(url.create)
|
||||
|
||||
const [newPage] = await Promise.all([
|
||||
page.context().waitForEvent('page'),
|
||||
await openDocDrawer({
|
||||
page,
|
||||
selector:
|
||||
'#field-relationWithAllowCreateToFalse .relationship--single-value__drawer-toggler',
|
||||
withMetaKey: true,
|
||||
}),
|
||||
])
|
||||
|
||||
// Wait for navigation to complete in the new tab and ensure the edit view is open
|
||||
await expect(newPage.locator('.collection-edit')).toBeVisible()
|
||||
})
|
||||
|
||||
test('multi value relationship should open document in a new tab', async () => {
|
||||
await page.goto(url.create)
|
||||
|
||||
// Select "Seeded text document" relationship
|
||||
await page.locator('#field-relationshipHasMany .rs__control').click()
|
||||
await page.locator('.rs__option:has-text("Seeded text document")').click()
|
||||
await expect(
|
||||
page.locator('#field-relationshipHasMany .relationship--multi-value-label__drawer-toggler'),
|
||||
).toBeVisible()
|
||||
|
||||
const [newPage] = await Promise.all([
|
||||
page.context().waitForEvent('page'),
|
||||
await openDocDrawer({
|
||||
page,
|
||||
selector: '#field-relationshipHasMany .relationship--multi-value-label__drawer-toggler',
|
||||
withMetaKey: true,
|
||||
}),
|
||||
])
|
||||
|
||||
// Wait for navigation to complete in the new tab and ensure the edit view is open
|
||||
await expect(newPage.locator('.collection-edit')).toBeVisible()
|
||||
})
|
||||
|
||||
// Drawers opened through the edit button are prone to issues due to the use of stopPropagation for certain
|
||||
// events - specifically for drawers opened through the edit button. This test is to ensure that drawers
|
||||
// opened through the edit button can be saved using the hotkey.
|
||||
|
||||
@@ -228,12 +228,6 @@ describe('Form State', () => {
|
||||
collection: postsSlug,
|
||||
data: {
|
||||
title: 'Test Post',
|
||||
blocks: [
|
||||
{
|
||||
blockType: 'text',
|
||||
text: 'Test block',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
@@ -254,7 +248,6 @@ describe('Form State', () => {
|
||||
})
|
||||
|
||||
expect(state.title?.addedByServer).toBe(true)
|
||||
expect(state['blocks.0.blockType']?.addedByServer).toBe(true)
|
||||
|
||||
// Ensure that `addedByServer` is removed after being received by the client
|
||||
const newState = mergeServerFormState({
|
||||
|
||||
@@ -104,50 +104,5 @@ describe('graphql', () => {
|
||||
|
||||
expect(res.hyphenated_name).toStrictEqual('example-hyphenated-name')
|
||||
})
|
||||
|
||||
it('should not error because of non nullable fields', async () => {
|
||||
await payload.delete({ collection: 'posts', where: {} })
|
||||
|
||||
// this is an array if any errors
|
||||
const res_1 = await restClient
|
||||
.GRAPHQL_POST({
|
||||
body: JSON.stringify({
|
||||
query: `
|
||||
query {
|
||||
Posts {
|
||||
docs {
|
||||
title
|
||||
}
|
||||
prevPage
|
||||
}
|
||||
}
|
||||
`,
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
expect(res_1.errors).toBeFalsy()
|
||||
|
||||
await payload.create({
|
||||
collection: 'posts',
|
||||
data: { title: 'any-title' },
|
||||
})
|
||||
|
||||
const res_2 = await restClient
|
||||
.GRAPHQL_POST({
|
||||
body: JSON.stringify({
|
||||
query: `
|
||||
query {
|
||||
Posts(limit: 1) {
|
||||
docs {
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
expect(res_2.errors).toBeFalsy()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -6,18 +6,12 @@ import { wait } from 'payload/shared'
|
||||
export async function openDocDrawer({
|
||||
page,
|
||||
selector,
|
||||
withMetaKey = false,
|
||||
}: {
|
||||
page: Page
|
||||
selector: string
|
||||
withMetaKey?: boolean
|
||||
}): Promise<void> {
|
||||
let clickProperties = {}
|
||||
if (withMetaKey) {
|
||||
clickProperties = { modifiers: ['ControlOrMeta'] }
|
||||
}
|
||||
await wait(500) // wait for parent form state to initialize
|
||||
await page.locator(selector).click(clickProperties)
|
||||
await page.locator(selector).click()
|
||||
await wait(500) // wait for drawer form state to initialize
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import { mediaWithSignedDownloadsSlug } from '../shared.js'
|
||||
|
||||
export const MediaWithSignedDownloads: CollectionConfig = {
|
||||
slug: mediaWithSignedDownloadsSlug,
|
||||
upload: true,
|
||||
fields: [],
|
||||
}
|
||||
@@ -7,9 +7,8 @@ import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
||||
import { devUser } from '../credentials.js'
|
||||
import { Media } from './collections/Media.js'
|
||||
import { MediaWithPrefix } from './collections/MediaWithPrefix.js'
|
||||
import { MediaWithSignedDownloads } from './collections/MediaWithSignedDownloads.js'
|
||||
import { Users } from './collections/Users.js'
|
||||
import { mediaSlug, mediaWithPrefixSlug, mediaWithSignedDownloadsSlug, prefix } from './shared.js'
|
||||
import { mediaSlug, mediaWithPrefixSlug, prefix } from './shared.js'
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
@@ -26,7 +25,7 @@ export default buildConfigWithDefaults({
|
||||
baseDir: path.resolve(dirname),
|
||||
},
|
||||
},
|
||||
collections: [Media, MediaWithPrefix, MediaWithSignedDownloads, Users],
|
||||
collections: [Media, MediaWithPrefix, Users],
|
||||
onInit: async (payload) => {
|
||||
await payload.create({
|
||||
collection: 'users',
|
||||
@@ -43,9 +42,6 @@ export default buildConfigWithDefaults({
|
||||
[mediaWithPrefixSlug]: {
|
||||
prefix,
|
||||
},
|
||||
[mediaWithSignedDownloadsSlug]: {
|
||||
signedDownloads: true,
|
||||
},
|
||||
},
|
||||
bucket: process.env.S3_BUCKET,
|
||||
config: {
|
||||
|
||||
@@ -4,16 +4,12 @@ import * as AWS from '@aws-sdk/client-s3'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import type { NextRESTClient } from '../helpers/NextRESTClient.js'
|
||||
|
||||
import { initPayloadInt } from '../helpers/initPayloadInt.js'
|
||||
import { mediaSlug, mediaWithPrefixSlug, mediaWithSignedDownloadsSlug, prefix } from './shared.js'
|
||||
import { mediaSlug, mediaWithPrefixSlug, prefix } from './shared.js'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
let restClient: NextRESTClient
|
||||
|
||||
let payload: Payload
|
||||
|
||||
describe('@payloadcms/storage-s3', () => {
|
||||
@@ -21,7 +17,7 @@ describe('@payloadcms/storage-s3', () => {
|
||||
let client: AWS.S3Client
|
||||
|
||||
beforeAll(async () => {
|
||||
;({ payload, restClient } = await initPayloadInt(dirname))
|
||||
;({ payload } = await initPayloadInt(dirname))
|
||||
TEST_BUCKET = process.env.S3_BUCKET
|
||||
|
||||
client = new AWS.S3({
|
||||
@@ -81,38 +77,15 @@ describe('@payloadcms/storage-s3', () => {
|
||||
expect(upload.url).toEqual(`/api/${mediaWithPrefixSlug}/file/${String(upload.filename)}`)
|
||||
})
|
||||
|
||||
it('can download with signed downloads', async () => {
|
||||
await payload.create({
|
||||
collection: mediaWithSignedDownloadsSlug,
|
||||
data: {},
|
||||
filePath: path.resolve(dirname, '../uploads/image.png'),
|
||||
})
|
||||
|
||||
const response = await restClient.GET(`/${mediaWithSignedDownloadsSlug}/file/image.png`)
|
||||
expect(response.status).toBe(302)
|
||||
const url = response.headers.get('Location')
|
||||
expect(url).toBeDefined()
|
||||
expect(url!).toContain(`/${TEST_BUCKET}/image.png`)
|
||||
expect(new URLSearchParams(url!).get('x-id')).toBe('GetObject')
|
||||
const file = await fetch(url!)
|
||||
expect(file.headers.get('Content-Type')).toBe('image/png')
|
||||
})
|
||||
|
||||
describe('R2', () => {
|
||||
it.todo('can upload')
|
||||
})
|
||||
|
||||
async function createTestBucket() {
|
||||
try {
|
||||
const makeBucketRes = await client.send(new AWS.CreateBucketCommand({ Bucket: TEST_BUCKET }))
|
||||
const makeBucketRes = await client.send(new AWS.CreateBucketCommand({ Bucket: TEST_BUCKET }))
|
||||
|
||||
if (makeBucketRes.$metadata.httpStatusCode !== 200) {
|
||||
throw new Error(`Failed to create bucket. ${makeBucketRes.$metadata.httpStatusCode}`)
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof AWS.BucketAlreadyOwnedByYou) {
|
||||
console.log('Bucket already exists')
|
||||
}
|
||||
if (makeBucketRes.$metadata.httpStatusCode !== 200) {
|
||||
throw new Error(`Failed to create bucket. ${makeBucketRes.$metadata.httpStatusCode}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,9 +96,7 @@ describe('@payloadcms/storage-s3', () => {
|
||||
}),
|
||||
)
|
||||
|
||||
if (!listedObjects?.Contents?.length) {
|
||||
return
|
||||
}
|
||||
if (!listedObjects?.Contents?.length) return
|
||||
|
||||
const deleteParams = {
|
||||
Bucket: TEST_BUCKET,
|
||||
|
||||
@@ -69,7 +69,6 @@ export interface Config {
|
||||
collections: {
|
||||
media: Media;
|
||||
'media-with-prefix': MediaWithPrefix;
|
||||
'media-with-signed-downloads': MediaWithSignedDownload;
|
||||
users: User;
|
||||
'payload-locked-documents': PayloadLockedDocument;
|
||||
'payload-preferences': PayloadPreference;
|
||||
@@ -79,7 +78,6 @@ export interface Config {
|
||||
collectionsSelect: {
|
||||
media: MediaSelect<false> | MediaSelect<true>;
|
||||
'media-with-prefix': MediaWithPrefixSelect<false> | MediaWithPrefixSelect<true>;
|
||||
'media-with-signed-downloads': MediaWithSignedDownloadsSelect<false> | MediaWithSignedDownloadsSelect<true>;
|
||||
users: UsersSelect<false> | UsersSelect<true>;
|
||||
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
||||
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
||||
@@ -173,24 +171,6 @@ export interface MediaWithPrefix {
|
||||
focalX?: number | null;
|
||||
focalY?: number | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "media-with-signed-downloads".
|
||||
*/
|
||||
export interface MediaWithSignedDownload {
|
||||
id: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
url?: string | null;
|
||||
thumbnailURL?: string | null;
|
||||
filename?: string | null;
|
||||
mimeType?: string | null;
|
||||
filesize?: number | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
focalX?: number | null;
|
||||
focalY?: number | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users".
|
||||
@@ -223,10 +203,6 @@ export interface PayloadLockedDocument {
|
||||
relationTo: 'media-with-prefix';
|
||||
value: string | MediaWithPrefix;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'media-with-signed-downloads';
|
||||
value: string | MediaWithSignedDownload;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
@@ -333,23 +309,6 @@ export interface MediaWithPrefixSelect<T extends boolean = true> {
|
||||
focalX?: T;
|
||||
focalY?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "media-with-signed-downloads_select".
|
||||
*/
|
||||
export interface MediaWithSignedDownloadsSelect<T extends boolean = true> {
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
url?: T;
|
||||
thumbnailURL?: T;
|
||||
filename?: T;
|
||||
mimeType?: T;
|
||||
filesize?: T;
|
||||
width?: T;
|
||||
height?: T;
|
||||
focalX?: T;
|
||||
focalY?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users_select".
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
export const mediaSlug = 'media'
|
||||
export const mediaWithPrefixSlug = 'media-with-prefix'
|
||||
export const prefix = 'test-prefix'
|
||||
|
||||
export const mediaWithSignedDownloadsSlug = 'media-with-signed-downloads'
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import { APIError } from 'payload'
|
||||
|
||||
import { errorOnUnpublishSlug } from '../slugs.js'
|
||||
|
||||
const ErrorOnUnpublish: CollectionConfig = {
|
||||
slug: errorOnUnpublishSlug,
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
versions: {
|
||||
drafts: true,
|
||||
},
|
||||
hooks: {
|
||||
beforeValidate: [
|
||||
({ data, originalDoc }) => {
|
||||
if (data?._status === 'draft' && originalDoc?._status === 'published') {
|
||||
throw new APIError('Custom error on unpublish', 400, {}, true)
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export default ErrorOnUnpublish
|
||||
@@ -12,7 +12,6 @@ import DisablePublish from './collections/DisablePublish.js'
|
||||
import DraftPosts from './collections/Drafts.js'
|
||||
import DraftWithMax from './collections/DraftsWithMax.js'
|
||||
import DraftsWithValidate from './collections/DraftsWithValidate.js'
|
||||
import ErrorOnUnpublish from './collections/ErrorOnUnpublish.js'
|
||||
import LocalizedPosts from './collections/Localized.js'
|
||||
import { Media } from './collections/Media.js'
|
||||
import Posts from './collections/Posts.js'
|
||||
@@ -43,7 +42,6 @@ export default buildConfigWithDefaults({
|
||||
DraftPosts,
|
||||
DraftWithMax,
|
||||
DraftsWithValidate,
|
||||
ErrorOnUnpublish,
|
||||
LocalizedPosts,
|
||||
VersionPosts,
|
||||
CustomIDs,
|
||||
|
||||
@@ -60,7 +60,6 @@ import {
|
||||
draftWithMaxCollectionSlug,
|
||||
draftWithMaxGlobalSlug,
|
||||
draftWithValidateCollectionSlug,
|
||||
errorOnUnpublishSlug,
|
||||
localizedCollectionSlug,
|
||||
localizedGlobalSlug,
|
||||
postCollectionSlug,
|
||||
@@ -87,7 +86,6 @@ describe('Versions', () => {
|
||||
let disablePublishURL: AdminUrlUtil
|
||||
let customIDURL: AdminUrlUtil
|
||||
let postURL: AdminUrlUtil
|
||||
let errorOnUnpublishURL: AdminUrlUtil
|
||||
let id: string
|
||||
|
||||
beforeAll(async ({ browser }, testInfo) => {
|
||||
@@ -126,7 +124,6 @@ describe('Versions', () => {
|
||||
disablePublishURL = new AdminUrlUtil(serverURL, disablePublishSlug)
|
||||
customIDURL = new AdminUrlUtil(serverURL, customIDSlug)
|
||||
postURL = new AdminUrlUtil(serverURL, postCollectionSlug)
|
||||
errorOnUnpublishURL = new AdminUrlUtil(serverURL, errorOnUnpublishSlug)
|
||||
})
|
||||
|
||||
test('collection — has versions tab', async () => {
|
||||
@@ -208,116 +205,6 @@ describe('Versions', () => {
|
||||
await expect(page.locator('#field-title')).toHaveValue('v1')
|
||||
})
|
||||
|
||||
test('should show currently published version status in versions view', async () => {
|
||||
const publishedDoc = await payload.create({
|
||||
collection: draftCollectionSlug,
|
||||
data: {
|
||||
_status: 'published',
|
||||
title: 'title',
|
||||
description: 'description',
|
||||
},
|
||||
overrideAccess: true,
|
||||
})
|
||||
|
||||
await page.goto(`${url.edit(publishedDoc.id)}/versions`)
|
||||
await expect(page.locator('main.versions')).toContainText('Current Published Version')
|
||||
})
|
||||
|
||||
test('should show unpublished version status in versions view', async () => {
|
||||
const publishedDoc = await payload.create({
|
||||
collection: draftCollectionSlug,
|
||||
data: {
|
||||
_status: 'published',
|
||||
title: 'title',
|
||||
description: 'description',
|
||||
},
|
||||
overrideAccess: true,
|
||||
})
|
||||
|
||||
// Unpublish the document
|
||||
await payload.update({
|
||||
collection: draftCollectionSlug,
|
||||
id: publishedDoc.id,
|
||||
data: {
|
||||
_status: 'draft',
|
||||
},
|
||||
draft: false,
|
||||
})
|
||||
|
||||
await page.goto(`${url.edit(publishedDoc.id)}/versions`)
|
||||
await expect(page.locator('main.versions')).toContainText('Previously Published')
|
||||
})
|
||||
|
||||
test('should show global versions view level action in globals versions view', async () => {
|
||||
const global = new AdminUrlUtil(serverURL, draftGlobalSlug)
|
||||
await page.goto(`${global.global(draftGlobalSlug)}/versions`)
|
||||
await expect(page.locator('.app-header .global-versions-button')).toHaveCount(1)
|
||||
})
|
||||
|
||||
test('global — has versions tab', async () => {
|
||||
const global = new AdminUrlUtil(serverURL, draftGlobalSlug)
|
||||
await page.goto(global.global(draftGlobalSlug))
|
||||
|
||||
const docURL = page.url()
|
||||
const pathname = new URL(docURL).pathname
|
||||
|
||||
const versionsTab = page.locator('.doc-tab', {
|
||||
hasText: 'Versions',
|
||||
})
|
||||
await versionsTab.waitFor({ state: 'visible' })
|
||||
|
||||
expect(versionsTab).toBeTruthy()
|
||||
const href = versionsTab.locator('a').first()
|
||||
await expect(href).toHaveAttribute('href', `${pathname}/versions`)
|
||||
})
|
||||
|
||||
test('global — respects max number of versions', async () => {
|
||||
await payload.updateGlobal({
|
||||
slug: draftWithMaxGlobalSlug,
|
||||
data: {
|
||||
title: 'initial title',
|
||||
},
|
||||
})
|
||||
|
||||
const global = new AdminUrlUtil(serverURL, draftWithMaxGlobalSlug)
|
||||
await page.goto(global.global(draftWithMaxGlobalSlug))
|
||||
|
||||
const titleFieldInitial = page.locator('#field-title')
|
||||
await titleFieldInitial.fill('updated title')
|
||||
await saveDocAndAssert(page, '#action-save-draft')
|
||||
await expect(titleFieldInitial).toHaveValue('updated title')
|
||||
|
||||
const versionsTab = page.locator('.doc-tab', {
|
||||
hasText: '1',
|
||||
})
|
||||
|
||||
await versionsTab.waitFor({ state: 'visible' })
|
||||
|
||||
expect(versionsTab).toBeTruthy()
|
||||
|
||||
const titleFieldUpdated = page.locator('#field-title')
|
||||
await titleFieldUpdated.fill('latest title')
|
||||
await saveDocAndAssert(page, '#action-save-draft')
|
||||
await expect(titleFieldUpdated).toHaveValue('latest title')
|
||||
|
||||
const versionsTabUpdated = page.locator('.doc-tab', {
|
||||
hasText: '1',
|
||||
})
|
||||
|
||||
await versionsTabUpdated.waitFor({ state: 'visible' })
|
||||
|
||||
expect(versionsTabUpdated).toBeTruthy()
|
||||
})
|
||||
|
||||
test('global — has versions route', async () => {
|
||||
const global = new AdminUrlUtil(serverURL, autoSaveGlobalSlug)
|
||||
const versionsURL = `${global.global(autoSaveGlobalSlug)}/versions`
|
||||
await page.goto(versionsURL)
|
||||
await expect(() => {
|
||||
expect(page.url()).toMatch(/\/versions/)
|
||||
}).toPass({ timeout: 10000, intervals: [100] })
|
||||
})
|
||||
|
||||
test('collection - should autosave', async () => {
|
||||
await page.goto(autosaveURL.create)
|
||||
await page.locator('#field-title').fill('autosave title')
|
||||
@@ -582,22 +469,6 @@ describe('Versions', () => {
|
||||
await expect(page.locator('#action-save')).not.toBeAttached()
|
||||
})
|
||||
|
||||
test('collections — should show custom error message when unpublishing fails', async () => {
|
||||
const publishedDoc = await payload.create({
|
||||
collection: errorOnUnpublishSlug,
|
||||
data: {
|
||||
_status: 'published',
|
||||
title: 'title',
|
||||
},
|
||||
})
|
||||
await page.goto(errorOnUnpublishURL.edit(String(publishedDoc.id)))
|
||||
await page.locator('#action-unpublish').click()
|
||||
await page.locator('[id^="confirm-un-publish-"] #confirm-action').click()
|
||||
await expect(
|
||||
page.locator('.payload-toast-item:has-text("Custom error on unpublish")'),
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('should show documents title in relationship even if draft document', async () => {
|
||||
await payload.create({
|
||||
collection: autosaveCollectionSlug,
|
||||
|
||||
@@ -75,7 +75,6 @@ export interface Config {
|
||||
'draft-posts': DraftPost;
|
||||
'draft-with-max-posts': DraftWithMaxPost;
|
||||
'draft-with-validate-posts': DraftWithValidatePost;
|
||||
'error-on-unpublish': ErrorOnUnpublish;
|
||||
'localized-posts': LocalizedPost;
|
||||
'version-posts': VersionPost;
|
||||
'custom-ids': CustomId;
|
||||
@@ -98,7 +97,6 @@ export interface Config {
|
||||
'draft-posts': DraftPostsSelect<false> | DraftPostsSelect<true>;
|
||||
'draft-with-max-posts': DraftWithMaxPostsSelect<false> | DraftWithMaxPostsSelect<true>;
|
||||
'draft-with-validate-posts': DraftWithValidatePostsSelect<false> | DraftWithValidatePostsSelect<true>;
|
||||
'error-on-unpublish': ErrorOnUnpublishSelect<false> | ErrorOnUnpublishSelect<true>;
|
||||
'localized-posts': LocalizedPostsSelect<false> | LocalizedPostsSelect<true>;
|
||||
'version-posts': VersionPostsSelect<false> | VersionPostsSelect<true>;
|
||||
'custom-ids': CustomIdsSelect<false> | CustomIdsSelect<true>;
|
||||
@@ -291,17 +289,6 @@ export interface DraftWithValidatePost {
|
||||
createdAt: string;
|
||||
_status?: ('draft' | 'published') | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "error-on-unpublish".
|
||||
*/
|
||||
export interface ErrorOnUnpublish {
|
||||
id: string;
|
||||
title: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
_status?: ('draft' | 'published') | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "localized-posts".
|
||||
@@ -602,10 +589,6 @@ export interface PayloadLockedDocument {
|
||||
relationTo: 'draft-with-validate-posts';
|
||||
value: string | DraftWithValidatePost;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'error-on-unpublish';
|
||||
value: string | ErrorOnUnpublish;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'localized-posts';
|
||||
value: string | LocalizedPost;
|
||||
@@ -795,16 +778,6 @@ export interface DraftWithValidatePostsSelect<T extends boolean = true> {
|
||||
createdAt?: T;
|
||||
_status?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "error-on-unpublish_select".
|
||||
*/
|
||||
export interface ErrorOnUnpublishSelect<T extends boolean = true> {
|
||||
title?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
_status?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "localized-posts_select".
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user