From 0a59707ea026bbcd36433aaf15e8f9abfd993ae1 Mon Sep 17 00:00:00 2001 From: Sasha <64744993+r1tsuu@users.noreply.github.com> Date: Thu, 17 Apr 2025 16:55:12 +0300 Subject: [PATCH 01/15] chore(db-postgres): improve table name length exceeded error message (#12142) Improves the error message when table name length exceeds 63 characters with the tip that you can use the `dbName` property. --- packages/drizzle/src/createTableName.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/drizzle/src/createTableName.ts b/packages/drizzle/src/createTableName.ts index bdd2ff2326..c52f5daa94 100644 --- a/packages/drizzle/src/createTableName.ts +++ b/packages/drizzle/src/createTableName.ts @@ -78,7 +78,9 @@ export const createTableName = ({ if (result.length > 63) { throw new APIError( - `Exceeded max identifier length for table or enum name of 63 characters. Invalid name: ${result}`, + `Exceeded max identifier length for table or enum name of 63 characters. Invalid name: ${result}. +Tip: You can use the dbName property to reduce the table name length. + `, ) } From ed50a796430b884a884eea23cd34b34ff213edbf Mon Sep 17 00:00:00 2001 From: Jessica Chowdhury <67977755+JessChowdhury@users.noreply.github.com> Date: Thu, 17 Apr 2025 15:55:11 +0100 Subject: [PATCH 02/15] fix(next): missing @payloadcms/next/auth export (#12144) Follow up to #11900. The `@payloadcms/next/auth` export was missing from the published package.json because it was excluded from the `publishConfig` property. --- packages/next/package.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/next/package.json b/packages/next/package.json index 16f75a9c7d..c3777f56bd 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -156,6 +156,11 @@ "types": "./dist/exports/templates.d.ts", "default": "./dist/exports/templates.js" }, + "./auth": { + "import": "./dist/exports/auth.js", + "types": "./dist/exports/auth.d.ts", + "default": "./dist/exports/auth.js" + }, "./utilities": { "import": "./dist/exports/utilities.js", "types": "./dist/exports/utilities.d.ts", From 17d5168728ef9f99dc7bfa5d22800509bf3e2fb1 Mon Sep 17 00:00:00 2001 From: Elliot DeNolf Date: Thu, 17 Apr 2025 11:02:39 -0400 Subject: [PATCH 03/15] chore(release): v3.35.1 [skip ci] --- package.json | 2 +- packages/admin-bar/package.json | 2 +- packages/create-payload-app/package.json | 2 +- packages/db-mongodb/package.json | 2 +- packages/db-postgres/package.json | 2 +- packages/db-sqlite/package.json | 2 +- packages/db-vercel-postgres/package.json | 2 +- packages/drizzle/package.json | 2 +- packages/email-nodemailer/package.json | 2 +- packages/email-resend/package.json | 2 +- packages/graphql/package.json | 2 +- packages/live-preview-react/package.json | 2 +- packages/live-preview-vue/package.json | 2 +- packages/live-preview/package.json | 2 +- packages/next/package.json | 2 +- packages/payload-cloud/package.json | 2 +- packages/payload/package.json | 2 +- packages/plugin-cloud-storage/package.json | 2 +- packages/plugin-form-builder/package.json | 2 +- packages/plugin-import-export/package.json | 2 +- packages/plugin-multi-tenant/package.json | 2 +- packages/plugin-nested-docs/package.json | 2 +- packages/plugin-redirects/package.json | 2 +- packages/plugin-search/package.json | 2 +- packages/plugin-sentry/package.json | 2 +- packages/plugin-seo/package.json | 2 +- packages/plugin-stripe/package.json | 2 +- packages/richtext-lexical/package.json | 2 +- packages/richtext-slate/package.json | 2 +- packages/storage-azure/package.json | 2 +- packages/storage-gcs/package.json | 2 +- packages/storage-s3/package.json | 2 +- packages/storage-uploadthing/package.json | 2 +- packages/storage-vercel-blob/package.json | 2 +- packages/translations/package.json | 2 +- packages/ui/package.json | 2 +- 36 files changed, 36 insertions(+), 36 deletions(-) diff --git a/package.json b/package.json index b21d857c34..026c32f976 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "payload-monorepo", - "version": "3.35.0", + "version": "3.35.1", "private": true, "type": "module", "scripts": { diff --git a/packages/admin-bar/package.json b/packages/admin-bar/package.json index aacb7a3fd2..6292ace878 100644 --- a/packages/admin-bar/package.json +++ b/packages/admin-bar/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/admin-bar", - "version": "3.35.0", + "version": "3.35.1", "description": "An admin bar for React apps using Payload", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/create-payload-app/package.json b/packages/create-payload-app/package.json index 7d79125578..d0a1244a45 100644 --- a/packages/create-payload-app/package.json +++ b/packages/create-payload-app/package.json @@ -1,6 +1,6 @@ { "name": "create-payload-app", - "version": "3.35.0", + "version": "3.35.1", "homepage": "https://payloadcms.com", "repository": { "type": "git", diff --git a/packages/db-mongodb/package.json b/packages/db-mongodb/package.json index a5629480da..b433605a93 100644 --- a/packages/db-mongodb/package.json +++ b/packages/db-mongodb/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/db-mongodb", - "version": "3.35.0", + "version": "3.35.1", "description": "The officially supported MongoDB database adapter for Payload", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/db-postgres/package.json b/packages/db-postgres/package.json index 63093a0ac6..b6017ea73c 100644 --- a/packages/db-postgres/package.json +++ b/packages/db-postgres/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/db-postgres", - "version": "3.35.0", + "version": "3.35.1", "description": "The officially supported Postgres database adapter for Payload", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/db-sqlite/package.json b/packages/db-sqlite/package.json index 392c3634c7..12390b6c5c 100644 --- a/packages/db-sqlite/package.json +++ b/packages/db-sqlite/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/db-sqlite", - "version": "3.35.0", + "version": "3.35.1", "description": "The officially supported SQLite database adapter for Payload", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/db-vercel-postgres/package.json b/packages/db-vercel-postgres/package.json index 6fd0f34a9d..f33b6c80b6 100644 --- a/packages/db-vercel-postgres/package.json +++ b/packages/db-vercel-postgres/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/db-vercel-postgres", - "version": "3.35.0", + "version": "3.35.1", "description": "Vercel Postgres adapter for Payload", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/drizzle/package.json b/packages/drizzle/package.json index 070350b1e3..866f2477b2 100644 --- a/packages/drizzle/package.json +++ b/packages/drizzle/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/drizzle", - "version": "3.35.0", + "version": "3.35.1", "description": "A library of shared functions used by different payload database adapters", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/email-nodemailer/package.json b/packages/email-nodemailer/package.json index 5034f53425..1cdc4983a8 100644 --- a/packages/email-nodemailer/package.json +++ b/packages/email-nodemailer/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/email-nodemailer", - "version": "3.35.0", + "version": "3.35.1", "description": "Payload Nodemailer Email Adapter", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/email-resend/package.json b/packages/email-resend/package.json index bc9d6154e8..338b35c5af 100644 --- a/packages/email-resend/package.json +++ b/packages/email-resend/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/email-resend", - "version": "3.35.0", + "version": "3.35.1", "description": "Payload Resend Email Adapter", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/graphql/package.json b/packages/graphql/package.json index d4a204e99a..db700269b7 100644 --- a/packages/graphql/package.json +++ b/packages/graphql/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/graphql", - "version": "3.35.0", + "version": "3.35.1", "homepage": "https://payloadcms.com", "repository": { "type": "git", diff --git a/packages/live-preview-react/package.json b/packages/live-preview-react/package.json index 24dcaa0d5c..5ae4fd75a6 100644 --- a/packages/live-preview-react/package.json +++ b/packages/live-preview-react/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/live-preview-react", - "version": "3.35.0", + "version": "3.35.1", "description": "The official React SDK for Payload Live Preview", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/live-preview-vue/package.json b/packages/live-preview-vue/package.json index 018d75af95..c6d2c35823 100644 --- a/packages/live-preview-vue/package.json +++ b/packages/live-preview-vue/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/live-preview-vue", - "version": "3.35.0", + "version": "3.35.1", "description": "The official Vue SDK for Payload Live Preview", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/live-preview/package.json b/packages/live-preview/package.json index 997a2319eb..338ffa0c2e 100644 --- a/packages/live-preview/package.json +++ b/packages/live-preview/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/live-preview", - "version": "3.35.0", + "version": "3.35.1", "description": "The official live preview JavaScript SDK for Payload", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/next/package.json b/packages/next/package.json index c3777f56bd..f4ae1af419 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/next", - "version": "3.35.0", + "version": "3.35.1", "homepage": "https://payloadcms.com", "repository": { "type": "git", diff --git a/packages/payload-cloud/package.json b/packages/payload-cloud/package.json index 546648ab2f..bbaf0361da 100644 --- a/packages/payload-cloud/package.json +++ b/packages/payload-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/payload-cloud", - "version": "3.35.0", + "version": "3.35.1", "description": "The official Payload Cloud plugin", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/payload/package.json b/packages/payload/package.json index 35fb761a2c..e294512b71 100644 --- a/packages/payload/package.json +++ b/packages/payload/package.json @@ -1,6 +1,6 @@ { "name": "payload", - "version": "3.35.0", + "version": "3.35.1", "description": "Node, React, Headless CMS and Application Framework built on Next.js", "keywords": [ "admin panel", diff --git a/packages/plugin-cloud-storage/package.json b/packages/plugin-cloud-storage/package.json index dea9eb6628..47dab5d7bd 100644 --- a/packages/plugin-cloud-storage/package.json +++ b/packages/plugin-cloud-storage/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-cloud-storage", - "version": "3.35.0", + "version": "3.35.1", "description": "The official cloud storage plugin for Payload CMS", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/plugin-form-builder/package.json b/packages/plugin-form-builder/package.json index 8c81751566..2ef45b15b1 100644 --- a/packages/plugin-form-builder/package.json +++ b/packages/plugin-form-builder/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-form-builder", - "version": "3.35.0", + "version": "3.35.1", "description": "Form builder plugin for Payload CMS", "keywords": [ "payload", diff --git a/packages/plugin-import-export/package.json b/packages/plugin-import-export/package.json index ad7e180a82..ce73cf23fb 100644 --- a/packages/plugin-import-export/package.json +++ b/packages/plugin-import-export/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-import-export", - "version": "3.35.0", + "version": "3.35.1", "description": "Import-Export plugin for Payload", "keywords": [ "payload", diff --git a/packages/plugin-multi-tenant/package.json b/packages/plugin-multi-tenant/package.json index b5f24688a0..37f1898379 100644 --- a/packages/plugin-multi-tenant/package.json +++ b/packages/plugin-multi-tenant/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-multi-tenant", - "version": "3.35.0", + "version": "3.35.1", "description": "Multi Tenant plugin for Payload", "keywords": [ "payload", diff --git a/packages/plugin-nested-docs/package.json b/packages/plugin-nested-docs/package.json index ecd0cca40e..a04087888d 100644 --- a/packages/plugin-nested-docs/package.json +++ b/packages/plugin-nested-docs/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-nested-docs", - "version": "3.35.0", + "version": "3.35.1", "description": "The official Nested Docs plugin for Payload", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/plugin-redirects/package.json b/packages/plugin-redirects/package.json index f3e53c3056..65c605e62a 100644 --- a/packages/plugin-redirects/package.json +++ b/packages/plugin-redirects/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-redirects", - "version": "3.35.0", + "version": "3.35.1", "description": "Redirects plugin for Payload", "keywords": [ "payload", diff --git a/packages/plugin-search/package.json b/packages/plugin-search/package.json index 8edc147dee..295d36315e 100644 --- a/packages/plugin-search/package.json +++ b/packages/plugin-search/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-search", - "version": "3.35.0", + "version": "3.35.1", "description": "Search plugin for Payload", "keywords": [ "payload", diff --git a/packages/plugin-sentry/package.json b/packages/plugin-sentry/package.json index 3f525715d4..9b1218a7b7 100644 --- a/packages/plugin-sentry/package.json +++ b/packages/plugin-sentry/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-sentry", - "version": "3.35.0", + "version": "3.35.1", "description": "Sentry plugin for Payload", "keywords": [ "payload", diff --git a/packages/plugin-seo/package.json b/packages/plugin-seo/package.json index eddfcd81a2..4f13a39f91 100644 --- a/packages/plugin-seo/package.json +++ b/packages/plugin-seo/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-seo", - "version": "3.35.0", + "version": "3.35.1", "description": "SEO plugin for Payload", "keywords": [ "payload", diff --git a/packages/plugin-stripe/package.json b/packages/plugin-stripe/package.json index 4f11acc100..e45211aaa1 100644 --- a/packages/plugin-stripe/package.json +++ b/packages/plugin-stripe/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-stripe", - "version": "3.35.0", + "version": "3.35.1", "description": "Stripe plugin for Payload", "keywords": [ "payload", diff --git a/packages/richtext-lexical/package.json b/packages/richtext-lexical/package.json index 9f9cfbd2ae..f6e2057f6b 100644 --- a/packages/richtext-lexical/package.json +++ b/packages/richtext-lexical/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/richtext-lexical", - "version": "3.35.0", + "version": "3.35.1", "description": "The officially supported Lexical richtext adapter for Payload", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/richtext-slate/package.json b/packages/richtext-slate/package.json index e3ef18ca02..af889d5f00 100644 --- a/packages/richtext-slate/package.json +++ b/packages/richtext-slate/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/richtext-slate", - "version": "3.35.0", + "version": "3.35.1", "description": "The officially supported Slate richtext adapter for Payload", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/storage-azure/package.json b/packages/storage-azure/package.json index 24a9e8a8f3..b342dbb4c8 100644 --- a/packages/storage-azure/package.json +++ b/packages/storage-azure/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/storage-azure", - "version": "3.35.0", + "version": "3.35.1", "description": "Payload storage adapter for Azure Blob Storage", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/storage-gcs/package.json b/packages/storage-gcs/package.json index 5af049acdf..625022b32e 100644 --- a/packages/storage-gcs/package.json +++ b/packages/storage-gcs/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/storage-gcs", - "version": "3.35.0", + "version": "3.35.1", "description": "Payload storage adapter for Google Cloud Storage", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/storage-s3/package.json b/packages/storage-s3/package.json index 9d78c08308..0e65946913 100644 --- a/packages/storage-s3/package.json +++ b/packages/storage-s3/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/storage-s3", - "version": "3.35.0", + "version": "3.35.1", "description": "Payload storage adapter for Amazon S3", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/storage-uploadthing/package.json b/packages/storage-uploadthing/package.json index 22610484a4..56f1ba3c99 100644 --- a/packages/storage-uploadthing/package.json +++ b/packages/storage-uploadthing/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/storage-uploadthing", - "version": "3.35.0", + "version": "3.35.1", "description": "Payload storage adapter for uploadthing", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/storage-vercel-blob/package.json b/packages/storage-vercel-blob/package.json index 3b90db8baa..84af5e067f 100644 --- a/packages/storage-vercel-blob/package.json +++ b/packages/storage-vercel-blob/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/storage-vercel-blob", - "version": "3.35.0", + "version": "3.35.1", "description": "Payload storage adapter for Vercel Blob Storage", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/translations/package.json b/packages/translations/package.json index 82efe96944..2608d3cb10 100644 --- a/packages/translations/package.json +++ b/packages/translations/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/translations", - "version": "3.35.0", + "version": "3.35.1", "homepage": "https://payloadcms.com", "repository": { "type": "git", diff --git a/packages/ui/package.json b/packages/ui/package.json index f7c361ab46..360012acd2 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/ui", - "version": "3.35.0", + "version": "3.35.1", "homepage": "https://payloadcms.com", "repository": { "type": "git", From 34ea6ec14f2ccea1ba6cdec9e910dee037972d37 Mon Sep 17 00:00:00 2001 From: Patrik Date: Thu, 17 Apr 2025 14:45:10 -0400 Subject: [PATCH 04/15] feat: adds `showSaveDraftButton` option to show draft button with autosave enabled (#12150) This adds a new `showSaveDraftButton` option to the `versions.drafts.autosave` config for collections and globals. By default, the "Save as draft" button is hidden when autosave is enabled. This new option allows the button to remain visible for manual saves while autosave is active. Also updates the admin UI logic to conditionally render the button when this flag is set, and updates the documentation with an example usage. --- docs/versions/autosave.mdx | 7 +- packages/payload/src/versions/types.ts | 7 + .../src/elements/DocumentControls/index.tsx | 22 +- .../collections/AutosaveWithDraftButton.ts | 32 +++ test/versions/config.ts | 12 +- test/versions/e2e.spec.ts | 204 ++++++++++-------- .../globals/AutosaveWithDraftButton.ts | 26 +++ test/versions/payload-types.ts | 64 +++++- test/versions/slugs.ts | 6 + 9 files changed, 282 insertions(+), 98 deletions(-) create mode 100644 test/versions/collections/AutosaveWithDraftButton.ts create mode 100644 test/versions/globals/AutosaveWithDraftButton.ts diff --git a/docs/versions/autosave.mdx b/docs/versions/autosave.mdx index 077c473259..5d1b9d6a73 100644 --- a/docs/versions/autosave.mdx +++ b/docs/versions/autosave.mdx @@ -22,6 +22,7 @@ Collections and Globals both support the same options for configuring autosave. | Drafts Autosave Options | Description | | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `interval` | Define an `interval` in milliseconds to automatically save progress while documents are edited. Document updates are "debounced" at this interval. Defaults to `800`. | +| `showSaveDraftButton` | Set this to `true` to show the "Save as draft" button even while autosave is enabled. Defaults to `false`. | **Example config with versions, drafts, and autosave enabled:** @@ -50,9 +51,13 @@ export const Pages: CollectionConfig = { drafts: { autosave: true, - // Alternatively, you can specify an `interval`: + // Alternatively, you can specify an object to customize autosave: // autosave: { + // Define how often the document should be autosaved (in milliseconds) // interval: 1500, + // + // Show the "Save as draft" button even while autosave is enabled + // showSaveDraftButton: true, // }, }, }, diff --git a/packages/payload/src/versions/types.ts b/packages/payload/src/versions/types.ts index 43c0d21854..efbc4e3c95 100644 --- a/packages/payload/src/versions/types.ts +++ b/packages/payload/src/versions/types.ts @@ -6,6 +6,13 @@ export type Autosave = { * @default 800 */ interval?: number + /** + * When set to `true`, the "Save as draft" button will be displayed even while autosave is enabled. + * By default, this button is hidden to avoid redundancy with autosave behavior. + * + * @default false + */ + showSaveDraftButton?: boolean } export type SchedulePublish = { diff --git a/packages/ui/src/elements/DocumentControls/index.tsx b/packages/ui/src/elements/DocumentControls/index.tsx index 0e99c0dcd5..2c0a6ed3e5 100644 --- a/packages/ui/src/elements/DocumentControls/index.tsx +++ b/packages/ui/src/elements/DocumentControls/index.tsx @@ -133,9 +133,23 @@ export const DocumentControls: React.FC<{ const unsavedDraftWithValidations = !id && collectionConfig?.versions?.drafts && collectionConfig.versions?.drafts.validate + const collectionConfigDrafts = collectionConfig?.versions?.drafts + const globalConfigDrafts = globalConfig?.versions?.drafts + const autosaveEnabled = - (collectionConfig?.versions?.drafts && collectionConfig?.versions?.drafts?.autosave) || - (globalConfig?.versions?.drafts && globalConfig?.versions?.drafts?.autosave) + (collectionConfigDrafts && collectionConfigDrafts?.autosave) || + (globalConfigDrafts && globalConfigDrafts?.autosave) + + const collectionAutosaveEnabled = collectionConfigDrafts && collectionConfigDrafts?.autosave + const globalAutosaveEnabled = globalConfigDrafts && globalConfigDrafts?.autosave + + const showSaveDraftButton = + (collectionAutosaveEnabled && + collectionConfigDrafts.autosave !== false && + collectionConfigDrafts.autosave.showSaveDraftButton === true) || + (globalAutosaveEnabled && + globalConfigDrafts.autosave !== false && + globalConfigDrafts.autosave.showSaveDraftButton === true) const showCopyToLocale = localization && !collectionConfig?.admin?.disableCopyToLocale @@ -218,7 +232,9 @@ export const DocumentControls: React.FC<{ {collectionConfig?.versions?.drafts || globalConfig?.versions?.drafts ? ( - {(unsavedDraftWithValidations || !autosaveEnabled) && ( + {(unsavedDraftWithValidations || + !autosaveEnabled || + (autosaveEnabled && showSaveDraftButton)) && ( } diff --git a/test/versions/collections/AutosaveWithDraftButton.ts b/test/versions/collections/AutosaveWithDraftButton.ts new file mode 100644 index 0000000000..3fc719a78d --- /dev/null +++ b/test/versions/collections/AutosaveWithDraftButton.ts @@ -0,0 +1,32 @@ +import type { CollectionConfig } from 'payload' + +import { autosaveWithDraftButtonSlug } from '../slugs.js' + +const AutosaveWithDraftButtonPosts: CollectionConfig = { + slug: autosaveWithDraftButtonSlug, + labels: { + singular: 'Autosave with Draft Button Post', + plural: 'Autosave with Draft Button Posts', + }, + admin: { + useAsTitle: 'title', + defaultColumns: ['title', 'subtitle', 'createdAt', '_status'], + }, + versions: { + drafts: { + autosave: { + showSaveDraftButton: true, + interval: 1000, + }, + }, + }, + fields: [ + { + name: 'title', + type: 'text', + required: true, + }, + ], +} + +export default AutosaveWithDraftButtonPosts diff --git a/test/versions/config.ts b/test/versions/config.ts index 1b23833611..36fdb76728 100644 --- a/test/versions/config.ts +++ b/test/versions/config.ts @@ -4,6 +4,7 @@ const filename = fileURLToPath(import.meta.url) const dirname = path.dirname(filename) import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js' import AutosavePosts from './collections/Autosave.js' +import AutosaveWithDraftButtonPosts from './collections/AutosaveWithDraftButton.js' import AutosaveWithValidate from './collections/AutosaveWithValidate.js' import CustomIDs from './collections/CustomIDs.js' import { Diff } from './collections/Diff/index.js' @@ -17,6 +18,7 @@ import Posts from './collections/Posts.js' import { TextCollection } from './collections/Text.js' import VersionPosts from './collections/Versions.js' import AutosaveGlobal from './globals/Autosave.js' +import AutosaveWithDraftButtonGlobal from './globals/AutosaveWithDraftButton.js' import DisablePublishGlobal from './globals/DisablePublish.js' import DraftGlobal from './globals/Draft.js' import DraftWithMaxGlobal from './globals/DraftWithMax.js' @@ -35,6 +37,7 @@ export default buildConfigWithDefaults({ DisablePublish, Posts, AutosavePosts, + AutosaveWithDraftButtonPosts, AutosaveWithValidate, DraftPosts, DraftWithMax, @@ -46,7 +49,14 @@ export default buildConfigWithDefaults({ TextCollection, Media, ], - globals: [AutosaveGlobal, DraftGlobal, DraftWithMaxGlobal, DisablePublishGlobal, LocalizedGlobal], + globals: [ + AutosaveGlobal, + AutosaveWithDraftButtonGlobal, + DraftGlobal, + DraftWithMaxGlobal, + DisablePublishGlobal, + LocalizedGlobal, + ], indexSortableFields: true, localization: { defaultLocale: 'en', diff --git a/test/versions/e2e.spec.ts b/test/versions/e2e.spec.ts index e5d9e7cd1a..2798fb52cd 100644 --- a/test/versions/e2e.spec.ts +++ b/test/versions/e2e.spec.ts @@ -48,6 +48,8 @@ import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../playwright.config.js' import { autosaveCollectionSlug, autoSaveGlobalSlug, + autosaveWithDraftButtonGlobal, + autosaveWithDraftButtonSlug, autosaveWithValidateCollectionSlug, customIDSlug, diffCollectionSlug, @@ -78,6 +80,7 @@ describe('Versions', () => { let url: AdminUrlUtil let serverURL: string let autosaveURL: AdminUrlUtil + let autosaveWithDraftButtonURL: AdminUrlUtil let autosaveWithValidateURL: AdminUrlUtil let draftWithValidateURL: AdminUrlUtil let disablePublishURL: AdminUrlUtil @@ -116,6 +119,7 @@ describe('Versions', () => { beforeAll(() => { url = new AdminUrlUtil(serverURL, draftCollectionSlug) autosaveURL = new AdminUrlUtil(serverURL, autosaveCollectionSlug) + autosaveWithDraftButtonURL = new AdminUrlUtil(serverURL, autosaveWithDraftButtonSlug) autosaveWithValidateURL = new AdminUrlUtil(serverURL, autosaveWithValidateCollectionSlug) disablePublishURL = new AdminUrlUtil(serverURL, disablePublishSlug) customIDURL = new AdminUrlUtil(serverURL, customIDSlug) @@ -201,78 +205,6 @@ describe('Versions', () => { await expect(page.locator('#field-title')).toHaveValue('v1') }) - 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) - }) - - // TODO: Check versions/:version-id view for collections / globals - - 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') @@ -309,6 +241,16 @@ describe('Versions', () => { await expect(drawer.locator('.id-label')).toBeVisible() }) + test('collection - should show "save as draft" button when showSaveDraftButton is true', async () => { + await page.goto(autosaveWithDraftButtonURL.create) + await expect(page.locator('#action-save-draft')).toBeVisible() + }) + + test('collection - should not show "save as draft" button when showSaveDraftButton is false', async () => { + await page.goto(autosaveURL.create) + await expect(page.locator('#action-save-draft')).toBeHidden() + }) + test('collection - autosave - should not create duplicates when clicking Create new', async () => { // This test checks that when we click "Create new" in the list view, it only creates 1 extra document and not more const { totalDocs: initialDocsCount } = await payload.find({ @@ -402,17 +344,6 @@ describe('Versions', () => { await expect(newUpdatedAt).not.toHaveText(initialUpdatedAt) }) - test('global - should autosave', async () => { - const url = new AdminUrlUtil(serverURL, autoSaveGlobalSlug) - await page.goto(url.global(autoSaveGlobalSlug)) - const titleField = page.locator('#field-title') - await titleField.fill('global title') - await waitForAutoSaveToRunAndComplete(page) - await expect(titleField).toHaveValue('global title') - await page.goto(url.global(autoSaveGlobalSlug)) - await expect(page.locator('#field-title')).toHaveValue('global title') - }) - test('should retain localized data during autosave', async () => { const en = 'en' const es = 'es' @@ -519,12 +450,6 @@ describe('Versions', () => { await expect(page.locator('#field-title')).toHaveValue('title') }) - test('globals — should hide publish button when access control prevents update', async () => { - const url = new AdminUrlUtil(serverURL, disablePublishGlobalSlug) - await page.goto(url.global(disablePublishGlobalSlug)) - await expect(page.locator('#action-save')).not.toBeAttached() - }) - test('collections — should hide publish button when access control prevents create', async () => { await page.goto(disablePublishURL.create) await expect(page.locator('#action-save')).not.toBeAttached() @@ -652,6 +577,107 @@ describe('Versions', () => { }) }) + describe('draft globals', () => { + 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('global - should show "save as draft" button when showSaveDraftButton is true', async () => { + const url = new AdminUrlUtil(serverURL, autosaveWithDraftButtonGlobal) + await page.goto(url.global(autosaveWithDraftButtonGlobal)) + await expect(page.locator('#action-save-draft')).toBeVisible() + }) + + test('global - should not show "save as draft" button when showSaveDraftButton is false', async () => { + const url = new AdminUrlUtil(serverURL, autoSaveGlobalSlug) + await page.goto(url.global(autoSaveGlobalSlug)) + await expect(page.locator('#action-save-draft')).toBeHidden() + }) + + test('global - should autosave', async () => { + const url = new AdminUrlUtil(serverURL, autoSaveGlobalSlug) + await page.goto(url.global(autoSaveGlobalSlug)) + const titleField = page.locator('#field-title') + await titleField.fill('global title') + await waitForAutoSaveToRunAndComplete(page) + await expect(titleField).toHaveValue('global title') + await page.goto(url.global(autoSaveGlobalSlug)) + await expect(page.locator('#field-title')).toHaveValue('global title') + }) + + test('globals — should hide publish button when access control prevents update', async () => { + const url = new AdminUrlUtil(serverURL, disablePublishGlobalSlug) + await page.goto(url.global(disablePublishGlobalSlug)) + await expect(page.locator('#action-save')).not.toBeAttached() + }) + }) + describe('Scheduled publish', () => { beforeAll(() => { url = new AdminUrlUtil(serverURL, draftCollectionSlug) diff --git a/test/versions/globals/AutosaveWithDraftButton.ts b/test/versions/globals/AutosaveWithDraftButton.ts new file mode 100644 index 0000000000..5d8f1372a8 --- /dev/null +++ b/test/versions/globals/AutosaveWithDraftButton.ts @@ -0,0 +1,26 @@ +import type { GlobalConfig } from 'payload' + +import { autosaveWithDraftButtonGlobal } from '../slugs.js' + +const AutosaveWithDraftButtonGlobal: GlobalConfig = { + slug: autosaveWithDraftButtonGlobal, + fields: [ + { + name: 'title', + type: 'text', + localized: true, + required: true, + }, + ], + label: 'Autosave with Draft Button Global', + versions: { + drafts: { + autosave: { + showSaveDraftButton: true, + interval: 1000, + }, + }, + }, +} + +export default AutosaveWithDraftButtonGlobal diff --git a/test/versions/payload-types.ts b/test/versions/payload-types.ts index 6e5e4fc8c6..c016d16822 100644 --- a/test/versions/payload-types.ts +++ b/test/versions/payload-types.ts @@ -70,6 +70,7 @@ export interface Config { 'disable-publish': DisablePublish; posts: Post; 'autosave-posts': AutosavePost; + 'autosave-with-draft-button-posts': AutosaveWithDraftButtonPost; 'autosave-with-validate-posts': AutosaveWithValidatePost; 'draft-posts': DraftPost; 'draft-with-max-posts': DraftWithMaxPost; @@ -91,6 +92,7 @@ export interface Config { 'disable-publish': DisablePublishSelect | DisablePublishSelect; posts: PostsSelect | PostsSelect; 'autosave-posts': AutosavePostsSelect | AutosavePostsSelect; + 'autosave-with-draft-button-posts': AutosaveWithDraftButtonPostsSelect | AutosaveWithDraftButtonPostsSelect; 'autosave-with-validate-posts': AutosaveWithValidatePostsSelect | AutosaveWithValidatePostsSelect; 'draft-posts': DraftPostsSelect | DraftPostsSelect; 'draft-with-max-posts': DraftWithMaxPostsSelect | DraftWithMaxPostsSelect; @@ -112,6 +114,7 @@ export interface Config { }; globals: { 'autosave-global': AutosaveGlobal; + 'autosave-with-draft-button-global': AutosaveWithDraftButtonGlobal; 'draft-global': DraftGlobal; 'draft-with-max-global': DraftWithMaxGlobal; 'disable-publish-global': DisablePublishGlobal; @@ -119,6 +122,7 @@ export interface Config { }; globalsSelect: { 'autosave-global': AutosaveGlobalSelect | AutosaveGlobalSelect; + 'autosave-with-draft-button-global': AutosaveWithDraftButtonGlobalSelect | AutosaveWithDraftButtonGlobalSelect; 'draft-global': DraftGlobalSelect | DraftGlobalSelect; 'draft-with-max-global': DraftWithMaxGlobalSelect | DraftWithMaxGlobalSelect; 'disable-publish-global': DisablePublishGlobalSelect | DisablePublishGlobalSelect; @@ -228,6 +232,17 @@ export interface DraftPost { createdAt: string; _status?: ('draft' | 'published') | null; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "autosave-with-draft-button-posts". + */ +export interface AutosaveWithDraftButtonPost { + id: string; + title: string; + updatedAt: string; + createdAt: string; + _status?: ('draft' | 'published') | null; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "autosave-with-validate-posts". @@ -554,6 +569,10 @@ export interface PayloadLockedDocument { relationTo: 'autosave-posts'; value: string | AutosavePost; } | null) + | ({ + relationTo: 'autosave-with-draft-button-posts'; + value: string | AutosaveWithDraftButtonPost; + } | null) | ({ relationTo: 'autosave-with-validate-posts'; value: string | AutosaveWithValidatePost; @@ -676,6 +695,16 @@ export interface AutosavePostsSelect { createdAt?: T; _status?: T; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "autosave-with-draft-button-posts_select". + */ +export interface AutosaveWithDraftButtonPostsSelect { + title?: T; + updatedAt?: T; + createdAt?: T; + _status?: T; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "autosave-with-validate-posts_select". @@ -973,6 +1002,17 @@ export interface AutosaveGlobal { updatedAt?: string | null; createdAt?: string | null; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "autosave-with-draft-button-global". + */ +export interface AutosaveWithDraftButtonGlobal { + id: string; + title: string; + _status?: ('draft' | 'published') | null; + updatedAt?: string | null; + createdAt?: string | null; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "draft-global". @@ -1029,6 +1069,17 @@ export interface AutosaveGlobalSelect { createdAt?: T; globalType?: T; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "autosave-with-draft-button-global_select". + */ +export interface AutosaveWithDraftButtonGlobalSelect { + title?: T; + _status?: T; + updatedAt?: T; + createdAt?: T; + globalType?: T; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "draft-global_select". @@ -1082,10 +1133,15 @@ export interface TaskSchedulePublish { input: { type?: ('publish' | 'unpublish') | null; locale?: string | null; - doc?: { - relationTo: 'draft-posts'; - value: string | DraftPost; - } | null; + doc?: + | ({ + relationTo: 'autosave-posts'; + value: string | AutosavePost; + } | null) + | ({ + relationTo: 'draft-posts'; + value: string | DraftPost; + } | null); global?: 'draft-global' | null; user?: (string | null) | User; }; diff --git a/test/versions/slugs.ts b/test/versions/slugs.ts index 2dccdb8b65..5807c381b5 100644 --- a/test/versions/slugs.ts +++ b/test/versions/slugs.ts @@ -1,5 +1,7 @@ export const autosaveCollectionSlug = 'autosave-posts' +export const autosaveWithDraftButtonSlug = 'autosave-with-draft-button-posts' + export const autosaveWithValidateCollectionSlug = 'autosave-with-validate-posts' export const customIDSlug = 'custom-ids' @@ -33,7 +35,11 @@ export const collectionSlugs = [ ] export const autoSaveGlobalSlug = 'autosave-global' + +export const autosaveWithDraftButtonGlobal = 'autosave-with-draft-button-global' + export const draftGlobalSlug = 'draft-global' + export const draftWithMaxGlobalSlug = 'draft-with-max-global' export const globalSlugs = [autoSaveGlobalSlug, draftGlobalSlug] From d55306980e149a085d4c05692e46d09d4341b9ee Mon Sep 17 00:00:00 2001 From: Patrik Date: Thu, 17 Apr 2025 15:23:17 -0400 Subject: [PATCH 05/15] feat: adds `beforeDocumentControls` slot to allow custom component injection next to document controls (#12104) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### What This PR introduces a new `beforeDocumentControls` slot to the edit view of both collections and globals. It allows injecting one or more custom components next to the document control buttons (e.g., Save, Publish, Save Draft) in the admin UI — useful for adding context, additional buttons, or custom UI elements. #### Usage ##### For collections: ``` admin: { components: { edit: { beforeDocumentControls: ['/path/to/CustomComponent'], }, }, }, ``` ##### For globals: ``` admin: { components: { elements: { beforeDocumentControls: ['/path/to/CustomComponent'], }, }, }, ``` --- docs/custom-components/edit-view.mdx | 99 ++++++++++++++++--- .../views/Document/renderDocumentSlots.tsx | 13 +++ packages/payload/src/admin/types.ts | 4 + packages/payload/src/admin/views/document.ts | 11 ++- .../generateImportMap/iterateCollections.ts | 1 + .../payload/src/collections/config/types.ts | 4 + packages/payload/src/globals/config/types.ts | 5 + .../src/elements/DocumentControls/index.tsx | 3 + packages/ui/src/views/Edit/index.tsx | 2 + test/admin/collections/Posts.ts | 6 ++ .../CustomDraftButton/index.tsx | 23 +++++ .../CustomSaveButton/index.tsx | 23 +++++ test/admin/e2e/document-view/e2e.spec.ts | 38 ++++++- test/admin/globals/Global.ts | 5 + 14 files changed, 216 insertions(+), 21 deletions(-) create mode 100644 test/admin/components/BeforeDocumentControls/CustomDraftButton/index.tsx create mode 100644 test/admin/components/BeforeDocumentControls/CustomSaveButton/index.tsx diff --git a/docs/custom-components/edit-view.mdx b/docs/custom-components/edit-view.mdx index 4f26004a86..03043e2daf 100644 --- a/docs/custom-components/edit-view.mdx +++ b/docs/custom-components/edit-view.mdx @@ -101,14 +101,15 @@ export const MyCollection: CollectionConfig = { The following options are available: -| Path | Description | -| ----------------- | -------------------------------------------------------------------------------------- | -| `SaveButton` | A button that saves the current document. [More details](#savebutton). | -| `SaveDraftButton` | A button that saves the current document as a draft. [More details](#savedraftbutton). | -| `PublishButton` | A button that publishes the current document. [More details](#publishbutton). | -| `PreviewButton` | A button that previews the current document. [More details](#previewbutton). | -| `Description` | A description of the Collection. [More details](#description). | -| `Upload` | A file upload component. [More details](#upload). | +| Path | Description | +| ------------------------ | ---------------------------------------------------------------------------------------------------- | +| `beforeDocumentControls` | Inject custom components before the Save / Publish buttons. [More details](#beforedocumentcontrols). | +| `SaveButton` | A button that saves the current document. [More details](#savebutton). | +| `SaveDraftButton` | A button that saves the current document as a draft. [More details](#savedraftbutton). | +| `PublishButton` | A button that publishes the current document. [More details](#publishbutton). | +| `PreviewButton` | A button that previews the current document. [More details](#previewbutton). | +| `Description` | A description of the Collection. [More details](#description). | +| `Upload` | A file upload component. [More details](#upload). | #### Globals @@ -133,13 +134,14 @@ export const MyGlobal: GlobalConfig = { The following options are available: -| Path | Description | -| ----------------- | -------------------------------------------------------------------------------------- | -| `SaveButton` | A button that saves the current document. [More details](#savebutton). | -| `SaveDraftButton` | A button that saves the current document as a draft. [More details](#savedraftbutton). | -| `PublishButton` | A button that publishes the current document. [More details](#publishbutton). | -| `PreviewButton` | A button that previews the current document. [More details](#previewbutton). | -| `Description` | A description of the Global. [More details](#description). | +| Path | Description | +| ------------------------ | ---------------------------------------------------------------------------------------------------- | +| `beforeDocumentControls` | Inject custom components before the Save / Publish buttons. [More details](#beforedocumentcontrols). | +| `SaveButton` | A button that saves the current document. [More details](#savebutton). | +| `SaveDraftButton` | A button that saves the current document as a draft. [More details](#savedraftbutton). | +| `PublishButton` | A button that publishes the current document. [More details](#publishbutton). | +| `PreviewButton` | A button that previews the current document. [More details](#previewbutton). | +| `Description` | A description of the Global. [More details](#description). | ### SaveButton @@ -191,6 +193,73 @@ export function MySaveButton(props: SaveButtonClientProps) { } ``` +### beforeDocumentControls + +The `beforeDocumentControls` property allows you to render custom components just before the default document action buttons (like Save, Publish, or Preview). This is useful for injecting custom buttons, status indicators, or any other UI elements before the built-in controls. + +To add `beforeDocumentControls` components, use the `components.edit.beforeDocumentControls` property in you [Collection Config](../configuration/collections) or `components.elements.beforeDocumentControls` in your [Global Config](../configuration/globals): + +#### Collections + +``` +export const MyCollection: CollectionConfig = { + admin: { + components: { + edit: { + // highlight-start + beforeDocumentControls: ['/path/to/CustomComponent'], + // highlight-end + }, + }, + }, +} +``` + +#### Globals + +``` +export const MyGlobal: GlobalConfig = { + admin: { + components: { + elements: { + // highlight-start + beforeDocumentControls: ['/path/to/CustomComponent'], + // highlight-end + }, + }, + }, +} +``` + +Here's an example of a custom `beforeDocumentControls` component: + +#### Server Component + +```tsx +import React from 'react' +import type { BeforeDocumentControlsServerProps } from 'payload' + +export function MyCustomDocumentControlButton( + props: BeforeDocumentControlsServerProps, +) { + return
This is a custom beforeDocumentControl button (Server)
+} +``` + +#### Client Component + +```tsx +'use client' +import React from 'react' +import type { BeforeDocumentControlsClientProps } from 'payload' + +export function MyCustomDocumentControlButton( + props: BeforeDocumentControlsClientProps, +) { + return
This is a custom beforeDocumentControl button (Client)
+} +``` + ### SaveDraftButton The `SaveDraftButton` property allows you to render a custom Save Draft Button in the Edit View. diff --git a/packages/next/src/views/Document/renderDocumentSlots.tsx b/packages/next/src/views/Document/renderDocumentSlots.tsx index dde74e5e26..dee93e9410 100644 --- a/packages/next/src/views/Document/renderDocumentSlots.tsx +++ b/packages/next/src/views/Document/renderDocumentSlots.tsx @@ -1,4 +1,5 @@ import type { + BeforeDocumentControlsServerPropsOnly, DefaultServerFunctionArgs, DocumentSlots, PayloadRequest, @@ -42,6 +43,18 @@ export const renderDocumentSlots: (args: { // TODO: Add remaining serverProps } + const BeforeDocumentControls = + collectionConfig?.admin?.components?.edit?.beforeDocumentControls || + globalConfig?.admin?.components?.elements?.beforeDocumentControls + + if (BeforeDocumentControls) { + components.BeforeDocumentControls = RenderServerComponent({ + Component: BeforeDocumentControls, + importMap: req.payload.importMap, + serverProps: serverProps satisfies BeforeDocumentControlsServerPropsOnly, + }) + } + const CustomPreviewButton = collectionConfig?.admin?.components?.edit?.PreviewButton || globalConfig?.admin?.components?.elements?.PreviewButton diff --git a/packages/payload/src/admin/types.ts b/packages/payload/src/admin/types.ts index f98d8075b4..40fb5abf40 100644 --- a/packages/payload/src/admin/types.ts +++ b/packages/payload/src/admin/types.ts @@ -553,6 +553,7 @@ export type FieldRow = { } export type DocumentSlots = { + BeforeDocumentControls?: React.ReactNode Description?: React.ReactNode PreviewButton?: React.ReactNode PublishButton?: React.ReactNode @@ -578,6 +579,9 @@ export type { LanguageOptions } from './LanguageOptions.js' export type { RichTextAdapter, RichTextAdapterProvider, RichTextHooks } from './RichText.js' export type { + BeforeDocumentControlsClientProps, + BeforeDocumentControlsServerProps, + BeforeDocumentControlsServerPropsOnly, DocumentSubViewTypes, DocumentTabClientProps, /** diff --git a/packages/payload/src/admin/views/document.ts b/packages/payload/src/admin/views/document.ts index db0b2ee2db..7e8d14f145 100644 --- a/packages/payload/src/admin/views/document.ts +++ b/packages/payload/src/admin/views/document.ts @@ -36,12 +36,12 @@ export type DocumentTabServerPropsOnly = { readonly permissions: SanitizedPermissions } & ServerProps -export type DocumentTabServerProps = DocumentTabClientProps & DocumentTabServerPropsOnly - export type DocumentTabClientProps = { path: string } +export type DocumentTabServerProps = DocumentTabClientProps & DocumentTabServerPropsOnly + export type DocumentTabCondition = (args: { collectionConfig: SanitizedCollectionConfig config: SanitizedConfig @@ -75,3 +75,10 @@ export type DocumentTabConfig = { export type DocumentTabComponent = PayloadComponent<{ path: string }> + +// BeforeDocumentControls + +export type BeforeDocumentControlsClientProps = {} +export type BeforeDocumentControlsServerPropsOnly = {} & ServerProps +export type BeforeDocumentControlsServerProps = BeforeDocumentControlsClientProps & + BeforeDocumentControlsServerPropsOnly diff --git a/packages/payload/src/bin/generateImportMap/iterateCollections.ts b/packages/payload/src/bin/generateImportMap/iterateCollections.ts index 9aa7783576..27134fc86f 100644 --- a/packages/payload/src/bin/generateImportMap/iterateCollections.ts +++ b/packages/payload/src/bin/generateImportMap/iterateCollections.ts @@ -36,6 +36,7 @@ export function iterateCollections({ addToImportMap(collection.admin?.components?.beforeListTable) addToImportMap(collection.admin?.components?.Description) + addToImportMap(collection.admin?.components?.edit?.beforeDocumentControls) addToImportMap(collection.admin?.components?.edit?.PreviewButton) addToImportMap(collection.admin?.components?.edit?.PublishButton) addToImportMap(collection.admin?.components?.edit?.SaveButton) diff --git a/packages/payload/src/collections/config/types.ts b/packages/payload/src/collections/config/types.ts index 7478a64af6..90be14abdc 100644 --- a/packages/payload/src/collections/config/types.ts +++ b/packages/payload/src/collections/config/types.ts @@ -279,6 +279,10 @@ export type CollectionAdminOptions = { * Components within the edit view */ edit?: { + /** + * Inject custom components before the document controls + */ + beforeDocumentControls?: CustomComponent[] /** * Replaces the "Preview" button */ diff --git a/packages/payload/src/globals/config/types.ts b/packages/payload/src/globals/config/types.ts index 7521695068..a2bbeb94c1 100644 --- a/packages/payload/src/globals/config/types.ts +++ b/packages/payload/src/globals/config/types.ts @@ -9,6 +9,7 @@ import type { } from '../../admin/types.js' import type { Access, + CustomComponent, EditConfig, Endpoint, EntityDescription, @@ -80,6 +81,10 @@ export type GlobalAdminOptions = { */ components?: { elements?: { + /** + * Inject custom components before the document controls + */ + beforeDocumentControls?: CustomComponent[] Description?: EntityDescriptionComponent /** * Replaces the "Preview" button diff --git a/packages/ui/src/elements/DocumentControls/index.tsx b/packages/ui/src/elements/DocumentControls/index.tsx index 2c0a6ed3e5..f13be0fd15 100644 --- a/packages/ui/src/elements/DocumentControls/index.tsx +++ b/packages/ui/src/elements/DocumentControls/index.tsx @@ -37,6 +37,7 @@ const baseClass = 'doc-controls' export const DocumentControls: React.FC<{ readonly apiURL: string + readonly BeforeDocumentControls?: React.ReactNode readonly customComponents?: { readonly PreviewButton?: React.ReactNode readonly PublishButton?: React.ReactNode @@ -67,6 +68,7 @@ export const DocumentControls: React.FC<{ const { id, slug, + BeforeDocumentControls, customComponents: { PreviewButton: CustomPreviewButton, PublishButton: CustomPublishButton, @@ -222,6 +224,7 @@ export const DocumentControls: React.FC<{
+ {BeforeDocumentControls} {(collectionConfig?.admin.preview || globalConfig?.admin.preview) && ( +

+ Custom Draft Button +

+
+ ) +} diff --git a/test/admin/components/BeforeDocumentControls/CustomSaveButton/index.tsx b/test/admin/components/BeforeDocumentControls/CustomSaveButton/index.tsx new file mode 100644 index 0000000000..d4ef70125e --- /dev/null +++ b/test/admin/components/BeforeDocumentControls/CustomSaveButton/index.tsx @@ -0,0 +1,23 @@ +import type { BeforeDocumentControlsServerProps } from 'payload' + +import React from 'react' + +const baseClass = 'custom-save-button' + +export function CustomSaveButton(props: BeforeDocumentControlsServerProps) { + return ( +
+

+ Custom Save Button +

+
+ ) +} diff --git a/test/admin/e2e/document-view/e2e.spec.ts b/test/admin/e2e/document-view/e2e.spec.ts index 0fee868040..d3830b255d 100644 --- a/test/admin/e2e/document-view/e2e.spec.ts +++ b/test/admin/e2e/document-view/e2e.spec.ts @@ -110,7 +110,7 @@ describe('Document View', () => { }) expect(collectionItems.docs.length).toBe(1) await page.goto( - `${postsUrl.collection(noApiViewGlobalSlug)}/${collectionItems.docs[0].id}/api`, + `${postsUrl.collection(noApiViewGlobalSlug)}/${collectionItems?.docs[0]?.id}/api`, ) await expect(page.locator('.not-found')).toHaveCount(1) }) @@ -333,20 +333,32 @@ describe('Document View', () => { await navigateToDoc(page, postsUrl) await page.locator('#field-title').fill(title) await saveDocAndAssert(page) + 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 drawerLeft = await drawer1Content.boundingBox().then((box) => box.x) + + const drawer1Box = await drawer1Content.boundingBox() + await expect.poll(() => drawer1Box).not.toBeNull() + const drawerLeft = drawer1Box!.x + await drawer1Content .locator('.field-type.relationship .relationship--single-value__drawer-toggler') .click() + const drawer2Content = page.locator('[id^=doc-drawer_posts_2_] .drawer__content') await expect(drawer2Content).toBeVisible() - const drawer2Left = await drawer2Content.boundingBox().then((box) => box.x) - expect(drawer2Left > drawerLeft).toBe(true) + + const drawer2Box = await drawer2Content.boundingBox() + await expect.poll(() => drawer2Box).not.toBeNull() + const drawer2Left = drawer2Box!.x + + await expect.poll(() => drawer2Left > drawerLeft).toBe(true) }) }) @@ -523,6 +535,24 @@ describe('Document View', () => { await expect(fileField).toHaveValue('some file text') }) }) + + describe('custom document controls', () => { + test('should show custom elements in document controls in collection', async () => { + await page.goto(postsUrl.create) + const customDraftButton = page.locator('#custom-draft-button') + const customSaveButton = page.locator('#custom-save-button') + + await expect(customDraftButton).toBeVisible() + await expect(customSaveButton).toBeVisible() + }) + + test('should show custom elements in document controls in global', async () => { + await page.goto(globalURL.global(globalSlug)) + const customDraftButton = page.locator('#custom-draft-button') + + await expect(customDraftButton).toBeVisible() + }) + }) }) async function createPost(overrides?: Partial): Promise { diff --git a/test/admin/globals/Global.ts b/test/admin/globals/Global.ts index e654c584b6..7f0d615102 100644 --- a/test/admin/globals/Global.ts +++ b/test/admin/globals/Global.ts @@ -6,6 +6,11 @@ export const Global: GlobalConfig = { slug: globalSlug, admin: { components: { + elements: { + beforeDocumentControls: [ + '/components/BeforeDocumentControls/CustomDraftButton/index.js#CustomDraftButton', + ], + }, views: { edit: { api: { From b750ba450917daf37d63ed5feaa67637f93318e5 Mon Sep 17 00:00:00 2001 From: Corey Larson Date: Fri, 18 Apr 2025 05:10:48 -0500 Subject: [PATCH 06/15] fix(ui): reflect default sort in join tables (#12084) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### What? This PR ensures defaultSort is reflected in join tables. ### Why? Currently, default sort is not reflected in the join table state. The data _is_ sorted correctly, but the table state sort is undefined. This is mainly an issue for join fields with `orderable: true` because you can't re-order the table until `order` is the selected sort column. ### How? Added `defaultSort` prop to the `` in the `` and ensured the default state gets set in `` when `modifySearchParams` is false. **Before:** Screenshot 2025-04-11 at 2 33 19 AM **After:** Screenshot 2025-04-11 at 3 04 07 AM Fixes #12083 --------- Co-authored-by: Germán Jabloñski <43938777+GermanJablo@users.noreply.github.com> --- packages/ui/src/elements/RelationshipTable/index.tsx | 1 + packages/ui/src/providers/ListQuery/index.tsx | 5 ++++- test/sort/e2e.spec.ts | 4 ---- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/elements/RelationshipTable/index.tsx b/packages/ui/src/elements/RelationshipTable/index.tsx index 3247dd3c94..79f998188f 100644 --- a/packages/ui/src/elements/RelationshipTable/index.tsx +++ b/packages/ui/src/elements/RelationshipTable/index.tsx @@ -335,6 +335,7 @@ export const RelationshipTable: React.FC = (pro defaultLimit={ field.defaultLimit ?? collectionConfig?.admin?.pagination?.defaultLimit } + defaultSort={field.defaultSort ?? collectionConfig?.defaultSort} modifySearchParams={false} onQueryChange={setQuery} orderableFieldName={ diff --git a/packages/ui/src/providers/ListQuery/index.tsx b/packages/ui/src/providers/ListQuery/index.tsx index 62a55dac86..53eee2fc72 100644 --- a/packages/ui/src/providers/ListQuery/index.tsx +++ b/packages/ui/src/providers/ListQuery/index.tsx @@ -48,7 +48,10 @@ export const ListQueryProvider: React.FC = ({ if (modifySearchParams) { return searchParams } else { - return {} + return { + limit: String(defaultLimit), + sort: defaultSort, + } } }) diff --git a/test/sort/e2e.spec.ts b/test/sort/e2e.spec.ts index fdc53d19dc..f8a0421378 100644 --- a/test/sort/e2e.spec.ts +++ b/test/sort/e2e.spec.ts @@ -82,19 +82,15 @@ describe('Sort functionality', () => { await page.getByText('Join A').click() await expect(page.locator('.sort-header button')).toHaveCount(2) - await page.locator('.sort-header button').nth(0).click() await assertRows(0, 'A', 'B', 'C', 'D') await moveRow(2, 3, 'success', 0) // move to middle await assertRows(0, 'A', 'C', 'B', 'D') - await page.locator('.sort-header button').nth(1).click() await assertRows(1, 'A', 'B', 'C', 'D') await moveRow(1, 4, 'success', 1) // move to end await assertRows(1, 'B', 'C', 'D', 'A') await page.reload() - await page.locator('.sort-header button').nth(0).click() - await page.locator('.sort-header button').nth(1).click() await assertRows(0, 'A', 'C', 'B', 'D') await assertRows(1, 'B', 'C', 'D', 'A') }) From df7a3692f761f777255e95c17a939daffd1b7389 Mon Sep 17 00:00:00 2001 From: Dan Ribbens Date: Fri, 18 Apr 2025 09:47:36 -0400 Subject: [PATCH 07/15] fix(plugin-search): delete does not also delete the search doc (#12148) The plugin-search collection uses an `afterDelete` hook to remove search records from the database. Since a deleted document in postgres causes cascade updates for the foreign key, the query for the document by relationship was not returning the record to be deleted. The solution was to change the delete hook to `beforeDelete` for the search enabled collections. This way we purge records before the main document so the search document query can find and delete the record as expected. An alternative solution in #9623 would remove the `req` so the delete query could still find the document, however, this just works outside of transactions which isn't desirable. fixes https://github.com/payloadcms/payload/issues/9443 --- .../src/Search/hooks/deleteFromSearch.ts | 57 +++++++------------ packages/plugin-search/src/index.ts | 14 ++--- packages/plugin-search/src/types.ts | 8 +-- test/plugin-search/int.spec.ts | 7 ++- 4 files changed, 31 insertions(+), 55 deletions(-) diff --git a/packages/plugin-search/src/Search/hooks/deleteFromSearch.ts b/packages/plugin-search/src/Search/hooks/deleteFromSearch.ts index 04a395598c..2c7fbcb64a 100644 --- a/packages/plugin-search/src/Search/hooks/deleteFromSearch.ts +++ b/packages/plugin-search/src/Search/hooks/deleteFromSearch.ts @@ -1,43 +1,28 @@ import type { DeleteFromSearch } from '../../types.js' -export const deleteFromSearch: DeleteFromSearch = async ({ - collection, - doc, - pluginConfig, - req: { payload }, - req, -}) => { - const searchSlug = pluginConfig?.searchOverrides?.slug || 'search' - try { - const searchDocQuery = await payload.find({ - collection: searchSlug, - depth: 0, - limit: 1, - pagination: false, - req, - where: { - doc: { - equals: { - relationTo: collection.slug, - value: doc.id, +export const deleteFromSearch: DeleteFromSearch = + (pluginConfig) => + async ({ id, collection, req: { payload }, req }) => { + const searchSlug = pluginConfig?.searchOverrides?.slug || 'search' + + try { + await payload.delete({ + collection: searchSlug, + depth: 0, + req, + where: { + 'doc.relationTo': { + equals: collection.slug, + }, + 'doc.value': { + equals: id, }, }, - }, - }) - - if (searchDocQuery?.docs?.[0]) { - await payload.delete({ - id: searchDocQuery?.docs?.[0]?.id, - collection: searchSlug, - req, + }) + } catch (err: unknown) { + payload.logger.error({ + err, + msg: `Error deleting ${searchSlug} doc.`, }) } - } catch (err: unknown) { - payload.logger.error({ - err, - msg: `Error deleting ${searchSlug} doc.`, - }) } - - return doc -} diff --git a/packages/plugin-search/src/index.ts b/packages/plugin-search/src/index.ts index 05a4939dc1..fc4be4bd5d 100644 --- a/packages/plugin-search/src/index.ts +++ b/packages/plugin-search/src/index.ts @@ -1,4 +1,4 @@ -import type { CollectionAfterChangeHook, CollectionAfterDeleteHook, Config } from 'payload' +import type { CollectionAfterChangeHook, Config } from 'payload' import type { SanitizedSearchPluginConfig, SearchPluginConfig } from './types.js' @@ -7,7 +7,6 @@ import { syncWithSearch } from './Search/hooks/syncWithSearch.js' import { generateSearchCollection } from './Search/index.js' type CollectionAfterChangeHookArgs = Parameters[0] -type CollectionAfterDeleteHookArgs = Parameters[0] export const searchPlugin = (incomingPluginConfig: SearchPluginConfig) => @@ -67,14 +66,9 @@ export const searchPlugin = }) }, ], - afterDelete: [ - ...(existingHooks?.afterDelete || []), - async (args: CollectionAfterDeleteHookArgs) => { - await deleteFromSearch({ - ...args, - pluginConfig, - }) - }, + beforeDelete: [ + ...(existingHooks?.beforeDelete || []), + deleteFromSearch(pluginConfig), ], }, } diff --git a/packages/plugin-search/src/types.ts b/packages/plugin-search/src/types.ts index 4b0e4c1f36..bd6dd0d15c 100644 --- a/packages/plugin-search/src/types.ts +++ b/packages/plugin-search/src/types.ts @@ -1,6 +1,6 @@ import type { CollectionAfterChangeHook, - CollectionAfterDeleteHook, + CollectionBeforeDeleteHook, CollectionConfig, Field, Locale, @@ -96,8 +96,4 @@ export type SyncDocArgs = { // Convert the `collection` arg from `SanitizedCollectionConfig` to a string export type SyncWithSearch = (Args: SyncWithSearchArgs) => ReturnType -export type DeleteFromSearch = ( - Args: { - pluginConfig: SearchPluginConfig - } & Parameters[0], -) => ReturnType +export type DeleteFromSearch = (args: SearchPluginConfig) => CollectionBeforeDeleteHook diff --git a/test/plugin-search/int.spec.ts b/test/plugin-search/int.spec.ts index faaa3bdb3d..cf34f031be 100644 --- a/test/plugin-search/int.spec.ts +++ b/test/plugin-search/int.spec.ts @@ -1,5 +1,6 @@ +import type { Payload } from 'payload' + import path from 'path' -import { NotFound, type Payload } from 'payload' import { wait } from 'payload/shared' import { fileURLToPath } from 'url' @@ -300,8 +301,8 @@ describe('@payloadcms/plugin-search', () => { collection: 'search', depth: 0, where: { - 'doc.value': { - equals: page.id, + id: { + equals: results[0].id, }, }, }) From fdff5871f6e16cd958712c7a691d30bd7b1a9ea3 Mon Sep 17 00:00:00 2001 From: Sasha <64744993+r1tsuu@users.noreply.github.com> Date: Fri, 18 Apr 2025 21:39:55 +0300 Subject: [PATCH 08/15] perf: optimize virtual fields that reference ID (#12159) This PR optimizes the new virtual fields with relationships feature https://github.com/payloadcms/payload/pull/11805 when the path references the ID field, for example: ``` { name: 'postCategoryID', type: 'number', virtual: 'post.category.id', }, ``` Previously, we did additional population of `category`, which is unnecessary as we can always grab the ID from the `category` value itself. One less querying step. --- .../virtualFieldPopulationPromise.ts | 5 ++ test/database/config.ts | 20 +++++++ test/database/int.spec.ts | 54 ++++++++++++++++++- test/database/payload-types.ts | 46 ++++++++++++---- 4 files changed, 113 insertions(+), 12 deletions(-) diff --git a/packages/payload/src/fields/hooks/afterRead/virtualFieldPopulationPromise.ts b/packages/payload/src/fields/hooks/afterRead/virtualFieldPopulationPromise.ts index 4c8eb758d1..6b9f9893b0 100644 --- a/packages/payload/src/fields/hooks/afterRead/virtualFieldPopulationPromise.ts +++ b/packages/payload/src/fields/hooks/afterRead/virtualFieldPopulationPromise.ts @@ -85,6 +85,11 @@ export const virtualFieldPopulationPromise = async ({ docID = currentValue } + if (segments[0] === 'id' && segments.length === 0) { + siblingDoc[name] = docID + return + } + if (typeof docID !== 'string' && typeof docID !== 'number') { return } diff --git a/test/database/config.ts b/test/database/config.ts index d1656c347a..806872ee68 100644 --- a/test/database/config.ts +++ b/test/database/config.ts @@ -471,6 +471,16 @@ export default buildConfigWithDefaults({ type: 'text', virtual: 'post.category.title', }, + { + name: 'postCategoryID', + type: 'json', + virtual: 'post.category.id', + }, + { + name: 'postID', + type: 'json', + virtual: 'post.id', + }, { name: 'postLocalized', type: 'text', @@ -481,6 +491,16 @@ export default buildConfigWithDefaults({ type: 'relationship', relationTo: 'posts', }, + { + name: 'customID', + type: 'relationship', + relationTo: 'custom-ids', + }, + { + name: 'customIDValue', + type: 'text', + virtual: 'customID.id', + }, ], versions: { drafts: true }, }, diff --git a/test/database/int.spec.ts b/test/database/int.spec.ts index a4c3210a9a..77e4d83091 100644 --- a/test/database/int.spec.ts +++ b/test/database/int.spec.ts @@ -1993,11 +1993,63 @@ describe('database', () => { collection: 'virtual-relations', depth: 0, where: { id: { equals: id } }, - draft: true, }) expect(draft.docs[0]?.postTitle).toBe('my-title') }) + it('should allow virtual field as reference to ID', async () => { + const post = await payload.create({ collection: 'posts', data: { title: 'my-title' } }) + const { id } = await payload.create({ + collection: 'virtual-relations', + depth: 0, + data: { post: post.id }, + }) + + const docDepth2 = await payload.findByID({ collection: 'virtual-relations', id }) + expect(docDepth2.postID).toBe(post.id) + const docDepth0 = await payload.findByID({ collection: 'virtual-relations', id, depth: 0 }) + expect(docDepth0.postID).toBe(post.id) + }) + + it('should allow virtual field as reference to custom ID', async () => { + const customID = await payload.create({ collection: 'custom-ids', data: {} }) + const { id } = await payload.create({ + collection: 'virtual-relations', + depth: 0, + data: { customID: customID.id }, + }) + + const docDepth2 = await payload.findByID({ collection: 'virtual-relations', id }) + expect(docDepth2.customIDValue).toBe(customID.id) + const docDepth0 = await payload.findByID({ + collection: 'virtual-relations', + id, + depth: 0, + }) + expect(docDepth0.customIDValue).toBe(customID.id) + }) + + it('should allow deep virtual field as reference to ID', async () => { + const category = await payload.create({ + collection: 'categories', + data: { title: 'category-3' }, + }) + const post = await payload.create({ + collection: 'posts', + data: { category: category.id, title: 'my-title-3' }, + }) + const { id } = await payload.create({ + collection: 'virtual-relations', + depth: 0, + data: { post: post.id }, + }) + + const docDepth2 = await payload.findByID({ collection: 'virtual-relations', id }) + expect(docDepth2.postCategoryID).toBe(category.id) + const docDepth0 = await payload.findByID({ collection: 'virtual-relations', id, depth: 0 }) + expect(docDepth0.postCategoryID).toBe(category.id) + }) + it('should allow virtual field with reference localized', async () => { const post = await payload.create({ collection: 'posts', diff --git a/test/database/payload-types.ts b/test/database/payload-types.ts index f29cf028fc..5cdc1faac9 100644 --- a/test/database/payload-types.ts +++ b/test/database/payload-types.ts @@ -373,8 +373,39 @@ export interface VirtualRelation { id: string; postTitle?: string | null; postCategoryTitle?: string | null; + postCategoryID?: + | { + [k: string]: unknown; + } + | unknown[] + | string + | number + | boolean + | null; + postID?: + | { + [k: string]: unknown; + } + | unknown[] + | string + | number + | boolean + | null; postLocalized?: string | null; post?: (string | null) | Post; + customID?: (string | null) | CustomId; + customIDValue?: string | null; + updatedAt: string; + createdAt: string; + _status?: ('draft' | 'published') | null; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "custom-ids". + */ +export interface CustomId { + id: string; + title?: string | null; updatedAt: string; createdAt: string; _status?: ('draft' | 'published') | null; @@ -398,17 +429,6 @@ export interface FieldsPersistance { updatedAt: string; createdAt: string; } -/** - * This interface was referenced by `Config`'s JSON-Schema - * via the `definition` "custom-ids". - */ -export interface CustomId { - id: string; - title?: string | null; - updatedAt: string; - createdAt: string; - _status?: ('draft' | 'published') | null; -} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "fake-custom-ids". @@ -807,8 +827,12 @@ export interface PlacesSelect { export interface VirtualRelationsSelect { postTitle?: T; postCategoryTitle?: T; + postCategoryID?: T; + postID?: T; postLocalized?: T; post?: T; + customID?: T; + customIDValue?: T; updatedAt?: T; createdAt?: T; _status?: T; From 6dc61ae6429359f0cce824ee2de243e380dab728 Mon Sep 17 00:00:00 2001 From: Sasha <64744993+r1tsuu@users.noreply.github.com> Date: Fri, 18 Apr 2025 21:43:53 +0300 Subject: [PATCH 09/15] fix(db-mongodb): fallback `version` when not selected (#12158) When doing `payload.db.queryDrafts` with `select` without `version`, or simply your select looks like: `select: { version: { nonExistingField: true } }` - the `queryDrafts` function will crash because it tries to access the `version` field. This PR adds a fallback. --- packages/db-mongodb/src/queryDrafts.ts | 2 +- test/database/int.spec.ts | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/db-mongodb/src/queryDrafts.ts b/packages/db-mongodb/src/queryDrafts.ts index e0e7a9e3ea..685bfa1ddb 100644 --- a/packages/db-mongodb/src/queryDrafts.ts +++ b/packages/db-mongodb/src/queryDrafts.ts @@ -166,7 +166,7 @@ export const queryDrafts: QueryDrafts = async function queryDrafts( for (let i = 0; i < result.docs.length; i++) { const id = result.docs[i].parent - result.docs[i] = result.docs[i].version + result.docs[i] = result.docs[i].version ?? {} result.docs[i].id = id } diff --git a/test/database/int.spec.ts b/test/database/int.spec.ts index 77e4d83091..ad45a88f40 100644 --- a/test/database/int.spec.ts +++ b/test/database/int.spec.ts @@ -2403,4 +2403,15 @@ describe('database', () => { payload.db.allowAdditionalKeys = false }) + + it('should not crash when the version field is not selected', async () => { + const customID = await payload.create({ collection: 'custom-ids', data: {} }) + const res = await payload.db.queryDrafts({ + collection: 'custom-ids', + where: { parent: { equals: customID.id } }, + select: { parent: true }, + }) + + expect(res.docs[0].id).toBe(customID.id) + }) }) From d91478cd24b54626d9b07cd3510669976196d1c8 Mon Sep 17 00:00:00 2001 From: Sasha <64744993+r1tsuu@users.noreply.github.com> Date: Fri, 18 Apr 2025 21:56:19 +0300 Subject: [PATCH 10/15] docs: virtual fields linking with relationship fields (#12145) Adds docs for these changes https://github.com/payloadcms/payload/pull/11805 --- docs/configuration/collections.mdx | 38 ++++++------- docs/fields/array.mdx | 44 +++++++-------- docs/fields/blocks.mdx | 40 +++++++------- docs/fields/checkbox.mdx | 34 ++++++------ docs/fields/code.mdx | 40 +++++++------- docs/fields/date.mdx | 36 ++++++------ docs/fields/email.mdx | 36 ++++++------ docs/fields/json.mdx | 38 ++++++------- docs/fields/number.mdx | 46 +++++++-------- docs/fields/point.mdx | 36 ++++++------ docs/fields/radio.mdx | 40 +++++++------- docs/fields/relationship.mdx | 89 +++++++++++++++++++++--------- docs/fields/rich-text.mdx | 34 ++++++------ docs/fields/select.mdx | 46 +++++++-------- docs/fields/tabs.mdx | 16 +++--- docs/fields/text.mdx | 46 +++++++-------- docs/fields/textarea.mdx | 40 +++++++------- docs/fields/upload.mdx | 52 ++++++++--------- docs/queries/sort.mdx | 2 +- 19 files changed, 396 insertions(+), 357 deletions(-) diff --git a/docs/configuration/collections.mdx b/docs/configuration/collections.mdx index bbea44a364..9dcebc92cc 100644 --- a/docs/configuration/collections.mdx +++ b/docs/configuration/collections.mdx @@ -120,25 +120,25 @@ export const MyCollection: CollectionConfig = { The following options are available: -| Option | Description | -| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `group` | Text or localization object used to group Collection and Global links in the admin navigation. Set to `false` to hide the link from the navigation while keeping its routes accessible. | -| `hidden` | Set to true or a function, called with the current user, returning true to exclude this Collection from navigation and admin routing. | -| `hooks` | Admin-specific hooks for this Collection. [More details](../hooks/collections). | -| `useAsTitle` | Specify a top-level field to use for a document title throughout the Admin Panel. If no field is defined, the ID of the document is used as the title. A field with `virtual: true` cannot be used as the title. | -| `description` | Text to display below the Collection label in the List View to give editors more information. Alternatively, you can use the `admin.components.Description` to render a React component. [More details](#custom-components). | -| `defaultColumns` | Array of field names that correspond to which columns to show by default in this Collection's List View. | -| `disableCopyToLocale` | Disables the "Copy to Locale" button while editing documents within this Collection. Only applicable when localization is enabled. | -| `hideAPIURL` | Hides the "API URL" meta field while editing documents within this Collection. | -| `enableRichTextLink` | The [Rich Text](../fields/rich-text) field features a `Link` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. | -| `enableRichTextRelationship` | The [Rich Text](../fields/rich-text) field features a `Relationship` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. | -| `meta` | Page metadata overrides to apply to this Collection within the Admin Panel. [More details](../admin/metadata). | -| `preview` | Function to generate preview URLs within the Admin Panel that can point to your app. [More details](../admin/preview). | -| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). | -| `components` | Swap in your own React components to be used within this Collection. [More details](#custom-components). | -| `listSearchableFields` | Specify which fields should be searched in the List search view. [More details](#list-searchable-fields). | -| `pagination` | Set pagination-specific options for this Collection. [More details](#pagination). | -| `baseListFilter` | You can define a default base filter for this collection's List view, which will be merged into any filters that the user performs. | +| Option | Description | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `group` | Text or localization object used to group Collection and Global links in the admin navigation. Set to `false` to hide the link from the navigation while keeping its routes accessible. | +| `hidden` | Set to true or a function, called with the current user, returning true to exclude this Collection from navigation and admin routing. | +| `hooks` | Admin-specific hooks for this Collection. [More details](../hooks/collections). | +| `useAsTitle` | Specify a top-level field to use for a document title throughout the Admin Panel. If no field is defined, the ID of the document is used as the title. A field with `virtual: true` cannot be used as the title, unless it's linked to a relationship'. | +| `description` | Text to display below the Collection label in the List View to give editors more information. Alternatively, you can use the `admin.components.Description` to render a React component. [More details](#custom-components). | +| `defaultColumns` | Array of field names that correspond to which columns to show by default in this Collection's List View. | +| `disableCopyToLocale` | Disables the "Copy to Locale" button while editing documents within this Collection. Only applicable when localization is enabled. | +| `hideAPIURL` | Hides the "API URL" meta field while editing documents within this Collection. | +| `enableRichTextLink` | The [Rich Text](../fields/rich-text) field features a `Link` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. | +| `enableRichTextRelationship` | The [Rich Text](../fields/rich-text) field features a `Relationship` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. | +| `meta` | Page metadata overrides to apply to this Collection within the Admin Panel. [More details](../admin/metadata). | +| `preview` | Function to generate preview URLs within the Admin Panel that can point to your app. [More details](../admin/preview). | +| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). | +| `components` | Swap in your own React components to be used within this Collection. [More details](#custom-components). | +| `listSearchableFields` | Specify which fields should be searched in the List search view. [More details](#list-searchable-fields). | +| `pagination` | Set pagination-specific options for this Collection. [More details](#pagination). | +| `baseListFilter` | You can define a default base filter for this collection's List view, which will be merged into any filters that the user performs. | ### Custom Components diff --git a/docs/fields/array.mdx b/docs/fields/array.mdx index d85426cbd0..a62a6a5285 100644 --- a/docs/fields/array.mdx +++ b/docs/fields/array.mdx @@ -39,28 +39,28 @@ export const MyArrayField: Field = { ## Config Options -| Option | Description | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | -| **`label`** | Text used as the heading in the [Admin Panel](../admin/overview) or an object with keys for each language. Auto-generated from name if not defined. | -| **`fields`** \* | Array of field types to correspond to each row of the Array. | -| **`validate`** | Provide a custom validation function that will be executed on both the [Admin Panel](../admin/overview) and the backend. [More](/docs/fields/overview#validation) | -| **`minRows`** | A number for the fewest allowed items during validation when a value is present. | -| **`maxRows`** | A number for the most allowed items during validation when a value is present. | -| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | -| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | -| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | -| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | -| **`defaultValue`** | Provide an array of row data to be used for this field's default value. [More](/docs/fields/overview#default-values) | -| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this Array will be kept, so there is no need to specify each nested field as `localized`. | -| **`required`** | Require this field to have a value. | -| **`labels`** | Customize the row labels appearing in the Admin dashboard. | -| **`admin`** | Admin-specific configuration. [More details](#admin-options). | -| **`custom`** | Extension point for adding custom data (e.g. for plugins) | -| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). | -| **`dbName`** | Custom table name for the field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. | -| **`typescriptSchema`** | Override field type generation with providing a JSON schema | -| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | +| Option | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | +| **`label`** | Text used as the heading in the [Admin Panel](../admin/overview) or an object with keys for each language. Auto-generated from name if not defined. | +| **`fields`** \* | Array of field types to correspond to each row of the Array. | +| **`validate`** | Provide a custom validation function that will be executed on both the [Admin Panel](../admin/overview) and the backend. [More](/docs/fields/overview#validation) | +| **`minRows`** | A number for the fewest allowed items during validation when a value is present. | +| **`maxRows`** | A number for the most allowed items during validation when a value is present. | +| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | +| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | +| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | +| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | +| **`defaultValue`** | Provide an array of row data to be used for this field's default value. [More](/docs/fields/overview#default-values) | +| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this Array will be kept, so there is no need to specify each nested field as `localized`. | +| **`required`** | Require this field to have a value. | +| **`labels`** | Customize the row labels appearing in the Admin dashboard. | +| **`admin`** | Admin-specific configuration. [More details](#admin-options). | +| **`custom`** | Extension point for adding custom data (e.g. for plugins) | +| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). | +| **`dbName`** | Custom table name for the field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. | +| **`typescriptSchema`** | Override field type generation with providing a JSON schema | +| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](/docs/fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | _\* An asterisk denotes that a property is required._ diff --git a/docs/fields/blocks.mdx b/docs/fields/blocks.mdx index aa1b860c2c..34410bb1da 100644 --- a/docs/fields/blocks.mdx +++ b/docs/fields/blocks.mdx @@ -39,26 +39,26 @@ export const MyBlocksField: Field = { ## Config Options -| Option | Description | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | -| **`label`** | Text used as the heading in the Admin Panel or an object with keys for each language. Auto-generated from name if not defined. | -| **`blocks`** \* | Array of [block configs](/docs/fields/blocks#block-configs) to be made available to this field. | -| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | -| **`minRows`** | A number for the fewest allowed items during validation when a value is present. | -| **`maxRows`** | A number for the most allowed items during validation when a value is present. | -| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | -| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | -| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | -| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API response or the Admin Panel. | -| **`defaultValue`** | Provide an array of block data to be used for this field's default value. [More](/docs/fields/overview#default-values) | -| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this field will be kept, so there is no need to specify each nested field as `localized`. | -| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | -| **`labels`** | Customize the block row labels appearing in the Admin dashboard. | -| **`admin`** | Admin-specific configuration. [More details](#admin-options). | -| **`custom`** | Extension point for adding custom data (e.g. for plugins) | -| **`typescriptSchema`** | Override field type generation with providing a JSON schema | -| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | +| Option | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | +| **`label`** | Text used as the heading in the Admin Panel or an object with keys for each language. Auto-generated from name if not defined. | +| **`blocks`** \* | Array of [block configs](/docs/fields/blocks#block-configs) to be made available to this field. | +| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | +| **`minRows`** | A number for the fewest allowed items during validation when a value is present. | +| **`maxRows`** | A number for the most allowed items during validation when a value is present. | +| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | +| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | +| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | +| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API response or the Admin Panel. | +| **`defaultValue`** | Provide an array of block data to be used for this field's default value. [More](/docs/fields/overview#default-values) | +| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this field will be kept, so there is no need to specify each nested field as `localized`. | +| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | +| **`labels`** | Customize the block row labels appearing in the Admin dashboard. | +| **`admin`** | Admin-specific configuration. [More details](#admin-options). | +| **`custom`** | Extension point for adding custom data (e.g. for plugins) | +| **`typescriptSchema`** | Override field type generation with providing a JSON schema | +| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](/docs/fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | _\* An asterisk denotes that a property is required._ diff --git a/docs/fields/checkbox.mdx b/docs/fields/checkbox.mdx index bb256fccae..c135b54b47 100644 --- a/docs/fields/checkbox.mdx +++ b/docs/fields/checkbox.mdx @@ -28,23 +28,23 @@ export const MyCheckboxField: Field = { ## Config Options -| Option | Description | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | -| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. | -| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | -| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | -| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | -| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | -| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | -| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | -| **`defaultValue`** | Provide data to be used for this field's default value, will default to false if field is also `required`. [More](/docs/fields/overview#default-values) | -| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | -| **`required`** | Require this field to have a value. | -| **`admin`** | Admin-specific configuration. [More details](./overview#admin-options). | -| **`custom`** | Extension point for adding custom data (e.g. for plugins) | -| **`typescriptSchema`** | Override field type generation with providing a JSON schema | -| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | +| Option | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | +| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. | +| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | +| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | +| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | +| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | +| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | +| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | +| **`defaultValue`** | Provide data to be used for this field's default value, will default to false if field is also `required`. [More](/docs/fields/overview#default-values) | +| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | +| **`required`** | Require this field to have a value. | +| **`admin`** | Admin-specific configuration. [More details](./overview#admin-options). | +| **`custom`** | Extension point for adding custom data (e.g. for plugins) | +| **`typescriptSchema`** | Override field type generation with providing a JSON schema | +| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](/docs/fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | _\* An asterisk denotes that a property is required._ diff --git a/docs/fields/code.mdx b/docs/fields/code.mdx index 26cc9abb39..e7fe52045d 100644 --- a/docs/fields/code.mdx +++ b/docs/fields/code.mdx @@ -29,26 +29,26 @@ export const MyBlocksField: Field = { ## Config Options -| Option | Description | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | -| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. | -| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | -| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | -| **`minLength`** | Used by the default validation function to ensure values are of a minimum character length. | -| **`maxLength`** | Used by the default validation function to ensure values are of a maximum character length. | -| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | -| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | -| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | -| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | -| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | -| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | -| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | -| **`required`** | Require this field to have a value. | -| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-options). | -| **`custom`** | Extension point for adding custom data (e.g. for plugins) | -| **`typescriptSchema`** | Override field type generation with providing a JSON schema | -| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | +| Option | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | +| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. | +| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | +| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | +| **`minLength`** | Used by the default validation function to ensure values are of a minimum character length. | +| **`maxLength`** | Used by the default validation function to ensure values are of a maximum character length. | +| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | +| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | +| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | +| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | +| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | +| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | +| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | +| **`required`** | Require this field to have a value. | +| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-options). | +| **`custom`** | Extension point for adding custom data (e.g. for plugins) | +| **`typescriptSchema`** | Override field type generation with providing a JSON schema | +| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](/docs/fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | _\* An asterisk denotes that a property is required._ diff --git a/docs/fields/date.mdx b/docs/fields/date.mdx index de939f53a3..e2a9d2ba02 100644 --- a/docs/fields/date.mdx +++ b/docs/fields/date.mdx @@ -28,24 +28,24 @@ export const MyDateField: Field = { ## Config Options -| Option | Description | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | -| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. | -| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | -| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | -| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | -| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | -| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | -| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | -| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | -| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | -| **`required`** | Require this field to have a value. | -| **`admin`** | Admin-specific configuration. [More details](#admin-options). | -| **`custom`** | Extension point for adding custom data (e.g. for plugins) | -| **`timezone`** \* | Set to `true` to enable timezone selection on this field. [More details](#timezones). | -| **`typescriptSchema`** | Override field type generation with providing a JSON schema | -| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | +| Option | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | +| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. | +| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | +| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | +| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | +| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | +| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | +| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | +| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | +| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | +| **`required`** | Require this field to have a value. | +| **`admin`** | Admin-specific configuration. [More details](#admin-options). | +| **`custom`** | Extension point for adding custom data (e.g. for plugins) | +| **`timezone`** \* | Set to `true` to enable timezone selection on this field. [More details](#timezones). | +| **`typescriptSchema`** | Override field type generation with providing a JSON schema | +| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](/docs/fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | _\* An asterisk denotes that a property is required._ diff --git a/docs/fields/email.mdx b/docs/fields/email.mdx index befa1019fd..a409386176 100644 --- a/docs/fields/email.mdx +++ b/docs/fields/email.mdx @@ -28,24 +28,24 @@ export const MyEmailField: Field = { ## Config Options -| Option | Description | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | -| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. | -| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | -| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | -| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | -| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | -| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | -| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | -| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | -| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | -| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | -| **`required`** | Require this field to have a value. | -| **`admin`** | Admin-specific configuration. [More details](#admin-options). | -| **`custom`** | Extension point for adding custom data (e.g. for plugins) | -| **`typescriptSchema`** | Override field type generation with providing a JSON schema | -| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | +| Option | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | +| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. | +| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | +| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | +| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | +| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | +| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | +| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | +| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | +| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | +| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | +| **`required`** | Require this field to have a value. | +| **`admin`** | Admin-specific configuration. [More details](#admin-options). | +| **`custom`** | Extension point for adding custom data (e.g. for plugins) | +| **`typescriptSchema`** | Override field type generation with providing a JSON schema | +| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](/docs/fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | _\* An asterisk denotes that a property is required._ diff --git a/docs/fields/json.mdx b/docs/fields/json.mdx index 23d443ea37..5d18735c29 100644 --- a/docs/fields/json.mdx +++ b/docs/fields/json.mdx @@ -29,25 +29,25 @@ export const MyJSONField: Field = { ## Config Options -| Option | Description | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | -| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. | -| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | -| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | -| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | -| **`jsonSchema`** | Provide a JSON schema that will be used for validation. [JSON schemas](https://json-schema.org/learn/getting-started-step-by-step) | -| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | -| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | -| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | -| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | -| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | -| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | -| **`required`** | Require this field to have a value. | -| **`admin`** | Admin-specific configuration. [More details](#admin-options). | -| **`custom`** | Extension point for adding custom data (e.g. for plugins) | -| **`typescriptSchema`** | Override field type generation with providing a JSON schema | -| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | +| Option | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | +| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. | +| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | +| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | +| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | +| **`jsonSchema`** | Provide a JSON schema that will be used for validation. [JSON schemas](https://json-schema.org/learn/getting-started-step-by-step) | +| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | +| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | +| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | +| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | +| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | +| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | +| **`required`** | Require this field to have a value. | +| **`admin`** | Admin-specific configuration. [More details](#admin-options). | +| **`custom`** | Extension point for adding custom data (e.g. for plugins) | +| **`typescriptSchema`** | Override field type generation with providing a JSON schema | +| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](/docs/fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | _\* An asterisk denotes that a property is required._ diff --git a/docs/fields/number.mdx b/docs/fields/number.mdx index 49cb8bf632..a69d78fb8e 100644 --- a/docs/fields/number.mdx +++ b/docs/fields/number.mdx @@ -28,29 +28,29 @@ export const MyNumberField: Field = { ## Config Options -| Option | Description | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | -| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. | -| **`min`** | Minimum value accepted. Used in the default `validation` function. | -| **`max`** | Maximum value accepted. Used in the default `validation` function. | -| **`hasMany`** | Makes this field an ordered array of numbers instead of just a single number. | -| **`minRows`** | Minimum number of numbers in the numbers array, if `hasMany` is set to true. | -| **`maxRows`** | Maximum number of numbers in the numbers array, if `hasMany` is set to true. | -| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | -| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | -| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | -| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | -| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | -| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | -| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | -| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | -| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | -| **`required`** | Require this field to have a value. | -| **`admin`** | Admin-specific configuration. [More details](#admin-options). | -| **`custom`** | Extension point for adding custom data (e.g. for plugins) | -| **`typescriptSchema`** | Override field type generation with providing a JSON schema | -| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | +| Option | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | +| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. | +| **`min`** | Minimum value accepted. Used in the default `validation` function. | +| **`max`** | Maximum value accepted. Used in the default `validation` function. | +| **`hasMany`** | Makes this field an ordered array of numbers instead of just a single number. | +| **`minRows`** | Minimum number of numbers in the numbers array, if `hasMany` is set to true. | +| **`maxRows`** | Maximum number of numbers in the numbers array, if `hasMany` is set to true. | +| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | +| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | +| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | +| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | +| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | +| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | +| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | +| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | +| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | +| **`required`** | Require this field to have a value. | +| **`admin`** | Admin-specific configuration. [More details](#admin-options). | +| **`custom`** | Extension point for adding custom data (e.g. for plugins) | +| **`typescriptSchema`** | Override field type generation with providing a JSON schema | +| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](/docs/fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | _\* An asterisk denotes that a property is required._ diff --git a/docs/fields/point.mdx b/docs/fields/point.mdx index 7c8e39f712..5a2c2e575b 100644 --- a/docs/fields/point.mdx +++ b/docs/fields/point.mdx @@ -32,24 +32,24 @@ export const MyPointField: Field = { ## Config -| Option | Description | -| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | -| **`label`** | Used as a field label in the Admin Panel and to name the generated GraphQL type. | -| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | -| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. To support location queries, point index defaults to `2dsphere`, to disable the index set to `false`. | -| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | -| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | -| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | -| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | -| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | -| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | -| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | -| **`required`** | Require this field to have a value. | -| **`admin`** | Admin-specific configuration. [More details](./overview#admin-options). | -| **`custom`** | Extension point for adding custom data (e.g. for plugins) | -| **`typescriptSchema`** | Override field type generation with providing a JSON schema | -| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | +| Option | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | +| **`label`** | Used as a field label in the Admin Panel and to name the generated GraphQL type. | +| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | +| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. To support location queries, point index defaults to `2dsphere`, to disable the index set to `false`. | +| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | +| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | +| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | +| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | +| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | +| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | +| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | +| **`required`** | Require this field to have a value. | +| **`admin`** | Admin-specific configuration. [More details](./overview#admin-options). | +| **`custom`** | Extension point for adding custom data (e.g. for plugins) | +| **`typescriptSchema`** | Override field type generation with providing a JSON schema | +| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](/docs/fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | _\* An asterisk denotes that a property is required._ diff --git a/docs/fields/radio.mdx b/docs/fields/radio.mdx index 385f20abf2..4ca69d7abc 100644 --- a/docs/fields/radio.mdx +++ b/docs/fields/radio.mdx @@ -33,26 +33,26 @@ export const MyRadioField: Field = { ## Config Options -| Option | Description | -| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | -| **`options`** \* | Array of options to allow the field to store. Can either be an array of strings, or an array of objects containing an `label` string and a `value` string. | -| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. | -| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | -| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | -| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | -| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | -| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | -| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | -| **`defaultValue`** | Provide data to be used for this field's default value. The default value must exist within provided values in `options`. [More](/docs/fields/overview#default-values) | -| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | -| **`required`** | Require this field to have a value. | -| **`admin`** | Admin-specific configuration. [More details](#admin-options). | -| **`custom`** | Extension point for adding custom data (e.g. for plugins) | -| **`enumName`** | Custom enum name for this field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. | -| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). | -| **`typescriptSchema`** | Override field type generation with providing a JSON schema | -| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | +| Option | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | +| **`options`** \* | Array of options to allow the field to store. Can either be an array of strings, or an array of objects containing an `label` string and a `value` string. | +| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. | +| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | +| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | +| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | +| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | +| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | +| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | +| **`defaultValue`** | Provide data to be used for this field's default value. The default value must exist within provided values in `options`. [More](/docs/fields/overview#default-values) | +| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | +| **`required`** | Require this field to have a value. | +| **`admin`** | Admin-specific configuration. [More details](#admin-options). | +| **`custom`** | Extension point for adding custom data (e.g. for plugins) | +| **`enumName`** | Custom enum name for this field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. | +| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). | +| **`typescriptSchema`** | Override field type generation with providing a JSON schema | +| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](/docs/fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | _\* An asterisk denotes that a property is required._ diff --git a/docs/fields/relationship.mdx b/docs/fields/relationship.mdx index e6c4faf81d..10d652a2c2 100644 --- a/docs/fields/relationship.mdx +++ b/docs/fields/relationship.mdx @@ -37,31 +37,31 @@ export const MyRelationshipField: Field = { ## Config Options -| Option | Description | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | -| **`relationTo`** \* | Provide one or many collection `slug`s to be able to assign relationships to. | -| **`filterOptions`** | A query to filter which options appear in the UI and validate against. [More](#filtering-relationship-options). | -| **`hasMany`** | Boolean when, if set to `true`, allows this field to have many relations instead of only one. | -| **`minRows`** | A number for the fewest allowed items during validation when a value is present. Used with `hasMany`. | -| **`maxRows`** | A number for the most allowed items during validation when a value is present. Used with `hasMany`. | -| **`maxDepth`** | Sets a maximum population depth for this field, regardless of the remaining depth when this field is reached. [Max Depth](/docs/queries/depth#max-depth) | -| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. | -| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | -| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | -| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | -| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | -| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | -| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | -| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | -| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | -| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | -| **`required`** | Require this field to have a value. | -| **`admin`** | Admin-specific configuration. [More details](#admin-options). | -| **`custom`** | Extension point for adding custom data (e.g. for plugins) | -| **`typescriptSchema`** | Override field type generation with providing a JSON schema | -| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | -| **`graphQL`** | Custom graphQL configuration for the field. [More details](/docs/graphql/overview#field-complexity) | +| Option | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | +| **`relationTo`** \* | Provide one or many collection `slug`s to be able to assign relationships to. | +| **`filterOptions`** | A query to filter which options appear in the UI and validate against. [More](#filtering-relationship-options). | +| **`hasMany`** | Boolean when, if set to `true`, allows this field to have many relations instead of only one. | +| **`minRows`** | A number for the fewest allowed items during validation when a value is present. Used with `hasMany`. | +| **`maxRows`** | A number for the most allowed items during validation when a value is present. Used with `hasMany`. | +| **`maxDepth`** | Sets a maximum population depth for this field, regardless of the remaining depth when this field is reached. [Max Depth](/docs/queries/depth#max-depth) | +| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. | +| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | +| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | +| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | +| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | +| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | +| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | +| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | +| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | +| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | +| **`required`** | Require this field to have a value. | +| **`admin`** | Admin-specific configuration. [More details](#admin-options). | +| **`custom`** | Extension point for adding custom data (e.g. for plugins) | +| **`typescriptSchema`** | Override field type generation with providing a JSON schema | +| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](/docs/fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | +| **`graphQL`** | Custom graphQL configuration for the field. [More details](/docs/graphql/overview#field-complexity) | _\* An asterisk denotes that a property is required._ @@ -376,6 +376,45 @@ non-polymorphic relationship. +### Linking virtual fields with relationships + +You can link virtual fields to fields in other collections through a relationship (or upload) field, for example: + +```ts +{ + collections: [ + { + slug: 'categories', + fields: [ + { + name: 'title', + type: 'text', + }, + ], + }, + { + slug: 'posts', + fields: [ + { + type: 'relationship', + name: 'category', + relationTo: 'categories', + }, + { + type: 'text', + name: 'categoryTitle', + virtual: 'category.title', + }, + ], + }, + ], +} +``` + +Here, `categoryTitle` will _always_ be populated with the corresponding value, even if the current `depth` is `0`, You can also query and sort by this field. +The relationship must not be `hasMany: true` or polymorphic. +The path can be deeply nested into 2 or more relationship fields, for example `post.category.title` as long as all the relationship fields meet the above requirement. + ## Custom Components ### Field diff --git a/docs/fields/rich-text.mdx b/docs/fields/rich-text.mdx index 4e9cc8fe6b..78c81917eb 100644 --- a/docs/fields/rich-text.mdx +++ b/docs/fields/rich-text.mdx @@ -21,23 +21,23 @@ Instead, you can invest your time and effort into learning the underlying open-s ## Config Options -| Option | Description | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](./overview#field-names) | -| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. | -| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](./overview#validation) | -| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](../authentication/overview), include its data in the user JWT. | -| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | -| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | -| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | -| **`defaultValue`** | Provide data to be used for this field's default value. [More](./overview#default-values) | -| **`localized`** | Enable localization for this field. Requires [localization to be enabled](../configuration/localization) in the Base config. | -| **`required`** | Require this field to have a value. | -| **`admin`** | Admin-specific configuration. [More details](#admin-options). | -| **`editor`** | Customize or override the rich text editor. [More details](../rich-text/overview). | -| **`custom`** | Extension point for adding custom data (e.g. for plugins) | -| **`typescriptSchema`** | Override field type generation with providing a JSON schema | -| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | +| Option | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](./overview#field-names) | +| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. | +| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](./overview#validation) | +| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](../authentication/overview), include its data in the user JWT. | +| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | +| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | +| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | +| **`defaultValue`** | Provide data to be used for this field's default value. [More](./overview#default-values) | +| **`localized`** | Enable localization for this field. Requires [localization to be enabled](../configuration/localization) in the Base config. | +| **`required`** | Require this field to have a value. | +| **`admin`** | Admin-specific configuration. [More details](#admin-options). | +| **`editor`** | Customize or override the rich text editor. [More details](../rich-text/overview). | +| **`custom`** | Extension point for adding custom data (e.g. for plugins) | +| **`typescriptSchema`** | Override field type generation with providing a JSON schema | +| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](/docs/fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | \*_ An asterisk denotes that a property is required._ diff --git a/docs/fields/select.mdx b/docs/fields/select.mdx index 2c53847984..c8328a8d8a 100644 --- a/docs/fields/select.mdx +++ b/docs/fields/select.mdx @@ -33,29 +33,29 @@ export const MySelectField: Field = { ## Config Options -| Option | Description | -| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | -| **`options`** \* | Array of options to allow the field to store. Can either be an array of strings, or an array of objects containing a `label` string and a `value` string. | -| **`hasMany`** | Boolean when, if set to `true`, allows this field to have many selections instead of only one. | -| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. | -| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | -| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | -| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | -| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | -| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | -| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | -| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | -| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | -| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | -| **`required`** | Require this field to have a value. | -| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-options) for more details. | -| **`custom`** | Extension point for adding custom data (e.g. for plugins) | -| **`enumName`** | Custom enum name for this field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. | -| **`dbName`** | Custom table name (if `hasMany` set to `true`) for this field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. | -| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). | -| **`typescriptSchema`** | Override field type generation with providing a JSON schema | -| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | +| Option | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | +| **`options`** \* | Array of options to allow the field to store. Can either be an array of strings, or an array of objects containing a `label` string and a `value` string. | +| **`hasMany`** | Boolean when, if set to `true`, allows this field to have many selections instead of only one. | +| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. | +| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | +| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | +| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | +| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | +| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | +| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | +| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | +| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | +| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | +| **`required`** | Require this field to have a value. | +| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-options) for more details. | +| **`custom`** | Extension point for adding custom data (e.g. for plugins) | +| **`enumName`** | Custom enum name for this field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. | +| **`dbName`** | Custom table name (if `hasMany` set to `true`) for this field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. | +| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). | +| **`typescriptSchema`** | Override field type generation with providing a JSON schema | +| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](/docs/fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | _\* An asterisk denotes that a property is required._ diff --git a/docs/fields/tabs.mdx b/docs/fields/tabs.mdx index cdbb01620c..5e603dd8cd 100644 --- a/docs/fields/tabs.mdx +++ b/docs/fields/tabs.mdx @@ -43,14 +43,14 @@ export const MyTabsField: Field = { Each tab must have either a `name` or `label` and the required `fields` array. You can also optionally pass a `description` to render within each individual tab. -| Option | Description | -| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| **`name`** | Groups field data into an object when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | -| **`label`** | The label to render on the tab itself. Required when name is undefined, defaults to name converted to words. | -| **`fields`** \* | The fields to render within this tab. | -| **`description`** | Optionally render a description within this tab to describe the contents of the tab itself. | -| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). (`name` must be present) | -| **`virtual`** | Provide `true` to disable field in the database (`name` must be present). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | +| Option | Description | +| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`name`** | Groups field data into an object when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | +| **`label`** | The label to render on the tab itself. Required when name is undefined, defaults to name converted to words. | +| **`fields`** \* | The fields to render within this tab. | +| **`description`** | Optionally render a description within this tab to describe the contents of the tab itself. | +| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). (`name` must be present) | +| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](/docs/fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | _\* An asterisk denotes that a property is required._ diff --git a/docs/fields/text.mdx b/docs/fields/text.mdx index 18d52809d3..e19df883bd 100644 --- a/docs/fields/text.mdx +++ b/docs/fields/text.mdx @@ -28,29 +28,29 @@ export const MyTextField: Field = { ## Config Options -| Option | Description | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | -| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. | -| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | -| **`minLength`** | Used by the default validation function to ensure values are of a minimum character length. | -| **`maxLength`** | Used by the default validation function to ensure values are of a maximum character length. | -| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | -| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | -| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | -| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | -| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | -| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | -| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | -| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | -| **`required`** | Require this field to have a value. | -| **`admin`** | Admin-specific configuration. [More details](#admin-options). | -| **`custom`** | Extension point for adding custom data (e.g. for plugins) | -| **`hasMany`** | Makes this field an ordered array of text instead of just a single text. | -| **`minRows`** | Minimum number of texts in the array, if `hasMany` is set to true. | -| **`maxRows`** | Maximum number of texts in the array, if `hasMany` is set to true. | -| **`typescriptSchema`** | Override field type generation with providing a JSON schema | -| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | +| Option | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | +| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. | +| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | +| **`minLength`** | Used by the default validation function to ensure values are of a minimum character length. | +| **`maxLength`** | Used by the default validation function to ensure values are of a maximum character length. | +| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | +| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | +| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | +| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | +| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | +| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | +| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | +| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | +| **`required`** | Require this field to have a value. | +| **`admin`** | Admin-specific configuration. [More details](#admin-options). | +| **`custom`** | Extension point for adding custom data (e.g. for plugins) | +| **`hasMany`** | Makes this field an ordered array of text instead of just a single text. | +| **`minRows`** | Minimum number of texts in the array, if `hasMany` is set to true. | +| **`maxRows`** | Maximum number of texts in the array, if `hasMany` is set to true. | +| **`typescriptSchema`** | Override field type generation with providing a JSON schema | +| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](/docs/fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | _\* An asterisk denotes that a property is required._ diff --git a/docs/fields/textarea.mdx b/docs/fields/textarea.mdx index e9754cd9b7..264749607a 100644 --- a/docs/fields/textarea.mdx +++ b/docs/fields/textarea.mdx @@ -28,26 +28,26 @@ export const MyTextareaField: Field = { ## Config Options -| Option | Description | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | -| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. | -| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | -| **`minLength`** | Used by the default validation function to ensure values are of a minimum character length. | -| **`maxLength`** | Used by the default validation function to ensure values are of a maximum character length. | -| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | -| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | -| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | -| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | -| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | -| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | -| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | -| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | -| **`required`** | Require this field to have a value. | -| **`admin`** | Admin-specific configuration. [More details](#admin-options). | -| **`custom`** | Extension point for adding custom data (e.g. for plugins) | -| **`typescriptSchema`** | Override field type generation with providing a JSON schema | -| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | +| Option | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | +| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. | +| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | +| **`minLength`** | Used by the default validation function to ensure values are of a minimum character length. | +| **`maxLength`** | Used by the default validation function to ensure values are of a maximum character length. | +| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | +| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | +| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | +| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | +| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | +| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | +| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | +| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | +| **`required`** | Require this field to have a value. | +| **`admin`** | Admin-specific configuration. [More details](#admin-options). | +| **`custom`** | Extension point for adding custom data (e.g. for plugins) | +| **`typescriptSchema`** | Override field type generation with providing a JSON schema | +| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](/docs/fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | _\* An asterisk denotes that a property is required._ diff --git a/docs/fields/upload.mdx b/docs/fields/upload.mdx index f8aefb4e53..2370c55492 100644 --- a/docs/fields/upload.mdx +++ b/docs/fields/upload.mdx @@ -44,32 +44,32 @@ export const MyUploadField: Field = { ## Config Options -| Option | Description | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | -| **`relationTo`** \* | Provide a single collection `slug` to allow this field to accept a relation to. **Note: the related collection must be configured to support Uploads.** | -| **`filterOptions`** | A query to filter which options appear in the UI and validate against. [More](#filtering-upload-options). | -| **`hasMany`** | Boolean which, if set to true, allows this field to have many relations instead of only one. | -| **`minRows`** | A number for the fewest allowed items during validation when a value is present. Used with hasMany. | -| **`maxRows`** | A number for the most allowed items during validation when a value is present. Used with hasMany. | -| **`maxDepth`** | Sets a number limit on iterations of related documents to populate when queried. [Depth](../queries/depth) | -| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. | -| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | -| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | -| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | -| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | -| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | -| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | -| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | -| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | -| **`displayPreview`** | Enable displaying preview of the uploaded file. Overrides related Collection's `displayPreview` option. [More](/docs/upload/overview#collection-upload-options). | -| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | -| **`required`** | Require this field to have a value. | -| **`admin`** | Admin-specific configuration. [Admin Options](./overview#admin-options). | -| **`custom`** | Extension point for adding custom data (e.g. for plugins) | -| **`typescriptSchema`** | Override field type generation with providing a JSON schema | -| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | -| **`graphQL`** | Custom graphQL configuration for the field. [More details](/docs/graphql/overview#field-complexity) | +| Option | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | +| **`relationTo`** \* | Provide a single collection `slug` to allow this field to accept a relation to. **Note: the related collection must be configured to support Uploads.** | +| **`filterOptions`** | A query to filter which options appear in the UI and validate against. [More](#filtering-upload-options). | +| **`hasMany`** | Boolean which, if set to true, allows this field to have many relations instead of only one. | +| **`minRows`** | A number for the fewest allowed items during validation when a value is present. Used with hasMany. | +| **`maxRows`** | A number for the most allowed items during validation when a value is present. Used with hasMany. | +| **`maxDepth`** | Sets a number limit on iterations of related documents to populate when queried. [Depth](../queries/depth) | +| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. | +| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | +| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) | +| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | +| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. | +| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | +| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | +| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. | +| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | +| **`displayPreview`** | Enable displaying preview of the uploaded file. Overrides related Collection's `displayPreview` option. [More](/docs/upload/overview#collection-upload-options). | +| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | +| **`required`** | Require this field to have a value. | +| **`admin`** | Admin-specific configuration. [Admin Options](./overview#admin-options). | +| **`custom`** | Extension point for adding custom data (e.g. for plugins) | +| **`typescriptSchema`** | Override field type generation with providing a JSON schema | +| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](/docs/fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | +| **`graphQL`** | Custom graphQL configuration for the field. [More details](/docs/graphql/overview#field-complexity) | _\* An asterisk denotes that a property is required._ diff --git a/docs/queries/sort.mdx b/docs/queries/sort.mdx index 01ecfc076c..d9f46b2f91 100644 --- a/docs/queries/sort.mdx +++ b/docs/queries/sort.mdx @@ -8,7 +8,7 @@ keywords: query, documents, pagination, documentation, Content Management System Documents in Payload can be easily sorted by a specific [Field](../fields/overview). When querying Documents, you can pass the name of any top-level field, and the response will sort the Documents by that field in _ascending_ order. If prefixed with a minus symbol ("-"), they will be sorted in _descending_ order. In Local API multiple fields can be specified by using an array of strings. In REST API multiple fields can be specified by separating fields with comma. The minus symbol can be in front of individual fields. -Because sorting is handled by the database, the field cannot be a [Virtual Field](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges). It must be stored in the database to be searchable. +Because sorting is handled by the database, the field cannot be a [Virtual Field](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) unless it's [linked with a relationship field](/docs/fields/relationship#linking-virtual-fields-with-relationships). It must be stored in the database to be searchable. **Tip:** For performance reasons, it is recommended to enable `index: true` From 2c20051dbf4f325568abe86b972e0171af391728 Mon Sep 17 00:00:00 2001 From: Tobias Odendahl Date: Tue, 22 Apr 2025 19:40:43 +0200 Subject: [PATCH 11/15] fix(richtext-lexical): reset indent on node transforms (#12183) ### What? Resets the indentation on editor updates for nodes for which indentation is disabled. ### Why? If a node gets transformed, e.g. from a list to a paragraph node, it remains the indent property by default. If indentation for this node is disabled, it would remain indented although it shouldn't. ### How? Adds a listener which resets the indent status on updates for non-indentable nodes. --- .../features/indent/client/IndentPlugin.tsx | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/packages/richtext-lexical/src/features/indent/client/IndentPlugin.tsx b/packages/richtext-lexical/src/features/indent/client/IndentPlugin.tsx index 5ac810777c..bee2fcfa58 100644 --- a/packages/richtext-lexical/src/features/indent/client/IndentPlugin.tsx +++ b/packages/richtext-lexical/src/features/indent/client/IndentPlugin.tsx @@ -26,17 +26,35 @@ export const IndentPlugin: PluginComponent = ({ clientProps if (!editor || !disabledNodes?.length) { return } - return editor.registerCommand( - INDENT_CONTENT_COMMAND, - () => { - return $handleIndentAndOutdent((block) => { - if (!disabledNodes.includes(block.getType())) { - const indent = block.getIndent() - block.setIndent(indent + 1) + return mergeRegister( + editor.registerCommand( + INDENT_CONTENT_COMMAND, + () => { + return $handleIndentAndOutdent((block) => { + if (!disabledNodes.includes(block.getType())) { + const indent = block.getIndent() + block.setIndent(indent + 1) + } + }) + }, + COMMAND_PRIORITY_LOW, + ), + // If we disable indenting for certain nodes, we need to ensure that these are not indented, + // if they get transformed from an indented state (e.g. an indented list node gets transformed into a + // paragraph node for which indenting is disabled). + editor.registerUpdateListener(({ dirtyElements, editorState }) => { + editor.update(() => { + for (const [nodeKey] of dirtyElements) { + const node = editorState._nodeMap.get(nodeKey) + if ($isElementNode(node) && disabledNodes.includes(node.getType())) { + const currentIndent = node.getIndent() + if (currentIndent > 0) { + node.setIndent(0) + } + } } }) - }, - COMMAND_PRIORITY_LOW, + }), ) }, [editor, disabledNodes]) From 99558185030aee171f5981a20577309cea02e28c Mon Sep 17 00:00:00 2001 From: Sasha <64744993+r1tsuu@users.noreply.github.com> Date: Tue, 22 Apr 2025 21:26:13 +0300 Subject: [PATCH 12/15] fix(db-postgres): sort by distance when the `near` operator is used (#12185) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes https://github.com/payloadcms/payload/issues/12090, in MongoDB the documents are sorted by distance automatically whenever the `near` operation is used, which we have in the docs: > When querying using the near operator, the returned documents will be sorted by nearest first. This fixes this incosistensty between Postgres and MongoDB. ⚠️ This change potentially can cause to produce different results, if you used the `near` operator without `sort: 'pointFieldName'`. --- .../src/queries/buildAndOrConditions.ts | 4 +++ packages/drizzle/src/queries/buildQuery.ts | 27 ++++++++++--------- packages/drizzle/src/queries/parseParams.ts | 8 +++++- test/collections-rest/int.spec.ts | 1 - 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/packages/drizzle/src/queries/buildAndOrConditions.ts b/packages/drizzle/src/queries/buildAndOrConditions.ts index 759eb843d5..63c8fb4b67 100644 --- a/packages/drizzle/src/queries/buildAndOrConditions.ts +++ b/packages/drizzle/src/queries/buildAndOrConditions.ts @@ -3,12 +3,14 @@ import type { FlattenedField, Where } from 'payload' import type { DrizzleAdapter, GenericColumn } from '../types.js' import type { BuildQueryJoinAliases } from './buildQuery.js' +import type { QueryContext } from './parseParams.js' import { parseParams } from './parseParams.js' export function buildAndOrConditions({ adapter, aliasTable, + context, fields, joins, locale, @@ -21,6 +23,7 @@ export function buildAndOrConditions({ adapter: DrizzleAdapter aliasTable?: Table collectionSlug?: string + context: QueryContext fields: FlattenedField[] globalSlug?: string joins: BuildQueryJoinAliases @@ -41,6 +44,7 @@ export function buildAndOrConditions({ const result = parseParams({ adapter, aliasTable, + context, fields, joins, locale, diff --git a/packages/drizzle/src/queries/buildQuery.ts b/packages/drizzle/src/queries/buildQuery.ts index a072a07e8d..9b5326b9cc 100644 --- a/packages/drizzle/src/queries/buildQuery.ts +++ b/packages/drizzle/src/queries/buildQuery.ts @@ -3,6 +3,7 @@ import type { PgTableWithColumns } from 'drizzle-orm/pg-core' import type { FlattenedField, Sort, Where } from 'payload' import type { DrizzleAdapter, GenericColumn, GenericTable } from '../types.js' +import type { QueryContext } from './parseParams.js' import { buildOrderBy } from './buildOrderBy.js' import { parseParams } from './parseParams.js' @@ -52,24 +53,14 @@ const buildQuery = function buildQuery({ id: adapter.tables[tableName].id, } - const orderBy = buildOrderBy({ - adapter, - aliasTable, - fields, - joins, - locale, - parentIsLocalized, - selectFields, - sort, - tableName, - }) - let where: SQL + const context: QueryContext = { sort } if (incomingWhere && Object.keys(incomingWhere).length > 0) { where = parseParams({ adapter, aliasTable, + context, fields, joins, locale, @@ -81,6 +72,18 @@ const buildQuery = function buildQuery({ }) } + const orderBy = buildOrderBy({ + adapter, + aliasTable, + fields, + joins, + locale, + parentIsLocalized, + selectFields, + sort: context.sort, + tableName, + }) + return { joins, orderBy, diff --git a/packages/drizzle/src/queries/parseParams.ts b/packages/drizzle/src/queries/parseParams.ts index d03876f3ae..90bba4136e 100644 --- a/packages/drizzle/src/queries/parseParams.ts +++ b/packages/drizzle/src/queries/parseParams.ts @@ -1,5 +1,5 @@ import type { SQL, Table } from 'drizzle-orm' -import type { FlattenedField, Operator, Where } from 'payload' +import type { FlattenedField, Operator, Sort, Where } from 'payload' import { and, isNotNull, isNull, ne, notInArray, or, sql } from 'drizzle-orm' import { PgUUID } from 'drizzle-orm/pg-core' @@ -14,9 +14,12 @@ import { buildAndOrConditions } from './buildAndOrConditions.js' import { getTableColumnFromPath } from './getTableColumnFromPath.js' import { sanitizeQueryValue } from './sanitizeQueryValue.js' +export type QueryContext = { sort: Sort } + type Args = { adapter: DrizzleAdapter aliasTable?: Table + context: QueryContext fields: FlattenedField[] joins: BuildQueryJoinAliases locale?: string @@ -30,6 +33,7 @@ type Args = { export function parseParams({ adapter, aliasTable, + context, fields, joins, locale, @@ -57,6 +61,7 @@ export function parseParams({ const builtConditions = buildAndOrConditions({ adapter, aliasTable, + context, fields, joins, locale, @@ -342,6 +347,7 @@ export function parseParams({ ) } if (geoConstraints.length) { + context.sort = relationOrPath constraints.push(and(...geoConstraints)) } break diff --git a/test/collections-rest/int.spec.ts b/test/collections-rest/int.spec.ts index 58cff5a117..3bc84072fb 100644 --- a/test/collections-rest/int.spec.ts +++ b/test/collections-rest/int.spec.ts @@ -1218,7 +1218,6 @@ describe('collections-rest', () => { .GET(`/${pointSlug}`, { query: { limit: 5, - sort: 'point', where: { point: { // querying large enough range to include all docs From 9b1dd2a8d87d2194d2b7129714361a3ab2966b06 Mon Sep 17 00:00:00 2001 From: Tobias Odendahl Date: Wed, 23 Apr 2025 12:48:51 +0200 Subject: [PATCH 13/15] fix(richtext-lexical): allow to indent and outdent if at least one selected node allows it (#12182) ### What? Enables the indent/outdent button if at least one selected node can be indented/outdented. ### Why? Before, the buttons were disabled e.g. if multiple nodes were selected of which one was not indentable/outdentable or if a child node was not indentable but the parent was, leading to inconsistent behavior. ### How? Checks if the node itself or any parent fulfills the criteria. The change affects only the buttons active state, not the actual indentation logic. Fixes https://github.com/payloadcms/payload/pull/12042 --- .../src/features/indent/client/index.tsx | 66 +++++++------------ 1 file changed, 22 insertions(+), 44 deletions(-) diff --git a/packages/richtext-lexical/src/features/indent/client/index.tsx b/packages/richtext-lexical/src/features/indent/client/index.tsx index cf56a2c0c6..49738272f6 100644 --- a/packages/richtext-lexical/src/features/indent/client/index.tsx +++ b/packages/richtext-lexical/src/features/indent/client/index.tsx @@ -1,14 +1,9 @@ 'use client' -import type { BaseSelection, ElementNode, LexicalNode } from 'lexical' +import type { ElementNode, LexicalNode } from 'lexical' import { $findMatchingParent } from '@lexical/utils' -import { - $isElementNode, - $isRangeSelection, - INDENT_CONTENT_COMMAND, - OUTDENT_CONTENT_COMMAND, -} from 'lexical' +import { $isElementNode, INDENT_CONTENT_COMMAND, OUTDENT_CONTENT_COMMAND } from 'lexical' import type { ToolbarGroup } from '../../toolbars/types.js' @@ -25,31 +20,15 @@ const toolbarGroups = ({ disabledNodes }: IndentFeatureProps): ToolbarGroup[] => ChildComponent: IndentDecreaseIcon, isActive: () => false, isEnabled: ({ selection }) => { - const nodes = selection?.getNodes() - if (!nodes?.length) { - return false - } - let atLeastOneNodeCanOutdent = false - const isIndentable = (node: LexicalNode): node is ElementNode => - $isElementNode(node) && node.canIndent() - for (const node of nodes) { - if (isIndentable(node)) { - if (node.getIndent() <= 0) { - return false - } else { - atLeastOneNodeCanOutdent = true - } - } + const nodes = selection?.getNodes() ?? [] + + const isOutdentable = (node: LexicalNode) => { + return isIndentable(node) && node.getIndent() > 0 } - if (!atLeastOneNodeCanOutdent) { - if ( - $pointsAncestorMatch(selection, (node) => isIndentable(node) && node.getIndent() > 0) - ) { - return true - } - } - return atLeastOneNodeCanOutdent + return nodes.some((node) => { + return isOutdentable(node) || Boolean($findMatchingParent(node, isOutdentable)) + }) }, key: 'indentDecrease', label: ({ i18n }) => { @@ -64,11 +43,18 @@ const toolbarGroups = ({ disabledNodes }: IndentFeatureProps): ToolbarGroup[] => ChildComponent: IndentIncreaseIcon, isActive: () => false, isEnabled: ({ selection }) => { - const nodes = selection?.getNodes() - if (!nodes?.length) { - return false + const nodes = selection?.getNodes() ?? [] + + const isIndentableAndNotDisabled = (node: LexicalNode) => { + return isIndentable(node) && !(disabledNodes ?? []).includes(node.getType()) } - return !nodes.some((node) => disabledNodes?.includes(node.getType())) + + return nodes.some((node) => { + return ( + isIndentableAndNotDisabled(node) || + Boolean($findMatchingParent(node, isIndentableAndNotDisabled)) + ) + }) }, key: 'indentIncrease', label: ({ i18n }) => { @@ -101,13 +87,5 @@ export const IndentFeatureClient = createClientFeature(({ pr } }) -function $pointsAncestorMatch( - selection: BaseSelection, - fn: (node: LexicalNode) => boolean, -): boolean { - return ( - $isRangeSelection(selection) && - (!!$findMatchingParent(selection.anchor.getNode(), fn) || - !!$findMatchingParent(selection.focus.getNode(), fn)) - ) -} +const isIndentable = (node: LexicalNode): node is ElementNode => + $isElementNode(node) && node.canIndent() From 92380bff872a55c79ead43dd164a5a16740cbdce Mon Sep 17 00:00:00 2001 From: Silas Krause Date: Wed, 23 Apr 2025 15:31:33 +0200 Subject: [PATCH 14/15] refactor(translations): improvements for german translations (#11807) ### What? As a native German speaker, I noticed some areas where the translations could be improved. While the ai translations are impressive, some of them are grammatically incorrect, inconsistent or just sound weird. This PR improves them to provide a better experience for german users. --- packages/translations/src/languages/de.ts | 204 +++++++++++----------- 1 file changed, 102 insertions(+), 102 deletions(-) diff --git a/packages/translations/src/languages/de.ts b/packages/translations/src/languages/de.ts index f926cf8fed..18a819017c 100644 --- a/packages/translations/src/languages/de.ts +++ b/packages/translations/src/languages/de.ts @@ -2,44 +2,44 @@ import type { DefaultTranslationsObject, Language } from '../types.js' export const deTranslations: DefaultTranslationsObject = { authentication: { - account: 'Konto', + account: 'Benutzerkonto', accountOfCurrentUser: 'Aktuelles Benutzerkonto', - accountVerified: 'Konto erfolgreich verifiziert.', + accountVerified: 'Benutzerkonto erfolgreich verifiziert.', alreadyActivated: 'Bereits aktiviert', alreadyLoggedIn: 'Bereits angemeldet', apiKey: 'API-Key', authenticated: 'Authentifiziert', backToLogin: 'Zurück zur Anmeldung', - beginCreateFirstUser: 'Erstelle deinen ersten Benutzer um zu beginnen', + beginCreateFirstUser: 'Erstelle deinen ersten Benutzer, um zu beginnen', changePassword: 'Passwort ändern', checkYourEmailForPasswordReset: - 'Wenn die E-Mail-Adresse mit einem Konto verknüpft ist, erhalten Sie in Kürze Anweisungen zur Zurücksetzung Ihres Passworts. Bitte überprüfen Sie Ihren Spam- oder Junk-Mail-Ordner, wenn Sie die E-Mail nicht in Ihrem Posteingang sehen.', + 'Wenn die E-Mail-Adresse mit einem Benutzerkonto verknüpft ist, erhältst du in Kürze Anweisungen zur Zurücksetzung deines Passworts. Bitte überprüfe deinen Spam-Ordner, wenn du die E-Mail nicht in deinem Posteingang siehst.', confirmGeneration: 'Generierung bestätigen', confirmPassword: 'Passwort bestätigen', createFirstUser: 'Ersten Benutzer erstellen', emailNotValid: 'Die angegebene E-Mail-Adresse ist ungültig', emailOrUsername: 'E-Mail oder Benutzername', - emailSent: 'E-Mail verschickt', + emailSent: 'E-Mail versendet', emailVerified: 'E-Mail erfolgreich verifiziert.', enableAPIKey: 'API-Key aktivieren', - failedToUnlock: 'Konnte nicht entsperren', + failedToUnlock: 'Entsperrung fehlgeschlagen', forceUnlock: 'Entsperrung erzwingen', forgotPassword: 'Passwort vergessen', forgotPasswordEmailInstructions: 'Bitte gib deine E-Mail-Adresse an. Du wirst eine E-Mail mit Instruktionen zum Zurücksetzen deines Passworts erhalten.', forgotPasswordQuestion: 'Passwort vergessen?', forgotPasswordUsernameInstructions: - 'Bitte geben Sie unten Ihren Benutzernamen ein. Anweisungen zum Zurücksetzen Ihres Passworts werden an die mit Ihrem Benutzernamen verknüpfte E-Mail-Adresse gesendet.', + 'Bitte gib deinen Benutzernamen ein. Anweisungen zum Zurücksetzen deines Passworts werden an die mit deinem Benutzernamen verknüpfte E-Mail-Adresse gesendet.', generate: 'Generieren', generateNewAPIKey: 'Neuen API-Key generieren', generatingNewAPIKeyWillInvalidate: - 'Die Generierung eines neuen API-Keys wird den vorherigen Key <1>ungültig machen. Bist du sicher, dass du fortfahren möchtest?', - lockUntil: 'Sperre bis', + 'Wenn du einen neuen Key generierst, wird der vorherige <1>ungültig. Bist du sicher, dass du fortfahren möchtest?', + lockUntil: 'Sperren bis', logBackIn: 'Wieder anmelden', loggedIn: - 'Um dich mit einem anderen Benutzer anzumelden, musst du dich zuerst <0>abmelden.', + 'Um dich mit einem anderen Benutzerkonto anzumelden, musst du dich zuerst <0>abmelden.', loggedInChangePassword: - 'Um dein Passwort zu ändern, gehe in dein <0>Konto und ändere dort dein Passwort.', + 'Um dein Passwort zu ändern, gehe zu deinem <0>Benutzerkonto und ändere dort dein Passwort.', loggedOutInactivity: 'Du wurdest aufgrund von Inaktivität abgemeldet.', loggedOutSuccessfully: 'Du wurdest erfolgreich abgemeldet.', loggingOut: 'Abmelden...', @@ -51,19 +51,19 @@ export const deTranslations: DefaultTranslationsObject = { logOut: 'Abmelden', logout: 'Abmelden', logoutSuccessful: 'Abmeldung erfolgreich.', - logoutUser: 'Benutzerabmeldung', + logoutUser: 'Benutzer abmelden', newAccountCreated: - 'Ein neues Konto wurde gerade für dich auf {{serverURL}} erstellt. Bitte klicke auf den folgenden Link oder kopiere die URL in deinen Browser um deine E-Mail-Adresse zu verifizieren: {{verificationURL}}
Nachdem du deine E-Mail-Adresse verifiziert hast, kannst du dich erfolgreich anmelden.', + 'Ein neues Konto wurde gerade für dich auf {{serverURL}} erstellt. Bitte klicke auf den folgenden Link oder kopiere die URL in deinen Browser, um deine E-Mail-Adresse zu verifizieren: {{verificationURL}}
Nachdem du deine E-Mail-Adresse verifiziert hast, kannst du dich erfolgreich anmelden.', newAPIKeyGenerated: 'Neuer API-Key wurde generiert', newPassword: 'Neues Passwort', passed: 'Authentifizierung erfolgreich', passwordResetSuccessfully: 'Passwort erfolgreich zurückgesetzt.', resetPassword: 'Passwort zurücksetzen', - resetPasswordExpiration: 'Passwort-Ablauf zurücksetzen', + resetPasswordExpiration: 'Passwort-Gültigkeitsdauer zurücksetzen', resetPasswordToken: 'Passwort-Token zurücksetzen', resetYourPassword: 'Dein Passwort zurücksetzen', stayLoggedIn: 'Angemeldet bleiben', - successfullyRegisteredFirstUser: 'Erster Benutzer erfolgreich registriert.', + successfullyRegisteredFirstUser: 'Ersten Benutzer erfolgreich registriert.', successfullyUnlocked: 'Erfolgreich entsperrt', tokenRefreshSuccessful: 'Token-Aktualisierung erfolgreich.', unableToVerify: 'Konnte nicht verifiziert werden', @@ -75,15 +75,15 @@ export const deTranslations: DefaultTranslationsObject = { verifyUser: 'Benutzer verifizieren', verifyYourEmail: 'Deine E-Mail-Adresse verifizieren', youAreInactive: - 'Du warst seit einiger Zeit inaktiv und wirst in kurzer Zeit zu deiner eigenen Sicherheit abgemeldet. Möchtest du angemeldet bleiben?', + 'Du warst einige Zeit inaktiv und wirst in Kürze zu deiner eigenen Sicherheit abgemeldet. Möchtest du angemeldet bleiben?', youAreReceivingResetPassword: - 'Du erhältst diese Nachricht, weil du (oder jemand anderes) das Zurücksetzen deines Passworts für dein Benutzerkonto angefordert hat. Bitte klicke auf den folgenden Link, oder kopiere die URL in deinen Browser den Prozess abzuschließen:', + 'Du erhältst diese Nachricht, weil du (oder jemand anderes) das Zurücksetzen deines Passworts für dein Benutzerkonto angefordert hat. Bitte klicke auf den folgenden Link, oder kopiere die URL in deinen Browser, um den Prozess abzuschließen:', youDidNotRequestPassword: 'Solltest du dies nicht angefordert haben, ignoriere diese E-Mail und dein Passwort bleibt unverändert.', }, error: { - accountAlreadyActivated: 'Dieses Konto wurde bereits aktiviert', - autosaving: 'Es gab ein Problem während der automatischen Speicherung für dieses Dokument', + accountAlreadyActivated: 'Dieses Benutzerkonto wurde bereits aktiviert', + autosaving: 'Es gab ein Problem bei der automatischen Speicherung für dieses Dokument', correctInvalidFields: 'Bitte ungültige Felder korrigieren.', deletingFile: 'Beim Löschen der Datei ist ein Fehler aufgetreten.', deletingTitle: @@ -96,8 +96,8 @@ export const deTranslations: DefaultTranslationsObject = { invalidFileTypeValue: 'Ungültiger Datei-Typ: {{value}}', invalidRequestArgs: 'Ungültige Argumente in der Anfrage: {{args}}', loadingDocument: 'Es gab ein Problem, das Dokument mit der ID {{id}} zu laden.', - localesNotSaved_one: 'Das folgende Gebietsschema konnte nicht gespeichert werden:', - localesNotSaved_other: 'Die folgenden Gebietsschemata konnten nicht gespeichert werden:', + localesNotSaved_one: 'Die folgende Sprache konnte nicht gespeichert werden:', + localesNotSaved_other: 'Die folgenden Sprachen konnten nicht gespeichert werden:', logoutFailed: 'Abmeldung fehlgeschlagen.', missingEmail: 'E-Mail-Adresse fehlt.', missingIDOfDocument: 'ID des zu speichernden Dokuments fehlt.', @@ -109,23 +109,23 @@ export const deTranslations: DefaultTranslationsObject = { notAllowedToPerformAction: 'Du hast keine Berechtigung, diese Aktion auszuführen.', notFound: 'Die angeforderte Ressource wurde nicht gefunden.', noUser: 'Kein Benutzer', - previewing: 'Es gab ein Problem beim Vorschauen dieses Dokuments.', - problemUploadingFile: 'Es gab ein Problem während des Hochladens der Datei.', + previewing: 'Bei der Vorschau dieses Dokuments ist ein Fehler aufgetreten.', + problemUploadingFile: 'Beim Hochladen der Datei ist ein Fehler aufgetreten.', tokenInvalidOrExpired: 'Token ist entweder ungültig oder abgelaufen.', - tokenNotProvided: 'Token nicht bereitgestellt.', - unableToDeleteCount: '{{count}} von {{total}} {{label}} konnte nicht gelöscht werden.', + tokenNotProvided: 'Kein Token vorhanden.', + unableToDeleteCount: '{{count}} von {{total}} {{label}} konnten nicht gelöscht werden.', unableToReindexCollection: 'Fehler beim Neuindizieren der Sammlung {{collection}}. Vorgang abgebrochen.', - unableToUpdateCount: '{{count}} von {{total}} {{label}} konnte nicht aktualisiert werden.', + unableToUpdateCount: '{{count}} von {{total}} {{label}} konnten nicht aktualisiert werden.', unauthorized: 'Nicht autorisiert - du musst angemeldet sein, um diese Anfrage zu stellen.', unauthorizedAdmin: 'Nicht autorisiert, dieser Benutzer hat keinen Zugriff auf das Admin-Panel.', unknown: 'Ein unbekannter Fehler ist aufgetreten.', - unPublishingDocument: 'Es gab ein Problem, dieses Dokument auf Entwurf zu setzen.', + unPublishingDocument: 'Bei der Aufhebung der Veröffentlichung dieses Dokuments ist ein Fehler aufgetreten.', unspecific: 'Ein Fehler ist aufgetreten.', - unverifiedEmail: 'Bitte verifizieren Sie Ihre E-Mail, bevor Sie sich anmelden.', + unverifiedEmail: 'Bitte verifiziere deine E-Mail, bevor du dich anmeldest.', userEmailAlreadyRegistered: 'Ein Benutzer mit der angegebenen E-Mail ist bereits registriert.', userLocked: - 'Dieser Benutzer ist auf Grund zu vieler unerfolgreicher Anmelde-Versuche gesperrt.', + 'Dieser Benutzer ist gesperrt, weil er zu viele fehlgeschlagene Anmeldeversuche hat.', usernameAlreadyRegistered: 'Ein Benutzer mit dem angegebenen Benutzernamen ist bereits registriert.', usernameOrPasswordIncorrect: 'Der angegebene Benutzername oder das Passwort ist falsch.', @@ -134,58 +134,58 @@ export const deTranslations: DefaultTranslationsObject = { }, fields: { addLabel: '{{label}} hinzufügen', - addLink: 'Link Hinzufügen', - addNew: 'Neu erstellen', + addLink: 'Link hinzufügen', + addNew: 'Neuen Eintrag hinzufügen', addNewLabel: '{{label}} erstellen', - addRelationship: 'Verknüpfung Hinzufügen', - addUpload: 'Hochladen Hinzufügen', + addRelationship: 'Verknüpfung hinzufügen', + addUpload: 'Neue Datei hochladen', block: 'Block', blocks: 'Blöcke', blockType: 'Block-Typ', chooseBetweenCustomTextOrDocument: - 'Wähle zwischen einer eigenen Text-URL oder verlinke zu einem anderen Dokument.', - chooseDocumentToLink: 'Wähle ein Dokument zum Verlinken', + 'Wähle zwischen der Eingabe einer benutzerdefinierten URL oder verknüpfe ein anderes Dokument.', + chooseDocumentToLink: 'Wähle ein Dokument, das du verlinken möchtest', chooseFromExisting: 'Aus vorhandenen auswählen', chooseLabel: '{{label}} auswählen', collapseAll: 'Alle einklappen', customURL: 'Eigene URL', editLabelData: '{{label}} bearbeiten', - editLink: 'Bearbeite Link', - editRelationship: 'Beziehung Hinzufügen', + editLink: 'Link bearbeiten', + editRelationship: 'Verknüpfung bearbeiten', enterURL: 'URL eingeben', - internalLink: 'Interner Link', + internalLink: 'Interne Verlinkung', itemsAndMore: '{{items}} und {{count}} mehr', - labelRelationship: '{{label}} Verknüpfung', + labelRelationship: '{{label}}-Verknüpfung', latitude: 'Breitengrad', linkedTo: 'Verweist auf <0>{{label}}', linkType: 'Linktyp', longitude: 'Längengrad', newLabel: '{{label}} erstellen', - openInNewTab: 'Öffne im neuen Tab', + openInNewTab: 'In neuem Tab öffnen', passwordsDoNotMatch: 'Passwörter stimmen nicht überein.', relatedDocument: 'Verknüpftes Dokument', relationTo: 'Verknüpfung zu', - removeRelationship: 'Beziehung Entfernen', - removeUpload: 'Hochgeladene Datei Löschen', + removeRelationship: 'Verknüpfung entfernen', + removeUpload: 'Hochgeladene Datei löschen', saveChanges: 'Änderungen speichern', searchForBlock: 'Nach Block suchen', selectExistingLabel: '{{label}} auswählen (vorhandene)', - selectFieldsToEdit: 'Wählen Sie die zu bearbeitenden Felder aus', + selectFieldsToEdit: 'Wähle die zu bearbeitenden Felder aus', showAll: 'Alle anzeigen', - swapRelationship: 'Beziehung Tauschen', - swapUpload: 'Datei Austauschen', + swapRelationship: 'Verknüpfung tauschen', + swapUpload: 'Datei austauschen', textToDisplay: 'Angezeigter Text', toggleBlock: 'Block umschalten', uploadNewLabel: '{{label}} neu hochladen', }, general: { aboutToDelete: 'Du bist dabei {{label}} <1>{{title}} zu löschen. Bist du dir sicher?', - aboutToDeleteCount_many: 'Sie sind dabei, {{count}} {{label}} zu löschen', - aboutToDeleteCount_one: 'Sie sind dabei, {{count}} {{label}} zu löschen', - aboutToDeleteCount_other: 'Sie sind dabei, {{count}} {{label}} zu löschen', - addBelow: 'Darunter hinzufügen', + aboutToDeleteCount_many: 'Du bist dabei, {{count}} {{label}} zu löschen', + aboutToDeleteCount_one: 'Du bist dabei, {{count}} {{label}} zu löschen', + aboutToDeleteCount_other: 'Du bist dabei, {{count}} {{label}} zu löschen', + addBelow: 'Unterhalb hinzufügen', addFilter: 'Filter hinzufügen', - adminTheme: 'Admin-Farbthema', + adminTheme: 'Admin-Erscheinungsbild', all: 'Alle', allCollections: 'Alle Sammlungen', and: 'Und', @@ -218,23 +218,23 @@ export const deTranslations: DefaultTranslationsObject = { copy: 'Kopieren', copying: 'Kopieren', copyWarning: - 'Sie sind dabei, {{to}} mit {{from}} für {{label}} {{title}} zu überschreiben. Sind Sie sicher?', + 'Du bist dabei, {{to}} mit {{from}} für {{label}} {{title}} zu überschreiben. Bist du dir sicher?', create: 'Erstellen', created: 'Erstellt', createdAt: 'Erstellt am', - createNew: 'Neu Erstellen', + createNew: 'Neu erstellen', createNewLabel: '{{label}} neu erstellen', creating: 'Erstelle', creatingNewLabel: 'Erstelle {{label}}', currentlyEditing: - 'bearbeitet gerade dieses Dokument. Wenn Sie übernehmen, wird die Bearbeitung blockiert und nicht gespeicherte Änderungen können verloren gehen.', + 'bearbeitet gerade dieses Dokument. Wenn du übernimmst, wird die Bearbeitung blockiert und nicht gespeicherte Änderungen können verloren gehen.', custom: 'Benutzerdefiniert', dark: 'Dunkel', dashboard: 'Übersicht', delete: 'Löschen', deletedCountSuccessfully: '{{count}} {{label}} erfolgreich gelöscht.', deletedSuccessfully: 'Erfolgreich gelöscht.', - deleting: 'Lösche...', + deleting: 'Löschen...', depth: 'Tiefe', descending: 'Absteigend', deselectAllRows: 'Alle Zeilen abwählen', @@ -242,7 +242,7 @@ export const deTranslations: DefaultTranslationsObject = { documentLocked: 'Dokument gesperrt', documents: 'Dokumente', duplicate: 'Duplizieren', - duplicateWithoutSaving: 'Dupliziere ohne Änderungen zu speichern', + duplicateWithoutSaving: 'Duplizieren ohne Änderungen zu speichern', edit: 'Bearbeiten', editAll: 'Bearbeite alle', editedSince: 'Bearbeitet seit', @@ -257,20 +257,20 @@ export const deTranslations: DefaultTranslationsObject = { enterAValue: 'Gib einen Wert ein', error: 'Fehler', errors: 'Fehler', - fallbackToDefaultLocale: 'Rückgriff auf das Standardgebietsschema', + fallbackToDefaultLocale: 'Auf die Standardsprache zurückfallen', false: 'Falsch', filter: 'Filter', filters: 'Filter', - filterWhere: 'Filter {{label}} wo', + filterWhere: 'Filter {{label}}, wo', globals: 'Globale Dokumente', goBack: 'Zurück', - isEditing: 'bearbeitet', + isEditing: 'bearbeitet gerade', language: 'Sprache', lastModified: 'Zuletzt geändert', leaveAnyway: 'Trotzdem verlassen', leaveWithoutSaving: 'Ohne speichern verlassen', light: 'Hell', - livePreview: 'Vorschau', + livePreview: 'Live-Vorschau', loading: 'Lädt', locale: 'Sprache', locales: 'Sprachen', @@ -296,10 +296,10 @@ export const deTranslations: DefaultTranslationsObject = { open: 'Öffnen', or: 'oder', order: 'Reihenfolge', - overwriteExistingData: 'Überschreiben Sie vorhandene Felddaten', + overwriteExistingData: 'Vorhandene Eingaben überschreiben', pageNotFound: 'Seite nicht gefunden', password: 'Passwort', - payloadSettings: 'Payload Einstellungen', + payloadSettings: 'Payload-Einstellungen', perPage: 'Pro Seite: {{limit}}', previous: 'Vorherige', reindex: 'Neuindizieren', @@ -307,25 +307,25 @@ export const deTranslations: DefaultTranslationsObject = { remove: 'Entfernen', reset: 'Zurücksetzen', resetPreferences: 'Präferenzen zurücksetzen', - resetPreferencesDescription: 'Dies setzt alle Ihre Präferenzen auf die Standardwerte zurück.', + resetPreferencesDescription: 'Alle Präferenzen werden auf die Standardwerte zurückgesetzt.', resettingPreferences: 'Präferenzen werden zurückgesetzt.', row: 'Zeile', rows: 'Zeilen', save: 'Speichern', - saving: 'Speichert...', - schedulePublishFor: 'Planen Sie die Veröffentlichung für {{title}}', + saving: 'Speichern...', + schedulePublishFor: 'Plane die Veröffentlichung für {{title}}', searchBy: 'Suche nach {{label}}', - selectAll: 'Alle auswählen {{count}} {{label}}', - selectAllRows: 'Wählen Sie alle Zeilen aus', + selectAll: 'Alle {{count}} {{label}} auswählen', + selectAllRows: 'Alle Zeilen auswählen', selectedCount: '{{count}} {{label}} ausgewählt', - selectLabel: 'Wählen Sie {{label}}', + selectLabel: 'Wähle {{label}}', selectValue: 'Wert auswählen', showAllLabel: 'Zeige alle {{label}}', - sorryNotFound: 'Entschuldige, es entspricht nichts deiner Anfrage', + sorryNotFound: 'Es tut uns leid, aber wir haben nichts gefunden, was deiner Anfrage entspricht.', sort: 'Sortieren', sortByLabelDirection: 'Sortieren nach {{label}} {{direction}}', stayOnThisPage: 'Auf dieser Seite bleiben', - submissionSuccessful: 'Einrichung erfolgreich.', + submissionSuccessful: 'Übermittlung erfolgreich.', submit: 'Senden', submitting: 'Wird aktualisiert...', success: 'Erfolg', @@ -341,7 +341,7 @@ export const deTranslations: DefaultTranslationsObject = { true: 'Wahr', unauthorized: 'Nicht autorisiert', unsavedChanges: - 'Sie haben ungespeicherte Änderungen. Speichern oder verwerfen Sie diese, bevor Sie fortfahren.', + 'Du hast ungespeicherte Änderungen. Speichern oder verwerfe sie, bevor du fortfahrst.', unsavedChangesDuplicate: 'Du hast ungespeicherte Änderungen, möchtest du mit dem Duplizieren fortfahren?', untitled: 'ohne Titel', @@ -350,7 +350,7 @@ export const deTranslations: DefaultTranslationsObject = { updatedCountSuccessfully: '{{count}} {{label}} erfolgreich aktualisiert.', updatedLabelSuccessfully: '{{label}} erfolgreich aktualisiert.', updatedSuccessfully: 'Erfolgreich aktualisiert.', - updateForEveryone: 'Aktualisierung für alle', + updateForEveryone: 'Für alle aktualisieren', updating: 'Aktualisierung', uploading: 'Hochladen', uploadingBulk: 'Hochladen von {{current}} von {{total}}', @@ -362,13 +362,13 @@ export const deTranslations: DefaultTranslationsObject = { welcome: 'Willkommen', }, localization: { - cannotCopySameLocale: 'Kann nicht in dieselbe Gebietsschema kopieren', + cannotCopySameLocale: 'Kann nicht in dieselbe Sprache kopiert werden', copyFrom: 'Kopieren von', copyFromTo: 'Kopieren von {{from}} zu {{to}}', copyTo: 'Kopieren nach', - copyToLocale: 'Kopieren in das Gebietsschema', - localeToPublish: 'Zu veröffentlichende Lokalität', - selectLocaleToCopy: 'Wählen Sie den Ort zum Kopieren aus', + copyToLocale: 'Erstelle Kopie für Sprach-Variante', + localeToPublish: 'Zu veröffentlichende Sprache', + selectLocaleToCopy: 'Wähle den Ort zum Kopieren aus', }, operators: { contains: 'enthält', @@ -390,12 +390,12 @@ export const deTranslations: DefaultTranslationsObject = { upload: { addFile: 'Datei hinzufügen', addFiles: 'Dateien hinzufügen', - bulkUpload: 'Massenupload', + bulkUpload: 'Mehrere Dateien hochladen', crop: 'Zuschneiden', cropToolDescription: - 'Ziehen Sie die Ecken des ausgewählten Bereichs, zeichnen Sie einen neuen Bereich oder passen Sie die Werte unten an.', - dragAndDrop: 'Ziehen Sie eine Datei per Drag-and-Drop', - dragAndDropHere: 'oder ziehe eine Datei hier', + 'Ziehe die Ecken des ausgewählten Bereichs, zeichne einen neuen Bereich oder passe die Werte unten an.', + dragAndDrop: 'Bewege eine Datei per Drag-and-Drop', + dragAndDropHere: 'oder lege eine Datei hier ab', editImage: 'Bild bearbeiten', fileName: 'Dateiname', fileSize: 'Dateigröße', @@ -403,7 +403,7 @@ export const deTranslations: DefaultTranslationsObject = { fileToUpload: 'Datei zum Hochladen', focalPoint: 'Brennpunkt', focalPointDescription: - 'Ziehen Sie den Fokuspunkt direkt auf die Vorschau oder passen Sie die Werte unten an.', + 'Setze den Fokuspunkt direkt auf der Vorschau oder passe die Werte unten an.', height: 'Höhe', lessInfo: 'Weniger Info', moreInfo: 'Mehr Info', @@ -420,12 +420,12 @@ export const deTranslations: DefaultTranslationsObject = { }, validation: { emailAddress: 'Bitte gib eine korrekte E-Mail-Adresse an.', - enterNumber: 'Bitte gib eine gültige Nummer an,', + enterNumber: 'Bitte gib eine gültige Nummer an.', fieldHasNo: 'Dieses Feld hat kein {{label}}', greaterThanMax: '{{value}} ist größer als der maximal erlaubte {{label}} von {{max}}.', invalidInput: 'Dieses Feld hat einen inkorrekten Wert.', invalidSelection: 'Dieses Feld hat eine inkorrekte Auswahl.', - invalidSelections: "'Dieses Feld enthält die folgenden inkorrekten Auswahlen:'", + invalidSelections: 'Dieses Feld enthält die folgenden inkorrekten Auswahlmöglichkeiten:', lessThanMin: '{{value}} ist kleiner als der minimal erlaubte {{label}} von {{min}}.', limitReached: 'Limit erreicht, es können nur {{max}} Elemente hinzugefügt werden.', longerThanMin: 'Dieser Wert muss länger als die minimale Länge von {{minLength}} Zeichen sein.', @@ -433,26 +433,26 @@ export const deTranslations: DefaultTranslationsObject = { required: 'Pflichtfeld', requiresAtLeast: 'Dieses Feld muss mindestens {{count}} {{label}} enthalten.', requiresNoMoreThan: 'Dieses Feld kann nicht mehr als {{count}} {{label}} enthalten.', - requiresTwoNumbers: 'Dieses Feld muss zwei Nummern enthalten.', + requiresTwoNumbers: 'Dieses Feld muss zwei Zahlen enthalten.', shorterThanMax: 'Dieser Wert muss kürzer als die maximale Länge von {{maxLength}} sein.', timezoneRequired: 'Eine Zeitzone ist erforderlich.', trueOrFalse: 'Dieses Feld kann nur wahr oder falsch sein.', username: - 'Bitte geben Sie einen gültigen Benutzernamen ein. Dieser kann Buchstaben, Zahlen, Bindestriche, Punkte und Unterstriche enthalten.', - validUploadID: "'Dieses Feld enthält keine valide Upload-ID.'", + 'Bitte gib einen gültigen Benutzernamen ein. Dieser kann Buchstaben, Zahlen, Bindestriche, Punkte und Unterstriche enthalten.', + validUploadID: 'Dieses Feld enthält keine valide Upload-ID.', }, version: { type: 'Typ', aboutToPublishSelection: - 'Sie sind dabei, alle {{label}} in der Auswahl zu veröffentlichen. Bist du dir sicher?', + 'Du bist dabei, alle {{label}} in der Auswahl zu veröffentlichen. Bist du dir sicher?', aboutToRestore: 'Du bist dabei, {{label}} auf den Stand vom {{versionDate}} zurücksetzen.', aboutToRestoreGlobal: 'Du bist dabei, das Globale Dokument {{label}} auf den Stand vom {{versionDate}} zurückzusetzen.', aboutToRevertToPublished: - 'Du bist dabei, dieses Dokument auf den Stand des ersten Veröffentlichungsdatums zurückzusetzen - Bist du sicher?', - aboutToUnpublish: 'Du bist dabei dieses Dokument auf Entwurf zu setzen - bist du dir sicher?', + 'Du bist dabei, dieses Dokument auf den Stand des ersten Veröffentlichungsdatums zurückzusetzen. Bist du sicher?', + aboutToUnpublish: 'Du bist dabei dieses Dokument auf Entwurf zu setzen. Bist du dir sicher?', aboutToUnpublishSelection: - 'Sie sind dabei, die Veröffentlichung aller {{label}} in der Auswahl aufzuheben. Bist du dir sicher?', + 'Du bist dabei, die Veröffentlichung aller {{label}} in der Auswahl aufzuheben. Bist du dir sicher?', autosave: 'Automatische Speicherung', autosavedSuccessfully: 'Erfolgreich automatisch gespeichert.', autosavedVersion: 'Automatisch gespeicherte Version', @@ -462,8 +462,8 @@ export const deTranslations: DefaultTranslationsObject = { compareVersion: 'Vergleiche Version zu:', confirmPublish: 'Veröffentlichung bestätigen', confirmRevertToSaved: 'Zurücksetzen auf die letzte Speicherung bestätigen', - confirmUnpublish: 'Setzen auf Entwurf bestätigen', - confirmVersionRestoration: ' Wiederherstellung der Version bestätigen', + confirmUnpublish: 'Aufhebung der Veröffentlichung bestätigen', + confirmVersionRestoration: 'Wiederherstellung der Version bestätigen', currentDocumentStatus: 'Aktueller Dokumentenstatus: {{docStatus}}', currentDraft: 'Aktueller Entwurf', currentPublishedVersion: 'Aktuell veröffentlichte Version', @@ -475,13 +475,13 @@ export const deTranslations: DefaultTranslationsObject = { noRowsFound: 'Kein {{label}} gefunden', noRowsSelected: 'Kein {{label}} ausgewählt', preview: 'Vorschau', - previouslyPublished: 'Zuvor Veröffentlicht', - problemRestoringVersion: 'Es gab ein Problem bei der Wiederherstellung dieser Version', + previouslyPublished: 'Zuvor veröffentlicht', + problemRestoringVersion: 'Bei der Wiederherstellung der Version ist ein Fehler aufgetreten', publish: 'Veröffentlichen', - publishAllLocales: 'Veröffentlichen Sie alle Lokalisierungen', + publishAllLocales: 'Alle Sprachen veröffentlichen', publishChanges: 'Änderungen veröffentlichen', published: 'Veröffentlicht', - publishIn: 'Veröffentlichen in {{locale}}', + publishIn: 'Veröffentlichen auf {{locale}}', publishing: 'Veröffentlichung', restoreAsDraft: 'Als Entwurf wiederherstellen', restoredSuccessfully: 'Erfolgreich wiederhergestellt.', @@ -497,20 +497,20 @@ export const deTranslations: DefaultTranslationsObject = { showingVersionsFor: 'Versionen anzeigen für:', showLocales: 'Sprachen anzeigen:', status: 'Status', - unpublish: 'Auf Entwurf setzen', - unpublishing: 'Setze auf Entwurf...', + unpublish: 'Veröffentlichung aufheben', + unpublishing: 'Veröffentlichung aufheben...', version: 'Version', versionCount_many: '{{count}} Versionen gefunden', versionCount_none: 'Keine Versionen gefunden', versionCount_one: '{{count}} Version gefunden', versionCount_other: '{{count}} Versionen gefunden', versionCreatedOn: '{{version}} erstellt am:', - versionID: 'Version ID', + versionID: 'Version-ID', versions: 'Versionen', - viewingVersion: 'Betrachte Version für {{entityLabel}} {{documentTitle}}', - viewingVersionGlobal: '`Betrachte Version für das Globale Dokument {{entityLabel}}', - viewingVersions: 'Betrachte Versionen für {{entityLabel}} {{documentTitle}}', - viewingVersionsGlobal: '`Betrachte Versionen für das Globale Dokument {{entityLabel}}', + viewingVersion: 'Version für {{entityLabel}} {{documentTitle}} ansehen', + viewingVersionGlobal: 'Version für das Globale Dokument {{entityLabel}} ansehen', + viewingVersions: 'Versionen für {{entityLabel}} {{documentTitle}} ansehen', + viewingVersionsGlobal: 'Versionen für das Globale Dokument {{entityLabel}} ansehen', }, } From 034a26754f4bd1ffb1cd69d72fefb40328257fbf Mon Sep 17 00:00:00 2001 From: Said Akhrarov <36972061+akhrarovsaid@users.noreply.github.com> Date: Wed, 23 Apr 2025 19:55:42 -0400 Subject: [PATCH 15/15] docs: fix query preset config link (#12156) ### What? This PR fixes a link to the Payload config in the query presets docs, and adjusts the links to the edit view components in the collections and global config pages. ### Why? To direct users to the correct location. ### How? Changes to a few docs. Fixes #12199 --- docs/configuration/collections.mdx | 14 +++++----- docs/configuration/globals.mdx | 12 ++++---- docs/query-presets/overview.mdx | 44 ++++++++++++++++-------------- 3 files changed, 36 insertions(+), 34 deletions(-) diff --git a/docs/configuration/collections.mdx b/docs/configuration/collections.mdx index 9dcebc92cc..e00043c9dc 100644 --- a/docs/configuration/collections.mdx +++ b/docs/configuration/collections.mdx @@ -193,13 +193,13 @@ export const MyCollection: CollectionConfig = { The following options are available: -| Option | Description | -| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `SaveButton` | Replace the default Save Button within the Edit View. [Drafts](../versions/drafts) must be disabled. [More details](../custom-components/edit-view#save-button). | -| `SaveDraftButton` | Replace the default Save Draft Button within the Edit View. [Drafts](../versions/drafts) must be enabled and autosave must be disabled. [More details](../custom-components/edit-view#save-draft-button). | -| `PublishButton` | Replace the default Publish Button within the Edit View. [Drafts](../versions/drafts) must be enabled. [More details](../custom-components/edit-view#publish-button). | -| `PreviewButton` | Replace the default Preview Button within the Edit View. [Preview](../admin/preview) must be enabled. [More details](../custom-components/edit-view#preview-button). | -| `Upload` | Replace the default Upload component within the Edit View. [Upload](../upload/overview) must be enabled. [More details](../custom-components/edit-view#upload). | +| Option | Description | +| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `SaveButton` | Replace the default Save Button within the Edit View. [Drafts](../versions/drafts) must be disabled. [More details](../custom-components/edit-view#savebutton). | +| `SaveDraftButton` | Replace the default Save Draft Button within the Edit View. [Drafts](../versions/drafts) must be enabled and autosave must be disabled. [More details](../custom-components/edit-view#savedraftbutton). | +| `PublishButton` | Replace the default Publish Button within the Edit View. [Drafts](../versions/drafts) must be enabled. [More details](../custom-components/edit-view#publishbutton). | +| `PreviewButton` | Replace the default Preview Button within the Edit View. [Preview](../admin/preview) must be enabled. [More details](../custom-components/edit-view#previewbutton). | +| `Upload` | Replace the default Upload component within the Edit View. [Upload](../upload/overview) must be enabled. [More details](../custom-components/edit-view#upload). | **Note:** For details on how to build Custom Components, see [Building Custom diff --git a/docs/configuration/globals.mdx b/docs/configuration/globals.mdx index c833bb0d2c..9339262100 100644 --- a/docs/configuration/globals.mdx +++ b/docs/configuration/globals.mdx @@ -179,12 +179,12 @@ export const MyGlobal: SanitizedGlobalConfig = { The following options are available: -| Option | Description | -| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `SaveButton` | Replace the default Save Button with a Custom Component. [Drafts](../versions/drafts) must be disabled. [More details](../custom-components/edit-view#save-button). | -| `SaveDraftButton` | Replace the default Save Draft Button with a Custom Component. [Drafts](../versions/drafts) must be enabled and autosave must be disabled. [More details](../custom-components/edit-view#save-draft-button). | -| `PublishButton` | Replace the default Publish Button with a Custom Component. [Drafts](../versions/drafts) must be enabled. [More details](../custom-components/edit-view#publish-button). | -| `PreviewButton` | Replace the default Preview Button with a Custom Component. [Preview](../admin/preview) must be enabled. [More details](../custom-components/edit-view#preview-button). | +| Option | Description | +| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `SaveButton` | Replace the default Save Button with a Custom Component. [Drafts](../versions/drafts) must be disabled. [More details](../custom-components/edit-view#savebutton). | +| `SaveDraftButton` | Replace the default Save Draft Button with a Custom Component. [Drafts](../versions/drafts) must be enabled and autosave must be disabled. [More details](../custom-components/edit-view#savedraftbutton). | +| `PublishButton` | Replace the default Publish Button with a Custom Component. [Drafts](../versions/drafts) must be enabled. [More details](../custom-components/edit-view#publishbutton). | +| `PreviewButton` | Replace the default Preview Button with a Custom Component. [Preview](../admin/preview) must be enabled. [More details](../custom-components/edit-view#previewbutton). | **Note:** For details on how to build Custom Components, see [Building Custom diff --git a/docs/query-presets/overview.mdx b/docs/query-presets/overview.mdx index abc5852279..a5ca006833 100644 --- a/docs/query-presets/overview.mdx +++ b/docs/query-presets/overview.mdx @@ -117,7 +117,7 @@ Adding custom access control rules requires: 2. A set of fields to conditionally render when that option is selected 3. A function that returns the access control rules for that option -To do this, use the `queryPresets.constraints` property in your [Payload Config](../configuration/payload-config). +To do this, use the `queryPresets.constraints` property in your [Payload Config](../configuration/overview). ```ts import { buildConfig } from 'payload' @@ -128,26 +128,28 @@ const config = buildConfig({ // ... // highlight-start constraints: { - read: { - label: 'Specific Roles', - value: 'specificRoles', - fields: [ - { - name: 'roles', - type: 'select', - hasMany: true, - options: [ - { label: 'Admin', value: 'admin' }, - { label: 'User', value: 'user' }, - ], - }, - ], - access: ({ req: { user } }) => ({ - 'access.read.roles': { - in: [user?.roles], - }, - }), - }, + read: [ + { + label: 'Specific Roles', + value: 'specificRoles', + fields: [ + { + name: 'roles', + type: 'select', + hasMany: true, + options: [ + { label: 'Admin', value: 'admin' }, + { label: 'User', value: 'user' }, + ], + }, + ], + access: ({ req: { user } }) => ({ + 'access.read.roles': { + in: [user?.roles], + }, + }), + }, + ], // highlight-end }, },