Compare commits
25 Commits
v3.0.0-bet
...
v3.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43fcccab93 | ||
|
|
e74906f555 | ||
|
|
1e002acce9 | ||
|
|
7a7a2f3918 | ||
|
|
f0edbb79f9 | ||
|
|
3605da1e3f | ||
|
|
7127c5507c | ||
|
|
00ed66b4ee | ||
|
|
44e52b0305 | ||
|
|
aea1b41f90 | ||
|
|
a8569b9e78 | ||
|
|
07a8a37fbd | ||
|
|
6c2eecc47e | ||
|
|
4f88fb046f | ||
|
|
1b1dc82cfb | ||
|
|
2df8f94a75 | ||
|
|
e72e81c3da | ||
|
|
b1b571d53e | ||
|
|
085e127217 | ||
|
|
4d44c378ed | ||
|
|
6e919cc83a | ||
|
|
1a15425b59 | ||
|
|
9069bd3fac | ||
|
|
2e11068f49 | ||
|
|
749a7d9131 |
1
.github/workflows/pr-title.yml
vendored
1
.github/workflows/pr-title.yml
vendored
@@ -5,7 +5,6 @@ on:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- synchronize
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
@@ -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. |
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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._
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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\"",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}`
|
||||
|
||||
@@ -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}`,
|
||||
|
||||
@@ -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}`,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ export const ToggleTheme: React.FC = () => {
|
||||
|
||||
return (
|
||||
<RadioGroupField
|
||||
disableModifyingForm={true}
|
||||
field={{
|
||||
name: 'theme',
|
||||
label: t('general:adminTheme'),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -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(' ')}>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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({
|
||||
@@ -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",
|
||||
@@ -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(', ')}`,
|
||||
)
|
||||
@@ -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) => {
|
||||
@@ -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({
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -71,6 +71,7 @@ export const sanitizeCollection = async (
|
||||
disableBulkEdit: true,
|
||||
hidden: true,
|
||||
},
|
||||
index: true,
|
||||
label: ({ t }) => t('general:updatedAt'),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -110,6 +110,8 @@ export type Where = {
|
||||
or?: Where[]
|
||||
}
|
||||
|
||||
export type Sort = Array<string> | string
|
||||
|
||||
/**
|
||||
* Applies pagination for join fields for including collection relationships
|
||||
*/
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -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}`
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export const payloadCloud = () => (config) => config
|
||||
@@ -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",
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user