diff --git a/docs/access-control/fields.mdx b/docs/access-control/fields.mdx index 8c60a6db653..575fef04c59 100644 --- a/docs/access-control/fields.mdx +++ b/docs/access-control/fields.mdx @@ -23,7 +23,7 @@ export const FieldWithAccessControl: Field = { ``` - **Note:** Field Access Controls does not support returning + **Note:** Field Access Control does not support returning [Query](../queries/overview) constraints like [Collection Access Control](./collections) does. diff --git a/docs/admin/metadata.mdx b/docs/admin/metadata.mdx index adca913f504..1f36af2ce80 100644 --- a/docs/admin/metadata.mdx +++ b/docs/admin/metadata.mdx @@ -10,7 +10,7 @@ Every page within the Admin Panel automatically receives dynamic, auto-generated Metadata is fully configurable at the root level and cascades down to individual collections, documents, and custom views. This allows for the ability to control metadata on any page with high precision, while also providing sensible defaults. -All metadata is injected into Next.js' [`generateMetadata`](https://nextjs.org/docs/app/api-reference/functions/generate-metadata) function. This used to generate the `` of pages within the Admin Panel. All metadata options that are available in Next.js are exposed by Payload. +All metadata is injected into Next.js' [`generateMetadata`](https://nextjs.org/docs/app/api-reference/functions/generate-metadata) function. This is used to generate the `` of pages within the Admin Panel. All metadata options that are available in Next.js are exposed by Payload. Within the Admin Panel, metadata can be customized at the following levels: diff --git a/docs/admin/overview.mdx b/docs/admin/overview.mdx index 8a959e2ebdb..1e38f39e39c 100644 --- a/docs/admin/overview.mdx +++ b/docs/admin/overview.mdx @@ -56,7 +56,7 @@ app As shown above, all Payload routes are nested within the `(payload)` route group. This creates a boundary between the Admin Panel and the rest of your application by scoping all layouts and styles. The `layout.tsx` file within this directory, for example, is where Payload manages the `html` tag of the document to set proper [`lang`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang) and [`dir`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/dir) attributes, etc. -The `admin` directory contains all the _pages_ related to the interface itself, whereas the `api` and `graphql` directories contains all the _routes_ related to the [REST API](../rest-api/overview) and [GraphQL API](../graphql/overview). All admin routes are [easily configurable](#customizing-routes) to meet your application's exact requirements. +The `admin` directory contains all the _pages_ related to the interface itself, whereas the `api` and `graphql` directories contain all the _routes_ related to the [REST API](../rest-api/overview) and [GraphQL API](../graphql/overview). All admin routes are [easily configurable](#customizing-routes) to meet your application's exact requirements. **Note:** If you don't intend to use the Admin Panel, [REST diff --git a/docs/admin/preview.mdx b/docs/admin/preview.mdx index 3abfd20bfce..6f5984c594f 100644 --- a/docs/admin/preview.mdx +++ b/docs/admin/preview.mdx @@ -53,7 +53,7 @@ The `options` object contains the following properties: | ------------ | ----------------------------------------------------- | | **`locale`** | The current locale of the Document being edited. | | **`req`** | The Payload Request object. | -| **`token`** | The JWT token of the currently authenticated in user. | +| **`token`** | The JWT token of the currently authenticated user. | If your application requires a fully qualified URL, such as within deploying to Vercel Preview Deployments, you can use the `req` property to build this URL: @@ -225,7 +225,7 @@ export default async function Page({ params: paramsPromise }) { ``` - **Note:** For fully working example of this, check of the official [Draft + **Note:** For fully working example of this, check out the official [Draft Preview Example](https://github.com/payloadcms/payload/tree/main/examples/draft-preview) in the [Examples diff --git a/docs/authentication/email.mdx b/docs/authentication/email.mdx index 15b49cc09b0..b54dc971e4e 100644 --- a/docs/authentication/email.mdx +++ b/docs/authentication/email.mdx @@ -2,7 +2,7 @@ title: Authentication Emails label: Email Verification order: 30 -desc: Email Verification allows users to verify their email address before they're account is fully activated. Email Verification ties directly into the Email functionality that Payload provides. +desc: Email Verification allows users to verify their email address before their account is fully activated. Email Verification ties directly into the Email functionality that Payload provides. keywords: authentication, email, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs --- diff --git a/docs/custom-components/document-views.mdx b/docs/custom-components/document-views.mdx index f85ed93c037..76d637a987e 100644 --- a/docs/custom-components/document-views.mdx +++ b/docs/custom-components/document-views.mdx @@ -6,7 +6,7 @@ desc: keywords: --- -Document Views consist of multiple, individual views that together represent any single [Collection](../configuration/collections) or [Global](../configuration/globals) Document. All Document Views and are scoped under the `/collections/:collectionSlug/:id` or the `/globals/:globalSlug` route, respectively. +Document Views consist of multiple, individual views that together represent any single [Collection](../configuration/collections) or [Global](../configuration/globals) Document. All Document Views are scoped under the `/collections/:collectionSlug/:id` or the `/globals/:globalSlug` route, respectively. There are a number of default Document Views, such as the [Edit View](./edit-view) and API View, but you can also create [entirely new views](./custom-views#adding-new-views) as needed. All Document Views share a layout and can be given their own tab-based navigation, if desired. diff --git a/docs/custom-components/edit-view.mdx b/docs/custom-components/edit-view.mdx index af7f371ce51..0640957bc25 100644 --- a/docs/custom-components/edit-view.mdx +++ b/docs/custom-components/edit-view.mdx @@ -6,14 +6,14 @@ desc: keywords: admin, components, custom, documentation, Content Management System, cms, headless, javascript, node, react, nextjs --- -The Edit View is where users interact with individual [Collection](../configuration/collections) and [Global](../configuration/globals) Documents within the [Admin Panel](../admin/overview). The Edit View contains the actual form in which submits the data to the server. This is where they can view, edit, and save their content. It contains controls for saving, publishing, and previewing the document, all of which can be customized to a high degree. +The Edit View is where users interact with individual [Collection](../configuration/collections) and [Global](../configuration/globals) Documents within the [Admin Panel](../admin/overview). The Edit View contains the actual form that submits the data to the server. This is where they can view, edit, and save their content. It contains controls for saving, publishing, and previewing the document, all of which can be customized to a high degree. The Edit View can be swapped out in its entirety for a Custom View, or it can be injected with a number of Custom Components to add additional functionality or presentational elements without replacing the entire view. **Note:** The Edit View is one of many [Document Views](./document-views) in the Payload Admin Panel. Each Document View is responsible for a different - aspect of the interacting with a single Document. + aspect of interacting with a single Document. ## Custom Edit View @@ -74,7 +74,7 @@ In addition to swapping out the entire Edit View with a [Custom View](./custom-v **Important:** Collection and Globals are keyed to a different property in the - `admin.components` object have slightly different options. Be sure to use the + `admin.components` object and have slightly different options. Be sure to use the correct key for the entity you are working with. @@ -199,7 +199,7 @@ export function MySaveButton(props: SaveButtonClientProps) { 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): +To add `beforeDocumentControls` components, use the `components.edit.beforeDocumentControls` property in your [Collection Config](../configuration/collections) or `components.elements.beforeDocumentControls` in your [Global Config](../configuration/globals): #### Collections diff --git a/docs/custom-components/root-components.mdx b/docs/custom-components/root-components.mdx index e6b66a1d05c..70bcc489970 100644 --- a/docs/custom-components/root-components.mdx +++ b/docs/custom-components/root-components.mdx @@ -52,7 +52,7 @@ The following options are available: _For details on how to build Custom Components, see [Building Custom Components](./overview#building-custom-components)._ - **Note:** You can also use set [Collection + **Note:** You can also set [Collection Components](../configuration/collections#custom-components) and [Global Components](../configuration/globals#custom-components) in their respective configs. diff --git a/docs/database/mongodb.mdx b/docs/database/mongodb.mdx index f258ae92d21..cf74e0dd0a2 100644 --- a/docs/database/mongodb.mdx +++ b/docs/database/mongodb.mdx @@ -77,4 +77,4 @@ export default buildConfig({ We export compatibility options for [DocumentDB](https://aws.amazon.com/documentdb/), [Azure Cosmos DB](https://azure.microsoft.com/en-us/products/cosmos-db) and [Firestore](https://cloud.google.com/firestore/mongodb-compatibility/docs/overview). Known limitations: - Azure Cosmos DB does not support transactions that update two or more documents in different collections, which is a common case when using Payload (via hooks). -- Azure Cosmos DB the root config property `indexSortableFields` must be set to `true`. +- Azure Cosmos DB requires the root config property `indexSortableFields` to be set to `true`. diff --git a/docs/database/postgres.mdx b/docs/database/postgres.mdx index 99e39aca072..3f2c8c1382e 100644 --- a/docs/database/postgres.mdx +++ b/docs/database/postgres.mdx @@ -51,7 +51,7 @@ export default buildConfig({ ``` - **Note:** If you're using `vercelPostgresAdapter` your + **Note:** If you're using `vercelPostgresAdapter` and your `process.env.POSTGRES_URL` or `pool.connectionString` points to a local database (e.g hostname has `localhost` or `127.0.0.1`) we use the `pg` module for pooling instead of `@vercel/postgres`. This is because `@vercel/postgres` diff --git a/docs/ecommerce/advanced.mdx b/docs/ecommerce/advanced.mdx index 77f7b47a06a..f916d3b8871 100644 --- a/docs/ecommerce/advanced.mdx +++ b/docs/ecommerce/advanced.mdx @@ -17,7 +17,7 @@ You can import the collections directly from the plugin and add them to your Pay | `createAddressesCollection` | `addresses` | Used for customer addresses (like shipping and billing). [More](#createAddressesCollection) | | `createCartsCollection` | `carts` | Carts can be used by customers, guests and once purchased are kept for records and analytics. [More](#createCartsCollection) | | `createOrdersCollection` | `orders` | Orders are used to store customer-side information and are related to at least one transaction. [More](#createOrdersCollection) | -| `createTransactionsCollection` | `transactions` | Handles payment information accessable by admins only, related to Orders. [More](#createTransactionsCollection) | +| `createTransactionsCollection` | `transactions` | Handles payment information accessible by admins only, related to Orders. [More](#createTransactionsCollection) | | `createProductsCollection` | `products` | All the product information lives here, contains prices, relations to Variant Types and joins to Variants. [More](#createProductsCollection) | | `createVariantsCollection` | `variants` | Product variants, unique purchasable items that are linked to a product and Variant Options. [More](#createVariantsCollection) | | `createVariantTypesCollection` | `variantTypes` | A taxonomy used by Products to relate Variant Options together. An example of a Variant Type is "size". [More](#createVariantTypesCollection) | diff --git a/docs/ecommerce/frontend.mdx b/docs/ecommerce/frontend.mdx index e66dad295f8..ca4218957e7 100644 --- a/docs/ecommerce/frontend.mdx +++ b/docs/ecommerce/frontend.mdx @@ -14,7 +14,7 @@ The following hooks and components are available: | ------------------- | ------------------------------------------------------------------------------ | | `EcommerceProvider` | A context provider to wrap your application and provide the ecommerce context. | | `useCart` | A hook to manage the cart state and actions. | -| `useAddresses` | A hook to fetch and manage products. | +| `useAddresses` | A hook to fetch and manage addresses. | | `usePayments` | A hook to manage the checkout process. | | `useCurrency` | A hook to format prices based on the selected currency. | | `useEcommerce` | A hook that encompasses all of the above in one. | diff --git a/docs/ecommerce/overview.mdx b/docs/ecommerce/overview.mdx index 951b952e741..92fb0a17183 100644 --- a/docs/ecommerce/overview.mdx +++ b/docs/ecommerce/overview.mdx @@ -13,7 +13,7 @@ keywords: plugins, ecommerce, stripe, plugin, payload, cms, shop, payments releases. -Payload provides an Ecommerce Plugin that allows you to add ecommerce functionality to your app. It comes a set of utilities and collections to manage products, orders, and payments. It also integrates with popular payment gateways like Stripe to handle transactions. +Payload provides an Ecommerce Plugin that allows you to add ecommerce functionality to your app. It comes with a set of utilities and collections to manage products, orders, and payments. It also integrates with popular payment gateways like Stripe to handle transactions. This plugin is completely open-source and the [source code can be found @@ -97,7 +97,7 @@ Each Variant Type can contain a set of Variant Options. For example, a T-Shirt p **Carts** -Carts are linked to Customers or they're left entirely public for guests users and can contain multiple Products and Variants. Carts are stored in the database and can be retrieved at any time. Carts are automatically created for Customers when they add a product to their cart for the first time. +Carts are linked to Customers or they're left entirely public for guest users and can contain multiple Products and Variants. Carts are stored in the database and can be retrieved at any time. Carts are automatically created for Customers when they add a product to their cart for the first time. **Transactions and Orders** diff --git a/docs/ecommerce/payments.mdx b/docs/ecommerce/payments.mdx index 27ae5e19a82..4d47c85cb43 100644 --- a/docs/ecommerce/payments.mdx +++ b/docs/ecommerce/payments.mdx @@ -156,12 +156,12 @@ The arguments can be extended but should always include the `PaymentAdapterArgs` | Property | Type | Description | | ---------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------- | -| `label` | `string` | (Optional) Allow overriding the default UI label for this adaper. | +| `label` | `string` | (Optional) Allow overriding the default UI label for this adapter. | | `groupOverrides` | `FieldsOverride` | (Optional) Allow overriding the default fields of the payment group. See [Payment Fields](#payment-fields) for more details. | #### initiatePayment -The `initiatePayment` function is called when a payment is initiated. At this step the transaction is created with a status "Processing", an abandoned purchaase will leave this transaction in this state. It receives an object with the following properties: +The `initiatePayment` function is called when a payment is initiated. At this step the transaction is created with a status "Processing", an abandoned purchase will leave this transaction in this state. It receives an object with the following properties: | Property | Type | Description | | ------------------ | ---------------- | --------------------------------------------- | @@ -407,7 +407,7 @@ And for the args use the `PaymentAdapterClientArgs` type: | Property | Type | Description | | -------- | -------- | ----------------------------------------------------------------- | -| `label` | `string` | (Optional) Allow overriding the default UI label for this adaper. | +| `label` | `string` | (Optional) Allow overriding the default UI label for this adapter. | ## Best Practices diff --git a/docs/fields/group.mdx b/docs/fields/group.mdx index 1772851f63f..693be20ca2c 100644 --- a/docs/fields/group.mdx +++ b/docs/fields/group.mdx @@ -111,7 +111,7 @@ export const ExampleCollection: CollectionConfig = { ## Presentational group fields -You can also use the Group field to only visually group fields without affecting the data structure. Not defining a label will render just the grouped fields. +You can also use the Group field to only visually group fields without affecting the data structure. Not defining a `name` will render just the grouped fields (no nested object is created). If you want the group to appear as a titled section in the Admin UI, set a `label`. ```ts import type { CollectionConfig } from 'payload' @@ -120,6 +120,39 @@ export const ExampleCollection: CollectionConfig = { slug: 'example-collection', fields: [ { + label: 'Page meta', // label only → presentational + type: 'group', // required + fields: [ + { + name: 'title', + type: 'text', + required: true, + minLength: 20, + maxLength: 100, + }, + { + name: 'description', + type: 'textarea', + required: true, + minLength: 40, + maxLength: 160, + }, + ], + }, + ], +} +``` + +## Named group + +```ts +import type { CollectionConfig } from 'payload' + +export const ExampleCollection: CollectionConfig = { + slug: 'example-collection', + fields: [ + { + name: 'pageMeta', // name → nested object in data label: 'Page meta', type: 'group', // required fields: [ diff --git a/docs/fields/overview.mdx b/docs/fields/overview.mdx index e7be07ef9b6..3a81ab5b9ad 100644 --- a/docs/fields/overview.mdx +++ b/docs/fields/overview.mdx @@ -96,7 +96,7 @@ Here are the available Presentational Fields: - [Collapsible](../fields/collapsible) - nests fields within a collapsible component - [Row](../fields/row) - aligns fields horizontally - [Tabs (Unnamed)](../fields/tabs) - nests fields within a tabbed layout. It is not presentational if the tab has a name. -- [Group (Unnamed)](../fields/group) - nests fields within a keyed object It is not presentational if the group has a name. +- [Group (Unnamed)](../fields/group) - nests fields within a keyed object. It is not presentational if the group has a name. - [UI](../fields/ui) - blank field for custom UI components ### Virtual Fields diff --git a/docs/hooks/fields.mdx b/docs/hooks/fields.mdx index 1c8e91ced0c..d37c2cf0d2a 100644 --- a/docs/hooks/fields.mdx +++ b/docs/hooks/fields.mdx @@ -24,7 +24,7 @@ export const FieldWithHooks: Field = { ## Config Options -All Field Hooks accept an array of synchronous or asynchronous functions. These functions can optionally modify the return value of the field before the operation continues. All Field Hooks are formatted to accept the same arguments, although some arguments may be `undefined` based the specific hook type. +All Field Hooks accept an array of synchronous or asynchronous functions. These functions can optionally modify the return value of the field before the operation continues. All Field Hooks are formatted to accept the same arguments, although some arguments may be `undefined` based on the specific hook type. **Important:** Due to GraphQL's typed nature, changing the type of data that diff --git a/docs/hooks/globals.mdx b/docs/hooks/globals.mdx index d6c650d3ab7..9b6c7d7e397 100644 --- a/docs/hooks/globals.mdx +++ b/docs/hooks/globals.mdx @@ -195,7 +195,7 @@ const afterReadHook: GlobalAfterReadHook = async ({ }) => {...} ``` -The following arguments are provided to the `beforeRead` hook: +The following arguments are provided to the `afterRead` hook: | Option | Description | | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | diff --git a/docs/hooks/overview.mdx b/docs/hooks/overview.mdx index 1d1afb49232..761b0b9ebca 100644 --- a/docs/hooks/overview.mdx +++ b/docs/hooks/overview.mdx @@ -161,7 +161,7 @@ Instead, you might want to use a `beforeChange` or `afterChange` hook, which onl ### Using Hook Context -Use [Hook Context](./context) avoid prevent infinite loops or avoid repeating expensive operations across multiple hooks in the same request. +Use [Hook Context](./context) to avoid infinite loops or to prevent repeating expensive operations across multiple hooks in the same request. ```ts { @@ -181,7 +181,7 @@ To learn more, see the [Hook Context documentation](./context). ### Offloading to the jobs queue -If your hooks perform any long-running tasks that don't direct affect request lifecycle, consider offloading them to the [jobs queue](../jobs-queue/overview). This will free up the request to continue processing without waiting for the task to complete. +If your hooks perform any long-running tasks that don't directly affect the request lifecycle, consider offloading them to the [jobs queue](../jobs-queue/overview). This will free up the request to continue processing without waiting for the task to complete. ```ts { diff --git a/docs/integrations/vercel-content-link.mdx b/docs/integrations/vercel-content-link.mdx index 29356b4ec7d..52879954a72 100644 --- a/docs/integrations/vercel-content-link.mdx +++ b/docs/integrations/vercel-content-link.mdx @@ -2,7 +2,7 @@ title: Vercel Content Link label: Vercel Content Link order: 10 -desc: Payload + Vercel Content Link allows yours editors to navigate directly from the content rendered on your front-end to the fields in Payload that control it. +desc: Payload + Vercel Content Link allows your editors to navigate directly from the content rendered on your front-end to the fields in Payload that control it. keywords: vercel, vercel content link, content link, visual editing, content source maps, Content Management System, cms, headless, javascript, node, react, nextjs --- diff --git a/docs/jobs-queue/tasks.mdx b/docs/jobs-queue/tasks.mdx index 32eb23e9620..0f1e69fbf0a 100644 --- a/docs/jobs-queue/tasks.mdx +++ b/docs/jobs-queue/tasks.mdx @@ -13,7 +13,7 @@ keywords: jobs queue, application framework, typescript, node, react, nextjs You can register Tasks on the Payload config, and then create [Jobs](/docs/jobs-queue/jobs) or [Workflows](/docs/jobs-queue/workflows) that use them. Think of Tasks like tidy, isolated "functions that do one specific thing". -Payload Tasks can be configured to automatically retried if they fail, which makes them valuable for "durable" workflows like AI applications where LLMs can return non-deterministic results, and might need to be retried. +Payload Tasks can be configured to be automatically retried if they fail, which makes them valuable for "durable" workflows like AI applications where LLMs can return non-deterministic results, and might need to be retried. Tasks can either be defined within the `jobs.tasks` array in your Payload config, or they can be defined inline within a workflow. @@ -157,7 +157,7 @@ You can configure this behavior through the `retries.shouldRestore` property. Th If `shouldRestore` is set to true, the task will only be re-run if it previously failed. This is the default behavior. -If `shouldRestore` this is set to false, the task will be re-run even if it previously succeeded, ignoring the maximum number of retries. +If `shouldRestore` is set to false, the task will be re-run even if it previously succeeded, ignoring the maximum number of retries. If `shouldRestore` is a function, the return value of the function will determine whether the task should be re-run. This can be used for more complex restore logic, e.g you may want to re-run a task up to X amount of times and then restore it for consecutive runs, or only re-run a task if the input has changed. diff --git a/docs/migration-guide/overview.mdx b/docs/migration-guide/overview.mdx index b17b01cd8b6..a7d31bb20ea 100644 --- a/docs/migration-guide/overview.mdx +++ b/docs/migration-guide/overview.mdx @@ -416,7 +416,7 @@ For more details, see the [Documentation](https://payloadcms.com/docs/getting-st 1. The `./src/public` directory is now located directly at root level `./public` [see Next.js docs for details](https://nextjs.org/docs/pages/building-your-application/optimizing/static-assets) -1. Payload now automatically removes `localized: true` property from sub-fields if a parent is localized, as it's redunant and unnecessary. If you have some existing data in this structure and you want to disable that behavior, you need to enable `allowLocalizedWithinLocalized` flag in your payload.config [read more in documentation](https://payloadcms.com/docs/configuration/overview#compatibility-flags), or create a migration script that aligns your data. +1. Payload now automatically removes `localized: true` property from sub-fields if a parent is localized, as it's redundant and unnecessary. If you have some existing data in this structure and you want to disable that behavior, you need to enable `allowLocalizedWithinLocalized` flag in your payload.config [read more in documentation](https://payloadcms.com/docs/configuration/overview#compatibility-flags), or create a migration script that aligns your data. Mongodb example for a link in a page layout. ```diff diff --git a/docs/plugins/build-your-own.mdx b/docs/plugins/build-your-own.mdx index 31ee685a1ac..c02f56a01e0 100644 --- a/docs/plugins/build-your-own.mdx +++ b/docs/plugins/build-your-own.mdx @@ -71,7 +71,7 @@ In the root folder, you will see various files related to the configuration of t ### The dev folder -The purpose of the **dev** folder is to provide a sanitized local Payload project. so you can run and test your plugin while you are actively developing it. +The purpose of the **dev** folder is to provide a sanitized local Payload project so you can run and test your plugin while you are actively developing it. Do **not** store any of the plugin functionality in this folder - it is purely an environment to _assist_ you with developing the plugin. diff --git a/docs/plugins/nested-docs.mdx b/docs/plugins/nested-docs.mdx index f9ffef26d75..f33c1fb624d 100644 --- a/docs/plugins/nested-docs.mdx +++ b/docs/plugins/nested-docs.mdx @@ -115,7 +115,7 @@ An array of collections slugs to enable nested docs. #### `generateLabel` Each `breadcrumb` has a required `label` field. By default, its value will be set to the collection's `admin.useAsTitle` -or fallback the the `ID` of the document. +or fallback to the `ID` of the document. You can also pass a function to dynamically set the `label` of your breadcrumb. diff --git a/docs/plugins/overview.mdx b/docs/plugins/overview.mdx index 9038f30391a..f7e0eec8dad 100644 --- a/docs/plugins/overview.mdx +++ b/docs/plugins/overview.mdx @@ -8,7 +8,7 @@ keywords: plugins, config, configuration, extensions, custom, documentation, Con Payload Plugins take full advantage of the modularity of the [Payload Config](../configuration/overview), allowing developers to easily inject custom—sometimes complex—functionality into Payload apps from a very small touch-point. This is especially useful for sharing your work across multiple projects or with the greater Payload community. -There are many [Official Plugins](#official-plugins) available that solve for some of the most common uses cases, such as the [Form Builder Plugin](./form-builder) or [SEO Plugin](./seo). There are also [Community Plugins](#community-plugins) available, maintained entirely by contributing members. To extend Payload's functionality in some other way, you can easily [build your own plugin](./build-your-own). +There are many [Official Plugins](#official-plugins) available that solve for some of the most common use cases, such as the [Form Builder Plugin](./form-builder) or [SEO Plugin](./seo). There are also [Community Plugins](#community-plugins) available, maintained entirely by contributing members. To extend Payload's functionality in some other way, you can easily [build your own plugin](./build-your-own). To configure Plugins, use the `plugins` property in your [Payload Config](../configuration/overview): @@ -29,7 +29,7 @@ Writing Plugins is no more complex than writing regular JavaScript. If you know Because we rely on a simple config-based structure, Payload Plugins simply - take in an existing config and returns a _modified_ config with new fields, + take in an existing config and return a _modified_ config with new fields, hooks, collections, admin views, or anything else you can think of. @@ -154,5 +154,5 @@ export const addLastModified: Plugin = (incomingConfig: Config): Config => { **Reminder:** See [how to build your own plugin](./build-your-own) for a more - in-depth explication on how create your own Payload Plugin. + in-depth explication on how to create your own Payload Plugin. diff --git a/docs/plugins/search.mdx b/docs/plugins/search.mdx index 8eee4073aaf..faf873c4336 100644 --- a/docs/plugins/search.mdx +++ b/docs/plugins/search.mdx @@ -10,9 +10,9 @@ keywords: plugins, search, search plugin, search engine, search index, search re This plugin generates records of your documents that are extremely fast to search on. It does so by creating a new `search` collection that is indexed in the database then saving a static copy of each of your documents using only search-critical data. Search records are automatically created, synced, and deleted behind-the-scenes as you manage your application's documents. -For example, if you have a posts collection that is extremely large and complex, this would allow you to sync just the title, excerpt, and slug of each post so you can query on _that_ instead of the original post directly. Search records are static, so querying them also has the significant advantage of bypassing any hooks that may present be on the original documents. You define exactly what data is synced, and you can even modify or fallback this data before it is saved on a per-document basis. +For example, if you have a posts collection that is extremely large and complex, this would allow you to sync just the title, excerpt, and slug of each post so you can query on _that_ instead of the original post directly. Search records are static, so querying them also has the significant advantage of bypassing any hooks that may be present on the original documents. You define exactly what data is synced, and you can even modify or fallback this data before it is saved on a per-document basis. -To query search results, use all the existing Payload APIs that you are already familiar with. You can also prioritize search results by setting a custom priority for each collection. For example, you may want to list blog posts before pages. Or you may want one specific post to always take appear first. Search records are given a `priority` field that can be used as the `?sort=` parameter in your queries. +To query search results, use all the existing Payload APIs that you are already familiar with. You can also prioritize search results by setting a custom priority for each collection. For example, you may want to list blog posts before pages. Or you may want one specific post to always appear first. Search records are given a `priority` field that can be used as the `?sort=` parameter in your queries. This plugin is a great way to implement a fast, immersive search experience such as a search bar in a front-end application. Many applications may not need the power and complexity of a third-party service like Algolia or ElasticSearch. This plugin provides a first-party alternative that is easy to set up and runs entirely on your own database. diff --git a/docs/plugins/seo.mdx b/docs/plugins/seo.mdx index b22e01c8296..b1b95b6f3fe 100644 --- a/docs/plugins/seo.mdx +++ b/docs/plugins/seo.mdx @@ -28,7 +28,7 @@ To help you visualize what your page might look like in a search engine, a previ - Adds a `meta` field group to every SEO-enabled collection or global - Allows you to define custom functions to auto-generate metadata -- Displays hints and indicators to help content editor write effective meta +- Displays hints and indicators to help content editors write effective meta - Renders a snippet of what a search engine might display - Extendable so you can define custom fields like `og:title` or `json-ld` - Soon will support dynamic variable injection diff --git a/docs/production/preventing-abuse.mdx b/docs/production/preventing-abuse.mdx index 32282d03c1a..4f090b73d6c 100644 --- a/docs/production/preventing-abuse.mdx +++ b/docs/production/preventing-abuse.mdx @@ -16,7 +16,7 @@ Set the max number of failed login attempts before a user account is locked out ## Max Depth -Querying a collection and automatically including related documents via `depth` incurs a performance cost. Also, it's possible that your configs may have circular relationships, meaning scenarios where an infinite amount of relationships might populate back and forth until your server times out and crashes. You can prevent any potential of depth-related issues by setting a `maxDepth` property on your Payload Config.. The maximum allowed depth should be as small as possible without interrupting dev experience, and it defaults to `10`. +Querying a collection and automatically including related documents via `depth` incurs a performance cost. Also, it's possible that your configs may have circular relationships, meaning scenarios where an infinite amount of relationships might populate back and forth until your server times out and crashes. You can prevent any potential of depth-related issues by setting a `maxDepth` property on your Payload Config. The maximum allowed depth should be as small as possible without interrupting dev experience, and it defaults to `10`. ## Cross-Site Request Forgery (CSRF) @@ -32,7 +32,7 @@ Because GraphQL gives the power of query writing outside a server's control, som Any GraphQL request that is calculated to be too expensive is rejected. On the Payload Config, in `graphQL` you can set the `maxComplexity` value as an integer. For reference, the default complexity value for each added field is 1, and all `relationship` and `upload` fields are assigned a value of 10. -If you do not need GraphQL it is advised that you disable it altogether with the Payload Config by setting `graphQL.disable: true`. Should you wish to enable GraphQL again, you can remove this property or set it `false`, any time. By turning it off, Payload will bypass creating schemas from your collections and will not register the route. +If you do not need GraphQL it is advised that you disable it altogether with the Payload Config by setting `graphQL.disable: true`. Should you wish to enable GraphQL again, you can remove this property or set it to `false` any time. By turning it off, Payload will bypass creating schemas from your collections and will not register the route. ## Malicious File Uploads diff --git a/docs/queries/overview.mdx b/docs/queries/overview.mdx index cd39f6d1744..53296406e59 100644 --- a/docs/queries/overview.mdx +++ b/docs/queries/overview.mdx @@ -103,7 +103,7 @@ Written in plain English, if the above query were passed to a `find` operation, ### Nested properties -When working with nested properties, which can happen when using relational fields, it is possible to use the dot notation to access the nested property. For example, when working with a `Song` collection that has a `artists` field which is related to an `Artists` collection using the `name: 'artists'`. You can access a property within the collection `Artists` like so: +When working with nested properties, which can happen when using relational fields, it is possible to use the dot notation to access the nested property. For example, when working with a `Song` collection that has an `artists` field which is related to an `Artists` collection using the `name: 'artists'`. You can access a property within the collection `Artists` like so: ```js import type { Where } from 'payload' diff --git a/docs/rest-api/overview.mdx b/docs/rest-api/overview.mdx index 53ae5b10c1a..97f188a7cee 100644 --- a/docs/rest-api/overview.mdx +++ b/docs/rest-api/overview.mdx @@ -21,8 +21,8 @@ To enhance DX, you can use [Payload SDK](#payload-rest-api-sdk) to query your RE - [depth](../queries/depth) - automatically populates relationships and uploads - [locale](/docs/configuration/localization#retrieving-localized-docs) - retrieves document(s) in a specific locale - [fallback-locale](/docs/configuration/localization#retrieving-localized-docs) - specifies a fallback locale if no locale value exists -- [select](../queries/select) - specifies which fields to include to the result -- [populate](../queries/select#populate) - specifies which fields to include to the result from populated documents +- [select](../queries/select) - specifies which fields to include in the result +- [populate](../queries/select#populate) - specifies which fields to include in the result from populated documents - [limit](../queries/pagination#pagination-controls) - limits the number of documents returned - [page](../queries/pagination#pagination-controls) - specifies which page to get documents from when used with a limit - [sort](../queries/sort#rest-api) - specifies the field(s) to use to sort the returned documents by diff --git a/docs/rich-text/custom-features.mdx b/docs/rich-text/custom-features.mdx index 217819ec030..f25fd327e78 100644 --- a/docs/rich-text/custom-features.mdx +++ b/docs/rich-text/custom-features.mdx @@ -849,7 +849,7 @@ export function mwnSlashMenuGroupWithItems( } ``` -By creating a helper function like this, you can easily re-use it and add items to it. All Slash Menu groups with the same keys will have their items merged together. +By creating a helper function like this, you can easily reuse it and add items to it. All Slash Menu groups with the same keys will have their items merged together. | Option | Description | | ----------- | ------------------------------------------------------------------------------------------------------------------------------------- | diff --git a/docs/rich-text/overview.mdx b/docs/rich-text/overview.mdx index 680f1f245a2..0591213f145 100644 --- a/docs/rich-text/overview.mdx +++ b/docs/rich-text/overview.mdx @@ -121,7 +121,7 @@ import { CallToAction } from '../blocks/CallToAction' }, }, }), - // This is incredibly powerful. You can re-use your Payload blocks + // This is incredibly powerful. You can reuse your Payload blocks // directly in the Lexical editor as follows: BlocksFeature({ blocks: [Banner, CallToAction], diff --git a/docs/troubleshooting/troubleshooting.mdx b/docs/troubleshooting/troubleshooting.mdx index 7cf91980ee6..b4527b9f9fc 100644 --- a/docs/troubleshooting/troubleshooting.mdx +++ b/docs/troubleshooting/troubleshooting.mdx @@ -53,7 +53,7 @@ Perform the same two checks for react and react-dom; a second copy of React can Any other deep import such as `@payloadcms/ui/elements/Button` should **only** be used in your own frontend, outside of the Payload Admin Panel. Those deep entries are published un-bundled to help you tree-shake and ship a smaller client bundle if you only need a few components from `@payloadcms/ui`. -### Fixing depedendency issues +### Fixing dependency issues These steps assume `pnpm`, which the Payload team recommends and uses internally. The principles apply to other package managers like npm and yarn as well. Do note that yarn 1.x is not supported by Payload. diff --git a/docs/upload/overview.mdx b/docs/upload/overview.mdx index e74c757c861..665477e7541 100644 --- a/docs/upload/overview.mdx +++ b/docs/upload/overview.mdx @@ -95,7 +95,7 @@ _An asterisk denotes that an option is required._ | **`adminThumbnail`** | Set the way that the [Admin Panel](../admin/overview) will display thumbnails for this Collection. [More](#admin-thumbnails) | | **`bulkUpload`** | Allow users to upload in bulk from the list view, default is true | | **`cacheTags`** | Set to `false` to disable the cache tag set in the UI for the admin thumbnail component. Useful for when CDNs don't allow certain cache queries. | -| **`constructorOptions`** | An object passed to the the Sharp image library that accepts any Constructor options and applies them to the upload file. [More](https://sharp.pixelplumbing.com/api-constructor/) | +| **`constructorOptions`** | An object passed to the Sharp image library that accepts any Constructor options and applies them to the upload file. [More](https://sharp.pixelplumbing.com/api-constructor/) | | **`crop`** | Set to `false` to disable the cropping tool in the [Admin Panel](../admin/overview). Crop is enabled by default. [More](#crop-and-focal-point-selector) | | **`disableLocalStorage`** | Completely disable uploading files to disk locally. [More](#disabling-local-upload-storage) | | **`displayPreview`** | Enable displaying preview of the uploaded file in Upload fields related to this Collection. Can be locally overridden by `displayPreview` option in Upload field. [More](/docs/fields/upload#config-options). | @@ -108,11 +108,11 @@ _An asterisk denotes that an option is required._ | **`imageSizes`** | If specified, image uploads will be automatically resized in accordance to these image sizes. [More](#image-sizes) | | **`mimeTypes`** | Restrict mimeTypes in the file picker. Array of valid mimetypes or mimetype wildcards [More](#mimetypes) | | **`pasteURL`** | Controls whether files can be uploaded from remote URLs by pasting them into the Upload field. **Enabled by default.** Accepts `false` to disable or an object with an `allowList` of valid remote URLs. [More](#uploading-files-from-remote-urls) | -| **`resizeOptions`** | An object passed to the the Sharp image library to resize the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize) | +| **`resizeOptions`** | An object passed to the Sharp image library to resize the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize) | | **`skipSafeFetch`** | Set to an `allowList` to skip the safe fetch check when fetching external files. Set to `true` to skip the safe fetch for all documents in this collection. Defaults to `false`. | | **`allowRestrictedFileTypes`** | Set to `true` to allow restricted file types. If your Collection has defined [mimeTypes](#mimetypes), restricted file verification will be skipped. Defaults to `false`. [More](#restricted-file-types) | | **`staticDir`** | The folder directory to use to store media in. Can be either an absolute path or relative to the directory that contains your config. Defaults to your collection slug | -| **`trimOptions`** | An object passed to the the Sharp image library to trim the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize#trim) | +| **`trimOptions`** | An object passed to the Sharp image library to trim the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize#trim) | | **`withMetadata`** | If specified, appends metadata to the output image file. Accepts a boolean or a function that receives `metadata` and `req`, returning a boolean. | | **`hideFileInputOnCreate`** | Set to `true` to prevent the admin UI from showing file inputs during document creation, useful for programmatic file generation. | | **`hideRemoveFile`** | Set to `true` to prevent the admin UI having a way to remove an existing file while editing. | @@ -219,7 +219,7 @@ This is useful for hiding large or rarely used image sizes from the list view UI #### Accessing the resized images in hooks -All auto-resized images are exposed to be re-used in hooks and similar via an object that is bound to `req.payloadUploadSizes`. +All auto-resized images are exposed to be reused in hooks and similar via an object that is bound to `req.payloadUploadSizes`. The object will have keys for each size generated, and each key will be set equal to a buffer containing the file data. diff --git a/docs/upload/storage-adapters.mdx b/docs/upload/storage-adapters.mdx index 362a692f091..b7c93df881d 100644 --- a/docs/upload/storage-adapters.mdx +++ b/docs/upload/storage-adapters.mdx @@ -126,7 +126,7 @@ export default buildConfig({ ### Configuration Options#s3-configuration -See the the [AWS SDK Package](https://github.com/aws/aws-sdk-js-v3) and [`S3ClientConfig`](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/s3) object for guidance on AWS S3 configuration. +See the [AWS SDK Package](https://github.com/aws/aws-sdk-js-v3) and [`S3ClientConfig`](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/s3) object for guidance on AWS S3 configuration. ## Azure Blob Storage @@ -299,7 +299,7 @@ pnpm add @payloadcms/storage-r2 - Configure the `collections` object to specify which collections should use r2. The slug _must_ match one of your existing collection slugs and be an `upload` type. - Pass in the R2 bucket binding to the `bucket` option, this should be done in the environment where Payload is running (e.g. Cloudflare Worker). -- You can conditionally datamine whether or not to enable the plugin with the `enabled` option. +- You can conditionally determine whether or not to enable the plugin with the `enabled` option. ```ts export default buildConfig({ diff --git a/package.json b/package.json index b06541b4765..82761f2e9f1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "payload-monorepo", - "version": "3.61.0", + "version": "3.61.1", "private": true, "type": "module", "workspaces": [ diff --git a/packages/admin-bar/package.json b/packages/admin-bar/package.json index b84c4c8c773..14f95c2adaf 100644 --- a/packages/admin-bar/package.json +++ b/packages/admin-bar/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/admin-bar", - "version": "3.61.0", + "version": "3.61.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 56abccb7143..8c8f3e2cff6 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.61.0", + "version": "3.61.1", "homepage": "https://payloadcms.com", "repository": { "type": "git", diff --git a/packages/db-d1-sqlite/package.json b/packages/db-d1-sqlite/package.json index aafdcb401ea..d4609f8a070 100644 --- a/packages/db-d1-sqlite/package.json +++ b/packages/db-d1-sqlite/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/db-d1-sqlite", - "version": "3.61.0", + "version": "3.61.1", "description": "The officially supported D1 SQLite database adapter for Payload", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/db-mongodb/package.json b/packages/db-mongodb/package.json index d6bcc8b7742..eab6210d42c 100644 --- a/packages/db-mongodb/package.json +++ b/packages/db-mongodb/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/db-mongodb", - "version": "3.61.0", + "version": "3.61.1", "description": "The officially supported MongoDB database adapter for Payload", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/db-mongodb/src/queries/buildSearchParams.ts b/packages/db-mongodb/src/queries/buildSearchParams.ts index a04587ecadc..b7f669051cd 100644 --- a/packages/db-mongodb/src/queries/buildSearchParams.ts +++ b/packages/db-mongodb/src/queries/buildSearchParams.ts @@ -114,7 +114,7 @@ export async function buildSearchParam({ const { operator: formattedOperator, rawQuery, val: formattedValue } = sanitizedQueryValue - if (rawQuery) { + if (rawQuery && paths.length === 1) { return { value: rawQuery } } diff --git a/packages/db-postgres/package.json b/packages/db-postgres/package.json index 03b218186f7..812a1eab324 100644 --- a/packages/db-postgres/package.json +++ b/packages/db-postgres/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/db-postgres", - "version": "3.61.0", + "version": "3.61.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 6569a6f0f0b..731eafe45a7 100644 --- a/packages/db-sqlite/package.json +++ b/packages/db-sqlite/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/db-sqlite", - "version": "3.61.0", + "version": "3.61.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 5636e71bccc..e816605e2b4 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.61.0", + "version": "3.61.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 2090beea92d..dc47ceee6b2 100644 --- a/packages/drizzle/package.json +++ b/packages/drizzle/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/drizzle", - "version": "3.61.0", + "version": "3.61.1", "description": "A library of shared functions used by different payload database adapters", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/drizzle/src/queries/getTableColumnFromPath.ts b/packages/drizzle/src/queries/getTableColumnFromPath.ts index 1a51104dffc..92056e23487 100644 --- a/packages/drizzle/src/queries/getTableColumnFromPath.ts +++ b/packages/drizzle/src/queries/getTableColumnFromPath.ts @@ -1,4 +1,4 @@ -import type { SQL } from 'drizzle-orm' +import type { SQL, Table } from 'drizzle-orm' import type { SQLiteTableWithColumns } from 'drizzle-orm/sqlite-core' import type { FlattenedBlock, @@ -8,7 +8,7 @@ import type { TextField, } from 'payload' -import { and, eq, getTableName, like, or, sql, Table } from 'drizzle-orm' +import { and, eq, getTableName, like, or, sql } from 'drizzle-orm' import { type PgTableWithColumns } from 'drizzle-orm/pg-core' import { APIError, getFieldByPath } from 'payload' import { fieldShouldBeLocalized, tabHasName } from 'payload/shared' @@ -368,10 +368,16 @@ export const getTableColumnFromPath = ({ if (field.hasMany) { const relationTableName = `${adapter.tableNameMap.get(toSnakeCase(field.collection))}${adapter.relationshipsSuffix}` - const { newAliasTable: aliasRelationshipTable } = getTableAlias({ - adapter, - tableName: relationTableName, - }) + + const existingTable = joins.find( + (e) => e.queryPath === `${constraintPath}${field.name}._rels`, + ) + + const aliasRelationshipTable = (existingTable?.table ?? + getTableAlias({ + adapter, + tableName: relationTableName, + }).newAliasTable) as PgTableWithColumns const relationshipField = getFieldByPath({ fields: adapter.payload.collections[field.collection].config.flattenedFields, @@ -381,20 +387,22 @@ export const getTableColumnFromPath = ({ throw new APIError('Relationship was not found') } - addJoinTable({ - condition: and( - eq( - adapter.tables[rootTableName].id, - aliasRelationshipTable[ - `${(relationshipField.field as RelationshipField).relationTo as string}ID` - ], + if (!existingTable) { + addJoinTable({ + condition: and( + eq( + adapter.tables[rootTableName].id, + aliasRelationshipTable[ + `${(relationshipField.field as RelationshipField).relationTo as string}ID` + ], + ), + like(aliasRelationshipTable.path, field.on), ), - like(aliasRelationshipTable.path, field.on), - ), - joins, - queryPath: field.on, - table: aliasRelationshipTable, - }) + joins, + queryPath: `${constraintPath}${field.name}._rels`, + table: aliasRelationshipTable, + }) + } if (newCollectionPath === 'id') { return { @@ -416,15 +424,23 @@ export const getTableColumnFromPath = ({ // parent to relationship join table const relationshipFields = relationshipConfig.flattenedFields - const { newAliasTable: relationshipTable } = getTableAlias({ - adapter, - tableName: relationshipTableName, - }) + const existingMainTable = joins.find( + (e) => e.queryPath === `${constraintPath}${field.name}`, + ) - joins.push({ - condition: eq(aliasRelationshipTable.parent, relationshipTable.id), - table: relationshipTable, - }) + const relationshipTable = (existingMainTable?.table ?? + getTableAlias({ + adapter, + tableName: relationshipTableName, + }).newAliasTable) as PgTableWithColumns + + if (!existingMainTable) { + joins.push({ + condition: eq(aliasRelationshipTable.parent, relationshipTable.id), + queryPath: `${constraintPath}${field.name}`, + table: relationshipTable, + }) + } return getTableColumnFromPath({ adapter, @@ -448,15 +464,23 @@ export const getTableColumnFromPath = ({ const newTableName = adapter.tableNameMap.get( toSnakeCase(adapter.payload.collections[field.collection].config.slug), ) - const { newAliasTable } = getTableAlias({ adapter, tableName: newTableName }) - - joins.push({ - condition: eq( - newAliasTable[field.on.replaceAll('.', '_')], - aliasTable ? aliasTable.id : adapter.tables[tableName].id, - ), - table: newAliasTable, - }) + + const existingTable = joins.find( + (e) => e.queryPath === `${constraintPath}${field.name}`, + )?.table + const newAliasTable = + existingTable || getTableAlias({ adapter, tableName: newTableName }).newAliasTable + + if (!existingTable) { + joins.push({ + condition: eq( + newAliasTable[field.on.replaceAll('.', '_')], + aliasTable ? aliasTable.id : adapter.tables[tableName].id, + ), + queryPath: `${constraintPath}${field.name}`, + table: newAliasTable, + }) + } if (newCollectionPath === 'id') { return { diff --git a/packages/drizzle/src/schema/build.ts b/packages/drizzle/src/schema/build.ts index 4a94013dc2d..64dcbb2b1f7 100644 --- a/packages/drizzle/src/schema/build.ts +++ b/packages/drizzle/src/schema/build.ts @@ -600,7 +600,7 @@ export const buildTable = ({ const relationshipForeignKeys: Record = { parentFk: { - name: buildIndexName({ name: `${relationshipsTableName}_parent`, adapter }), + name: buildForeignKeyName({ name: `${relationshipsTableName}_parent`, adapter }), columns: ['parent'], foreignColumns: [ { diff --git a/packages/email-nodemailer/package.json b/packages/email-nodemailer/package.json index 8a461e0d824..fb8af773c6d 100644 --- a/packages/email-nodemailer/package.json +++ b/packages/email-nodemailer/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/email-nodemailer", - "version": "3.61.0", + "version": "3.61.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 77e00e0bfa9..9647cfc09a8 100644 --- a/packages/email-resend/package.json +++ b/packages/email-resend/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/email-resend", - "version": "3.61.0", + "version": "3.61.1", "description": "Payload Resend Email Adapter", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/graphql/package.json b/packages/graphql/package.json index adc88553638..8ba00510fe1 100644 --- a/packages/graphql/package.json +++ b/packages/graphql/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/graphql", - "version": "3.61.0", + "version": "3.61.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 cd52c0f8080..6d1f3bce4a7 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.61.0", + "version": "3.61.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 be1cce60a26..1e067464ade 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.61.0", + "version": "3.61.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 e9ad4a02a6f..6d70eb0baed 100644 --- a/packages/live-preview/package.json +++ b/packages/live-preview/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/live-preview", - "version": "3.61.0", + "version": "3.61.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 9741a94c6d1..72217e4f441 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/next", - "version": "3.61.0", + "version": "3.61.1", "homepage": "https://payloadcms.com", "repository": { "type": "git", diff --git a/packages/next/src/layouts/Root/index.tsx b/packages/next/src/layouts/Root/index.tsx index 7a0b58fedc5..397038ddee7 100644 --- a/packages/next/src/layouts/Root/index.tsx +++ b/packages/next/src/layouts/Root/index.tsx @@ -5,6 +5,7 @@ import { rtlLanguages } from '@payloadcms/translations' import { ProgressBar, RootProvider } from '@payloadcms/ui' import { getClientConfig } from '@payloadcms/ui/utilities/getClientConfig' import { cookies as nextCookies } from 'next/headers.js' +import { applyLocaleFiltering } from 'payload/shared' import React from 'react' import { getNavPrefs } from '../../elements/Nav/getNavPrefs.js' @@ -87,20 +88,7 @@ export const RootLayout = async ({ importMap, user: req.user, }) - - if ( - clientConfig.localization && - config.localization && - typeof config.localization.filterAvailableLocales === 'function' - ) { - clientConfig.localization.locales = ( - await config.localization.filterAvailableLocales({ - locales: config.localization.locales, - req, - }) - ).map(({ toString, ...rest }) => rest) - clientConfig.localization.localeCodes = config.localization.locales.map(({ code }) => code) - } + await applyLocaleFiltering({ clientConfig, config, req }) return ( `${collectionSlug}-${id}`, ), items, + noResults: !Array.isArray(items) || items.length === 0, parentFieldName: '_parentDoc', search, TreeViewComponent, diff --git a/packages/next/src/views/Document/handleServerFunction.tsx b/packages/next/src/views/Document/handleServerFunction.tsx index 2da9aa5259d..85aec01539e 100644 --- a/packages/next/src/views/Document/handleServerFunction.tsx +++ b/packages/next/src/views/Document/handleServerFunction.tsx @@ -4,6 +4,7 @@ import type { DocumentPreferences, VisibleEntities } from 'payload' import { getClientConfig } from '@payloadcms/ui/utilities/getClientConfig' import { headers as getHeaders } from 'next/headers.js' import { canAccessAdmin, getAccessResults, isEntityHidden, parseCookies } from 'payload' +import { applyLocaleFiltering } from 'payload/shared' import { renderDocument } from './index.js' @@ -43,6 +44,7 @@ export const renderDocumentHandler: RenderDocumentServerFunction = async (args) importMap: req.payload.importMap, user, }) + await applyLocaleFiltering({ clientConfig, config, req }) let preferences: DocumentPreferences diff --git a/packages/next/src/views/List/handleServerFunction.tsx b/packages/next/src/views/List/handleServerFunction.tsx index 27fb8e654c3..d30e332a5a7 100644 --- a/packages/next/src/views/List/handleServerFunction.tsx +++ b/packages/next/src/views/List/handleServerFunction.tsx @@ -3,6 +3,7 @@ import type { CollectionPreferences, ListQuery, ServerFunction, VisibleEntities import { getClientConfig } from '@payloadcms/ui/utilities/getClientConfig' import { headers as getHeaders } from 'next/headers.js' import { canAccessAdmin, getAccessResults, isEntityHidden, parseCookies } from 'payload' +import { applyLocaleFiltering } from 'payload/shared' import { renderListView } from './index.js' @@ -61,6 +62,7 @@ export const renderListHandler: ServerFunction< importMap: payload.importMap, user, }) + await applyLocaleFiltering({ clientConfig, config, req }) const preferencesKey = `collection-${collectionSlug}` diff --git a/packages/next/src/views/Root/index.tsx b/packages/next/src/views/Root/index.tsx index ccba83f5648..2d9ae8a33a6 100644 --- a/packages/next/src/views/Root/index.tsx +++ b/packages/next/src/views/Root/index.tsx @@ -14,7 +14,7 @@ import { PageConfigProvider } from '@payloadcms/ui' import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent' import { getClientConfig } from '@payloadcms/ui/utilities/getClientConfig' import { notFound, redirect } from 'next/navigation.js' -import { formatAdminURL } from 'payload/shared' +import { applyLocaleFiltering, formatAdminURL } from 'payload/shared' import * as qs from 'qs-esm' import React from 'react' @@ -253,6 +253,7 @@ export const RootPage = async ({ importMap, user: viewType === 'createFirstUser' ? true : req.user, }) + await applyLocaleFiltering({ clientConfig, config, req }) const visibleEntities = getVisibleEntities({ req }) diff --git a/packages/next/src/views/Version/index.tsx b/packages/next/src/views/Version/index.tsx index fdc0446b57e..693300e2698 100644 --- a/packages/next/src/views/Version/index.tsx +++ b/packages/next/src/views/Version/index.tsx @@ -213,7 +213,12 @@ export async function VersionView(props: DocumentViewServerProps) { const clientSchemaMap = getClientSchemaMap({ collectionSlug, - config: getClientConfig({ config: payload.config, i18n, importMap: payload.importMap, user }), + config: getClientConfig({ + config: payload.config, + i18n, + importMap: payload.importMap, + user, + }), globalSlug, i18n, payload, diff --git a/packages/payload-cloud/package.json b/packages/payload-cloud/package.json index a53b448114b..140895e3c6c 100644 --- a/packages/payload-cloud/package.json +++ b/packages/payload-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/payload-cloud", - "version": "3.61.0", + "version": "3.61.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 d65e876c298..16619a40b01 100644 --- a/packages/payload/package.json +++ b/packages/payload/package.json @@ -1,6 +1,6 @@ { "name": "payload", - "version": "3.61.0", + "version": "3.61.1", "description": "Node, React, Headless CMS and Application Framework built on Next.js", "keywords": [ "admin panel", diff --git a/packages/payload/src/config/client.ts b/packages/payload/src/config/client.ts index 961076a3161..05d4327e38d 100644 --- a/packages/payload/src/config/client.ts +++ b/packages/payload/src/config/client.ts @@ -4,6 +4,7 @@ import type { DeepPartial } from 'ts-essentials' import type { ImportMap } from '../bin/generateImportMap/index.js' import type { ClientBlock } from '../fields/config/types.js' import type { BlockSlug, TypedUser } from '../index.js' +import type { PayloadRequest } from '../types/index.js' import type { RootLivePreviewConfig, SanitizedConfig, diff --git a/packages/payload/src/exports/shared.ts b/packages/payload/src/exports/shared.ts index 7031d2c20f1..95f6fd10d25 100644 --- a/packages/payload/src/exports/shared.ts +++ b/packages/payload/src/exports/shared.ts @@ -39,8 +39,8 @@ export { } from '../fields/config/types.js' export { getFieldPaths } from '../fields/getFieldPaths.js' -export * from '../fields/validations.js' +export * from '../fields/validations.js' export type { FolderBreadcrumb, FolderDocumentItemKey, @@ -52,15 +52,17 @@ export type { } from '../folders/types.js' export { buildFolderWhereConstraints } from '../folders/utils/buildFolderWhereConstraints.js' + export { formatFolderOrDocumentItem } from '../folders/utils/formatFolderOrDocumentItem.js' export type { TreeViewItem, TreeViewItemKey } from '../treeView/types.js' export { validOperators, validOperatorSet } from '../types/constants.js' - export { formatFilesize } from '../uploads/formatFilesize.js' export { isImage } from '../uploads/isImage.js' + export { appendUploadSelectFields } from '../utilities/appendUploadSelectFields.js' +export { applyLocaleFiltering } from '../utilities/applyLocaleFiltering.js' export { combineWhereConstraints } from '../utilities/combineWhereConstraints.js' export { diff --git a/packages/payload/src/utilities/applyLocaleFiltering.ts b/packages/payload/src/utilities/applyLocaleFiltering.ts new file mode 100644 index 00000000000..74c59eb4627 --- /dev/null +++ b/packages/payload/src/utilities/applyLocaleFiltering.ts @@ -0,0 +1,31 @@ +import type { ClientConfig } from '../config/client.js' +import type { SanitizedConfig } from '../config/types.js' +import type { PayloadRequest } from '../types/index.js' + +export async function applyLocaleFiltering({ + clientConfig, + config, + req, +}: { + clientConfig: ClientConfig + config: SanitizedConfig + req: PayloadRequest +}): Promise { + if ( + !clientConfig.localization || + !config.localization || + typeof config.localization.filterAvailableLocales !== 'function' + ) { + return + } + + const filteredLocales = ( + await config.localization.filterAvailableLocales({ + locales: config.localization.locales, + req, + }) + ).map(({ toString, ...rest }) => rest) + + clientConfig.localization.localeCodes = filteredLocales.map(({ code }) => code) + clientConfig.localization.locales = filteredLocales +} diff --git a/packages/plugin-cloud-storage/package.json b/packages/plugin-cloud-storage/package.json index 59745c7052e..a4e6f9c34ce 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.61.0", + "version": "3.61.1", "description": "The official cloud storage plugin for Payload CMS", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/plugin-ecommerce/package.json b/packages/plugin-ecommerce/package.json index da9a80a99fa..a2806820940 100644 --- a/packages/plugin-ecommerce/package.json +++ b/packages/plugin-ecommerce/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-ecommerce", - "version": "3.61.0", + "version": "3.61.1", "description": "Ecommerce plugin for Payload", "keywords": [ "payload", diff --git a/packages/plugin-form-builder/package.json b/packages/plugin-form-builder/package.json index cabda3cfbfb..5f8fee6fc75 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.61.0", + "version": "3.61.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 1de3ada173d..5aeb999a224 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.61.0", + "version": "3.61.1", "description": "Import-Export plugin for Payload", "keywords": [ "payload", diff --git a/packages/plugin-mcp/package.json b/packages/plugin-mcp/package.json index 34791d700b2..49c8239971e 100644 --- a/packages/plugin-mcp/package.json +++ b/packages/plugin-mcp/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-mcp", - "version": "3.61.0", + "version": "3.61.1", "description": "MCP (Model Context Protocol) capabilities with Payload", "keywords": [ "plugin", diff --git a/packages/plugin-multi-tenant/package.json b/packages/plugin-multi-tenant/package.json index 825d75bac82..a9bc0d41f3e 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.61.0", + "version": "3.61.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 dbc7720a0d7..5fa06da0cba 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.61.0", + "version": "3.61.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 37248aefea9..dc348ea5762 100644 --- a/packages/plugin-redirects/package.json +++ b/packages/plugin-redirects/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-redirects", - "version": "3.61.0", + "version": "3.61.1", "description": "Redirects plugin for Payload", "keywords": [ "payload", diff --git a/packages/plugin-search/package.json b/packages/plugin-search/package.json index ae0097798c8..b4e6b949867 100644 --- a/packages/plugin-search/package.json +++ b/packages/plugin-search/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-search", - "version": "3.61.0", + "version": "3.61.1", "description": "Search plugin for Payload", "keywords": [ "payload", diff --git a/packages/plugin-sentry/package.json b/packages/plugin-sentry/package.json index 645df597491..106d03c25e5 100644 --- a/packages/plugin-sentry/package.json +++ b/packages/plugin-sentry/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-sentry", - "version": "3.61.0", + "version": "3.61.1", "description": "Sentry plugin for Payload", "keywords": [ "payload", diff --git a/packages/plugin-seo/package.json b/packages/plugin-seo/package.json index 6fb6380281e..a9484e9e459 100644 --- a/packages/plugin-seo/package.json +++ b/packages/plugin-seo/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-seo", - "version": "3.61.0", + "version": "3.61.1", "description": "SEO plugin for Payload", "keywords": [ "payload", diff --git a/packages/plugin-stripe/package.json b/packages/plugin-stripe/package.json index a98f4f2567a..c8e71961979 100644 --- a/packages/plugin-stripe/package.json +++ b/packages/plugin-stripe/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-stripe", - "version": "3.61.0", + "version": "3.61.1", "description": "Stripe plugin for Payload", "keywords": [ "payload", diff --git a/packages/richtext-lexical/package.json b/packages/richtext-lexical/package.json index 852cbb2b11d..3932f09a627 100644 --- a/packages/richtext-lexical/package.json +++ b/packages/richtext-lexical/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/richtext-lexical", - "version": "3.61.0", + "version": "3.61.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 cfc9f845e2a..612c8d4fe1f 100644 --- a/packages/richtext-slate/package.json +++ b/packages/richtext-slate/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/richtext-slate", - "version": "3.61.0", + "version": "3.61.1", "description": "The officially supported Slate richtext adapter for Payload", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 99949c8db70..dc94d86664a 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/sdk", - "version": "3.61.0", + "version": "3.61.1", "description": "The official Payload REST API SDK", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/storage-azure/package.json b/packages/storage-azure/package.json index 9fc7387d743..da98ff49164 100644 --- a/packages/storage-azure/package.json +++ b/packages/storage-azure/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/storage-azure", - "version": "3.61.0", + "version": "3.61.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 c60c9727548..31842e87b75 100644 --- a/packages/storage-gcs/package.json +++ b/packages/storage-gcs/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/storage-gcs", - "version": "3.61.0", + "version": "3.61.1", "description": "Payload storage adapter for Google Cloud Storage", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/storage-r2/package.json b/packages/storage-r2/package.json index c397441200f..f0bf8500252 100644 --- a/packages/storage-r2/package.json +++ b/packages/storage-r2/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/storage-r2", - "version": "3.61.0", + "version": "3.61.1", "description": "Payload storage adapter for Cloudflare R2", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/storage-s3/package.json b/packages/storage-s3/package.json index 3518839938a..416050a17a9 100644 --- a/packages/storage-s3/package.json +++ b/packages/storage-s3/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/storage-s3", - "version": "3.61.0", + "version": "3.61.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 db3826f5d95..03363f5b539 100644 --- a/packages/storage-uploadthing/package.json +++ b/packages/storage-uploadthing/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/storage-uploadthing", - "version": "3.61.0", + "version": "3.61.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 ba6fb4aff85..21046926582 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.61.0", + "version": "3.61.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 ef6c384e559..548e7648860 100644 --- a/packages/translations/package.json +++ b/packages/translations/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/translations", - "version": "3.61.0", + "version": "3.61.1", "homepage": "https://payloadcms.com", "repository": { "type": "git", diff --git a/packages/ui/package.json b/packages/ui/package.json index 978092ff5ed..326b809fc13 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/ui", - "version": "3.61.0", + "version": "3.61.1", "homepage": "https://payloadcms.com", "repository": { "type": "git", diff --git a/packages/ui/src/elements/DocumentDrawer/DrawerHeader/index.tsx b/packages/ui/src/elements/DocumentDrawer/DrawerHeader/index.tsx index b8c620c2530..d2bd9042a39 100644 --- a/packages/ui/src/elements/DocumentDrawer/DrawerHeader/index.tsx +++ b/packages/ui/src/elements/DocumentDrawer/DrawerHeader/index.tsx @@ -1,23 +1,38 @@ 'use client' +import { useCallback } from 'react' + import { Gutter } from '../../../elements/Gutter/index.js' import { useModal } from '../../../elements/Modal/index.js' import { RenderTitle } from '../../../elements/RenderTitle/index.js' +import { useFormModified } from '../../../forms/Form/index.js' import { XIcon } from '../../../icons/X/index.js' import { useDocumentInfo } from '../../../providers/DocumentInfo/index.js' import { useDocumentTitle } from '../../../providers/DocumentTitle/index.js' import { useTranslation } from '../../../providers/Translation/index.js' import { IDLabel } from '../../IDLabel/index.js' +import { LeaveWithoutSavingModal } from '../../LeaveWithoutSaving/index.js' import { documentDrawerBaseClass } from '../index.js' import './index.scss' +const leaveWithoutSavingModalSlug = 'leave-without-saving-doc-drawer' + export const DocumentDrawerHeader: React.FC<{ AfterHeader?: React.ReactNode drawerSlug: string showDocumentID?: boolean }> = ({ AfterHeader, drawerSlug, showDocumentID = true }) => { - const { closeModal } = useModal() + const { closeModal, openModal } = useModal() const { t } = useTranslation() + const isModified = useFormModified() + + const handleOnClose = useCallback(() => { + if (isModified) { + openModal(leaveWithoutSavingModalSlug) + } else { + closeModal(drawerSlug) + } + }, [isModified, openModal, closeModal, drawerSlug]) return ( @@ -28,7 +43,7 @@ export const DocumentDrawerHeader: React.FC<{