Compare commits

..

25 Commits

Author SHA1 Message Date
Elliot DeNolf
43fcccab93 chore(release): v3.0.0-beta.120 [skip ci] 2024-10-28 22:08:50 -04:00
Patrik
e74906f555 fix(next, ui): exclude expired locks for globals (#8914)
Continued PR off of https://github.com/payloadcms/payload/pull/8899
2024-10-28 21:49:50 -04:00
Patrik
1e002acce9 fix(next, ui): only show locked docs that are not expired (#8899)
`Issue`:

Previously, documents that were locked but expired would still show in
the list view / render the `DocumentLocked` modal upon other users
entering the document.

The expected outcome should be having expired locked documents seen as
unlocked to other users.

I.e:

- Removing the lock icon from expired locks in the list view.
- Prevent the `DocumentLocked` modal from appearing for other users -
requiring a take over.

`Fix`:

- Only query for locked documents that are not expired, aka their
`updatedAt` dates are greater than the the current time minus the lock
duration.
- Performs a `deleteMany` on expired documents when any user edits any
other document in the same collection.

Fixes #8778 

`TODO`: Add tests
2024-10-28 20:05:26 -04:00
Sasha
7a7a2f3918 fix(db-mongodb): query 'in' with a comma-delimited value (#8910)
### What?
Fixes the issue with `in` querying when the collection has a join field.

### Why?
When using `.aggregate`, MongoDB doesn't cast a comma delimited value
for the `$in` operator to an array automatically as it's not handled by
Mongoose.

### How?
Sanitizes the incoming value to an array if it should.

Fixes https://github.com/payloadcms/payload/issues/8901
2024-10-28 19:41:28 -04:00
Dan Ribbens
f0edbb79f9 feat: join field defaultLimit and defaultSort (#8908)
### What?

Allow specifying the defaultSort and defaultLimit to use for populating
a join field

### Why?

It is much easier to set defaults rather than be forced to always call
the join query using the query pattern ("?joins[categories][limit]=0").

### How?

See docs and type changes
2024-10-28 17:52:37 -04:00
Said Akhrarov
3605da1e3f docs: update metadata icons example to use url and fix images type in table (#8898)
<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

### Why?

### How?

Fixes #

-->
### What?
Updates the examples in the
[admin/metadata#root-metadata](https://payloadcms.com/docs/beta/admin/metadata#root-metadata)
and
[admin/metadata#icons](https://payloadcms.com/docs/beta/admin/metadata#icons)
sections from using `href` to `url` and fixes the way that the `images`
Type excludes the description due to `|` being parsed as column
separator

### Why?
As of right now, the examples are incorrect and the `images` type bleeds
into the description and omits it entirely

See image of table issue at `images`:

![image](https://github.com/user-attachments/assets/086dc44c-5c1b-4b5e-9658-521eb60fff0d)

### How?
Changes to `metadata.mdx`

Fixes #8887

Credit to @thgh for the `href` to `url` find
2024-10-28 17:02:06 -04:00
Brandon Kocur
7127c5507c docs: update database adpater installation commands to use beta version (#8895)
While following the "Adding to an existing app" instructions for the
**beta** docs, I noticed that the pnpm installation commands for the
database adapters were missing the `@beta` tag, which will result in
errors in the project.
2024-10-28 17:00:38 -04:00
James Mikrut
00ed66b4ee fix: autosave potentially losing latest: true w/ race condition (#8894)
Fixes a potential race condition where versions could lose `latest:
true` and potentially also introduce a conflict with the `parent` field.

We now explicitly define these as we update versions in the
`saveVersion` function.
2024-10-28 16:28:57 -04:00
James Mikrut
44e52b0305 fix: #8903, form-builder payment static value field (#8905)
Fixes #8903
2024-10-28 16:28:41 -04:00
Sasha
aea1b41f90 fix(cpa): write POSTGRES_URL to .env for vercel postgres (#8743)
Fixes https://github.com/payloadcms/payload/issues/8877
Current behaviour:
`pnpx create-payload-app@beta`, select "Vercel Postgres",

`.env` file:
```env
# Added by Payload
# should be POSTGRES_URL here!
DATABASE_URI=postgres://postgres:<password>@127.0.0.1:5432/f
PAYLOAD_SECRET=4415faad68a15727b4ebf582
```

`payload.config.ts`:
```ts
db: vercelPostgresAdapter({
  pool: {
    connectionString: process.env.POSTGRES_URL || '',
  },
}),
```
2024-10-28 17:34:58 +00:00
Alessio Gravili
a8569b9e78 fix(richtext-lexical): ensure editor cursor / selection state is preserved when working with drawers (#8872)
Previously, when opening e.g. a link drawer, clicking within the drawer,
and then closing it, the cursor / selection of the lexical editor will
reset to the beginning of the editor.

Now, we have dedicated logic to storing, preserving and restoring the
lexical selection when working with drawers.

This will work with all drawers. Links, uploads, relationships etc.


https://github.com/user-attachments/assets/ab3858b1-0f52-4ee5-813f-02b848355998
2024-10-27 22:32:31 +00:00
Elliot DeNolf
07a8a37fbd chore(templates): use payload cloud (#8871)
Update templates to use `@payloadcms/payload-cloud`. 

**NOTE:** This should not be merged until beta.119 is released.
2024-10-25 16:20:58 -04:00
Elliot DeNolf
6c2eecc47e chore(release): v3.0.0-beta.119 [skip ci] 2024-10-25 16:11:53 -04:00
Paul
4f88fb046f chore: update docs mention on payload cloud plugin (#8869) 2024-10-25 13:09:35 -06:00
Elliot DeNolf
1b1dc82cfb feat!: rename @payloadcms/plugin-cloud (#8828)
BREAKING CHANGE: Rename `@payloadcms/plugin-cloud` to
`@payloadcms/payload-cloud`. Anyone using the existing plugin will need
to switch to using the new package.

## Why?

Since v3 will be using _fixed versioning_, all versions of `^3` must be
available. Unfortunately, the `@payloadcms/plugin-cloud` version has
already breached that version number. Renaming will allow it to be on
the same version as other monorepo packages.

Additionally, the name `plugin-cloud` is quite ambiguous and sometimes
is confused with `plugin-cloud-storage`, so using `payload-cloud` feels
like a good move to make this more evident.
2024-10-24 21:19:15 -04:00
Paul
2df8f94a75 fix(plugin-search): issues with overriding the search collection slug consistently across hooks (#8847)
Fixes https://github.com/payloadcms/payload/issues/8842
2024-10-25 00:29:39 +00:00
Paul
e72e81c3da fix(ui): upload buttons being hidden at mobile screen widths (#8860) 2024-10-24 22:29:29 +00:00
Elliot DeNolf
b1b571d53e ci: do not run pr-title on synchronize event 2024-10-24 16:48:05 -04:00
Paul
085e127217 fix(ui): leave without saving when changing /account theme (#8724)
Fixes an annoying instance where on the /account page if you change your
theme then navigate away the Leaving without save popup is triggered
even though you don't need to submit a form or trigger a save in order
to change your admin theme.
2024-10-24 16:46:19 -04:00
Anders Semb Hermansen
4d44c378ed feat: sort by multiple fields (#8799)
This change adds support for sort with multiple fields in local API and
REST API. Related discussion #2089

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2024-10-24 15:46:30 -04:00
Sasha
6e919cc83a perf: index globals versions timestamps by default (#8821) 2024-10-24 14:58:59 -04:00
Elliot DeNolf
1a15425b59 fix(cpa): properly detect if pnpm exists on windows (#8855)
Use `where <command>` if using windows when detecting package manager
2024-10-24 14:53:11 -04:00
Sasha
9069bd3fac fix(db-postgres): sort by localized fields (#8839)
### What?
Fixes https://github.com/payloadcms/payload/issues/5152 issue related to
sorting by a localized field with SQLite / Postgres database adapters.

### Why?
It was an incorrect behaviour.


### How?
Modifies the `getTableColumnFromPath` file to have correct join
conditions. Previously if you had this structure in the _locales table
_locale title parent
en          A    1
es          B     1
we sorted by everything that's here, but we need to sort only by the
passed locale.

Additionally fixes a typescript error in `dev.ts` that I added here
https://github.com/payloadcms/payload/pull/8834

Also, removes the condition with `joins.length` in `countDistinct`. It
was there as for this issue
https://github.com/payloadcms/payload/issues/4889 because sorting by a
localized property caused duplication. This can simnifically improve
performance for `.find` with nested querying/sorting on large data sets,
because `count(*)` is faster than `count(DISTINCT id)`
2024-10-24 14:47:58 -04:00
Hristiyan Dodov
2e11068f49 feat(richtext-slate): add useElement hook to richtext-slate client exports (#8856)
I'm extending the Slate editor with a custom component and everything
works great, except I have to import `useElement()` like this:

```tsx
import { useElement } from 'node_modules/.pnpm/@payloadcms+richtext-slate@3.0.0-beta.113_monaco-editor@0.51.0_next@15.0.0-canary.191_@babel+_qmdxs6s5hpzjhuopohgawpvl6i/node_modules/@payloadcms/richtext-slate/dist/field/providers/ElementProvider.js'

export function Element() {
	const { attributes, children } = useElement()
	return (
		<p {...attributes} className="rich-text-preheading">
			{children}
		</p>
	)
}
```

That's because it's not in the `@payloadcms/richtext-slate/client`
module. This PR fixes this and would allow me to do:

```tsx
import { useElement } from '@payloadcms/richtext-slate/client'
```
2024-10-24 11:50:09 -06:00
Elliot DeNolf
749a7d9131 chore(cpa): remove beta from postgres selection 2024-10-24 13:07:38 -04:00
188 changed files with 2593 additions and 526 deletions

View File

@@ -5,7 +5,6 @@ on:
types:
- opened
- edited
- synchronize
permissions:
pull-requests: write

View File

@@ -36,7 +36,7 @@ To customize Root Metadata, use the `admin.meta` key in your Payload Config:
{
rel: 'icon',
type: 'image/png',
href: '/favicon.png',
url: '/favicon.png',
},
],
},
@@ -80,12 +80,12 @@ To customize icons, use the `icons` key within the `admin.meta` object in your P
{
rel: 'icon',
type: 'image/png',
href: '/favicon.png',
url: '/favicon.png',
},
{
rel: 'apple-touch-icon',
type: 'image/png',
href: '/apple-touch-icon.png',
url: '/apple-touch-icon.png',
},
],
},
@@ -140,7 +140,7 @@ The following options are available for Open Graph Metadata:
| Key | Type | Description |
| --- | --- | --- |
| **`description`** | `string` | The description of the Admin Panel. |
| **`images`** | `OGImageConfig | OGImageConfig[]` | An array of image objects. |
| **`images`** | `OGImageConfig` or `OGImageConfig[]` | An array of image objects. |
| **`siteName`** | `string` | The name of the site. |
| **`title`** | `string` | The title of the Admin Panel. |

View File

@@ -98,14 +98,14 @@ From there, you are ready to make updates to your project. When you are ready to
Projects generated from a template will come pre-configured with the official Cloud Plugin, but if you are using your own repository you will need to add this into your project. To do so, add the plugin to your Payload Config:
`yarn add @payloadcms/plugin-cloud`
`yarn add @payloadcms/payload-cloud`
```js
import { payloadCloud } from '@payloadcms/plugin-cloud'
import { payloadCloudPlugin } from '@payloadcms/payload-cloud'
import { buildConfig } from 'payload'
export default buildConfig({
plugins: [payloadCloud()],
plugins: [payloadCloudPlugin()],
// rest of config
})
```
@@ -115,6 +115,11 @@ export default buildConfig({
over Payload Cloud's email service.
</Banner>
<Banner type="info">
Good to know: the Payload Cloud Plugin was previously named `@payloadcms/plugin-cloud`. If you are
using this plugin, you should update to the new package name.
</Banner>
#### **Optional configuration**
If you wish to opt-out of any Payload cloud features, the plugin also accepts options to do so.

View File

@@ -57,26 +57,26 @@ export const Posts: CollectionConfig = {
The following options are available:
| Option | Description |
|------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`admin`** | The configuration options for the Admin Panel. [More details](../admin/collections). |
| **`access`** | Provide Access Control functions to define exactly who should be able to do what with Documents in this Collection. [More details](../access-control/collections). |
| **`auth`** | Specify options if you would like this Collection to feature authentication. [More details](../authentication/overview). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`disableDuplicate`** | When true, do not show the "Duplicate" button while editing documents within this Collection and prevent `duplicate` from all APIs. |
| **`defaultSort`** | Pass a top-level field to sort by default in the Collection List View. Prefix the name of the field with a minus symbol ("-") to sort in descending order. |
| **`dbName`** | Custom table or Collection name depending on the Database Adapter. Auto-generated from slug if not defined. |
| **`endpoints`** | Add custom routes to the REST API. Set to `false` to disable routes. [More details](../rest-api/overview#custom-endpoints). |
| **`fields`** \* | Array of field types that will determine the structure and functionality of the data stored within this Collection. [More details](../fields/overview). |
| **`graphQL`** | An object with `singularName` and `pluralName` strings used in schema generation. Auto-generated from slug if not defined. Set to `false` to disable GraphQL. |
| **`hooks`** | Entry point for Hooks. [More details](../hooks/overview#collection-hooks). |
| **`labels`** | Singular and plural labels for use in identifying this Collection throughout Payload. Auto-generated from slug if not defined. |
| **`lockDocuments`** | Enables or disables document locking. By default, document locking is enabled. Set to an object to configure, or set to `false` to disable locking. [More details](../admin/locked-documents). |
| **`slug`** \* | Unique, URL-friendly string that will act as an identifier for this Collection. |
| **`timestamps`** | Set to false to disable documents' automatically generated `createdAt` and `updatedAt` timestamps. |
| **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
| **`upload`** | Specify options if you would like this Collection to support file uploads. For more, consult the [Uploads](../upload/overview) documentation. |
| **`versions`** | Set to true to enable default options, or configure with object properties. [More details](../versions/overview#collection-config). |
| Option | Description |
|------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`admin`** | The configuration options for the Admin Panel. [More details](../admin/collections). |
| **`access`** | Provide Access Control functions to define exactly who should be able to do what with Documents in this Collection. [More details](../access-control/collections). |
| **`auth`** | Specify options if you would like this Collection to feature authentication. [More details](../authentication/overview). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`disableDuplicate`** | When true, do not show the "Duplicate" button while editing documents within this Collection and prevent `duplicate` from all APIs. |
| **`defaultSort`** | Pass a top-level field to sort by default in the Collection List View. Prefix the name of the field with a minus symbol ("-") to sort in descending order. Multiple fields can be specified by using a string array. |
| **`dbName`** | Custom table or Collection name depending on the Database Adapter. Auto-generated from slug if not defined. |
| **`endpoints`** | Add custom routes to the REST API. Set to `false` to disable routes. [More details](../rest-api/overview#custom-endpoints). |
| **`fields`** \* | Array of field types that will determine the structure and functionality of the data stored within this Collection. [More details](../fields/overview). |
| **`graphQL`** | An object with `singularName` and `pluralName` strings used in schema generation. Auto-generated from slug if not defined. Set to `false` to disable GraphQL. |
| **`hooks`** | Entry point for Hooks. [More details](../hooks/overview#collection-hooks). |
| **`labels`** | Singular and plural labels for use in identifying this Collection throughout Payload. Auto-generated from slug if not defined. |
| **`lockDocuments`** | Enables or disables document locking. By default, document locking is enabled. Set to an object to configure, or set to `false` to disable locking. [More details](../admin/locked-documents). |
| **`slug`** \* | Unique, URL-friendly string that will act as an identifier for this Collection. |
| **`timestamps`** | Set to false to disable documents' automatically generated `createdAt` and `updatedAt` timestamps. |
| **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
| **`upload`** | Specify options if you would like this Collection to support file uploads. For more, consult the [Uploads](../upload/overview) documentation. |
| **`versions`** | Set to true to enable default options, or configure with object properties. [More details](../versions/overview#collection-config). |
_\* An asterisk denotes that a property is required._

View File

@@ -6,8 +6,10 @@ desc: The Join field provides the ability to work on related documents. Learn ho
keywords: join, relationship, junction, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
The Join Field is used to make Relationship and Upload fields available in the opposite direction. With a Join you can edit and view collections
having reference to a specific collection document. The field itself acts as a virtual field, in that no new data is stored on the collection with a Join
The Join Field is used to make Relationship and Upload fields available in the opposite direction. With a Join you can
edit and view collections
having reference to a specific collection document. The field itself acts as a virtual field, in that no new data is
stored on the collection with a Join
field. Instead, the Admin UI surfaces the related documents for a better editing experience and is surfaced by Payload's
APIs.
@@ -19,10 +21,10 @@ The Join field is useful in scenarios including:
- Displaying where a document or upload is used in other documents
<LightDarkImage
srcLight="https://payloadcms.com/images/docs/fields/join.png"
srcDark="https://payloadcms.com/images/docs/fields/join-dark.png"
alt="Shows Join field in the Payload Admin Panel"
caption="Admin Panel screenshot of Join field"
srcLight="https://payloadcms.com/images/docs/fields/join.png"
srcDark="https://payloadcms.com/images/docs/fields/join-dark.png"
alt="Shows Join field in the Payload Admin Panel"
caption="Admin Panel screenshot of Join field"
/>
For the Join field to work, you must have an existing [relationship](./relationship) or [upload](./upload) field in the
@@ -111,9 +113,11 @@ related docs from a new pseudo-junction collection called `categories_posts`. No
third junction collection, and can be surfaced on both Posts and Categories. But, importantly, you could add
additional "context" fields to this shared junction collection.
For example, on this `categories_posts` collection, in addition to having the `category` and `post` fields, we could add custom "context" fields like `featured` or `spotlight`,
For example, on this `categories_posts` collection, in addition to having the `category` and `post` fields, we could add
custom "context" fields like `featured` or `spotlight`,
which would allow you to store additional information directly on relationships.
The `join` field gives you complete control over any type of relational architecture in Payload, all wrapped up in a powerful Admin UI.
The `join` field gives you complete control over any type of relational architecture in Payload, all wrapped up in a
powerful Admin UI.
## Config Options
@@ -126,11 +130,11 @@ The `join` field gives you complete control over any type of relational architec
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`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). |
| **`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. |
| **`defaultLimit`** | The number of documents to return. Set to 0 to return all related documents. |
| **`defaultSort`** | The field name used to specify the order the joined documents are returned. |
| **`admin`** | Admin-specific configuration. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`custom`** | Extension point for adding custom data (e.g. for plugins). |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema. |
_\* An asterisk denotes that a property is required._
@@ -150,12 +154,12 @@ object with:
{
"id": "66e3431a3f23e684075aaeb9",
// other fields...
"category": "66e3431a3f23e684075aae9c",
},
"category": "66e3431a3f23e684075aae9c"
}
// { ... }
],
"hasNextPage": false
},
}
// other fields...
}
```
@@ -213,7 +217,8 @@ You can specify as many `joins` parameters as needed for the same or different j
### GraphQL
The GraphQL API supports the same query options as the local and REST APIs. You can specify the query options for each join field in your query.
The GraphQL API supports the same query options as the local and REST APIs. You can specify the query options for each
join field in your query.
Example:
@@ -226,9 +231,9 @@ query {
limit: 5
where: {
author: {
equals: "66e3431a3f23e684075aaeb9"
}
equals: "66e3431a3f23e684075aaeb9"
}
}
) {
docs {
title

View File

@@ -54,12 +54,12 @@ To install a Database Adapter, you can run **one** of the following commands:
- To install the [MongoDB Adapter](../database/mongodb), run:
```bash
pnpm i @payloadcms/db-mongodb
pnpm i @payloadcms/db-mongodb@beta
```
- To install the [Postgres Adapter](../database/postgres), run:
```bash
pnpm i @payloadcms/db-postgres
pnpm i @payloadcms/db-postgres@beta
```
<Banner type="success">

View File

@@ -6,7 +6,7 @@ desc: Payload sort allows you to order your documents by a field in ascending or
keywords: query, documents, pagination, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
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.
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 specificed 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.
@@ -30,6 +30,19 @@ const getPosts = async () => {
}
```
To sort by multiple fields, you can use the `sort` option with fields in an array:
```ts
const getPosts = async () => {
const posts = await payload.find({
collection: 'posts',
sort: ['priority', '-createdAt'], // highlight-line
})
return posts
}
```
## REST API
To sort in the [REST API](../rest-api/overview), you can use the `sort` parameter in your query:
@@ -40,6 +53,14 @@ fetch('https://localhost:3000/api/posts?sort=-createdAt') // highlight-line
.then((data) => console.log(data))
```
To sort by multiple fields, you can use the `sort` parameter with fields separated by comma:
```ts
fetch('https://localhost:3000/api/posts?sort=priority,-createdAt') // highlight-line
.then((response) => response.json())
.then((data) => console.log(data))
```
## GraphQL API
To sort in the [GraphQL API](../graphql/overview), you can use the `sort` parameter in your query:

View File

@@ -1,6 +1,6 @@
{
"name": "payload-monorepo",
"version": "3.0.0-beta.118",
"version": "3.0.0-beta.120",
"private": true,
"type": "module",
"scripts": {
@@ -29,7 +29,7 @@
"build:live-preview-vue": "turbo build --filter \"@payloadcms/live-preview-vue\"",
"build:next": "turbo build --filter \"@payloadcms/next\"",
"build:payload": "turbo build --filter payload",
"build:plugin-cloud": "turbo build --filter \"@payloadcms/plugin-cloud\"",
"build:payload-cloud": "turbo build --filter \"@payloadcms/payload-cloud\"",
"build:plugin-cloud-storage": "turbo build --filter \"@payloadcms/plugin-cloud-storage\"",
"build:plugin-form-builder": "turbo build --filter \"@payloadcms/plugin-form-builder\"",
"build:plugin-nested-docs": "turbo build --filter \"@payloadcms/plugin-nested-docs\"",

View File

@@ -1,6 +1,6 @@
{
"name": "create-payload-app",
"version": "3.0.0-beta.118",
"version": "3.0.0-beta.120",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -54,7 +54,7 @@ function getEnvironmentPackageManager(): PackageManager {
async function commandExists(command: string): Promise<boolean> {
try {
await execa.command(`command -v ${command}`)
await execa.command(process.platform === 'win32' ? `where ${command}` : `command -v ${command}`)
return true
} catch {
return false

View File

@@ -226,7 +226,7 @@ async function installDeps(projectDir: string, packageManager: PackageManager, d
'payload',
'@payloadcms/next',
'@payloadcms/richtext-lexical',
'@payloadcms/plugin-cloud',
'@payloadcms/payload-cloud',
].map((pkg) => `${pkg}@beta`)
packagesToInstall.push(`@payloadcms/db-${dbType}@beta`)

View File

@@ -82,8 +82,8 @@ const vercelBlobStorageReplacement: StorageAdapterReplacement = {
const payloadCloudReplacement: StorageAdapterReplacement = {
configReplacement: [' payloadCloudPlugin(),'],
importReplacement: "import { payloadCloudPlugin } from '@payloadcms/plugin-cloud'",
packageName: '@payloadcms/plugin-cloud',
importReplacement: "import { payloadCloudPlugin } from '@payloadcms/payload-cloud'",
packageName: '@payloadcms/payload-cloud',
}
// Removes placeholders

View File

@@ -18,7 +18,7 @@ const dbChoiceRecord: Record<DbType, DbChoice> = {
},
postgres: {
dbConnectionPrefix: 'postgres://postgres:<password>@127.0.0.1:5432/',
title: 'PostgreSQL (beta)',
title: 'PostgreSQL',
value: 'postgres',
},
sqlite: {

View File

@@ -1,19 +1,20 @@
import fs from 'fs-extra'
import path from 'path'
import type { CliArgs, ProjectTemplate } from '../types.js'
import type { CliArgs, DbType, ProjectTemplate } from '../types.js'
import { debug, error } from '../utils/log.js'
/** Parse and swap .env.example values and write .env */
export async function writeEnvFile(args: {
cliArgs: CliArgs
databaseType?: DbType
databaseUri: string
payloadSecret: string
projectDir: string
template?: ProjectTemplate
}): Promise<void> {
const { cliArgs, databaseUri, payloadSecret, projectDir, template } = args
const { cliArgs, databaseType, databaseUri, payloadSecret, projectDir, template } = args
if (cliArgs['--dry-run']) {
debug(`DRY RUN: .env file created`)
@@ -41,7 +42,7 @@ export async function writeEnvFile(args: {
}
const split = line.split('=')
const key = split[0]
let key = split[0]
let value = split[1]
if (
@@ -50,6 +51,9 @@ export async function writeEnvFile(args: {
key === 'DATABASE_URI' ||
key === 'POSTGRES_URL'
) {
if (databaseType === 'vercel-postgres') {
key = 'POSTGRES_URL'
}
value = databaseUri
}
if (key === 'PAYLOAD_SECRET' || key === 'PAYLOAD_SECRET_KEY') {

View File

@@ -180,6 +180,7 @@ export class Main {
await writeEnvFile({
cliArgs: this.args,
databaseType: dbDetails.type,
databaseUri: dbDetails.dbUri,
payloadSecret: generateSecret(),
projectDir,
@@ -222,6 +223,7 @@ export class Main {
})
await writeEnvFile({
cliArgs: this.args,
databaseType: dbDetails.type,
databaseUri: dbDetails.dbUri,
payloadSecret,
projectDir,

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-mongodb",
"version": "3.0.0-beta.118",
"version": "3.0.0-beta.120",
"description": "The officially supported MongoDB database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,5 +1,5 @@
import type { PaginateOptions } from 'mongoose'
import type { Field, SanitizedConfig } from 'payload'
import type { Field, SanitizedConfig, Sort } from 'payload'
import { getLocalizedSortProperty } from './getLocalizedSortProperty.js'
@@ -7,7 +7,7 @@ type Args = {
config: SanitizedConfig
fields: Field[]
locale: string
sort: string
sort: Sort
timestamps: boolean
}
@@ -25,32 +25,41 @@ export const buildSortParam = ({
sort,
timestamps,
}: Args): PaginateOptions['sort'] => {
let sortProperty: string
let sortDirection: SortDirection = 'desc'
if (!sort) {
if (timestamps) {
sortProperty = 'createdAt'
sort = '-createdAt'
} else {
sortProperty = '_id'
sort = '-id'
}
} else if (sort.indexOf('-') === 0) {
sortProperty = sort.substring(1)
} else {
sortProperty = sort
sortDirection = 'asc'
}
if (sortProperty === 'id') {
sortProperty = '_id'
} else {
sortProperty = getLocalizedSortProperty({
if (typeof sort === 'string') {
sort = [sort]
}
const sorting = sort.reduce<PaginateOptions['sort']>((acc, item) => {
let sortProperty: string
let sortDirection: SortDirection
if (item.indexOf('-') === 0) {
sortProperty = item.substring(1)
sortDirection = 'desc'
} else {
sortProperty = item
sortDirection = 'asc'
}
if (sortProperty === 'id') {
acc['_id'] = sortDirection
return acc
}
const localizedProperty = getLocalizedSortProperty({
config,
fields,
locale,
segments: sortProperty.split('.'),
})
}
acc[localizedProperty] = sortDirection
return acc
}, {})
return { [sortProperty]: sortDirection }
return sorting
}

View File

@@ -78,7 +78,11 @@ export const sanitizeQueryValue = ({
return { operator: formattedOperator, val: undefined }
}
}
} else if (Array.isArray(val)) {
} else if (Array.isArray(val) || (typeof val === 'string' && val.split(',').length > 1)) {
if (typeof val === 'string') {
formattedValue = createArrayFromCommaDelineated(val)
}
formattedValue = formattedValue.reduce((formattedValues, inVal) => {
const newValues = [inVal]
if (!hasCustomID) {

View File

@@ -57,8 +57,8 @@ export const buildJoinAggregation = async ({
const joinModel = adapter.collections[join.field.collection]
const {
limit: limitJoin = 10,
sort: sortJoin,
limit: limitJoin = join.field.defaultLimit ?? 10,
sort: sortJoin = join.field.defaultSort || collectionConfig.defaultSort,
where: whereJoin,
} = joins?.[join.schemaPath] || {}
@@ -66,7 +66,7 @@ export const buildJoinAggregation = async ({
config: adapter.payload.config,
fields: adapter.payload.collections[slug].config.fields,
locale,
sort: sortJoin || collectionConfig.defaultSort,
sort: sortJoin,
timestamps: true,
})
const sortProperty = Object.keys(sort)[0]

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "3.0.0-beta.118",
"version": "3.0.0-beta.120",
"description": "The officially supported Postgres database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {
@@ -43,7 +43,7 @@
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"prepack": "pnpm clean && pnpm turbo build",
"prepublishOnly": "pnpm clean && pnpm turbo build",
"prepublishOnly": "pnpm clean && pnpm turbo build --filter=./",
"renamePredefinedMigrations": "node --no-deprecation --import @swc-node/register/esm-register ./scripts/renamePredefinedMigrations.ts"
},
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-sqlite",
"version": "3.0.0-beta.118",
"version": "3.0.0-beta.120",
"description": "The officially supported SQLite database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,7 +1,7 @@
import type { ChainedMethods } from '@payloadcms/drizzle/types'
import { chainMethods } from '@payloadcms/drizzle'
import { count, sql } from 'drizzle-orm'
import { count } from 'drizzle-orm'
import type { CountDistinct, SQLiteAdapter } from './types.js'
@@ -22,11 +22,7 @@ export const countDistinct: CountDistinct = async function countDistinct(
methods: chainedMethods,
query: db
.select({
count:
joins.length > 0
? sql`count
(DISTINCT ${this.tables[tableName].id})`.mapWith(Number)
: count(),
count: count(),
})
.from(this.tables[tableName])
.where(where),

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-vercel-postgres",
"version": "3.0.0-beta.118",
"version": "3.0.0-beta.120",
"description": "Vercel Postgres adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/drizzle",
"version": "3.0.0-beta.118",
"version": "3.0.0-beta.120",
"description": "A library of shared functions used by different payload database adapters",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -21,7 +21,7 @@ export const find: Find = async function find(
},
) {
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
const sort = typeof sortArg === 'string' ? sortArg : collectionConfig.defaultSort
const sort = sortArg !== undefined && sortArg !== null ? sortArg : collectionConfig.defaultSort
const tableName = this.tableNameMap.get(toSnakeCase(collectionConfig.slug))

View File

@@ -59,9 +59,9 @@ export const findMany = async function find({
const selectDistinctMethods: ChainedMethods = []
if (orderBy?.order && orderBy?.column) {
if (orderBy) {
selectDistinctMethods.push({
args: [orderBy.order(orderBy.column)],
args: [() => orderBy.map(({ column, order }) => order(column))],
method: 'orderBy',
})
}
@@ -114,7 +114,7 @@ export const findMany = async function find({
} else {
findManyArgs.limit = limit
findManyArgs.offset = offset
findManyArgs.orderBy = orderBy.order(orderBy.column)
findManyArgs.orderBy = () => orderBy.map(({ column, order }) => order(column))
if (where) {
findManyArgs.where = where

View File

@@ -238,8 +238,8 @@ export const traverseFields = ({
}
const {
limit: limitArg = 10,
sort,
limit: limitArg = field.defaultLimit ?? 10,
sort = field.defaultSort,
where,
} = joinQuery[`${path.replaceAll('_', '.')}${field.name}`] || {}
let limit = limitArg
@@ -285,7 +285,9 @@ export const traverseFields = ({
let columnReferenceToCurrentID: string
if (versions) {
columnReferenceToCurrentID = `${topLevelTableName.replace('_', '').replace(new RegExp(`${adapter.versionsSuffix}$`), '')}_id`
columnReferenceToCurrentID = `${topLevelTableName
.replace('_', '')
.replace(new RegExp(`${adapter.versionsSuffix}$`), '')}_id`
} else {
columnReferenceToCurrentID = `${topLevelTableName}_id`
}
@@ -373,7 +375,7 @@ export const traverseFields = ({
})
.from(adapter.tables[joinCollectionTableName])
.where(subQueryWhere)
.orderBy(orderBy.order(orderBy.column)),
.orderBy(() => orderBy.map(({ column, order }) => order(column))),
})
const columnName = `${path.replaceAll('.', '_')}${field.name}`

View File

@@ -24,7 +24,7 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
const globalConfig: SanitizedGlobalConfig = this.payload.globals.config.find(
({ slug }) => slug === global,
)
const sort = typeof sortArg === 'string' ? sortArg : '-createdAt'
const sort = sortArg !== undefined && sortArg !== null ? sortArg : '-createdAt'
const tableName = this.tableNameMap.get(
`_${toSnakeCase(globalConfig.slug)}${this.versionsSuffix}`,

View File

@@ -22,7 +22,7 @@ export const findVersions: FindVersions = async function findVersions(
},
) {
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
const sort = typeof sortArg === 'string' ? sortArg : collectionConfig.defaultSort
const sort = sortArg !== undefined && sortArg !== null ? sortArg : collectionConfig.defaultSort
const tableName = this.tableNameMap.get(
`_${toSnakeCase(collectionConfig.slug)}${this.versionsSuffix}`,

View File

@@ -1,4 +1,4 @@
import { count, sql } from 'drizzle-orm'
import { count } from 'drizzle-orm'
import type { ChainedMethods, TransactionPg } from '../types.js'
import type { BasePostgresAdapter, CountDistinct } from './types.js'
@@ -22,11 +22,7 @@ export const countDistinct: CountDistinct = async function countDistinct(
methods: chainedMethods,
query: (db as TransactionPg)
.select({
count:
joins.length > 0
? sql`count
(DISTINCT ${this.tables[tableName].id})`.mapWith(Number)
: count(),
count: count(),
})
.from(this.tables[tableName])
.where(where),

View File

@@ -27,8 +27,9 @@ type Args = {
schemaName?: string
}
export const createDatabase = async function (this: BasePostgresAdapter, args: Args = {}) {
// DATABASE_URL - default Vercel env
const connectionString = this.poolOptions?.connectionString ?? process.env.DATABASE_URL
// POSTGRES_URL - default Vercel env
const connectionString =
this.poolOptions?.connectionString ?? process.env.POSTGRES_URL ?? process.env.DATABASE_URL
let managementClientConfig: ClientConfig = {}
let dbName = args.name
const schemaName = this.schemaName || 'public'

View File

@@ -1,4 +1,4 @@
import type { Field } from 'payload'
import type { Field, Sort } from 'payload'
import { asc, desc } from 'drizzle-orm'
@@ -13,7 +13,7 @@ type Args = {
joins: BuildQueryJoinAliases
locale?: string
selectFields: Record<string, GenericColumn>
sort?: string
sort?: Sort
tableName: string
}
@@ -29,54 +29,55 @@ export const buildOrderBy = ({
sort,
tableName,
}: Args): BuildQueryResult['orderBy'] => {
const orderBy: BuildQueryResult['orderBy'] = {
column: null,
order: null,
const orderBy: BuildQueryResult['orderBy'] = []
if (!sort) {
const createdAt = adapter.tables[tableName]?.createdAt
if (createdAt) {
sort = '-createdAt'
} else {
sort = '-id'
}
}
if (sort) {
let sortPath
if (typeof sort === 'string') {
sort = [sort]
}
if (sort[0] === '-') {
sortPath = sort.substring(1)
orderBy.order = desc
for (const sortItem of sort) {
let sortProperty: string
let sortDirection: 'asc' | 'desc'
if (sortItem[0] === '-') {
sortProperty = sortItem.substring(1)
sortDirection = 'desc'
} else {
sortPath = sort
orderBy.order = asc
sortProperty = sortItem
sortDirection = 'asc'
}
try {
const { columnName: sortTableColumnName, table: sortTable } = getTableColumnFromPath({
adapter,
collectionPath: sortPath,
collectionPath: sortProperty,
fields,
joins,
locale,
pathSegments: sortPath.replace(/__/g, '.').split('.'),
pathSegments: sortProperty.replace(/__/g, '.').split('.'),
selectFields,
tableName,
value: sortPath,
value: sortProperty,
})
orderBy.column = sortTable?.[sortTableColumnName]
if (sortTable?.[sortTableColumnName]) {
orderBy.push({
column: sortTable[sortTableColumnName],
order: sortDirection === 'asc' ? asc : desc,
})
selectFields[sortTableColumnName] = sortTable[sortTableColumnName]
}
} catch (err) {
// continue
}
}
if (!orderBy?.column) {
orderBy.order = desc
const createdAt = adapter.tables[tableName]?.createdAt
if (createdAt) {
orderBy.column = createdAt
} else {
orderBy.column = adapter.tables[tableName].id
}
}
if (orderBy.column) {
selectFields.sort = orderBy.column
}
return orderBy
}

View File

@@ -1,6 +1,6 @@
import type { asc, desc, SQL } from 'drizzle-orm'
import type { PgTableWithColumns } from 'drizzle-orm/pg-core'
import type { Field, Where } from 'payload'
import type { Field, Sort, Where } from 'payload'
import type { DrizzleAdapter, GenericColumn, GenericTable } from '../types.js'
@@ -18,7 +18,7 @@ type BuildQueryArgs = {
fields: Field[]
joins?: BuildQueryJoinAliases
locale?: string
sort?: string
sort?: Sort
tableName: string
where: Where
}
@@ -28,7 +28,7 @@ export type BuildQueryResult = {
orderBy: {
column: GenericColumn
order: typeof asc | typeof desc
}
}[]
selectFields: Record<string, GenericColumn>
where: SQL
}

View File

@@ -186,18 +186,17 @@ export const getTableColumnFromPath = ({
if (locale && field.localized && adapter.payload.config.localization) {
newTableName = `${tableName}${adapter.localesSuffix}`
let condition = eq(adapter.tables[tableName].id, adapter.tables[newTableName]._parentID)
if (locale !== 'all') {
condition = and(condition, eq(adapter.tables[newTableName]._locale, locale))
}
addJoinTable({
condition: eq(adapter.tables[tableName].id, adapter.tables[newTableName]._parentID),
condition,
joins,
table: adapter.tables[newTableName],
})
if (locale !== 'all') {
constraints.push({
columnName: '_locale',
table: adapter.tables[newTableName],
value: locale,
})
}
}
return getTableColumnFromPath({
adapter,
@@ -225,21 +224,20 @@ export const getTableColumnFromPath = ({
)
if (locale && field.localized && adapter.payload.config.localization) {
const conditions = [
eq(adapter.tables[tableName].id, adapter.tables[newTableName].parent),
eq(adapter.tables[newTableName]._locale, locale),
]
if (locale !== 'all') {
conditions.push(eq(adapter.tables[newTableName]._locale, locale))
}
addJoinTable({
condition: and(
eq(adapter.tables[tableName].id, adapter.tables[newTableName].parent),
eq(adapter.tables[newTableName]._locale, locale),
),
condition: and(...conditions),
joins,
table: adapter.tables[newTableName],
})
if (locale !== 'all') {
constraints.push({
columnName: '_locale',
table: adapter.tables[newTableName],
value: locale,
})
}
} else {
addJoinTable({
condition: eq(adapter.tables[tableName].id, adapter.tables[newTableName].parent),
@@ -274,18 +272,16 @@ export const getTableColumnFromPath = ({
]
if (locale && field.localized && adapter.payload.config.localization) {
const conditions = [...joinConstraints]
if (locale !== 'all') {
conditions.push(eq(adapter.tables[newTableName]._locale, locale))
}
addJoinTable({
condition: and(...joinConstraints, eq(adapter.tables[newTableName]._locale, locale)),
condition: and(...conditions),
joins,
table: adapter.tables[newTableName],
})
if (locale !== 'all') {
constraints.push({
columnName: 'locale',
table: adapter.tables[newTableName],
value: locale,
})
}
} else {
addJoinTable({
condition: and(...joinConstraints),
@@ -313,21 +309,16 @@ export const getTableColumnFromPath = ({
constraintPath = `${constraintPath}${field.name}.%.`
if (locale && field.localized && adapter.payload.config.localization) {
const conditions = [eq(arrayParentTable.id, adapter.tables[newTableName]._parentID)]
if (locale !== 'all') {
conditions.push(eq(adapter.tables[newTableName]._locale, locale))
}
addJoinTable({
condition: and(
eq(arrayParentTable.id, adapter.tables[newTableName]._parentID),
eq(adapter.tables[newTableName]._locale, locale),
),
condition: and(...conditions),
joins,
table: adapter.tables[newTableName],
})
if (locale !== 'all') {
constraints.push({
columnName: '_locale',
table: adapter.tables[newTableName],
value: locale,
})
}
} else {
addJoinTable({
condition: eq(arrayParentTable.id, adapter.tables[newTableName]._parentID),
@@ -417,23 +408,21 @@ export const getTableColumnFromPath = ({
constraints = constraints.concat(blockConstraints)
selectFields = { ...selectFields, ...blockSelectFields }
if (field.localized && adapter.payload.config.localization) {
joins.push({
condition: and(
eq(
(aliasTable || adapter.tables[tableName]).id,
adapter.tables[newTableName]._parentID,
),
eq(adapter.tables[newTableName]._locale, locale),
const conditions = [
eq(
(aliasTable || adapter.tables[tableName]).id,
adapter.tables[newTableName]._parentID,
),
]
if (locale !== 'all') {
conditions.push(eq(adapter.tables[newTableName]._locale, locale))
}
joins.push({
condition: and(...conditions),
table: adapter.tables[newTableName],
})
if (locale) {
constraints.push({
columnName: '_locale',
table: adapter.tables[newTableName],
value: locale,
})
}
} else {
joins.push({
condition: eq(
@@ -471,21 +460,18 @@ export const getTableColumnFromPath = ({
// Join in the relationships table
if (locale && field.localized && adapter.payload.config.localization) {
const conditions = [
eq((aliasTable || adapter.tables[rootTableName]).id, aliasRelationshipTable.parent),
like(aliasRelationshipTable.path, `${constraintPath}${field.name}`),
]
if (locale !== 'all') {
conditions.push(eq(aliasRelationshipTable.locale, locale))
}
joins.push({
condition: and(
eq((aliasTable || adapter.tables[rootTableName]).id, aliasRelationshipTable.parent),
eq(aliasRelationshipTable.locale, locale),
like(aliasRelationshipTable.path, `${constraintPath}${field.name}`),
),
condition: and(...conditions),
table: aliasRelationshipTable,
})
if (locale !== 'all') {
constraints.push({
columnName: 'locale',
table: aliasRelationshipTable,
value: locale,
})
}
} else {
// Join in the relationships table
joins.push({
@@ -660,15 +646,22 @@ export const getTableColumnFromPath = ({
tableName: `${rootTableName}${adapter.localesSuffix}`,
})
joins.push({
condition: and(
eq(aliasLocaleTable._parentID, adapter.tables[rootTableName].id),
eq(aliasLocaleTable._locale, locale),
),
table: aliasLocaleTable,
const condtions = [eq(aliasLocaleTable._parentID, adapter.tables[rootTableName].id)]
if (locale !== 'all') {
condtions.push(eq(aliasLocaleTable._locale, locale))
}
const localesTable = adapter.tables[`${rootTableName}${adapter.localesSuffix}`]
addJoinTable({
condition: and(...condtions),
joins,
table: localesTable,
})
joins.push({
condition: eq(aliasLocaleTable[columnName], newAliasTable.id),
condition: eq(localesTable[columnName], newAliasTable.id),
table: newAliasTable,
})
} else {
@@ -716,21 +709,19 @@ export const getTableColumnFromPath = ({
newTable = adapter.tables[newTableName]
let condition = eq(parentTable.id, newTable._parentID)
if (locale !== 'all') {
condition = and(condition, eq(newTable._locale, locale))
}
addJoinTable({
condition: eq(parentTable.id, newTable._parentID),
condition,
joins,
table: newTable,
})
aliasTable = undefined
if (locale !== 'all') {
constraints.push({
columnName: '_locale',
table: newTable,
value: locale,
})
}
}
const targetTable = aliasTable || newTable

View File

@@ -420,7 +420,8 @@ export const traverseFields = <T extends Record<string, unknown>>({
}
if (field.type === 'join') {
const { limit = 10 } = joinQuery?.[`${fieldPrefix.replaceAll('_', '.')}${field.name}`] || {}
const { limit = field.defaultLimit ?? 10 } =
joinQuery?.[`${fieldPrefix.replaceAll('_', '.')}${field.name}`] || {}
// raw hasMany results from SQLite
if (typeof fieldData === 'string') {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/email-nodemailer",
"version": "3.0.0-beta.118",
"version": "3.0.0-beta.120",
"description": "Payload Nodemailer Email Adapter",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/email-resend",
"version": "3.0.0-beta.118",
"version": "3.0.0-beta.120",
"description": "Payload Resend Email Adapter",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/graphql",
"version": "3.0.0-beta.118",
"version": "3.0.0-beta.120",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview-react",
"version": "3.0.0-beta.118",
"version": "3.0.0-beta.120",
"description": "The official React SDK for Payload Live Preview",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview-vue",
"version": "3.0.0-beta.118",
"version": "3.0.0-beta.120",
"description": "The official Vue SDK for Payload Live Preview",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview",
"version": "3.0.0-beta.118",
"version": "3.0.0-beta.120",
"description": "The official live preview JavaScript SDK for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/next",
"version": "3.0.0-beta.118",
"version": "3.0.0-beta.120",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -28,7 +28,7 @@ export const find: CollectionRouteHandler = async ({ collection, req }) => {
limit: isNumber(limit) ? Number(limit) : undefined,
page: isNumber(page) ? Number(page) : undefined,
req,
sort,
sort: typeof sort === 'string' ? sort.split(',') : undefined,
where,
})

View File

@@ -23,7 +23,7 @@ export const findVersions: CollectionRouteHandler = async ({ collection, req })
limit: isNumber(limit) ? Number(limit) : undefined,
page: isNumber(page) ? Number(page) : undefined,
req,
sort,
sort: typeof sort === 'string' ? sort.split(',') : undefined,
where,
})

View File

@@ -23,7 +23,7 @@ export const findVersions: GlobalRouteHandler = async ({ globalConfig, req }) =>
limit: isNumber(limit) ? Number(limit) : undefined,
page: isNumber(page) ? Number(page) : undefined,
req,
sort,
sort: typeof sort === 'string' ? sort.split(',') : undefined,
where,
})

View File

@@ -16,6 +16,7 @@ export const ToggleTheme: React.FC = () => {
return (
<RadioGroupField
disableModifyingForm={true}
field={{
name: 'theme',
label: t('general:adminTheme'),

View File

@@ -16,7 +16,11 @@ import './index.scss'
const baseClass = 'dashboard'
export type DashboardProps = {
globalData: Array<{ data: { _isLocked: boolean; _userEditing: ClientUser | null }; slug: string }>
globalData: Array<{
data: { _isLocked: boolean; _lastEditedAt: string; _userEditing: ClientUser | null }
lockDuration?: number
slug: string
}>
Link: React.ComponentType<any>
navGroups?: ReturnType<typeof groupNavItems>
permissions: Permissions
@@ -95,7 +99,7 @@ export const DefaultDashboard: React.FC<DashboardProps> = (props) => {
let createHREF: string
let href: string
let hasCreatePermission: boolean
let lockStatus = null
let isLocked = null
let userEditing = null
if (type === EntityType.collection) {
@@ -130,9 +134,24 @@ export const DefaultDashboard: React.FC<DashboardProps> = (props) => {
const globalLockData = globalData.find(
(global) => global.slug === entity.slug,
)
if (globalLockData) {
lockStatus = globalLockData.data._isLocked
isLocked = globalLockData.data._isLocked
userEditing = globalLockData.data._userEditing
// Check if the lock is expired
const lockDuration = globalLockData?.lockDuration
const lastEditedAt = new Date(
globalLockData.data?._lastEditedAt,
).getTime()
const lockDurationInMilliseconds = lockDuration * 1000
const lockExpirationTime = lastEditedAt + lockDurationInMilliseconds
if (new Date().getTime() > lockExpirationTime) {
isLocked = false
userEditing = null
}
}
}
@@ -140,7 +159,7 @@ export const DefaultDashboard: React.FC<DashboardProps> = (props) => {
<li key={entityIndex}>
<Card
actions={
lockStatus && user?.id !== userEditing?.id ? (
isLocked && user?.id !== userEditing?.id ? (
<Locked className={`${baseClass}__locked`} user={userEditing} />
) : hasCreatePermission && type === EntityType.collection ? (
<Button

View File

@@ -34,6 +34,8 @@ export const Dashboard: React.FC<AdminViewProps> = async ({
visibleEntities,
} = initPageResult
const lockDurationDefault = 300 // Default 5 minutes in seconds
const CustomDashboardComponent = config.admin.components?.views?.Dashboard
const collections = config.collections.filter(
@@ -48,16 +50,26 @@ export const Dashboard: React.FC<AdminViewProps> = async ({
visibleEntities.globals.includes(global.slug),
)
const globalSlugs = config.globals.map((global) => global.slug)
const globalConfigs = config.globals.map((global) => ({
slug: global.slug,
lockDuration:
global.lockDocuments === false
? null // Set lockDuration to null if locking is disabled
: typeof global.lockDocuments === 'object'
? global.lockDocuments.duration
: lockDurationDefault,
}))
// Filter the slugs based on permissions and visibility
const filteredGlobalSlugs = globalSlugs.filter(
(slug) =>
permissions?.globals?.[slug]?.read?.permission && visibleEntities.globals.includes(slug),
const filteredGlobalConfigs = globalConfigs.filter(
({ slug, lockDuration }) =>
lockDuration !== null && // Ensure lockDuration is valid
permissions?.globals?.[slug]?.read?.permission &&
visibleEntities.globals.includes(slug),
)
const globalData = await Promise.all(
filteredGlobalSlugs.map(async (slug) => {
filteredGlobalConfigs.map(async ({ slug, lockDuration }) => {
const data = await payload.findGlobal({
slug,
depth: 0,
@@ -67,6 +79,7 @@ export const Dashboard: React.FC<AdminViewProps> = async ({
return {
slug,
data,
lockDuration,
}
}),
)

View File

@@ -65,6 +65,7 @@ export const DefaultEditView: React.FC = () => {
initialState,
isEditing,
isInitializing,
lastUpdateTime,
onDelete,
onDrawerCreate,
onDuplicate,
@@ -110,9 +111,13 @@ export const DefaultEditView: React.FC = () => {
const docConfig = collectionConfig || globalConfig
const lockDocumentsProp = docConfig?.lockDocuments !== undefined ? docConfig?.lockDocuments : true
const isLockingEnabled = lockDocumentsProp !== false
const lockDurationDefault = 300 // Default 5 minutes in seconds
const lockDuration =
typeof lockDocumentsProp === 'object' ? lockDocumentsProp.duration : lockDurationDefault
const lockDurationInMilliseconds = lockDuration * 1000
let preventLeaveWithoutSaving = true
if (collectionConfig) {
@@ -130,6 +135,12 @@ export const DefaultEditView: React.FC = () => {
const [isReadOnlyForIncomingUser, setIsReadOnlyForIncomingUser] = useState(false)
const [showTakeOverModal, setShowTakeOverModal] = useState(false)
const [editSessionStartTime, setEditSessionStartTime] = useState(Date.now())
const lockExpiryTime = lastUpdateTime + lockDurationInMilliseconds
const isLockExpired = Date.now() > lockExpiryTime
const documentLockStateRef = useRef<{
hasShownLockedModal: boolean
isLocked: boolean
@@ -140,8 +151,6 @@ export const DefaultEditView: React.FC = () => {
user: null,
})
const [lastUpdateTime, setLastUpdateTime] = useState(Date.now())
const classes = [baseClass, (id || globalSlug) && `${baseClass}--is-editing`]
if (globalSlug) {
@@ -230,12 +239,12 @@ export const DefaultEditView: React.FC = () => {
const onChange: FormProps['onChange'][0] = useCallback(
async ({ formState: prevFormState }) => {
const currentTime = Date.now()
const timeSinceLastUpdate = currentTime - lastUpdateTime
const timeSinceLastUpdate = currentTime - editSessionStartTime
const updateLastEdited = isLockingEnabled && timeSinceLastUpdate >= 10000 // 10 seconds
if (updateLastEdited) {
setLastUpdateTime(currentTime)
setEditSessionStartTime(currentTime)
}
const docPreferences = await getDocPreferences()
@@ -283,6 +292,7 @@ export const DefaultEditView: React.FC = () => {
[
apiRoute,
collectionSlug,
editSessionStartTime,
schemaPath,
getDocPreferences,
globalSlug,
@@ -294,7 +304,6 @@ export const DefaultEditView: React.FC = () => {
setCurrentEditor,
isLockingEnabled,
setDocumentIsLocked,
lastUpdateTime,
],
)
@@ -346,7 +355,8 @@ export const DefaultEditView: React.FC = () => {
currentEditor.id !== user?.id &&
!isReadOnlyForIncomingUser &&
!showTakeOverModal &&
!documentLockStateRef.current?.hasShownLockedModal
!documentLockStateRef.current?.hasShownLockedModal &&
!isLockExpired
return (
<main className={classes.filter(Boolean).join(' ')}>

View File

@@ -104,7 +104,10 @@ export const ListView: React.FC<AdminViewProps> = async ({
const sort =
query?.sort && typeof query.sort === 'string'
? query.sort
: listPreferences?.sort || collectionConfig.defaultSort || undefined
: listPreferences?.sort ||
(typeof collectionConfig.defaultSort === 'string'
? collectionConfig.defaultSort
: undefined)
const data = await payload.find({
collection: collectionSlug,

View File

@@ -85,6 +85,7 @@ const PreviewView: React.FC<Props> = ({
initialState,
isEditing,
isInitializing,
lastUpdateTime,
onSave: onSaveFromProps,
setCurrentEditor,
setDocumentIsLocked,
@@ -109,12 +110,22 @@ const PreviewView: React.FC<Props> = ({
const docConfig = collectionConfig || globalConfig
const lockDocumentsProp = docConfig?.lockDocuments !== undefined ? docConfig?.lockDocuments : true
const isLockingEnabled = lockDocumentsProp !== false
const lockDurationDefault = 300 // Default 5 minutes in seconds
const lockDuration =
typeof lockDocumentsProp === 'object' ? lockDocumentsProp.duration : lockDurationDefault
const lockDurationInMilliseconds = lockDuration * 1000
const [isReadOnlyForIncomingUser, setIsReadOnlyForIncomingUser] = useState(false)
const [showTakeOverModal, setShowTakeOverModal] = useState(false)
const [editSessionStartTime, setEditSessionStartTime] = useState(Date.now())
const lockExpiryTime = lastUpdateTime + lockDurationInMilliseconds
const isLockExpired = Date.now() > lockExpiryTime
const documentLockStateRef = useRef<{
hasShownLockedModal: boolean
isLocked: boolean
@@ -125,8 +136,6 @@ const PreviewView: React.FC<Props> = ({
user: null,
})
const [lastUpdateTime, setLastUpdateTime] = useState(Date.now())
const onSave = useCallback(
(json) => {
reportUpdate({
@@ -170,12 +179,12 @@ const PreviewView: React.FC<Props> = ({
const onChange: FormProps['onChange'][0] = useCallback(
async ({ formState: prevFormState }) => {
const currentTime = Date.now()
const timeSinceLastUpdate = currentTime - lastUpdateTime
const timeSinceLastUpdate = currentTime - editSessionStartTime
const updateLastEdited = isLockingEnabled && timeSinceLastUpdate >= 10000 // 10 seconds
if (updateLastEdited) {
setLastUpdateTime(currentTime)
setEditSessionStartTime(currentTime)
}
const docPreferences = await getDocPreferences()
@@ -222,12 +231,12 @@ const PreviewView: React.FC<Props> = ({
},
[
collectionSlug,
editSessionStartTime,
globalSlug,
serverURL,
apiRoute,
id,
isLockingEnabled,
lastUpdateTime,
operation,
schemaPath,
getDocPreferences,
@@ -286,7 +295,8 @@ const PreviewView: React.FC<Props> = ({
!isReadOnlyForIncomingUser &&
!showTakeOverModal &&
// eslint-disable-next-line react-compiler/react-compiler
!documentLockStateRef.current?.hasShownLockedModal
!documentLockStateRef.current?.hasShownLockedModal &&
!isLockExpired
return (
<OperationProvider operation={operation}>

View File

@@ -18,10 +18,10 @@ Payload Cloud provides a caching for all upload collections by default through C
Add the plugin to your Payload config
`yarn add @payloadcms/plugin-cloud`
`yarn add @payloadcms/payload-cloud`
```ts
import { payloadCloud } from '@payloadcms/plugin-cloud'
import { payloadCloud } from '@payloadcms/payload-cloud'
import { buildConfig } from 'payload'
export default buildConfig({

View File

@@ -1,12 +1,12 @@
{
"name": "@payloadcms/plugin-cloud",
"version": "3.0.0-beta.118",
"name": "@payloadcms/payload-cloud",
"version": "3.0.0-beta.120",
"description": "The official Payload Cloud plugin",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",
"url": "https://github.com/payloadcms/payload.git",
"directory": "packages/plugin-cloud"
"directory": "packages/payload-cloud"
},
"license": "MIT",
"type": "module",

View File

@@ -22,6 +22,7 @@ export const payloadCloudEmail = async (
// Check if already has email configuration
if (args.config.email) {
// eslint-disable-next-line no-console
console.log(
'Payload Cloud Email is enabled but email configuration is already provided in Payload config. If this is intentional, set `email: false` in the Payload Cloud plugin options.',
)
@@ -37,6 +38,7 @@ export const payloadCloudEmail = async (
const customDomains = customDomainEnvs.map((e) => process.env[e]).filter(Boolean)
if (customDomains.length) {
// eslint-disable-next-line no-console
console.log(
`Configuring Payload Cloud Email for ${[defaultDomain, ...(customDomains || [])].join(', ')}`,
)

View File

@@ -30,7 +30,7 @@ export const authAsCognitoUser = async (
const result: CognitoUserSession = await new Promise((resolve, reject) => {
cognitoUser.authenticateUser(authenticationDetails, {
onFailure: (err) => {
onFailure: (err: Error) => {
reject(err)
},
onSuccess: (res) => {

View File

@@ -43,7 +43,7 @@ export const getStorageClient: GetStorageClient = async () => {
const credentials = await cognitoIdentity.config.credentials()
// @ts-expect-error
// @ts-expect-error - Incorrect AWS types
identityID = credentials.identityId
storageClient = new AWS.S3({

View File

@@ -1,6 +1,6 @@
{
"name": "payload",
"version": "3.0.0-beta.118",
"version": "3.0.0-beta.120",
"description": "Node, React, Headless CMS and Application Framework built on Next.js",
"keywords": [
"admin panel",

View File

@@ -19,6 +19,10 @@ import type {
type RadioFieldClientWithoutType = MarkOptional<RadioFieldClient, 'type'>
type RadioFieldBaseClientProps = {
/**
* Threaded through to the setValue function from the form context when the value changes
*/
readonly disableModifyingForm?: boolean
readonly onChange?: OnChange
readonly validate?: RadioFieldValidation
readonly value?: string

View File

@@ -71,6 +71,7 @@ export const sanitizeCollection = async (
disableBulkEdit: true,
hidden: true,
},
index: true,
label: ({ t }) => t('general:updatedAt'),
})
}

View File

@@ -38,7 +38,7 @@ import type {
TypedAuthOperations,
TypedCollection,
} from '../../index.js'
import type { PayloadRequest, RequestContext } from '../../types/index.js'
import type { PayloadRequest, RequestContext, Sort } from '../../types/index.js'
import type { SanitizedUploadConfig, UploadConfig } from '../../uploads/types.js'
import type {
IncomingCollectionVersions,
@@ -375,7 +375,7 @@ export type CollectionConfig<TSlug extends CollectionSlug = any> = {
/**
* Default field to sort by in collection list view
*/
defaultSort?: string
defaultSort?: Sort
/**
* When true, do not show the "Duplicate" button while editing documents within this collection and prevent `duplicate` from all APIs
*/

View File

@@ -1,7 +1,7 @@
import type { AccessResult } from '../../config/types.js'
import type { PaginatedDocs } from '../../database/types.js'
import type { CollectionSlug, JoinQuery } from '../../index.js'
import type { PayloadRequest, Where } from '../../types/index.js'
import type { PayloadRequest, Sort, Where } from '../../types/index.js'
import type { Collection, DataFromCollectionSlug } from '../config/types.js'
import executeAccess from '../../auth/executeAccess.js'
@@ -28,7 +28,7 @@ export type Arguments = {
pagination?: boolean
req?: PayloadRequest
showHiddenFields?: boolean
sort?: string
sort?: Sort
where?: Where
}
@@ -158,6 +158,13 @@ export const findOperation = async <TSlug extends CollectionSlug>(
if (includeLockStatus) {
try {
const lockDocumentsProp = collectionConfig?.lockDocuments
const lockDurationDefault = 300 // Default 5 minutes in seconds
const lockDuration =
typeof lockDocumentsProp === 'object' ? lockDocumentsProp.duration : lockDurationDefault
const lockDurationInMilliseconds = lockDuration * 1000
const lockedDocuments = await payload.find({
collection: 'payload-locked-documents',
depth: 1,
@@ -176,14 +183,27 @@ export const findOperation = async <TSlug extends CollectionSlug>(
in: result.docs.map((doc) => doc.id),
},
},
// Query where the lock is newer than the current time minus lock time
{
updatedAt: {
greater_than: new Date(new Date().getTime() - lockDurationInMilliseconds),
},
},
],
},
})
const now = new Date().getTime()
const lockedDocs = Array.isArray(lockedDocuments?.docs) ? lockedDocuments.docs : []
// Filter out stale locks
const validLockedDocs = lockedDocs.filter((lock) => {
const lastEditedAt = new Date(lock?.updatedAt).getTime()
return lastEditedAt + lockDurationInMilliseconds > now
})
result.docs = result.docs.map((doc) => {
const lockedDoc = lockedDocs.find((lock) => lock?.document?.value === doc.id)
const lockedDoc = validLockedDocs.find((lock) => lock?.document?.value === doc.id)
return {
...doc,
_isLocked: !!lockedDoc,

View File

@@ -112,6 +112,13 @@ export const findByIDOperation = async <TSlug extends CollectionSlug>(
let lockStatus = null
try {
const lockDocumentsProp = collectionConfig?.lockDocuments
const lockDurationDefault = 300 // Default 5 minutes in seconds
const lockDuration =
typeof lockDocumentsProp === 'object' ? lockDocumentsProp.duration : lockDurationDefault
const lockDurationInMilliseconds = lockDuration * 1000
const lockedDocument = await req.payload.find({
collection: 'payload-locked-documents',
depth: 1,
@@ -130,6 +137,12 @@ export const findByIDOperation = async <TSlug extends CollectionSlug>(
equals: id,
},
},
// Query where the lock is newer than the current time minus lock time
{
updatedAt: {
greater_than: new Date(new Date().getTime() - lockDurationInMilliseconds),
},
},
],
},
})

View File

@@ -1,5 +1,5 @@
import type { PaginatedDocs } from '../../database/types.js'
import type { PayloadRequest, Where } from '../../types/index.js'
import type { PayloadRequest, Sort, Where } from '../../types/index.js'
import type { TypeWithVersion } from '../../versions/types.js'
import type { Collection } from '../config/types.js'
@@ -20,7 +20,7 @@ export type Arguments = {
pagination?: boolean
req?: PayloadRequest
showHiddenFields?: boolean
sort?: string
sort?: Sort
where?: Where
}

View File

@@ -1,6 +1,6 @@
import type { PaginatedDocs } from '../../../database/types.js'
import type { CollectionSlug, JoinQuery, Payload, TypedLocale } from '../../../index.js'
import type { Document, PayloadRequest, RequestContext, Where } from '../../../types/index.js'
import type { Document, PayloadRequest, RequestContext, Sort, Where } from '../../../types/index.js'
import type { DataFromCollectionSlug } from '../../config/types.js'
import { APIError } from '../../../errors/index.js'
@@ -27,7 +27,7 @@ export type Options<TSlug extends CollectionSlug> = {
pagination?: boolean
req?: PayloadRequest
showHiddenFields?: boolean
sort?: string
sort?: Sort
user?: Document
where?: Where
}

View File

@@ -1,6 +1,6 @@
import type { PaginatedDocs } from '../../../database/types.js'
import type { CollectionSlug, Payload, TypedLocale } from '../../../index.js'
import type { Document, PayloadRequest, RequestContext, Where } from '../../../types/index.js'
import type { Document, PayloadRequest, RequestContext, Sort, Where } from '../../../types/index.js'
import type { TypeWithVersion } from '../../../versions/types.js'
import type { DataFromCollectionSlug } from '../../config/types.js'
@@ -23,7 +23,7 @@ export type Options<TSlug extends CollectionSlug> = {
page?: number
req?: PayloadRequest
showHiddenFields?: boolean
sort?: string
sort?: Sort
user?: Document
where?: Where
}

View File

@@ -1,5 +1,5 @@
import type { TypeWithID } from '../collections/config/types.js'
import type { Document, JoinQuery, Payload, PayloadRequest, Where } from '../types/index.js'
import type { Document, JoinQuery, Payload, PayloadRequest, Sort, Where } from '../types/index.js'
import type { TypeWithVersion } from '../versions/types.js'
export type { TypeWithVersion }
@@ -180,7 +180,7 @@ export type QueryDraftsArgs = {
page?: number
pagination?: boolean
req: PayloadRequest
sort?: string
sort?: Sort
where?: Where
}
@@ -207,7 +207,7 @@ export type FindArgs = {
projection?: Record<string, unknown>
req: PayloadRequest
skip?: number
sort?: string
sort?: Sort
versions?: boolean
where?: Where
}
@@ -230,7 +230,7 @@ type BaseVersionArgs = {
pagination?: boolean
req: PayloadRequest
skip?: number
sort?: string
sort?: Sort
versions?: boolean
where?: Where
}

View File

@@ -121,6 +121,7 @@ import type {
JSONFieldValidation,
PointFieldValidation,
RadioFieldValidation,
Sort,
TextareaFieldValidation,
} from '../../index.js'
import type { DocumentPreferences } from '../../preferences/types.js'
@@ -1452,6 +1453,8 @@ export type JoinField = {
* The slug of the collection to relate with.
*/
collection: CollectionSlug
defaultLimit?: number
defaultSort?: Sort
defaultValue?: never
/**
* This does not need to be set and will be overridden by the relationship field's hasMany property.

View File

@@ -87,6 +87,7 @@ export const findOneOperation = async <T extends Record<string, unknown>>(
}
doc._isLocked = !!lockStatus
doc._lastEditedAt = lockStatus?.updatedAt ?? null
doc._userEditing = lockStatus?.user?.value ?? null
}

View File

@@ -1,5 +1,5 @@
import type { PaginatedDocs } from '../../database/types.js'
import type { PayloadRequest, Where } from '../../types/index.js'
import type { PayloadRequest, Sort, Where } from '../../types/index.js'
import type { TypeWithVersion } from '../../versions/types.js'
import type { SanitizedGlobalConfig } from '../config/types.js'
@@ -20,7 +20,7 @@ export type Arguments = {
pagination?: boolean
req?: PayloadRequest
showHiddenFields?: boolean
sort?: string
sort?: Sort
where?: Where
}

View File

@@ -1,6 +1,6 @@
import type { PaginatedDocs } from '../../../database/types.js'
import type { GlobalSlug, Payload, RequestContext, TypedLocale } from '../../../index.js'
import type { Document, PayloadRequest, Where } from '../../../types/index.js'
import type { Document, PayloadRequest, Sort, Where } from '../../../types/index.js'
import type { TypeWithVersion } from '../../../versions/types.js'
import type { DataFromGlobalSlug } from '../../config/types.js'
@@ -19,7 +19,7 @@ export type Options<TSlug extends GlobalSlug> = {
req?: PayloadRequest
showHiddenFields?: boolean
slug: TSlug
sort?: string
sort?: Sort
user?: Document
where?: Where
}

View File

@@ -110,6 +110,8 @@ export type Where = {
or?: Where[]
}
export type Sort = Array<string> | string
/**
* Applies pagination for join fields for including collection relationships
*/

View File

@@ -20,6 +20,7 @@ export const buildVersionGlobalFields = (
admin: {
disabled: true,
},
index: true,
},
{
name: 'updatedAt',
@@ -27,6 +28,7 @@ export const buildVersionGlobalFields = (
admin: {
disabled: true,
},
index: true,
},
]

View File

@@ -1,4 +1,5 @@
import type { SanitizedCollectionConfig } from '../../collections/config/types.js'
import type { Sort } from '../../types/index.js'
/**
* Takes the incoming sort argument and prefixes it with `versions.` and preserves any `-` prefixes for descending order
@@ -9,8 +10,8 @@ export const getQueryDraftsSort = ({
sort,
}: {
collectionConfig: SanitizedCollectionConfig
sort: string
}): string => {
sort?: Sort
}): Sort => {
if (!sort) {
if (collectionConfig.defaultSort) {
sort = collectionConfig.defaultSort
@@ -19,17 +20,24 @@ export const getQueryDraftsSort = ({
}
}
let direction = ''
let orderBy = sort
if (sort[0] === '-') {
direction = '-'
orderBy = sort.substring(1)
if (typeof sort === 'string') {
sort = [sort]
}
if (orderBy === 'id') {
return `${direction}parent`
}
return sort.map((field: string) => {
let orderBy: string
let direction = ''
if (field[0] === '-') {
orderBy = field.substring(1)
direction = '-'
} else {
orderBy = field
}
return `${direction}version.${orderBy}`
if (orderBy === 'id') {
return `${direction}parent`
}
return `${direction}version.${orderBy}`
})
}

View File

@@ -11,7 +11,7 @@ export const PAYLOAD_PACKAGE_LIST = [
'@payloadcms/live-preview',
'@payloadcms/next/utilities',
'@payloadcms/plugin-cloud-storage',
'@payloadcms/plugin-cloud',
'@payloadcms/payload-cloud',
'@payloadcms/plugin-form-builder',
'@payloadcms/plugin-nested-docs',
'@payloadcms/plugin-redirects',

View File

@@ -82,6 +82,8 @@ export const saveVersion = async ({
const data: Record<string, unknown> = {
createdAt: new Date(latestVersion.createdAt).toISOString(),
latest: true,
parent: id,
updatedAt: now,
version: {
...versionData,

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-cloud-storage",
"version": "3.0.0-beta.118",
"version": "3.0.0-beta.120",
"description": "The official cloud storage plugin for Payload CMS",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1 +0,0 @@
export const payloadCloud = () => (config) => config

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-form-builder",
"version": "3.0.0-beta.118",
"version": "3.0.0-beta.120",
"description": "Form builder plugin for Payload CMS",
"keywords": [
"payload",

View File

@@ -2,7 +2,7 @@
import type { Data, TextFieldClientComponent } from 'payload'
import { TextField, useLocale, useWatchForm } from '@payloadcms/ui'
import { TextField, useFieldProps, useLocale, useWatchForm } from '@payloadcms/ui'
import React, { useEffect, useState } from 'react'
type FieldWithID = {
@@ -17,13 +17,15 @@ export const DynamicPriceSelector: TextFieldClientComponent = (props) => {
const locale = useLocale()
const { path } = useFieldProps()
const [isNumberField, setIsNumberField] = useState<boolean>()
const [valueType, setValueType] = useState<'static' | 'valueOfField'>()
// only number fields can use 'valueOfField`
useEffect(() => {
if (field?._path) {
const parentPath = field._path.split('.').slice(0, -1).join('.')
if (path) {
const parentPath = path.split('.').slice(0, -1).join('.')
const paymentFieldData: any = getDataByPath(parentPath)
if (paymentFieldData) {
@@ -40,7 +42,7 @@ export const DynamicPriceSelector: TextFieldClientComponent = (props) => {
}
}
}
}, [fields, field._path, getDataByPath, getData])
}, [fields, getDataByPath, getData, path])
// TODO: make this a number field, block by Payload
if (valueType === 'static') {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-nested-docs",
"version": "3.0.0-beta.118",
"version": "3.0.0-beta.120",
"description": "The official Nested Docs plugin for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-redirects",
"version": "3.0.0-beta.118",
"version": "3.0.0-beta.120",
"description": "Redirects plugin for Payload",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-search",
"version": "3.0.0-beta.118",
"version": "3.0.0-beta.120",
"description": "Search plugin for Payload",
"keywords": [
"payload",

Some files were not shown because too many files have changed in this diff Show More