Compare commits
33 Commits
v3.34.0
...
fix/virtua
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
219aebbec0 | ||
|
|
df7a3692f7 | ||
|
|
b750ba4509 | ||
|
|
d55306980e | ||
|
|
34ea6ec14f | ||
|
|
17d5168728 | ||
|
|
ed50a79643 | ||
|
|
0a59707ea0 | ||
|
|
bcbb912d50 | ||
|
|
1c99f46e4f | ||
|
|
c877b1ad43 | ||
|
|
4426625b83 | ||
|
|
23628996d0 | ||
|
|
b9832f40e4 | ||
|
|
a675c04c99 | ||
|
|
e79b20363e | ||
|
|
21599b87f5 | ||
|
|
e90ff72b37 | ||
|
|
babf4f965d | ||
|
|
6572bf4ae1 | ||
|
|
da7be35a15 | ||
|
|
55d00e2b1d | ||
|
|
5b554e5256 | ||
|
|
85e6edf21e | ||
|
|
b354d00aa4 | ||
|
|
c661d33b13 | ||
|
|
6b349378e0 | ||
|
|
39462bc6b9 | ||
|
|
3a7cd717b2 | ||
|
|
3287f7062f | ||
|
|
a9eca3a785 | ||
|
|
71e3c7839b | ||
|
|
a66f90ebb6 |
8
.github/workflows/main.yml
vendored
8
.github/workflows/main.yml
vendored
@@ -294,14 +294,10 @@ jobs:
|
||||
- fields__collections__Email
|
||||
- fields__collections__Indexed
|
||||
- fields__collections__JSON
|
||||
- fields__collections__Lexical__e2e__main
|
||||
- fields__collections__Lexical__e2e__blocks
|
||||
- fields__collections__Lexical__e2e__blocks#config.blockreferences.ts
|
||||
- fields__collections__Number
|
||||
- fields__collections__Point
|
||||
- fields__collections__Radio
|
||||
- fields__collections__Relationship
|
||||
- fields__collections__RichText
|
||||
- fields__collections__Row
|
||||
- fields__collections__Select
|
||||
- fields__collections__Tabs
|
||||
@@ -310,6 +306,10 @@ jobs:
|
||||
- fields__collections__UI
|
||||
- fields__collections__Upload
|
||||
- hooks
|
||||
- lexical__collections__Lexical__e2e__main
|
||||
- lexical__collections__Lexical__e2e__blocks
|
||||
- lexical__collections__Lexical__e2e__blocks#config.blockreferences.ts
|
||||
- lexical__collections__RichText
|
||||
- query-presets
|
||||
- form-state
|
||||
- live-preview
|
||||
|
||||
@@ -158,7 +158,7 @@ mutation {
|
||||
|
||||
```ts
|
||||
const result = await payload.login({
|
||||
collection: '[collection-slug]',
|
||||
collection: 'collection-slug',
|
||||
data: {
|
||||
email: 'dev@payloadcms.com',
|
||||
password: 'get-out',
|
||||
@@ -166,6 +166,13 @@ const result = await payload.login({
|
||||
})
|
||||
```
|
||||
|
||||
<Banner type="success">
|
||||
**Server Functions:** Payload offers a ready-to-use `login` server function
|
||||
that utilizes the Local API. For integration details and examples, check out
|
||||
the [Server Function
|
||||
docs](../local-api/server-functions#reusable-payload-server-functions).
|
||||
</Banner>
|
||||
|
||||
## Logout
|
||||
|
||||
As Payload sets HTTP-only cookies, logging out cannot be done by just removing a cookie in JavaScript, as HTTP-only cookies are inaccessible by JS within the browser. So, Payload exposes a `logout` operation to delete the token in a safe way.
|
||||
@@ -189,6 +196,13 @@ mutation {
|
||||
}
|
||||
```
|
||||
|
||||
<Banner type="success">
|
||||
**Server Functions:** Payload provides a ready-to-use `logout` server function
|
||||
that manages the user's cookie for a seamless logout. For integration details
|
||||
and examples, check out the [Server Function
|
||||
docs](../local-api/server-functions#reusable-payload-server-functions).
|
||||
</Banner>
|
||||
|
||||
## Refresh
|
||||
|
||||
Allows for "refreshing" JWTs. If your user has a token that is about to expire, but the user is still active and using the app, you might want to use the `refresh` operation to receive a new token by executing this operation via the authenticated user.
|
||||
@@ -240,6 +254,13 @@ mutation {
|
||||
}
|
||||
```
|
||||
|
||||
<Banner type="success">
|
||||
**Server Functions:** Payload exports a ready-to-use `refresh` server function
|
||||
that automatically renews the user's token and updates the associated cookie.
|
||||
For integration details and examples, check out the [Server Function
|
||||
docs](../local-api/server-functions#reusable-payload-server-functions).
|
||||
</Banner>
|
||||
|
||||
## Verify by Email
|
||||
|
||||
If your collection supports email verification, the Verify operation will be exposed which accepts a verification token and sets the user's `_verified` property to `true`, thereby allowing the user to authenticate with the Payload API.
|
||||
@@ -270,7 +291,7 @@ mutation {
|
||||
|
||||
```ts
|
||||
const result = await payload.verifyEmail({
|
||||
collection: '[collection-slug]',
|
||||
collection: 'collection-slug',
|
||||
token: 'TOKEN_HERE',
|
||||
})
|
||||
```
|
||||
@@ -308,7 +329,7 @@ mutation {
|
||||
|
||||
```ts
|
||||
const result = await payload.unlock({
|
||||
collection: '[collection-slug]',
|
||||
collection: 'collection-slug',
|
||||
})
|
||||
```
|
||||
|
||||
@@ -349,7 +370,7 @@ mutation {
|
||||
|
||||
```ts
|
||||
const token = await payload.forgotPassword({
|
||||
collection: '[collection-slug]',
|
||||
collection: 'collection-slug',
|
||||
data: {
|
||||
email: 'dev@payloadcms.com',
|
||||
},
|
||||
|
||||
@@ -240,8 +240,8 @@ export default buildConfig({
|
||||
// highlight-start
|
||||
cors: {
|
||||
origins: ['http://localhost:3000'],
|
||||
headers: ['x-custom-header']
|
||||
}
|
||||
headers: ['x-custom-header'],
|
||||
},
|
||||
// highlight-end
|
||||
})
|
||||
```
|
||||
|
||||
@@ -101,14 +101,15 @@ export const MyCollection: CollectionConfig = {
|
||||
|
||||
The following options are available:
|
||||
|
||||
| Path | Description |
|
||||
| ----------------- | -------------------------------------------------------------------------------------- |
|
||||
| `SaveButton` | A button that saves the current document. [More details](#savebutton). |
|
||||
| `SaveDraftButton` | A button that saves the current document as a draft. [More details](#savedraftbutton). |
|
||||
| `PublishButton` | A button that publishes the current document. [More details](#publishbutton). |
|
||||
| `PreviewButton` | A button that previews the current document. [More details](#previewbutton). |
|
||||
| `Description` | A description of the Collection. [More details](#description). |
|
||||
| `Upload` | A file upload component. [More details](#upload). |
|
||||
| Path | Description |
|
||||
| ------------------------ | ---------------------------------------------------------------------------------------------------- |
|
||||
| `beforeDocumentControls` | Inject custom components before the Save / Publish buttons. [More details](#beforedocumentcontrols). |
|
||||
| `SaveButton` | A button that saves the current document. [More details](#savebutton). |
|
||||
| `SaveDraftButton` | A button that saves the current document as a draft. [More details](#savedraftbutton). |
|
||||
| `PublishButton` | A button that publishes the current document. [More details](#publishbutton). |
|
||||
| `PreviewButton` | A button that previews the current document. [More details](#previewbutton). |
|
||||
| `Description` | A description of the Collection. [More details](#description). |
|
||||
| `Upload` | A file upload component. [More details](#upload). |
|
||||
|
||||
#### Globals
|
||||
|
||||
@@ -133,13 +134,14 @@ export const MyGlobal: GlobalConfig = {
|
||||
|
||||
The following options are available:
|
||||
|
||||
| Path | Description |
|
||||
| ----------------- | -------------------------------------------------------------------------------------- |
|
||||
| `SaveButton` | A button that saves the current document. [More details](#savebutton). |
|
||||
| `SaveDraftButton` | A button that saves the current document as a draft. [More details](#savedraftbutton). |
|
||||
| `PublishButton` | A button that publishes the current document. [More details](#publishbutton). |
|
||||
| `PreviewButton` | A button that previews the current document. [More details](#previewbutton). |
|
||||
| `Description` | A description of the Global. [More details](#description). |
|
||||
| Path | Description |
|
||||
| ------------------------ | ---------------------------------------------------------------------------------------------------- |
|
||||
| `beforeDocumentControls` | Inject custom components before the Save / Publish buttons. [More details](#beforedocumentcontrols). |
|
||||
| `SaveButton` | A button that saves the current document. [More details](#savebutton). |
|
||||
| `SaveDraftButton` | A button that saves the current document as a draft. [More details](#savedraftbutton). |
|
||||
| `PublishButton` | A button that publishes the current document. [More details](#publishbutton). |
|
||||
| `PreviewButton` | A button that previews the current document. [More details](#previewbutton). |
|
||||
| `Description` | A description of the Global. [More details](#description). |
|
||||
|
||||
### SaveButton
|
||||
|
||||
@@ -191,6 +193,73 @@ export function MySaveButton(props: SaveButtonClientProps) {
|
||||
}
|
||||
```
|
||||
|
||||
### beforeDocumentControls
|
||||
|
||||
The `beforeDocumentControls` property allows you to render custom components just before the default document action buttons (like Save, Publish, or Preview). This is useful for injecting custom buttons, status indicators, or any other UI elements before the built-in controls.
|
||||
|
||||
To add `beforeDocumentControls` components, use the `components.edit.beforeDocumentControls` property in you [Collection Config](../configuration/collections) or `components.elements.beforeDocumentControls` in your [Global Config](../configuration/globals):
|
||||
|
||||
#### Collections
|
||||
|
||||
```
|
||||
export const MyCollection: CollectionConfig = {
|
||||
admin: {
|
||||
components: {
|
||||
edit: {
|
||||
// highlight-start
|
||||
beforeDocumentControls: ['/path/to/CustomComponent'],
|
||||
// highlight-end
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
#### Globals
|
||||
|
||||
```
|
||||
export const MyGlobal: GlobalConfig = {
|
||||
admin: {
|
||||
components: {
|
||||
elements: {
|
||||
// highlight-start
|
||||
beforeDocumentControls: ['/path/to/CustomComponent'],
|
||||
// highlight-end
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Here's an example of a custom `beforeDocumentControls` component:
|
||||
|
||||
#### Server Component
|
||||
|
||||
```tsx
|
||||
import React from 'react'
|
||||
import type { BeforeDocumentControlsServerProps } from 'payload'
|
||||
|
||||
export function MyCustomDocumentControlButton(
|
||||
props: BeforeDocumentControlsServerProps,
|
||||
) {
|
||||
return <div>This is a custom beforeDocumentControl button (Server)</div>
|
||||
}
|
||||
```
|
||||
|
||||
#### Client Component
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import type { BeforeDocumentControlsClientProps } from 'payload'
|
||||
|
||||
export function MyCustomDocumentControlButton(
|
||||
props: BeforeDocumentControlsClientProps,
|
||||
) {
|
||||
return <div>This is a custom beforeDocumentControl button (Client)</div>
|
||||
}
|
||||
```
|
||||
|
||||
### SaveDraftButton
|
||||
|
||||
The `SaveDraftButton` property allows you to render a custom Save Draft Button in the Edit View.
|
||||
|
||||
@@ -352,18 +352,20 @@ const config = buildConfig({
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
{
|
||||
slug: 'collection2',
|
||||
fields: [
|
||||
{
|
||||
name: 'editor',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
BlocksFeature({
|
||||
// Same reference can be reused anywhere, even in the lexical editor, without incurred performance hit
|
||||
blocks: ['TextBlock'],
|
||||
})
|
||||
})
|
||||
features: [
|
||||
BlocksFeature({
|
||||
// Same reference can be reused anywhere, even in the lexical editor, without incurred performance hit
|
||||
blocks: ['TextBlock'],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -541,6 +541,7 @@ The `ctx` object:
|
||||
| Property | Description |
|
||||
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **`blockData`** | The nearest parent block's data. If the field is not inside a block, this will be `undefined`. |
|
||||
| **`operation`** | A string relating to which operation the field type is currently executing within. |
|
||||
| **`path`** | The full path to the field in the schema, represented as an array of string segments, including array indexes. I.e `['group', 'myArray', '1', 'textField']`. |
|
||||
| **`user`** | The currently authenticated user object. |
|
||||
|
||||
|
||||
@@ -94,6 +94,7 @@ The Relationship Field inherits all of the default options from the base [Field
|
||||
| **`allowCreate`** | Set to `false` if you'd like to disable the ability to create new documents from within the relationship field. |
|
||||
| **`allowEdit`** | Set to `false` if you'd like to disable the ability to edit documents from within the relationship field. |
|
||||
| **`sortOptions`** | Define a default sorting order for the options within a Relationship field's dropdown. [More](#sort-options) |
|
||||
| **`appearance`** | Set to `drawer` or `select` to change the behavior of the field. Defaults to `select`. |
|
||||
|
||||
### Sort Options
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ To install a Database Adapter, you can run **one** of the following commands:
|
||||
```
|
||||
|
||||
- To install the [Postgres Adapter](../database/postgres), run:
|
||||
|
||||
```bash
|
||||
pnpm i @payloadcms/db-postgres
|
||||
```
|
||||
@@ -80,7 +81,7 @@ To install a Database Adapter, you can run **one** of the following commands:
|
||||
|
||||
#### 2. Copy Payload files into your Next.js app folder
|
||||
|
||||
Payload installs directly in your Next.js `/app` folder, and you'll need to place some files into that folder for Payload to run. You can copy these files from the [Blank Template](https://github.com/payloadcms/payload/tree/main/templates/blank/src/app/(payload)) on GitHub. Once you have the required Payload files in place in your `/app` folder, you should have something like this:
|
||||
Payload installs directly in your Next.js `/app` folder, and you'll need to place some files into that folder for Payload to run. You can copy these files from the [Blank Template](<https://github.com/payloadcms/payload/tree/main/templates/blank/src/app/(payload)>) on GitHub. Once you have the required Payload files in place in your `/app` folder, you should have something like this:
|
||||
|
||||
```plaintext
|
||||
app/
|
||||
|
||||
@@ -310,7 +310,171 @@ export const PostForm: React.FC = () => {
|
||||
|
||||
## Reusable Payload Server Functions
|
||||
|
||||
Coming soon…
|
||||
Managing authentication with the Local API can be tricky as you have to handle cookies and tokens yourself, and there aren't built-in logout or refresh functions since these only modify cookies. To make this easier, we provide `login`, `logout`, and `refresh` as ready-to-use server functions. They take care of the underlying complexity so you don't have to.
|
||||
|
||||
### Login
|
||||
|
||||
Logs in a user by verifying credentials and setting the authentication cookie. This function allows login via username or email, depending on the collection auth configuration.
|
||||
|
||||
#### Importing the `login` function
|
||||
|
||||
```ts
|
||||
import { login } from '@payloadcms/next/auth'
|
||||
```
|
||||
|
||||
The login function needs your Payload config, which cannot be imported in a client component. To work around this, create a simple server function like the one below, and call it from your client.
|
||||
|
||||
```ts
|
||||
'use server'
|
||||
|
||||
import { login } from '@payloadcms/next/auth'
|
||||
import config from '@payload-config'
|
||||
|
||||
export async function loginAction({
|
||||
email,
|
||||
password,
|
||||
}: {
|
||||
email: string
|
||||
password: string
|
||||
}) {
|
||||
try {
|
||||
const result = await login({
|
||||
collection: 'users',
|
||||
config,
|
||||
email,
|
||||
password,
|
||||
})
|
||||
return result
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Login failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Login from the React Client Component
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { loginAction } from '../loginAction'
|
||||
|
||||
export default function LoginForm() {
|
||||
const [email, setEmail] = useState<string>('')
|
||||
const [password, setPassword] = useState<string>('')
|
||||
|
||||
return (
|
||||
<form onSubmit={() => loginAction({ email, password })}>
|
||||
<label htmlFor="email">Email</label>
|
||||
<input
|
||||
id="email"
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setEmail(e.target.value)
|
||||
}
|
||||
type="email"
|
||||
value={email}
|
||||
/>
|
||||
<label htmlFor="password">Password</label>
|
||||
<input
|
||||
id="password"
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setPassword(e.target.value)
|
||||
}
|
||||
type="password"
|
||||
value={password}
|
||||
/>
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Logout
|
||||
|
||||
Logs out the current user by clearing the authentication cookie.
|
||||
|
||||
#### Importing the `logout` function
|
||||
|
||||
```ts
|
||||
import { logout } from '@payloadcms/next/auth'
|
||||
```
|
||||
|
||||
Similar to the login function, you now need to pass your Payload config to this function and this cannot be done in a client component. Use a helper server function as shown below.
|
||||
|
||||
```ts
|
||||
'use server'
|
||||
|
||||
import { logout } from '@payloadcms/next/auth'
|
||||
import config from '@payload-config'
|
||||
|
||||
export async function logoutAction() {
|
||||
try {
|
||||
return await logout({ config })
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Logout failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Logout from the React Client Component
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import { logoutAction } from '../logoutAction'
|
||||
|
||||
export default function LogoutButton() {
|
||||
return <button onClick={() => logoutFunction()}>Logout</button>
|
||||
}
|
||||
```
|
||||
|
||||
### Refresh
|
||||
|
||||
Refreshes the authentication token for the logged-in user.
|
||||
|
||||
#### Importing the `refresh` function
|
||||
|
||||
```ts
|
||||
import { refresh } from '@payloadcms/next/auth'
|
||||
```
|
||||
|
||||
As with login and logout, you need to pass your Payload config to this function. Create a helper server function like the one below. Passing the config directly to the client is not possible and will throw errors.
|
||||
|
||||
```ts
|
||||
'use server'
|
||||
|
||||
import { refresh } from '@payloadcms/next/auth'
|
||||
import config from '@payload-config'
|
||||
|
||||
export async function refreshAction() {
|
||||
try {
|
||||
return await refresh({
|
||||
collection: 'users', // pass your collection slug
|
||||
config,
|
||||
})
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Refresh failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Using Refresh from the React Client Component
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import { refreshAction } from '../actions/refreshAction'
|
||||
|
||||
export default function RefreshTokenButton() {
|
||||
return <button onClick={() => refreshFunction()}>Refresh</button>
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling in Server Functions
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ Collections and Globals both support the same options for configuring autosave.
|
||||
| Drafts Autosave Options | Description |
|
||||
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `interval` | Define an `interval` in milliseconds to automatically save progress while documents are edited. Document updates are "debounced" at this interval. Defaults to `800`. |
|
||||
| `showSaveDraftButton` | Set this to `true` to show the "Save as draft" button even while autosave is enabled. Defaults to `false`. |
|
||||
|
||||
**Example config with versions, drafts, and autosave enabled:**
|
||||
|
||||
@@ -50,9 +51,13 @@ export const Pages: CollectionConfig = {
|
||||
drafts: {
|
||||
autosave: true,
|
||||
|
||||
// Alternatively, you can specify an `interval`:
|
||||
// Alternatively, you can specify an object to customize autosave:
|
||||
// autosave: {
|
||||
// Define how often the document should be autosaved (in milliseconds)
|
||||
// interval: 1500,
|
||||
//
|
||||
// Show the "Save as draft" button even while autosave is enabled
|
||||
// showSaveDraftButton: true,
|
||||
// },
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload-monorepo",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/admin-bar",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "An admin bar for React apps using Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { Connect, Migration, Payload } from 'payload'
|
||||
|
||||
import { pushDevSchema } from '@payloadcms/drizzle'
|
||||
import { drizzle } from 'drizzle-orm/node-postgres'
|
||||
import pg from 'pg'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
@@ -61,7 +60,7 @@ export const connect: Connect = async function connect(
|
||||
|
||||
try {
|
||||
if (!this.pool) {
|
||||
this.pool = new pg.Pool(this.poolOptions)
|
||||
this.pool = new this.pg.Pool(this.poolOptions)
|
||||
await connectWithReconnect({ adapter: this, payload: this.payload })
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ import {
|
||||
} from '@payloadcms/drizzle/postgres'
|
||||
import { pgEnum, pgSchema, pgTable } from 'drizzle-orm/pg-core'
|
||||
import { createDatabaseAdapter, defaultBeginTransaction } from 'payload'
|
||||
import pgDependency from 'pg'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import type { Args, PostgresAdapter } from './types.js'
|
||||
@@ -130,6 +131,7 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
|
||||
localesSuffix: args.localesSuffix || '_locales',
|
||||
logger: args.logger,
|
||||
operators: operatorMap,
|
||||
pg: args.pg || pgDependency,
|
||||
pgSchema: adapterSchema,
|
||||
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
|
||||
pool: undefined,
|
||||
|
||||
@@ -12,6 +12,8 @@ import type { NodePgDatabase } from 'drizzle-orm/node-postgres'
|
||||
import type { PgSchema, PgTableFn, PgTransactionConfig } from 'drizzle-orm/pg-core'
|
||||
import type { Pool, PoolConfig } from 'pg'
|
||||
|
||||
type PgDependency = typeof import('pg')
|
||||
|
||||
export type Args = {
|
||||
/**
|
||||
* Transform the schema after it's built.
|
||||
@@ -45,6 +47,7 @@ export type Args = {
|
||||
localesSuffix?: string
|
||||
logger?: DrizzleConfig['logger']
|
||||
migrationDir?: string
|
||||
pg?: PgDependency
|
||||
pool: PoolConfig
|
||||
prodMigrations?: {
|
||||
down: (args: MigrateDownArgs) => Promise<void>
|
||||
@@ -74,6 +77,7 @@ type ResolveSchemaType<T> = 'schema' extends keyof T
|
||||
type Drizzle = NodePgDatabase<ResolveSchemaType<GeneratedDatabaseSchema>>
|
||||
export type PostgresAdapter = {
|
||||
drizzle: Drizzle
|
||||
pg: PgDependency
|
||||
pool: Pool
|
||||
poolOptions: PoolConfig
|
||||
} & BasePostgresAdapter
|
||||
@@ -98,6 +102,8 @@ declare module 'payload' {
|
||||
initializing: Promise<void>
|
||||
localesSuffix?: string
|
||||
logger: DrizzleConfig['logger']
|
||||
/** Optionally inject your own node-postgres. This is required if you wish to instrument the driver with @payloadcms/plugin-sentry. */
|
||||
pg?: PgDependency
|
||||
pgSchema?: { table: PgTableFn } | PgSchema
|
||||
pool: Pool
|
||||
poolOptions: Args['pool']
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-sqlite",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "The officially supported SQLite database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-vercel-postgres",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "Vercel Postgres adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/drizzle",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "A library of shared functions used by different payload database adapters",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -78,7 +78,9 @@ export const createTableName = ({
|
||||
|
||||
if (result.length > 63) {
|
||||
throw new APIError(
|
||||
`Exceeded max identifier length for table or enum name of 63 characters. Invalid name: ${result}`,
|
||||
`Exceeded max identifier length for table or enum name of 63 characters. Invalid name: ${result}.
|
||||
Tip: You can use the dbName property to reduce the table name length.
|
||||
`,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -496,6 +496,10 @@ export const traverseFields = ({
|
||||
formattedValue = sql`ST_GeomFromGeoJSON(${JSON.stringify(value)})`
|
||||
}
|
||||
|
||||
if (field.type === 'text' && value && typeof value !== 'string') {
|
||||
formattedValue = JSON.stringify(value)
|
||||
}
|
||||
|
||||
if (field.type === 'date') {
|
||||
if (typeof value === 'number' && !Number.isNaN(value)) {
|
||||
formattedValue = new Date(value).toISOString()
|
||||
|
||||
@@ -131,7 +131,7 @@ export const createSchemaGenerator = ({
|
||||
let foreignKeyDeclaration = `${sanitizeObjectKey(key)}: foreignKey({
|
||||
columns: [${foreignKey.columns.map((col) => `columns['${col}']`).join(', ')}],
|
||||
foreignColumns: [${foreignKey.foreignColumns.map((col) => `${accessProperty(col.table, col.name)}`).join(', ')}],
|
||||
name: '${foreignKey.name}'
|
||||
name: '${foreignKey.name}'
|
||||
})`
|
||||
|
||||
if (foreignKey.onDelete) {
|
||||
@@ -167,11 +167,11 @@ ${Object.entries(table.columns)
|
||||
}${
|
||||
extrasDeclarations.length
|
||||
? `, (columns) => ({
|
||||
${extrasDeclarations.join('\n ')}
|
||||
${extrasDeclarations.join('\n ')}
|
||||
})`
|
||||
: ''
|
||||
}
|
||||
)
|
||||
)
|
||||
`
|
||||
|
||||
tableDeclarations.push(tableCode)
|
||||
@@ -250,7 +250,7 @@ type DatabaseSchema = {
|
||||
`
|
||||
|
||||
const finalDeclaration = `
|
||||
declare module '${this.packageName}/types' {
|
||||
declare module '${this.packageName}' {
|
||||
export interface GeneratedDatabaseSchema {
|
||||
schema: DatabaseSchema
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-nodemailer",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "Payload Nodemailer Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-resend",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "Payload Resend Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/graphql",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-react",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"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.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "The official Vue SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "The official live preview JavaScript SDK for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/next",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -37,6 +37,11 @@
|
||||
"types": "./src/exports/routes.ts",
|
||||
"default": "./src/exports/routes.ts"
|
||||
},
|
||||
"./auth": {
|
||||
"import": "./src/exports/auth.ts",
|
||||
"types": "./src/exports/auth.ts",
|
||||
"default": "./src/exports/auth.ts"
|
||||
},
|
||||
"./templates": {
|
||||
"import": "./src/exports/templates.ts",
|
||||
"types": "./src/exports/templates.ts",
|
||||
@@ -151,6 +156,11 @@
|
||||
"types": "./dist/exports/templates.d.ts",
|
||||
"default": "./dist/exports/templates.js"
|
||||
},
|
||||
"./auth": {
|
||||
"import": "./dist/exports/auth.js",
|
||||
"types": "./dist/exports/auth.d.ts",
|
||||
"default": "./dist/exports/auth.js"
|
||||
},
|
||||
"./utilities": {
|
||||
"import": "./dist/exports/utilities.js",
|
||||
"types": "./dist/exports/utilities.d.ts",
|
||||
|
||||
87
packages/next/src/auth/login.ts
Normal file
87
packages/next/src/auth/login.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
'use server'
|
||||
|
||||
import type { CollectionSlug } from 'payload'
|
||||
|
||||
import { cookies as getCookies } from 'next/headers.js'
|
||||
import { generatePayloadCookie, getPayload } from 'payload'
|
||||
|
||||
import { setPayloadAuthCookie } from '../utilities/setPayloadAuthCookie.js'
|
||||
|
||||
type LoginWithEmail = {
|
||||
collection: CollectionSlug
|
||||
config: any
|
||||
email: string
|
||||
password: string
|
||||
username?: never
|
||||
}
|
||||
|
||||
type LoginWithUsername = {
|
||||
collection: CollectionSlug
|
||||
config: any
|
||||
email?: never
|
||||
password: string
|
||||
username: string
|
||||
}
|
||||
type LoginArgs = LoginWithEmail | LoginWithUsername
|
||||
|
||||
export async function login({ collection, config, email, password, username }: LoginArgs): Promise<{
|
||||
token?: string
|
||||
user: any
|
||||
}> {
|
||||
const payload = await getPayload({ config })
|
||||
|
||||
const authConfig = payload.collections[collection]?.config.auth
|
||||
if (!authConfig) {
|
||||
throw new Error(`No auth config found for collection: ${collection}`)
|
||||
}
|
||||
|
||||
const loginWithUsername = authConfig?.loginWithUsername ?? false
|
||||
|
||||
if (loginWithUsername) {
|
||||
if (loginWithUsername.allowEmailLogin) {
|
||||
if (!email && !username) {
|
||||
throw new Error('Email or username is required.')
|
||||
}
|
||||
} else {
|
||||
if (!username) {
|
||||
throw new Error('Username is required.')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!email) {
|
||||
throw new Error('Email is required.')
|
||||
}
|
||||
}
|
||||
|
||||
let loginData
|
||||
|
||||
if (loginWithUsername) {
|
||||
loginData = username ? { password, username } : { email, password }
|
||||
} else {
|
||||
loginData = { email, password }
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await payload.login({
|
||||
collection,
|
||||
data: loginData,
|
||||
})
|
||||
|
||||
if (result.token) {
|
||||
await setPayloadAuthCookie({
|
||||
authConfig,
|
||||
cookiePrefix: payload.config.cookiePrefix,
|
||||
token: result.token,
|
||||
})
|
||||
}
|
||||
|
||||
if ('removeTokenFromResponses' in config && config.removeTokenFromResponses) {
|
||||
delete result.token
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (e) {
|
||||
console.error('Login error:', e)
|
||||
throw new Error(`${e}`)
|
||||
}
|
||||
}
|
||||
29
packages/next/src/auth/logout.ts
Normal file
29
packages/next/src/auth/logout.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
'use server'
|
||||
|
||||
import { cookies as getCookies, headers as nextHeaders } from 'next/headers.js'
|
||||
import { getPayload } from 'payload'
|
||||
|
||||
import { getExistingAuthToken } from '../utilities/getExistingAuthToken.js'
|
||||
|
||||
export async function logout({ config }: { config: any }) {
|
||||
try {
|
||||
const payload = await getPayload({ config })
|
||||
const headers = await nextHeaders()
|
||||
const result = await payload.auth({ headers })
|
||||
|
||||
if (!result.user) {
|
||||
return { message: 'User already logged out', success: true }
|
||||
}
|
||||
|
||||
const existingCookie = await getExistingAuthToken(payload.config.cookiePrefix)
|
||||
|
||||
if (existingCookie) {
|
||||
const cookies = await getCookies()
|
||||
cookies.delete(existingCookie.name)
|
||||
return { message: 'User logged out successfully', success: true }
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Logout error:', e)
|
||||
throw new Error(`${e}`)
|
||||
}
|
||||
}
|
||||
42
packages/next/src/auth/refresh.ts
Normal file
42
packages/next/src/auth/refresh.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
'use server'
|
||||
|
||||
import type { CollectionSlug } from 'payload'
|
||||
|
||||
import { headers as nextHeaders } from 'next/headers.js'
|
||||
import { getPayload } from 'payload'
|
||||
|
||||
import { getExistingAuthToken } from '../utilities/getExistingAuthToken.js'
|
||||
import { setPayloadAuthCookie } from '../utilities/setPayloadAuthCookie.js'
|
||||
|
||||
export async function refresh({ collection, config }: { collection: CollectionSlug; config: any }) {
|
||||
try {
|
||||
const payload = await getPayload({ config })
|
||||
const authConfig = payload.collections[collection]?.config.auth
|
||||
|
||||
if (!authConfig) {
|
||||
throw new Error(`No auth config found for collection: ${collection}`)
|
||||
}
|
||||
|
||||
const { user } = await payload.auth({ headers: await nextHeaders() })
|
||||
if (!user) {
|
||||
throw new Error('User not authenticated')
|
||||
}
|
||||
|
||||
const existingCookie = await getExistingAuthToken(payload.config.cookiePrefix)
|
||||
|
||||
if (!existingCookie) {
|
||||
return { message: 'No valid token found', success: false }
|
||||
}
|
||||
|
||||
await setPayloadAuthCookie({
|
||||
authConfig,
|
||||
cookiePrefix: payload.config.cookiePrefix,
|
||||
token: existingCookie.value,
|
||||
})
|
||||
|
||||
return { message: 'Token refreshed successfully', success: true }
|
||||
} catch (e) {
|
||||
console.error('Refresh error:', e)
|
||||
throw new Error(`${e}`)
|
||||
}
|
||||
}
|
||||
3
packages/next/src/exports/auth.ts
Normal file
3
packages/next/src/exports/auth.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { login } from '../auth/login.js'
|
||||
export { logout } from '../auth/logout.js'
|
||||
export { refresh } from '../auth/refresh.js'
|
||||
10
packages/next/src/utilities/getExistingAuthToken.ts
Normal file
10
packages/next/src/utilities/getExistingAuthToken.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { cookies as getCookies } from 'next/headers.js'
|
||||
|
||||
type Cookie = {
|
||||
name: string
|
||||
value: string
|
||||
}
|
||||
export async function getExistingAuthToken(cookiePrefix: string): Promise<Cookie | undefined> {
|
||||
const cookies = await getCookies()
|
||||
return cookies.getAll().find((cookie) => cookie.name.startsWith(cookiePrefix))
|
||||
}
|
||||
42
packages/next/src/utilities/setPayloadAuthCookie.ts
Normal file
42
packages/next/src/utilities/setPayloadAuthCookie.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import type { Auth } from 'payload'
|
||||
|
||||
import { cookies as getCookies } from 'next/headers.js'
|
||||
import { generatePayloadCookie } from 'payload'
|
||||
|
||||
type SetPayloadAuthCookieArgs = {
|
||||
authConfig: Auth
|
||||
cookiePrefix: string
|
||||
token: string
|
||||
}
|
||||
|
||||
export async function setPayloadAuthCookie({
|
||||
authConfig,
|
||||
cookiePrefix,
|
||||
token,
|
||||
}: SetPayloadAuthCookieArgs): Promise<void> {
|
||||
const cookies = await getCookies()
|
||||
|
||||
const cookieExpiration = authConfig.tokenExpiration
|
||||
? new Date(Date.now() + authConfig.tokenExpiration)
|
||||
: undefined
|
||||
|
||||
const payloadCookie = generatePayloadCookie({
|
||||
collectionAuthConfig: authConfig,
|
||||
cookiePrefix,
|
||||
expires: cookieExpiration,
|
||||
returnCookieAsObject: true,
|
||||
token,
|
||||
})
|
||||
|
||||
if (payloadCookie.value) {
|
||||
cookies.set(payloadCookie.name, payloadCookie.value, {
|
||||
domain: authConfig.cookies.domain,
|
||||
expires: payloadCookie.expires ? new Date(payloadCookie.expires) : undefined,
|
||||
httpOnly: true,
|
||||
sameSite: (typeof authConfig.cookies.sameSite === 'string'
|
||||
? authConfig.cookies.sameSite.toLowerCase()
|
||||
: 'lax') as 'lax' | 'none' | 'strict',
|
||||
secure: authConfig.cookies.secure || false,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ export const APIViewClient: React.FC = () => {
|
||||
|
||||
const {
|
||||
config: {
|
||||
defaultDepth,
|
||||
localization,
|
||||
routes: { api: apiRoute },
|
||||
serverURL,
|
||||
@@ -62,7 +63,9 @@ export const APIViewClient: React.FC = () => {
|
||||
const [data, setData] = React.useState<any>(initialData)
|
||||
const [draft, setDraft] = React.useState<boolean>(searchParams.get('draft') === 'true')
|
||||
const [locale, setLocale] = React.useState<string>(searchParams?.get('locale') || code)
|
||||
const [depth, setDepth] = React.useState<string>(searchParams.get('depth') || '1')
|
||||
const [depth, setDepth] = React.useState<string>(
|
||||
searchParams.get('depth') || defaultDepth.toString(),
|
||||
)
|
||||
const [authenticated, setAuthenticated] = React.useState<boolean>(true)
|
||||
const [fullscreen, setFullscreen] = React.useState<boolean>(false)
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type {
|
||||
BeforeDocumentControlsServerPropsOnly,
|
||||
DefaultServerFunctionArgs,
|
||||
DocumentSlots,
|
||||
PayloadRequest,
|
||||
@@ -42,6 +43,18 @@ export const renderDocumentSlots: (args: {
|
||||
// TODO: Add remaining serverProps
|
||||
}
|
||||
|
||||
const BeforeDocumentControls =
|
||||
collectionConfig?.admin?.components?.edit?.beforeDocumentControls ||
|
||||
globalConfig?.admin?.components?.elements?.beforeDocumentControls
|
||||
|
||||
if (BeforeDocumentControls) {
|
||||
components.BeforeDocumentControls = RenderServerComponent({
|
||||
Component: BeforeDocumentControls,
|
||||
importMap: req.payload.importMap,
|
||||
serverProps: serverProps satisfies BeforeDocumentControlsServerPropsOnly,
|
||||
})
|
||||
}
|
||||
|
||||
const CustomPreviewButton =
|
||||
collectionConfig?.admin?.components?.edit?.PreviewButton ||
|
||||
globalConfig?.admin?.components?.elements?.PreviewButton
|
||||
|
||||
@@ -31,7 +31,6 @@ export const ToolbarControls: React.FC<EditViewProps> = () => {
|
||||
<span>
|
||||
{breakpoints.find((bp) => bp.name == breakpoint)?.label ?? customOption.label}
|
||||
</span>
|
||||
|
||||
<ChevronIcon className={`${baseClass}__chevron`} />
|
||||
</React.Fragment>
|
||||
}
|
||||
@@ -82,7 +81,6 @@ export const ToolbarControls: React.FC<EditViewProps> = () => {
|
||||
button={
|
||||
<React.Fragment>
|
||||
<span>{zoom * 100}%</span>
|
||||
|
||||
<ChevronIcon className={`${baseClass}__chevron`} />
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/payload-cloud",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "The official Payload Cloud plugin",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "Node, React, Headless CMS and Application Framework built on Next.js",
|
||||
"keywords": [
|
||||
"admin panel",
|
||||
|
||||
@@ -553,6 +553,7 @@ export type FieldRow = {
|
||||
}
|
||||
|
||||
export type DocumentSlots = {
|
||||
BeforeDocumentControls?: React.ReactNode
|
||||
Description?: React.ReactNode
|
||||
PreviewButton?: React.ReactNode
|
||||
PublishButton?: React.ReactNode
|
||||
@@ -578,6 +579,9 @@ export type { LanguageOptions } from './LanguageOptions.js'
|
||||
export type { RichTextAdapter, RichTextAdapterProvider, RichTextHooks } from './RichText.js'
|
||||
|
||||
export type {
|
||||
BeforeDocumentControlsClientProps,
|
||||
BeforeDocumentControlsServerProps,
|
||||
BeforeDocumentControlsServerPropsOnly,
|
||||
DocumentSubViewTypes,
|
||||
DocumentTabClientProps,
|
||||
/**
|
||||
|
||||
@@ -36,12 +36,12 @@ export type DocumentTabServerPropsOnly = {
|
||||
readonly permissions: SanitizedPermissions
|
||||
} & ServerProps
|
||||
|
||||
export type DocumentTabServerProps = DocumentTabClientProps & DocumentTabServerPropsOnly
|
||||
|
||||
export type DocumentTabClientProps = {
|
||||
path: string
|
||||
}
|
||||
|
||||
export type DocumentTabServerProps = DocumentTabClientProps & DocumentTabServerPropsOnly
|
||||
|
||||
export type DocumentTabCondition = (args: {
|
||||
collectionConfig: SanitizedCollectionConfig
|
||||
config: SanitizedConfig
|
||||
@@ -75,3 +75,10 @@ export type DocumentTabConfig = {
|
||||
export type DocumentTabComponent = PayloadComponent<{
|
||||
path: string
|
||||
}>
|
||||
|
||||
// BeforeDocumentControls
|
||||
|
||||
export type BeforeDocumentControlsClientProps = {}
|
||||
export type BeforeDocumentControlsServerPropsOnly = {} & ServerProps
|
||||
export type BeforeDocumentControlsServerProps = BeforeDocumentControlsClientProps &
|
||||
BeforeDocumentControlsServerPropsOnly
|
||||
|
||||
@@ -36,6 +36,7 @@ export function iterateCollections({
|
||||
addToImportMap(collection.admin?.components?.beforeListTable)
|
||||
addToImportMap(collection.admin?.components?.Description)
|
||||
|
||||
addToImportMap(collection.admin?.components?.edit?.beforeDocumentControls)
|
||||
addToImportMap(collection.admin?.components?.edit?.PreviewButton)
|
||||
addToImportMap(collection.admin?.components?.edit?.PublishButton)
|
||||
addToImportMap(collection.admin?.components?.edit?.SaveButton)
|
||||
|
||||
@@ -279,6 +279,10 @@ export type CollectionAdminOptions = {
|
||||
* Components within the edit view
|
||||
*/
|
||||
edit?: {
|
||||
/**
|
||||
* Inject custom components before the document controls
|
||||
*/
|
||||
beforeDocumentControls?: CustomComponent[]
|
||||
/**
|
||||
* Replaces the "Preview" button
|
||||
*/
|
||||
|
||||
@@ -33,9 +33,9 @@ export const validateUseAsTitle = (config: CollectionConfig) => {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (useAsTitleField && fieldIsVirtual(useAsTitleField)) {
|
||||
if (useAsTitleField && 'virtual' in useAsTitleField && useAsTitleField.virtual === true) {
|
||||
throw new InvalidConfiguration(
|
||||
`The field "${config.admin.useAsTitle}" specified in "admin.useAsTitle" in the collection "${config.slug}" is virtual. A virtual field cannot be used as the title.`,
|
||||
`The field "${config.admin.useAsTitle}" specified in "admin.useAsTitle" in the collection "${config.slug}" is virtual. A virtual field can be used as the title only when linked to a relationship field.`,
|
||||
)
|
||||
}
|
||||
if (!useAsTitleField) {
|
||||
|
||||
@@ -28,6 +28,7 @@ import { buildVersionCollectionFields } from '../../versions/buildCollectionFiel
|
||||
import { appendVersionToQueryKey } from '../../versions/drafts/appendVersionToQueryKey.js'
|
||||
import { getQueryDraftsSelect } from '../../versions/drafts/getQueryDraftsSelect.js'
|
||||
import { getQueryDraftsSort } from '../../versions/drafts/getQueryDraftsSort.js'
|
||||
import { sanitizeSortQuery } from './utilities/sanitizeSortQuery.js'
|
||||
import { buildAfterOperation } from './utils.js'
|
||||
|
||||
export type Arguments = {
|
||||
@@ -96,7 +97,7 @@ export const findOperation = async <
|
||||
req,
|
||||
select: incomingSelect,
|
||||
showHiddenFields,
|
||||
sort,
|
||||
sort: incomingSort,
|
||||
where,
|
||||
} = args
|
||||
|
||||
@@ -143,6 +144,11 @@ export const findOperation = async <
|
||||
|
||||
let fullWhere = combineQueries(where, accessResult)
|
||||
|
||||
const sort = sanitizeSortQuery({
|
||||
fields: collection.config.flattenedFields,
|
||||
sort: incomingSort,
|
||||
})
|
||||
|
||||
const sanitizedJoins = await sanitizeJoinQuery({
|
||||
collectionConfig,
|
||||
joins,
|
||||
@@ -170,7 +176,10 @@ export const findOperation = async <
|
||||
pagination: usePagination,
|
||||
req,
|
||||
select: getQueryDraftsSelect({ select }),
|
||||
sort: getQueryDraftsSort({ collectionConfig, sort }),
|
||||
sort: getQueryDraftsSort({
|
||||
collectionConfig,
|
||||
sort,
|
||||
}),
|
||||
where: fullWhere,
|
||||
})
|
||||
} else {
|
||||
|
||||
@@ -27,6 +27,7 @@ import { sanitizeSelect } from '../../utilities/sanitizeSelect.js'
|
||||
import { buildVersionCollectionFields } from '../../versions/buildCollectionFields.js'
|
||||
import { appendVersionToQueryKey } from '../../versions/drafts/appendVersionToQueryKey.js'
|
||||
import { getQueryDraftsSort } from '../../versions/drafts/getQueryDraftsSort.js'
|
||||
import { sanitizeSortQuery } from './utilities/sanitizeSortQuery.js'
|
||||
import { updateDocument } from './utilities/update.js'
|
||||
import { buildAfterOperation } from './utils.js'
|
||||
|
||||
@@ -103,7 +104,7 @@ export const updateOperation = async <
|
||||
req,
|
||||
select: incomingSelect,
|
||||
showHiddenFields,
|
||||
sort,
|
||||
sort: incomingSort,
|
||||
where,
|
||||
} = args
|
||||
|
||||
@@ -136,6 +137,11 @@ export const updateOperation = async <
|
||||
|
||||
const fullWhere = combineQueries(where, accessResult)
|
||||
|
||||
const sort = sanitizeSortQuery({
|
||||
fields: collection.config.flattenedFields,
|
||||
sort: incomingSort,
|
||||
})
|
||||
|
||||
let docs
|
||||
|
||||
if (collectionConfig.versions?.drafts && shouldSaveDraft) {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import type { FlattenedField } from '../../../fields/config/types.js'
|
||||
|
||||
const sanitizeSort = ({ fields, sort }: { fields: FlattenedField[]; sort: string }): string => {
|
||||
let sortProperty = sort
|
||||
let desc = false
|
||||
if (sort.indexOf('-') === 0) {
|
||||
desc = true
|
||||
sortProperty = sortProperty.substring(1)
|
||||
}
|
||||
|
||||
const segments = sortProperty.split('.')
|
||||
|
||||
for (const segment of segments) {
|
||||
const field = fields.find((each) => each.name === segment)
|
||||
if (!field) {
|
||||
return sort
|
||||
}
|
||||
|
||||
if ('fields' in field) {
|
||||
fields = field.flattenedFields
|
||||
continue
|
||||
}
|
||||
|
||||
if ('virtual' in field && typeof field.virtual === 'string') {
|
||||
return `${desc ? '-' : ''}${field.virtual}`
|
||||
}
|
||||
}
|
||||
|
||||
return sort
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the sort parameter, for example virtual fields linked to relationships are replaced with the full path.
|
||||
*/
|
||||
export const sanitizeSortQuery = ({
|
||||
fields,
|
||||
sort,
|
||||
}: {
|
||||
fields: FlattenedField[]
|
||||
sort?: string | string[]
|
||||
}): string | string[] | undefined => {
|
||||
if (!sort) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (Array.isArray(sort)) {
|
||||
return sort.map((sort) => sanitizeSort({ fields, sort }))
|
||||
}
|
||||
|
||||
return sanitizeSort({ fields, sort })
|
||||
}
|
||||
@@ -109,7 +109,6 @@ export const addOrderableFieldsAndHook = (
|
||||
const orderBeforeChangeHook: BeforeChangeHook = async ({ data, originalDoc, req }) => {
|
||||
for (const orderableFieldName of orderableFieldNames) {
|
||||
if (!data[orderableFieldName] && !originalDoc?.[orderableFieldName]) {
|
||||
console.log('do not enter')
|
||||
const lastDoc = await req.payload.find({
|
||||
collection: collection.slug,
|
||||
depth: 0,
|
||||
@@ -258,7 +257,6 @@ export const addOrderableEndpoint = (config: SanitizedConfig) => {
|
||||
},
|
||||
depth: 0,
|
||||
req,
|
||||
select: { id: true },
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -28,22 +28,6 @@ type Args = {
|
||||
}
|
||||
)
|
||||
|
||||
const flattenWhere = (query: Where): WhereField[] => {
|
||||
const flattenedConstraints: WhereField[] = []
|
||||
|
||||
for (const [key, val] of Object.entries(query)) {
|
||||
if ((key === 'and' || key === 'or') && Array.isArray(val)) {
|
||||
for (const subVal of val) {
|
||||
flattenedConstraints.push(...flattenWhere(subVal))
|
||||
}
|
||||
} else {
|
||||
flattenedConstraints.push({ [key]: val })
|
||||
}
|
||||
}
|
||||
|
||||
return flattenedConstraints
|
||||
}
|
||||
|
||||
export async function validateQueryPaths({
|
||||
collectionConfig,
|
||||
errors = [],
|
||||
@@ -61,17 +45,47 @@ export async function validateQueryPaths({
|
||||
const fields = versionFields || (globalConfig || collectionConfig).flattenedFields
|
||||
|
||||
if (typeof where === 'object') {
|
||||
const whereFields = flattenWhere(where)
|
||||
// We need to determine if the whereKey is an AND, OR, or a schema path
|
||||
const promises = []
|
||||
for (const constraint of whereFields) {
|
||||
for (const path in constraint) {
|
||||
for (const operator in constraint[path]) {
|
||||
const val = constraint[path][operator]
|
||||
for (const path in where) {
|
||||
const constraint = where[path]
|
||||
|
||||
if ((path === 'and' || path === 'or') && Array.isArray(constraint)) {
|
||||
for (const item of constraint) {
|
||||
if (collectionConfig) {
|
||||
promises.push(
|
||||
validateQueryPaths({
|
||||
collectionConfig,
|
||||
errors,
|
||||
overrideAccess,
|
||||
policies,
|
||||
req,
|
||||
versionFields,
|
||||
where: item,
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
promises.push(
|
||||
validateQueryPaths({
|
||||
errors,
|
||||
globalConfig,
|
||||
overrideAccess,
|
||||
policies,
|
||||
req,
|
||||
versionFields,
|
||||
where: item,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (!Array.isArray(constraint)) {
|
||||
for (const operator in constraint) {
|
||||
const val = constraint[operator]
|
||||
if (validOperatorSet.has(operator as Operator)) {
|
||||
promises.push(
|
||||
validateSearchParam({
|
||||
collectionConfig,
|
||||
constraint: where as WhereField,
|
||||
errors,
|
||||
fields,
|
||||
globalConfig,
|
||||
|
||||
@@ -2,17 +2,19 @@
|
||||
import type { SanitizedCollectionConfig } from '../../collections/config/types.js'
|
||||
import type { FlattenedField } from '../../fields/config/types.js'
|
||||
import type { SanitizedGlobalConfig } from '../../globals/config/types.js'
|
||||
import type { PayloadRequest } from '../../types/index.js'
|
||||
import type { PayloadRequest, WhereField } from '../../types/index.js'
|
||||
import type { EntityPolicies, PathToQuery } from './types.js'
|
||||
|
||||
import { fieldAffectsData, fieldIsVirtual } from '../../fields/config/types.js'
|
||||
import { getEntityPolicies } from '../../utilities/getEntityPolicies.js'
|
||||
import { getFieldByPath } from '../../utilities/getFieldByPath.js'
|
||||
import isolateObjectProperty from '../../utilities/isolateObjectProperty.js'
|
||||
import { getLocalizedPaths } from '../getLocalizedPaths.js'
|
||||
import { validateQueryPaths } from './validateQueryPaths.js'
|
||||
|
||||
type Args = {
|
||||
collectionConfig?: SanitizedCollectionConfig
|
||||
constraint: WhereField
|
||||
errors: { path: string }[]
|
||||
fields: FlattenedField[]
|
||||
globalConfig?: SanitizedGlobalConfig
|
||||
@@ -32,6 +34,7 @@ type Args = {
|
||||
*/
|
||||
export async function validateSearchParam({
|
||||
collectionConfig,
|
||||
constraint,
|
||||
errors,
|
||||
fields,
|
||||
globalConfig,
|
||||
@@ -100,8 +103,13 @@ export async function validateSearchParam({
|
||||
return
|
||||
}
|
||||
|
||||
if (fieldIsVirtual(field)) {
|
||||
errors.push({ path })
|
||||
if ('virtual' in field && field.virtual) {
|
||||
if (field.virtual === true) {
|
||||
errors.push({ path })
|
||||
} else {
|
||||
constraint[`${field.virtual}`] = constraint[path]
|
||||
delete constraint[path]
|
||||
}
|
||||
}
|
||||
|
||||
if (polymorphicJoin && path === 'relationTo') {
|
||||
|
||||
@@ -269,6 +269,7 @@ export type Condition<TData extends TypeWithID = any, TSiblingData = any> = (
|
||||
siblingData: Partial<TSiblingData>,
|
||||
{
|
||||
blockData,
|
||||
operation,
|
||||
path,
|
||||
user,
|
||||
}: {
|
||||
@@ -276,6 +277,10 @@ export type Condition<TData extends TypeWithID = any, TSiblingData = any> = (
|
||||
* The data of the nearest parent block. If the field is not within a block, `blockData` will be equal to `undefined`.
|
||||
*/
|
||||
blockData: Partial<TData>
|
||||
/**
|
||||
* A string relating to which operation the field type is currently executing within.
|
||||
*/
|
||||
operation: Operation
|
||||
/**
|
||||
* The path of the field, e.g. ["group", "myArray", 1, "textField"]. The path is the schemaPath but with indexes and would be used in the context of field data, not field schemas.
|
||||
*/
|
||||
@@ -509,9 +514,9 @@ export interface FieldBase {
|
||||
/**
|
||||
* Pass `true` to disable field in the DB
|
||||
* for [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges):
|
||||
* A virtual field cannot be used in `admin.useAsTitle`
|
||||
* A virtual field can be used in `admin.useAsTitle` only when linked to a relationship.
|
||||
*/
|
||||
virtual?: boolean
|
||||
virtual?: boolean | string
|
||||
}
|
||||
|
||||
export interface FieldBaseClient {
|
||||
@@ -1143,6 +1148,7 @@ type SharedRelationshipPropertiesClient = FieldBaseClient &
|
||||
type RelationshipAdmin = {
|
||||
allowCreate?: boolean
|
||||
allowEdit?: boolean
|
||||
appearance?: 'drawer' | 'select'
|
||||
components?: {
|
||||
afterInput?: CustomComponent[]
|
||||
beforeInput?: CustomComponent[]
|
||||
@@ -1157,7 +1163,7 @@ type RelationshipAdmin = {
|
||||
} & Admin
|
||||
|
||||
type RelationshipAdminClient = AdminClient &
|
||||
Pick<RelationshipAdmin, 'allowCreate' | 'allowEdit' | 'isSortable'>
|
||||
Pick<RelationshipAdmin, 'allowCreate' | 'allowEdit' | 'appearance' | 'isSortable'>
|
||||
|
||||
export type PolymorphicRelationshipField = {
|
||||
admin?: {
|
||||
@@ -1949,7 +1955,7 @@ export function fieldShouldBeLocalized({
|
||||
}
|
||||
|
||||
export function fieldIsVirtual(field: Field | Tab): boolean {
|
||||
return 'virtual' in field && field.virtual
|
||||
return 'virtual' in field && Boolean(field.virtual)
|
||||
}
|
||||
|
||||
export type HookName =
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { RichTextAdapter } from '../../../admin/RichText.js'
|
||||
import type { SanitizedCollectionConfig } from '../../../collections/config/types.js'
|
||||
import type { SanitizedGlobalConfig } from '../../../globals/config/types.js'
|
||||
import type { RequestContext } from '../../../index.js'
|
||||
import type {
|
||||
JsonObject,
|
||||
PayloadRequest,
|
||||
@@ -13,6 +12,7 @@ import type {
|
||||
import type { Block, Field, TabAsField } from '../../config/types.js'
|
||||
|
||||
import { MissingEditorProp } from '../../../errors/index.js'
|
||||
import { type RequestContext } from '../../../index.js'
|
||||
import { getBlockSelect } from '../../../utilities/getBlockSelect.js'
|
||||
import { stripUnselectedFields } from '../../../utilities/stripUnselectedFields.js'
|
||||
import { fieldAffectsData, fieldShouldBeLocalized, tabHasName } from '../../config/types.js'
|
||||
@@ -20,6 +20,7 @@ import { getDefaultValue } from '../../getDefaultValue.js'
|
||||
import { getFieldPathsModified as getFieldPaths } from '../../getFieldPaths.js'
|
||||
import { relationshipPopulationPromise } from './relationshipPopulationPromise.js'
|
||||
import { traverseFields } from './traverseFields.js'
|
||||
import { virtualFieldPopulationPromise } from './virtualFieldPopulationPromise.js'
|
||||
|
||||
type Args = {
|
||||
/**
|
||||
@@ -306,6 +307,24 @@ export const promise = async ({
|
||||
}
|
||||
}
|
||||
|
||||
if ('virtual' in field && typeof field.virtual === 'string') {
|
||||
populationPromises.push(
|
||||
virtualFieldPopulationPromise({
|
||||
name: field.name,
|
||||
draft,
|
||||
fallbackLocale,
|
||||
fields: (collection || global).flattenedFields,
|
||||
locale,
|
||||
overrideAccess,
|
||||
ref: doc,
|
||||
req,
|
||||
segments: field.virtual.split('.'),
|
||||
showHiddenFields,
|
||||
siblingDoc,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
// Execute access control
|
||||
let allowDefaultValue = true
|
||||
if (triggerAccessControl && field.access && field.access.read) {
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
import type { PayloadRequest } from '../../../types/index.js'
|
||||
import type { FlattenedField } from '../../config/types.js'
|
||||
|
||||
import { createDataloaderCacheKey } from '../../../collections/dataloader.js'
|
||||
|
||||
export const virtualFieldPopulationPromise = async ({
|
||||
name,
|
||||
draft,
|
||||
fallbackLocale,
|
||||
fields,
|
||||
locale,
|
||||
overrideAccess,
|
||||
ref,
|
||||
req,
|
||||
segments,
|
||||
showHiddenFields,
|
||||
siblingDoc,
|
||||
}: {
|
||||
draft: boolean
|
||||
fallbackLocale: string
|
||||
fields: FlattenedField[]
|
||||
locale: string
|
||||
name: string
|
||||
overrideAccess: boolean
|
||||
ref: any
|
||||
req: PayloadRequest
|
||||
segments: string[]
|
||||
showHiddenFields: boolean
|
||||
siblingDoc: Record<string, unknown>
|
||||
}): Promise<void> => {
|
||||
const currentSegment = segments.shift()
|
||||
|
||||
if (!currentSegment) {
|
||||
return
|
||||
}
|
||||
|
||||
const currentValue = ref[currentSegment]
|
||||
|
||||
if (typeof currentValue === 'undefined') {
|
||||
return
|
||||
}
|
||||
|
||||
// Final step
|
||||
if (segments.length === 0) {
|
||||
siblingDoc[name] = currentValue
|
||||
return
|
||||
}
|
||||
|
||||
const currentField = fields.find((each) => each.name === currentSegment)
|
||||
|
||||
if (!currentField) {
|
||||
return
|
||||
}
|
||||
|
||||
if (currentField.type === 'group' || currentField.type === 'tab') {
|
||||
if (!currentValue || typeof currentValue !== 'object') {
|
||||
return
|
||||
}
|
||||
|
||||
return virtualFieldPopulationPromise({
|
||||
name,
|
||||
draft,
|
||||
fallbackLocale,
|
||||
fields: currentField.flattenedFields,
|
||||
locale,
|
||||
overrideAccess,
|
||||
ref: currentValue,
|
||||
req,
|
||||
segments,
|
||||
showHiddenFields,
|
||||
siblingDoc,
|
||||
})
|
||||
}
|
||||
|
||||
if (
|
||||
(currentField.type === 'relationship' || currentField.type === 'upload') &&
|
||||
typeof currentField.relationTo === 'string' &&
|
||||
!currentField.hasMany
|
||||
) {
|
||||
let docID: number | string
|
||||
|
||||
if (typeof currentValue === 'object' && currentValue) {
|
||||
docID = currentValue.id
|
||||
} else {
|
||||
docID = currentValue
|
||||
}
|
||||
|
||||
if (typeof docID !== 'string' && typeof docID !== 'number') {
|
||||
return
|
||||
}
|
||||
|
||||
const select = {}
|
||||
let currentSelectRef: any = select
|
||||
const currentFields = req.payload.collections[currentField.relationTo].config.flattenedFields
|
||||
|
||||
for (let i = 0; i < segments.length; i++) {
|
||||
const field = currentFields.find((each) => each.name === segments[i])
|
||||
|
||||
const shouldBreak =
|
||||
i === segments.length - 1 || field?.type === 'relationship' || field?.type === 'upload'
|
||||
|
||||
currentSelectRef[segments[i]] = shouldBreak ? true : {}
|
||||
currentSelectRef = currentSelectRef[segments[i]]
|
||||
|
||||
if (shouldBreak) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const populatedDoc = await req.payloadDataLoader.load(
|
||||
createDataloaderCacheKey({
|
||||
collectionSlug: currentField.relationTo,
|
||||
currentDepth: 0,
|
||||
depth: 0,
|
||||
docID,
|
||||
draft,
|
||||
fallbackLocale,
|
||||
locale,
|
||||
overrideAccess,
|
||||
select,
|
||||
showHiddenFields,
|
||||
transactionID: req.transactionID as number,
|
||||
}),
|
||||
)
|
||||
|
||||
if (!populatedDoc) {
|
||||
return
|
||||
}
|
||||
|
||||
return virtualFieldPopulationPromise({
|
||||
name,
|
||||
draft,
|
||||
fallbackLocale,
|
||||
fields: req.payload.collections[currentField.relationTo].config.flattenedFields,
|
||||
locale,
|
||||
overrideAccess,
|
||||
ref: populatedDoc,
|
||||
req,
|
||||
segments,
|
||||
showHiddenFields,
|
||||
siblingDoc,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -109,7 +109,12 @@ export const promise = async ({
|
||||
|
||||
const passesCondition = field.admin?.condition
|
||||
? Boolean(
|
||||
field.admin.condition(data, siblingData, { blockData, path: pathSegments, user: req.user }),
|
||||
field.admin.condition(data, siblingData, {
|
||||
blockData,
|
||||
operation,
|
||||
path: pathSegments,
|
||||
user: req.user,
|
||||
}),
|
||||
)
|
||||
: true
|
||||
let skipValidationFromHere = skipValidation || !passesCondition
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
} from '../../admin/types.js'
|
||||
import type {
|
||||
Access,
|
||||
CustomComponent,
|
||||
EditConfig,
|
||||
Endpoint,
|
||||
EntityDescription,
|
||||
@@ -80,6 +81,10 @@ export type GlobalAdminOptions = {
|
||||
*/
|
||||
components?: {
|
||||
elements?: {
|
||||
/**
|
||||
* Inject custom components before the document controls
|
||||
*/
|
||||
beforeDocumentControls?: CustomComponent[]
|
||||
Description?: EntityDescriptionComponent
|
||||
/**
|
||||
* Replaces the "Preview" button
|
||||
|
||||
@@ -71,7 +71,17 @@ export const getAccess = (config: Config): Record<Operation, Access> =>
|
||||
|
||||
return {
|
||||
and: [
|
||||
...(typeof constraintAccess === 'object' ? [constraintAccess] : []),
|
||||
...(typeof constraintAccess === 'object'
|
||||
? [constraintAccess]
|
||||
: constraintAccess === false
|
||||
? [
|
||||
{
|
||||
id: {
|
||||
equals: null,
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
[`access.${operation}.constraint`]: {
|
||||
equals: constraint.value,
|
||||
|
||||
@@ -78,7 +78,7 @@ export const getConstraints = (config: Config): Field => ({
|
||||
},
|
||||
...(config?.queryPresets?.constraints?.[operation]?.reduce(
|
||||
(acc: Field[], option: QueryPresetConstraint) => {
|
||||
option.fields.forEach((field, index) => {
|
||||
option.fields?.forEach((field, index) => {
|
||||
acc.push({ ...field })
|
||||
|
||||
if (fieldAffectsData(field)) {
|
||||
|
||||
@@ -25,7 +25,7 @@ export type QueryPreset = {
|
||||
|
||||
export type QueryPresetConstraint = {
|
||||
access: Access<QueryPreset>
|
||||
fields: Field[]
|
||||
fields?: Field[]
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
@@ -222,8 +222,12 @@ export const handleEndpoints = async ({
|
||||
}
|
||||
|
||||
const response = await handler(req)
|
||||
|
||||
return new Response(response.body, {
|
||||
headers: mergeHeaders(req.responseHeaders ?? new Headers(), response.headers),
|
||||
headers: headersWithCors({
|
||||
headers: mergeHeaders(req.responseHeaders ?? new Headers(), response.headers),
|
||||
req,
|
||||
}),
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
})
|
||||
|
||||
@@ -6,6 +6,13 @@ export type Autosave = {
|
||||
* @default 800
|
||||
*/
|
||||
interval?: number
|
||||
/**
|
||||
* When set to `true`, the "Save as draft" button will be displayed even while autosave is enabled.
|
||||
* By default, this button is hidden to avoid redundancy with autosave behavior.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
showSaveDraftButton?: boolean
|
||||
}
|
||||
|
||||
export type SchedulePublish = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-cloud-storage",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "The official cloud storage plugin for Payload CMS",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-form-builder",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "Form builder plugin for Payload CMS",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-import-export",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "Import-Export plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -20,8 +20,7 @@ const baseClass = 'fields-to-export'
|
||||
|
||||
export const FieldsToExport: SelectFieldClientComponent = (props) => {
|
||||
const { id } = useDocumentInfo()
|
||||
const { path } = props
|
||||
const { setValue, value } = useField<string[]>({ path })
|
||||
const { setValue, value } = useField<string[]>()
|
||||
const { value: collectionSlug } = useField<string>({ path: 'collectionSlug' })
|
||||
const { getEntityConfig } = useConfig()
|
||||
const { collection } = useImportExport()
|
||||
|
||||
@@ -20,12 +20,12 @@ const baseClass = 'sort-by-fields'
|
||||
|
||||
export const SortBy: SelectFieldClientComponent = (props) => {
|
||||
const { id } = useDocumentInfo()
|
||||
const { path } = props
|
||||
const { setValue, value } = useField<string>({ path })
|
||||
const { setValue, value } = useField<string>()
|
||||
const { value: collectionSlug } = useField<string>({ path: 'collectionSlug' })
|
||||
const { query } = useListQuery()
|
||||
const { getEntityConfig } = useConfig()
|
||||
const { collection } = useImportExport()
|
||||
|
||||
const [displayedValue, setDisplayedValue] = useState<{
|
||||
id: string
|
||||
label: ReactNode
|
||||
|
||||
@@ -11,6 +11,7 @@ export const WhereField: React.FC = () => {
|
||||
const { setValue: setSelectionToUseValue, value: selectionToUseValue } = useField({
|
||||
path: 'selectionToUse',
|
||||
})
|
||||
|
||||
const { setValue } = useField({ path: 'where' })
|
||||
const { selectAll, selected } = useSelection()
|
||||
const { query } = useListQuery()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-multi-tenant",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "Multi Tenant plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -16,8 +16,8 @@ type Props = {
|
||||
} & RelationshipFieldClientProps
|
||||
|
||||
export const TenantField = (args: Props) => {
|
||||
const { debug, path, unique } = args
|
||||
const { setValue, value } = useField<number | string>({ path })
|
||||
const { debug, unique } = args
|
||||
const { setValue, value } = useField<number | string>()
|
||||
const { options, selectedTenantID, setPreventRefreshOnChange, setTenant } = useTenantSelection()
|
||||
|
||||
const hasSetValueRef = React.useRef(false)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-nested-docs",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "The official Nested Docs plugin for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-redirects",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "Redirects plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-search",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "Search plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,43 +1,28 @@
|
||||
import type { DeleteFromSearch } from '../../types.js'
|
||||
|
||||
export const deleteFromSearch: DeleteFromSearch = async ({
|
||||
collection,
|
||||
doc,
|
||||
pluginConfig,
|
||||
req: { payload },
|
||||
req,
|
||||
}) => {
|
||||
const searchSlug = pluginConfig?.searchOverrides?.slug || 'search'
|
||||
try {
|
||||
const searchDocQuery = await payload.find({
|
||||
collection: searchSlug,
|
||||
depth: 0,
|
||||
limit: 1,
|
||||
pagination: false,
|
||||
req,
|
||||
where: {
|
||||
doc: {
|
||||
equals: {
|
||||
relationTo: collection.slug,
|
||||
value: doc.id,
|
||||
export const deleteFromSearch: DeleteFromSearch =
|
||||
(pluginConfig) =>
|
||||
async ({ id, collection, req: { payload }, req }) => {
|
||||
const searchSlug = pluginConfig?.searchOverrides?.slug || 'search'
|
||||
|
||||
try {
|
||||
await payload.delete({
|
||||
collection: searchSlug,
|
||||
depth: 0,
|
||||
req,
|
||||
where: {
|
||||
'doc.relationTo': {
|
||||
equals: collection.slug,
|
||||
},
|
||||
'doc.value': {
|
||||
equals: id,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if (searchDocQuery?.docs?.[0]) {
|
||||
await payload.delete({
|
||||
id: searchDocQuery?.docs?.[0]?.id,
|
||||
collection: searchSlug,
|
||||
req,
|
||||
})
|
||||
} catch (err: unknown) {
|
||||
payload.logger.error({
|
||||
err,
|
||||
msg: `Error deleting ${searchSlug} doc.`,
|
||||
})
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
payload.logger.error({
|
||||
err,
|
||||
msg: `Error deleting ${searchSlug} doc.`,
|
||||
})
|
||||
}
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CollectionAfterChangeHook, CollectionAfterDeleteHook, Config } from 'payload'
|
||||
import type { CollectionAfterChangeHook, Config } from 'payload'
|
||||
|
||||
import type { SanitizedSearchPluginConfig, SearchPluginConfig } from './types.js'
|
||||
|
||||
@@ -7,7 +7,6 @@ import { syncWithSearch } from './Search/hooks/syncWithSearch.js'
|
||||
import { generateSearchCollection } from './Search/index.js'
|
||||
|
||||
type CollectionAfterChangeHookArgs = Parameters<CollectionAfterChangeHook>[0]
|
||||
type CollectionAfterDeleteHookArgs = Parameters<CollectionAfterDeleteHook>[0]
|
||||
|
||||
export const searchPlugin =
|
||||
(incomingPluginConfig: SearchPluginConfig) =>
|
||||
@@ -67,14 +66,9 @@ export const searchPlugin =
|
||||
})
|
||||
},
|
||||
],
|
||||
afterDelete: [
|
||||
...(existingHooks?.afterDelete || []),
|
||||
async (args: CollectionAfterDeleteHookArgs) => {
|
||||
await deleteFromSearch({
|
||||
...args,
|
||||
pluginConfig,
|
||||
})
|
||||
},
|
||||
beforeDelete: [
|
||||
...(existingHooks?.beforeDelete || []),
|
||||
deleteFromSearch(pluginConfig),
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type {
|
||||
CollectionAfterChangeHook,
|
||||
CollectionAfterDeleteHook,
|
||||
CollectionBeforeDeleteHook,
|
||||
CollectionConfig,
|
||||
Field,
|
||||
Locale,
|
||||
@@ -96,8 +96,4 @@ export type SyncDocArgs = {
|
||||
// Convert the `collection` arg from `SanitizedCollectionConfig` to a string
|
||||
export type SyncWithSearch = (Args: SyncWithSearchArgs) => ReturnType<CollectionAfterChangeHook>
|
||||
|
||||
export type DeleteFromSearch = (
|
||||
Args: {
|
||||
pluginConfig: SearchPluginConfig
|
||||
} & Parameters<CollectionAfterDeleteHook>[0],
|
||||
) => ReturnType<CollectionAfterDeleteHook>
|
||||
export type DeleteFromSearch = (args: SearchPluginConfig) => CollectionBeforeDeleteHook
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-sentry",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "Sentry plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-seo",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "SEO plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import type { FieldType, Options } from '@payloadcms/ui'
|
||||
import type { FieldType } from '@payloadcms/ui'
|
||||
import type { TextareaFieldClientProps } from 'payload'
|
||||
|
||||
import {
|
||||
@@ -38,7 +38,6 @@ export const MetaDescriptionComponent: React.FC<MetaDescriptionProps> = (props)
|
||||
required,
|
||||
},
|
||||
hasGenerateDescriptionFn,
|
||||
path,
|
||||
readOnly,
|
||||
} = props
|
||||
|
||||
@@ -58,12 +57,14 @@ export const MetaDescriptionComponent: React.FC<MetaDescriptionProps> = (props)
|
||||
const maxLength = maxLengthFromProps || maxLengthDefault
|
||||
const minLength = minLengthFromProps || minLengthDefault
|
||||
|
||||
const { customComponents, errorMessage, setValue, showError, value }: FieldType<string> =
|
||||
useField({
|
||||
path,
|
||||
} as Options)
|
||||
|
||||
const { AfterInput, BeforeInput, Label } = customComponents ?? {}
|
||||
const {
|
||||
customComponents: { AfterInput, BeforeInput, Label } = {},
|
||||
errorMessage,
|
||||
path,
|
||||
setValue,
|
||||
showError,
|
||||
value,
|
||||
}: FieldType<string> = useField()
|
||||
|
||||
const regenerateDescription = useCallback(async () => {
|
||||
if (!hasGenerateDescriptionFn) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import type { FieldType, Options } from '@payloadcms/ui'
|
||||
import type { FieldType } from '@payloadcms/ui'
|
||||
import type { UploadFieldClientProps } from 'payload'
|
||||
|
||||
import {
|
||||
@@ -30,9 +30,8 @@ export const MetaImageComponent: React.FC<MetaImageProps> = (props) => {
|
||||
const {
|
||||
field: { label, localized, relationTo, required },
|
||||
hasGenerateImageFn,
|
||||
path,
|
||||
readOnly,
|
||||
} = props || {}
|
||||
} = props
|
||||
|
||||
const {
|
||||
config: {
|
||||
@@ -42,10 +41,14 @@ export const MetaImageComponent: React.FC<MetaImageProps> = (props) => {
|
||||
getEntityConfig,
|
||||
} = useConfig()
|
||||
|
||||
const field: FieldType<string> = useField({ ...props, path } as Options)
|
||||
const { customComponents } = field
|
||||
|
||||
const { Error, Label } = customComponents ?? {}
|
||||
const {
|
||||
customComponents: { Error, Label } = {},
|
||||
filterOptions,
|
||||
path,
|
||||
setValue,
|
||||
showError,
|
||||
value,
|
||||
}: FieldType<string> = useField()
|
||||
|
||||
const { t } = useTranslation<PluginSEOTranslations, PluginSEOTranslationKeys>()
|
||||
|
||||
@@ -53,8 +56,6 @@ export const MetaImageComponent: React.FC<MetaImageProps> = (props) => {
|
||||
const { getData } = useForm()
|
||||
const docInfo = useDocumentInfo()
|
||||
|
||||
const { setValue, showError, value } = field
|
||||
|
||||
const regenerateImage = useCallback(async () => {
|
||||
if (!hasGenerateImageFn) {
|
||||
return
|
||||
@@ -174,7 +175,7 @@ export const MetaImageComponent: React.FC<MetaImageProps> = (props) => {
|
||||
api={api}
|
||||
collection={collection}
|
||||
Error={Error}
|
||||
filterOptions={field.filterOptions}
|
||||
filterOptions={filterOptions}
|
||||
onChange={(incomingImage) => {
|
||||
if (incomingImage !== null) {
|
||||
if (typeof incomingImage === 'object') {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import type { FieldType, Options } from '@payloadcms/ui'
|
||||
import type { FieldType } from '@payloadcms/ui'
|
||||
import type { TextFieldClientProps } from 'payload'
|
||||
|
||||
import {
|
||||
@@ -33,9 +33,8 @@ export const MetaTitleComponent: React.FC<MetaTitleProps> = (props) => {
|
||||
const {
|
||||
field: { label, maxLength: maxLengthFromProps, minLength: minLengthFromProps, required },
|
||||
hasGenerateTitleFn,
|
||||
path,
|
||||
readOnly,
|
||||
} = props || {}
|
||||
} = props
|
||||
|
||||
const { t } = useTranslation<PluginSEOTranslations, PluginSEOTranslationKeys>()
|
||||
|
||||
@@ -46,8 +45,14 @@ export const MetaTitleComponent: React.FC<MetaTitleProps> = (props) => {
|
||||
},
|
||||
} = useConfig()
|
||||
|
||||
const field: FieldType<string> = useField({ path } as Options)
|
||||
const { customComponents: { AfterInput, BeforeInput, Label } = {} } = field
|
||||
const {
|
||||
customComponents: { AfterInput, BeforeInput, Label } = {},
|
||||
errorMessage,
|
||||
path,
|
||||
setValue,
|
||||
showError,
|
||||
value,
|
||||
}: FieldType<string> = useField()
|
||||
|
||||
const locale = useLocale()
|
||||
const { getData } = useForm()
|
||||
@@ -56,8 +61,6 @@ export const MetaTitleComponent: React.FC<MetaTitleProps> = (props) => {
|
||||
const minLength = minLengthFromProps || minLengthDefault
|
||||
const maxLength = maxLengthFromProps || maxLengthDefault
|
||||
|
||||
const { errorMessage, setValue, showError, value } = field
|
||||
|
||||
const regenerateTitle = useCallback(async () => {
|
||||
if (!hasGenerateTitleFn) {
|
||||
return
|
||||
|
||||
@@ -25,4 +25,4 @@ export const es: GenericTranslationsObject = {
|
||||
tooLong: 'Demasiado largo',
|
||||
tooShort: 'Demasiado corto',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-stripe",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "Stripe plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/richtext-lexical",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "The officially supported Lexical richtext adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -68,12 +68,7 @@ const toolbarGroups = ({ disabledNodes }: IndentFeatureProps): ToolbarGroup[] =>
|
||||
if (!nodes?.length) {
|
||||
return false
|
||||
}
|
||||
if (nodes.some((node) => disabledNodes?.includes(node.getType()))) {
|
||||
return false
|
||||
}
|
||||
return !$pointsAncestorMatch(selection, (node) =>
|
||||
(disabledNodes ?? []).includes(node.getType()),
|
||||
)
|
||||
return !nodes.some((node) => disabledNodes?.includes(node.getType()))
|
||||
},
|
||||
key: 'indentIncrease',
|
||||
label: ({ i18n }) => {
|
||||
|
||||
@@ -36,7 +36,6 @@ const RichTextComponent: React.FC<
|
||||
editorConfig,
|
||||
field,
|
||||
field: {
|
||||
name,
|
||||
admin: { className, description, readOnly: readOnlyFromAdmin } = {},
|
||||
label,
|
||||
localized,
|
||||
@@ -48,7 +47,6 @@ const RichTextComponent: React.FC<
|
||||
} = props
|
||||
|
||||
const readOnlyFromProps = readOnlyFromTopLevelProps || readOnlyFromAdmin
|
||||
const path = pathFromProps ?? name
|
||||
|
||||
const editDepth = useEditDepth()
|
||||
|
||||
@@ -70,11 +68,12 @@ const RichTextComponent: React.FC<
|
||||
customComponents: { AfterInput, BeforeInput, Description, Error, Label } = {},
|
||||
disabled: disabledFromField,
|
||||
initialValue,
|
||||
path,
|
||||
setValue,
|
||||
showError,
|
||||
value,
|
||||
} = useField<SerializedEditorState>({
|
||||
path,
|
||||
potentiallyStalePath: pathFromProps,
|
||||
validate: memoizedValidate,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/richtext-slate",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "The officially supported Slate richtext adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -28,7 +28,6 @@ import type { LoadedSlateFieldProps } from './types.js'
|
||||
import { defaultRichTextValue } from '../data/defaultValue.js'
|
||||
import { richTextValidate } from '../data/validation.js'
|
||||
import { listTypes } from './elements/listTypes.js'
|
||||
import './index.scss'
|
||||
import { hotkeys } from './hotkeys.js'
|
||||
import { toggleLeaf } from './leaves/toggle.js'
|
||||
import { withEnterBreakOut } from './plugins/withEnterBreakOut.js'
|
||||
@@ -37,6 +36,7 @@ import { ElementButtonProvider } from './providers/ElementButtonProvider.js'
|
||||
import { ElementProvider } from './providers/ElementProvider.js'
|
||||
import { LeafButtonProvider } from './providers/LeafButtonProvider.js'
|
||||
import { LeafProvider } from './providers/LeafProvider.js'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'rich-text'
|
||||
|
||||
@@ -66,7 +66,6 @@ const RichTextField: React.FC<LoadedSlateFieldProps> = (props) => {
|
||||
validate = richTextValidate,
|
||||
} = props
|
||||
|
||||
const path = pathFromProps ?? name
|
||||
const schemaPath = schemaPathFromProps ?? name
|
||||
|
||||
const readOnlyFromProps = readOnlyFromTopLevelProps || readOnlyFromAdmin
|
||||
@@ -97,11 +96,12 @@ const RichTextField: React.FC<LoadedSlateFieldProps> = (props) => {
|
||||
customComponents: { Description, Error, Label } = {},
|
||||
disabled: disabledFromField,
|
||||
initialValue,
|
||||
path,
|
||||
setValue,
|
||||
showError,
|
||||
value,
|
||||
} = useField({
|
||||
path,
|
||||
potentiallyStalePath: pathFromProps,
|
||||
validate: memoizedValidate,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-azure",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "Payload storage adapter for Azure Blob Storage",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-gcs",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "Payload storage adapter for Google Cloud Storage",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-s3",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "Payload storage adapter for Amazon S3",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-uploadthing",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "Payload storage adapter for uploadthing",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-vercel-blob",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"description": "Payload storage adapter for Vercel Blob Storage",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/translations",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -197,7 +197,7 @@ export const caTranslations: DefaultTranslationsObject = {
|
||||
clearAll: 'Esborra-ho tot',
|
||||
close: 'Tanca',
|
||||
collapse: 'Replegar',
|
||||
collections: 'Collections',
|
||||
collections: 'Col·leccions',
|
||||
columns: 'Columnes',
|
||||
columnToSort: 'Columna per ordenar',
|
||||
confirm: 'Confirma',
|
||||
@@ -205,7 +205,7 @@ export const caTranslations: DefaultTranslationsObject = {
|
||||
confirmDeletion: "Confirma l'eliminació",
|
||||
confirmDuplication: 'Confirma duplicacat',
|
||||
confirmReindex: 'Reindexa {{collections}}?',
|
||||
confirmReindexAll: 'Reindexa totes les collections?',
|
||||
confirmReindexAll: 'Reindexa totes les col·leccions?',
|
||||
confirmReindexDescription:
|
||||
'Aixo eliminarà els índexs existents i reindexarà els documents de les col·leccions {{collections}}.',
|
||||
confirmReindexDescriptionAll:
|
||||
|
||||
@@ -54,7 +54,6 @@ export const acceptedLanguages = [
|
||||
* 'bn-BD',
|
||||
* 'bn-IN',
|
||||
* 'bs',
|
||||
* 'ca',
|
||||
* 'ca-ES-valencia',
|
||||
* 'cy',
|
||||
* 'el',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/ui",
|
||||
"version": "3.34.0",
|
||||
"version": "3.35.1",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -33,10 +33,6 @@
|
||||
width: calc(var(--base) * 1.2);
|
||||
height: calc(var(--base) * 1.2);
|
||||
|
||||
svg {
|
||||
max-width: 1rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--theme-elevation-200);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
}
|
||||
|
||||
.btn--withPopup {
|
||||
margin-block: 24px;
|
||||
margin-block: 4px;
|
||||
.btn {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user