Compare commits

..

123 Commits

Author SHA1 Message Date
Dan Ribbens
46e82d2931 chore: improve folder hook queries 2025-05-19 15:35:25 -04:00
Jarrod Flesch
8d507996c8 Merge branch 'main' into feat/folders 2025-05-19 13:24:19 -04:00
Jarrod Flesch
aa82763cb8 fix list menu items test 2025-05-19 13:20:17 -04:00
Jarrod Flesch
e7e6a7dd97 remove multiple listItem toggle clicks in preset query tests 2025-05-19 12:16:21 -04:00
Jarrod Flesch
e14c670a51 query preset passing tests 2025-05-19 11:01:09 -04:00
Jarrod Flesch
21f5d3473c fixes versions count test selectors 2025-05-19 09:14:32 -04:00
Jarrod Flesch
36597110e9 fix missing styles in admin-root 2025-05-16 15:59:25 -04:00
Jarrod Flesch
4f2b237858 fix: column selector bug 2025-05-16 14:26:04 -04:00
Jarrod Flesch
d90afba70d fixes more tests 2025-05-16 13:37:25 -04:00
Jarrod Flesch
decd512daa fix versions test selectors 2025-05-16 13:19:04 -04:00
Jarrod Flesch
b2bf95b17b allow using wait for ssr test 2025-05-16 11:46:40 -04:00
Jarrod Flesch
10e29dd5e2 fix outdated selectors in failing tests 2025-05-16 11:30:05 -04:00
Jarrod Flesch
4c4ae1295e fix css drawer header title selector in test 2025-05-16 10:27:31 -04:00
Jarrod Flesch
adb805dadb revert renderList type change 2025-05-15 17:00:19 -04:00
Jarrod Flesch
e5f0ca3d45 revert unused generics on ServerFunctionClient 2025-05-15 16:58:02 -04:00
Jarrod Flesch
fe2b7693cc successful build 2025-05-15 16:37:05 -04:00
Jarrod Flesch
30d4a098b1 adds missing translations 2025-05-15 16:34:39 -04:00
Jarrod Flesch
aa7918fe6e chore: extract tab style into button element 2025-05-15 16:30:13 -04:00
Jarrod Flesch
f23f87243c fixes issue with add folders in drawers 2025-05-15 16:29:51 -04:00
Jarrod Flesch
41b75882a1 Merge branch 'main' into HEAD 2025-05-15 16:28:06 -04:00
Jarrod Flesch
cfd3c34aba fix lint issues 2025-05-08 09:50:07 -07:00
Jarrod Flesch
270137e92e chore: add better preferences generic default 2025-05-07 15:07:49 -07:00
Jarrod Flesch
cab817d2aa build 2025-05-05 17:03:27 -04:00
Jarrod Flesch
db622e2b79 remove old folder int tests 2025-05-05 16:50:46 -04:00
Jarrod Flesch
c52204317a lint fixes 2025-05-05 16:35:08 -04:00
Jarrod Flesch
a85af1a6d3 import fix 2025-05-05 16:02:22 -04:00
Jarrod Flesch
bccadd5101 fix bad imports 2025-05-05 15:49:26 -04:00
Jarrod Flesch
2fa723743e chore: more test fixes 2025-05-05 14:53:25 -04:00
Jarrod Flesch
2904de778d fix unpublish and locked-docs suite 2025-05-05 12:53:48 -04:00
Jarrod Flesch
d37dfb1376 fix form-state tests 2025-05-05 12:02:42 -04:00
Jarrod Flesch
b203f617af fixes some failing test suites 2025-05-05 11:12:42 -04:00
Jarrod Flesch
ee46f27881 Merge branch 'main' into feat/folders 2025-05-05 09:01:32 -04:00
Jarrod Flesch
2426784726 passing versions suite 2025-05-05 08:31:48 -04:00
Jarrod Flesch
95b78a5951 fix admin locale picker test 2025-05-02 16:55:59 -04:00
Jarrod Flesch
d1c1ad2a1d fix build 2025-05-02 15:26:34 -04:00
Jarrod Flesch
2980bdb799 Merge branch 'main' into feat/folders 2025-05-02 14:37:46 -04:00
Jarrod Flesch
fe6923d0a7 bulk edit folder selection 2025-05-02 12:33:40 -04:00
Jarrod Flesch
7eec63ae69 fix toast missing title for media docs 2025-05-02 10:00:58 -04:00
Jarrod Flesch
77a7bc5e9c fix move to drawers layering and usage 2025-05-02 09:21:44 -04:00
Jarrod Flesch
780becc88a fix global views throwing errors on render 2025-05-02 08:23:37 -04:00
Jarrod Flesch
a2d394ec82 fix breadcrumb click issue 2025-05-02 08:20:49 -04:00
Jarrod Flesch
b1c8c96e97 chore: move doc to folder not updating label 2025-05-02 08:16:52 -04:00
Jarrod Flesch
6b6b596489 feat: allow searching all docs at root on collection-folder views 2025-05-01 16:52:36 -04:00
Jarrod Flesch
a89bc1479f better list selection organization 2025-05-01 14:13:48 -04:00
Jarrod Flesch
bd9f3b5bd2 chore: adds custom cell component for managing doc folder placement in default list view 2025-05-01 14:03:44 -04:00
Jarrod Flesch
2584ff42eb unifies move-to-drawer rather than needing many drawers 2025-05-01 00:34:37 -04:00
Jarrod Flesch
cc1f5fb70c add relationTo cell on browse-by-folder table 2025-04-30 16:46:33 -04:00
Jarrod Flesch
4f7f378b84 rename _parentFolder to _folder 2025-04-30 16:20:13 -04:00
Jarrod Flesch
b279fa7bde language edits for moving folders and documents 2025-04-30 16:13:10 -04:00
Jarrod Flesch
93230a5915 Merge branch 'main' into feat/folders 2025-04-23 06:46:18 -07:00
Jarrod Flesch
9f31daf8e7 prevent views from loading if folders are not enabled 2025-04-23 06:45:12 -07:00
Jarrod Flesch
ded7164dca pill sizing, fix e2e rendering issue 2025-04-21 16:24:09 -04:00
Jarrod Flesch
cc4526844a feat: allow add to folder from move drawer 2025-04-21 15:02:35 -04:00
Jarrod Flesch
362f25f593 commit updated types 2025-04-21 14:53:14 -04:00
Jarrod Flesch
7567d2358a fix: mismatch icon and overall size from small button and small pill 2025-04-21 14:52:44 -04:00
Jarrod Flesch
b418c3cade fix: folder not opening to current selection in doc view 2025-04-21 14:52:15 -04:00
Jarrod Flesch
db0d07d9a7 fix build 2025-04-21 12:41:02 -04:00
Jarrod Flesch
b56e2faad2 fixes folder specific build errors 2025-04-21 12:02:58 -04:00
Jarrod Flesch
56982f9811 Merge branch 'main' into feat/folders 2025-04-21 11:41:43 -04:00
Jarrod Flesch
9e1258811a finishes translations, adjusts create perms 2025-04-21 11:39:09 -04:00
Jarrod Flesch
1d2accfcbb chore: client side functional sorting 2025-04-18 15:50:56 -04:00
Jarrod Flesch
4a8bea2dde chore: adjust search placeholder and table column label 2025-04-18 11:22:36 -04:00
Jarrod Flesch
0149e42276 revert button styles with new chevron icon 2025-04-18 11:05:54 -04:00
Jarrod Flesch
36d9900774 Merge branch 'main' into feat/folders 2025-04-18 10:56:46 -04:00
Jarrod Flesch
f4d624a0c5 rm stale file 2025-04-18 09:46:51 -04:00
Jarrod Flesch
57bcfcc8be cleanup 2025-04-18 09:45:43 -04:00
Jarrod Flesch
d73ddbde0c browse by folder translation 2025-04-18 08:16:00 -04:00
Jarrod Flesch
189dd64799 debug false in test config 2025-04-17 15:21:01 -04:00
Jarrod Flesch
572ce2955a about done 2025-04-17 15:20:18 -04:00
Jarrod Flesch
87ca312a54 Merge branch 'main' into feat/folders 2025-04-14 14:24:12 -04:00
Jarrod Flesch
2b2fa67031 Merge remote-tracking branch 'origin/main' into feat/folders 2025-04-14 14:16:52 -04:00
Jarrod Flesch
2f45749634 rendering of root folder view 2025-04-14 14:16:01 -04:00
Jarrod Flesch
0534dd9506 add folder route to config 2025-04-14 08:05:25 -04:00
Jarrod Flesch
aee1ea1346 rename functions/files 2025-04-11 12:57:32 -04:00
Jarrod Flesch
82ba8cf8f4 feat: completes move-to folder drawer 2025-04-11 12:42:35 -04:00
Jarrod Flesch
1dfff2b0b0 adds no results and adjusts data functions 2025-04-08 10:43:20 -04:00
Jarrod Flesch
6b695355c3 wires up create new polymorphic list view button 2025-04-07 14:14:51 -04:00
Jarrod Flesch
29c32d3141 fix bad imports 2025-04-07 09:05:50 -04:00
Jarrod Flesch
f6afbed5d2 buildable 2025-04-07 08:56:56 -04:00
Jarrod Flesch
5dcf96ca10 chore: adjust translations function 2025-04-07 08:56:21 -04:00
Jarrod Flesch
91c22bd88c Merge branch 'main' into feat/folders 2025-04-04 16:22:57 -04:00
Jarrod Flesch
0d8b5677d9 feat: working collection-folder list view 2025-04-04 15:43:12 -04:00
Jarrod Flesch
e55c89c4b7 refactor: consolidates buildTableColumnState 2025-03-25 14:25:10 -04:00
Jarrod Flesch
f762d683c6 chore: refactoring, routing structure 2025-03-25 14:09:32 -04:00
Jarrod Flesch
953c538af0 Merge branch 'main' into feat/folders 2025-03-24 10:05:53 -04:00
Jarrod Flesch
65d0272950 refactor(folders): folder configuration 2025-03-13 10:05:30 -04:00
Jarrod Flesch
c570d6178c Merge branch 'main' into feat/folders 2025-02-27 15:30:14 -05:00
Jarrod Flesch
c935420937 lockfile 2025-02-26 16:00:10 -05:00
Jarrod Flesch
72dd527a15 Merge branch 'main' into feat/folders 2025-02-26 15:58:03 -05:00
Jarrod Flesch
1d6e0941e7 feat: search 2025-02-26 15:26:55 -05:00
Jarrod Flesch
91f7deb278 try/catch moveTo, fix button hover colors 2025-02-21 16:23:37 -05:00
Jarrod Flesch
d3986cfaf0 folder popup fix 2025-02-21 15:25:51 -05:00
Jarrod Flesch
4a9de40098 feat: wire up delete interations with confirm modal 2025-02-21 14:00:35 -05:00
Jarrod Flesch
f4679a4088 chore: adds folder delete confirmation translation 2025-02-21 13:59:55 -05:00
Jarrod Flesch
cb8ee7d2b0 fixes for moveTo drawer doc handling 2025-02-21 12:33:25 -05:00
Jarrod Flesch
06ef8da836 adds confirm translation for move drawer 2025-02-21 12:31:19 -05:00
Jarrod Flesch
91dc98978d chore: disabled instead of hide folders inside move drawer 2025-02-20 17:15:04 -05:00
Jarrod Flesch
07ff1ee7be misc fixes 2025-02-20 16:34:41 -05:00
Jarrod Flesch
72d393d24c more translations 2025-02-20 16:32:26 -05:00
Jarrod Flesch
95d4324af3 remove old files 2025-02-20 16:08:00 -05:00
Jarrod Flesch
a617d3166c use translations 2025-02-20 16:07:41 -05:00
Jarrod Flesch
6770e7a1b3 adds translation strings 2025-02-20 16:03:11 -05:00
Jarrod Flesch
f5e535dacf confirm modal for move 2025-02-20 15:21:43 -05:00
Jarrod Flesch
3d9c25e278 Merge branch 'main' into feat/folders 2025-02-20 13:37:13 -05:00
Jarrod Flesch
3bdb127ad4 start of modal tidy up 2025-02-20 13:31:46 -05:00
Jarrod Flesch
de4be1eb78 ui/ux fixes 2025-02-20 12:49:30 -05:00
Jarrod Flesch
d26529282f feat: move docs to folders within edit view 2025-02-19 16:52:33 -05:00
Jarrod Flesch
2694603353 feat: wire in pref for view type 2025-02-19 14:50:35 -05:00
Jarrod Flesch
9630de1bac feat: droppable crumbs, image cards, reusable item grid/table display 2025-02-19 13:59:38 -05:00
Jarrod Flesch
91e867ecb1 chore: regenerates lockfile 2025-02-14 14:54:54 -05:00
Jarrod Flesch
25e196bdd7 chore: successful build 2025-02-14 14:05:35 -05:00
Jarrod Flesch
b631eebdb5 Merge branch 'main' into feat/folders 2025-02-14 12:48:21 -05:00
Jarrod Flesch
a66f134cf6 feat: adds base folder functionality, list and grid views 2025-02-14 12:22:52 -05:00
Jarrod Flesch
b3aec9a23f chore: updates css imports 2025-01-16 15:10:49 -05:00
Jarrod Flesch
31911d87c1 chore: more merge resolutions 2025-01-14 16:05:19 -05:00
Jarrod Flesch
2d37ac41a2 merges main 2025-01-14 15:57:23 -05:00
Jarrod Flesch
b68af8ba31 Merge branch 'main' into feat/folders 2025-01-14 15:48:39 -05:00
Jarrod Flesch
4c775d1ced Merge branch 'main' into feat/folders 2025-01-02 12:25:43 -05:00
Jarrod Flesch
4706019a22 feat: brings back subfolder viewing, folder assigning etc 2024-12-18 14:40:26 -05:00
Jarrod Flesch
9c33a48192 Merge branch 'main' into feat/folders 2024-12-17 14:17:40 -05:00
Jarrod Flesch
8ec6784645 Merge branch 'main' into feat/beta/folders 2024-12-13 12:58:07 -05:00
Jarrod Flesch
8d9bf835a0 Merge branch 'main' into feat/beta/folders 2024-11-19 13:54:57 -05:00
Jarrod Flesch
2eef7ee388 moves base folder work into beta 2024-10-01 09:50:50 -04:00
414 changed files with 1641 additions and 6607 deletions

1
.gitignore vendored
View File

@@ -3,7 +3,6 @@ package-lock.json
dist
/.idea/*
!/.idea/runConfigurations
/.idea/runConfigurations/_template*
!/.idea/payload.iml
# Custom actions

View File

@@ -39,7 +39,7 @@ const GlobalWithAccessControl: GlobalConfig = {
update: ({ req: { user } }) => {...},
// Version-enabled Globals only
readVersions: () => {...},
readVersion: () => {...},
},
// highlight-end
}
@@ -64,7 +64,7 @@ If a Global supports [Versions](../versions/overview), the following additional
Returns a boolean result or optionally a [query constraint](../queries/overview) which limits who can read this global based on its current properties.
To add read Access Control to a [Global](../configuration/globals), use the `access` property in the [Global Config](../configuration/globals):
To add read Access Control to a [Global](../configuration/globals), use the `read` property in the [Global Config](../configuration/globals):
```ts
import { GlobalConfig } from 'payload'
@@ -72,7 +72,7 @@ import { GlobalConfig } from 'payload'
const Header: GlobalConfig = {
// ...
// highlight-start
access: {
read: {
read: ({ req: { user } }) => {
return Boolean(user)
},

View File

@@ -65,7 +65,7 @@ preview: (doc, { req }) => `${req.protocol}//${req.host}/${doc.slug}` // highlig
The Preview feature can be used to achieve "Draft Preview". After clicking the preview button from the Admin Panel, you can enter into "draft mode" within your front-end application. This will allow you to adjust your page queries to include the `draft: true` param. When this param is present on the request, Payload will send back a draft document as opposed to a published one based on the document's `_status` field.
To enter draft mode, the URL provided to the `preview` function can point to a custom endpoint in your front-end application that sets a cookie or session variable to indicate that draft mode is enabled. This is framework specific, so the mechanisms here vary from framework to framework although the underlying concept is the same.
To enter draft mode, the URL provided to the `preview` function can point to a custom endpoint in your front-end application that sets a cookie or session variable to indicate that draft mode is enabled. This is framework specific, so the mechanisms here very from framework to framework although the underlying concept is the same.
### Next.js

View File

@@ -605,7 +605,7 @@ return (
textField: {
initialValue: 'Updated text',
valid: true,
value: 'Updated text',
value: 'Upddated text',
},
},
// blockType: "yourBlockSlug",
@@ -875,7 +875,7 @@ Useful to retrieve info about the currently logged in user as well as methods fo
| **`refreshCookie`** | A method to trigger the silent refreshing of a user's auth token |
| **`setToken`** | Set the token of the user, to be decoded and used to reset the user and token in memory |
| **`token`** | The logged in user's token (useful for creating preview links, etc.) |
| **`refreshPermissions`** | Load new permissions (useful when content that affects permissions has been changed) |
| **`refreshPermissions`** | Load new permissions (useful when content that effects permissions has been changed) |
| **`permissions`** | The permissions of the current user |
```tsx
@@ -1143,7 +1143,7 @@ This is useful for scenarios where you need to trigger another fetch regardless
Route transitions are useful in showing immediate visual feedback to the user when navigating between pages. This is especially useful on slow networks when navigating to data heavy or process intensive pages.
By default, any instances of `Link` from `@payloadcms/ui` will trigger route transitions by default.
By default, any instances of `Link` from `@payloadcms/ui` will trigger route transitions dy default.
```tsx
import { Link } from '@payloadcms/ui'

View File

@@ -62,7 +62,7 @@ In this scenario, if your cookie was still valid, malicious-intent.com would be
### CSRF Prevention
Define domains that you trust and are willing to accept Payload HTTP-only cookie based requests from. Use the `csrf` option on the base Payload Config to do this:
Define domains that your trust and are willing to accept Payload HTTP-only cookie based requests from. Use the `csrf` option on the base Payload Config to do this:
```ts
// payload.config.ts
@@ -102,8 +102,8 @@ If option 1 isn't possible, then you can get around this limitation by [configur
```
SameSite: None // allows the cookie to cross domains
Secure: true // ensures it's sent over HTTPS only
HttpOnly: true // ensures it's not accessible via client side JavaScript
Secure: true // ensures its sent over HTTPS only
HttpOnly: true // ensures its not accessible via client side JavaScript
```
Configuration example:

View File

@@ -71,7 +71,7 @@ export const Customers: CollectionConfig = {
#### generateEmailSubject
Similarly to the above `generateEmailHTML`, you can also customize the subject of the email. The function arguments are the same but you can only return a string - not HTML.
Similarly to the above `generateEmailHTML`, you can also customize the subject of the email. The function argument are the same but you can only return a string - not HTML.
```ts
import type { CollectionConfig } from 'payload'
@@ -178,7 +178,7 @@ The following arguments are passed to the `generateEmailHTML` function:
#### generateEmailSubject
Similarly to the above `generateEmailHTML`, you can also customize the subject of the email. The function arguments are the same but you can only return a string - not HTML.
Similarly to the above `generateEmailHTML`, you can also customize the subject of the email. The function argument are the same but you can only return a string - not HTML.
```ts
import type { CollectionConfig } from 'payload'

View File

@@ -38,7 +38,7 @@ const request = await fetch('http://localhost:3000', {
### Omitting The Token
In some cases you may want to prevent the token from being returned from the auth operations. You can do that by setting `removeTokenFromResponses` to `true` like so:
In some cases you may want to prevent the token from being returned from the auth operations. You can do that by setting `removeTokenFromResponse` to `true` like so:
```ts
import type { CollectionConfig } from 'payload'
@@ -46,7 +46,7 @@ import type { CollectionConfig } from 'payload'
export const UsersWithoutJWTs: CollectionConfig = {
slug: 'users-without-jwts',
auth: {
removeTokenFromResponses: true, // highlight-line
removeTokenFromResponse: true, // highlight-line
},
}
```

View File

@@ -67,7 +67,7 @@ query {
}
```
Document access can also be queried on a collection/global basis. Access on a global can be queried like `http://localhost:3000/api/global-slug/access`, Collection document access can be queried like `http://localhost:3000/api/collection-slug/access/:id`.
Document access can also be queried on a collection/global basis. Access on a global can queried like `http://localhost:3000/api/global-slug/access`, Collection document access can be queried like `http://localhost:3000/api/collection-slug/access/:id`.
## Me

View File

@@ -71,7 +71,7 @@ export const Admins: CollectionConfig = {
</Banner>
<Banner type="warning">
**Note:** Auth-enabled Collections will be automatically injected with the
**Note:** Auth-enabled Collections with be automatically injected with the
`hash`, `salt`, and `email` fields. [More
details](../fields/overview#field-names).
</Banner>

View File

@@ -8,7 +8,7 @@ keywords: authentication, config, configuration, documentation, Content Manageme
During the lifecycle of a request you will be able to access the data you have configured to be stored in the JWT by accessing `req.user`. The user object is automatically appended to the request for you.
### Defining Token Data
### Definining Token Data
You can specify what data gets encoded to the Cookie/JWT-Token by setting `saveToJWT` property on fields within your auth collection.

View File

@@ -132,7 +132,6 @@ The following options are available:
| `hideAPIURL` | Hides the "API URL" meta field while editing documents within this Collection. |
| `enableRichTextLink` | The [Rich Text](../fields/rich-text) field features a `Link` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
| `enableRichTextRelationship` | The [Rich Text](../fields/rich-text) field features a `Relationship` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
| `folders` | A boolean to enable folders for a given collection. Defaults to `false`. [More details](../folders/overview). |
| `meta` | Page metadata overrides to apply to this Collection within the Admin Panel. [More details](../admin/metadata). |
| `preview` | Function to generate preview URLs within the Admin Panel that can point to your app. [More details](../admin/preview). |
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
@@ -177,7 +176,7 @@ The following options are available:
#### Edit View Options
```ts
import type { CollectionConfig } from 'payload'
import type { CollectionCOnfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
@@ -275,7 +274,7 @@ You can also pass an object to the collection's `graphQL` property, which allows
## TypeScript
You can import types from Payload to help make writing your Collection configs easier and type-safe. There are two main types that represent the Collection Config, `CollectionConfig` and `SanitizedCollectionConfig`.
You can import types from Payload to help make writing your Collection configs easier and type-safe. There are two main types that represent the Collection Config, `CollectionConfig` and `SanitizeCollectionConfig`.
The `CollectionConfig` type represents a raw Collection Config in its full form, where only the bare minimum properties are marked as required. The `SanitizedCollectionConfig` type represents a Collection Config after it has been fully sanitized. Generally, this is only used internally by Payload.

View File

@@ -205,7 +205,7 @@ You can also pass an object to the global's `graphQL` property, which allows you
## TypeScript
You can import types from Payload to help make writing your Global configs easier and type-safe. There are two main types that represent the Global Config, `GlobalConfig` and `SanitizedGlobalConfig`.
You can import types from Payload to help make writing your Global configs easier and type-safe. There are two main types that represent the Global Config, `GlobalConfig` and `SanitizeGlobalConfig`.
The `GlobalConfig` type represents a raw Global Config in its full form, where only the bare minimum properties are marked as required. The `SanitizedGlobalConfig` type represents a Global Config after it has been fully sanitized. Generally, this is only used internally by Payload.

View File

@@ -84,7 +84,6 @@ The following options are available:
| **`csrf`** | A whitelist array of URLs to allow Payload to accept cookies from. [More details](../authentication/cookies#csrf-attacks). |
| **`defaultDepth`** | If a user does not specify `depth` while requesting a resource, this depth will be used. [More details](../queries/depth). |
| **`defaultMaxTextLength`** | The maximum allowed string length to be permitted application-wide. Helps to prevent malicious public document creation. |
| `folders` | An optional object to configure global folder settings. [More details](../folders/overview). |
| `queryPresets` | An object that to configure Collection Query Presets. [More details](../query-presets/overview). |
| **`maxDepth`** | The maximum allowed depth to be permitted application-wide. This setting helps prevent against malicious queries. Defaults to `10`. [More details](../queries/depth). |
| **`indexSortableFields`** | Automatically index all sortable top-level fields in the database to improve sort performance and add database compatibility for Azure Cosmos and similar. |
@@ -213,7 +212,7 @@ For more information about what we track, take a look at our [privacy policy](/p
## Cross-origin resource sharing (CORS)#cors
Cross-origin resource sharing (CORS) can be configured with either a whitelist array of URLS to allow CORS requests from, a wildcard string (`*`) to accept incoming requests from any domain, or an object with the following properties:
Cross-origin resource sharing (CORS) can be configured with either a whitelist array of URLS to allow CORS requests from, a wildcard string (`*`) to accept incoming requests from any domain, or a object with the following properties:
| Option | Description |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
@@ -292,7 +291,7 @@ export const script = async (config: SanitizedConfig) => {
collection: 'pages',
data: { title: 'my title' },
})
payload.logger.info('Successfully seeded!')
payload.logger.info('Succesffully seeded!')
process.exit(0)
}
```

View File

@@ -59,7 +59,7 @@ _For details on how to build Custom Views, including all available props, see [B
### Document Root
The Document Root is mounted on the top-level route for a Document. Setting this property will completely take over the entire Document View layout, including the title, [Document Tabs](#document-tabs), _and all other nested Document Views_ including the [Edit View](./edit-view), API View, etc.
The Document Root is mounted on the top-level route for a Document. Setting this property will completely take over the entire Document View layout, including the title, [Document Tabs](#ocument-tabs), _and all other nested Document Views_ including the [Edit View](./edit-view), API View, etc.
When setting a Document Root, you are responsible for rendering all necessary components and controls, as no document controls or tabs would be rendered. To replace only the Edit View precisely, use the `edit.default` key instead.

View File

@@ -40,7 +40,7 @@ The following options are available:
| `beforeDashboard` | An array of Custom Components to inject into the built-in Dashboard, _before_ the default dashboard contents. [More details](#beforedashboard). |
| `beforeLogin` | An array of Custom Components to inject into the built-in Login, _before_ the default login form. [More details](#beforelogin). |
| `beforeNavLinks` | An array of Custom Components to inject into the built-in Nav, _before_ the links themselves. [More details](#beforenavlinks). |
| `graphics.Icon` | The simplified logo used in contexts like the `Nav` component. [More details](#graphicsicon). |
| `graphics.Icon` | The simplified logo used in contexts like the the `Nav` component. [More details](#graphicsicon). |
| `graphics.Logo` | The full logo used in contexts like the `Login` view. [More details](#graphicslogo). |
| `header` | An array of Custom Components to be injected above the Payload header. [More details](#header). |
| `logout.Button` | The button displayed in the sidebar that logs the user out. [More details](#logoutbutton). |
@@ -345,7 +345,7 @@ export default function MyCustomIcon() {
The `Logo` property is the full logo used in contexts like the `Login` view. This is typically a larger, more detailed representation of your brand.
To add a custom logo, use the `admin.components.graphics.Logo` property in your Payload Config:
To add a custom logo, use the `admin.components.graphic.Logo` property in your Payload Config:
```ts
import { buildConfig } from 'payload'

View File

@@ -39,7 +39,7 @@ export default buildConfig({
import { vercelPostgresAdapter } from '@payloadcms/db-vercel-postgres'
export default buildConfig({
// Automatically uses process.env.POSTGRES_URL if no options are provided.
// Automatically uses proces.env.POSTGRES_URL if no options are provided.
db: vercelPostgresAdapter(),
// Optionally, can accept the same options as the @vercel/postgres package.
db: vercelPostgresAdapter({
@@ -224,7 +224,7 @@ Make sure Payload doesn't overlap table names with its collections. For example,
### afterSchemaInit
Runs after the Drizzle schema is built. You can use this hook to modify the schema with features that aren't supported by Payload, or if you want to add a column that you don't want to be in the Payload config.
To extend a table, Payload exposes `extendTable` utility to the args. You can refer to the [Drizzle documentation](https://orm.drizzle.team/docs/sql-schema-declaration).
To extend a table, Payload exposes `extendTable` utillity to the args. You can refer to the [Drizzle documentation](https://orm.drizzle.team/docs/sql-schema-declaration).
The following example adds the `extra_integer_column` column and a composite index on `country` and `city` columns.
```ts

View File

@@ -189,7 +189,7 @@ Make sure Payload doesn't overlap table names with its collections. For example,
### afterSchemaInit
Runs after the Drizzle schema is built. You can use this hook to modify the schema with features that aren't supported by Payload, or if you want to add a column that you don't want to be in the Payload config.
To extend a table, Payload exposes `extendTable` utility to the args. You can refer to the [Drizzle documentation](https://orm.drizzle.team/docs/sql-schema-declaration).
To extend a table, Payload exposes `extendTable` utillity to the args. You can refer to the [Drizzle documentation](https://orm.drizzle.team/docs/sql-schema-declaration).
The following example adds the `extra_integer_column` column and a composite index on `country` and `city` columns.
```ts

View File

@@ -80,7 +80,7 @@ export const MyArrayField: Field = {
}
```
The Array Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Array Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Option | Description |
| ------------------------- | ----------------------------------------------------------------------------------- |

View File

@@ -78,7 +78,7 @@ export const MyBlocksField: Field = {
}
```
The Blocks Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Blocks Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Option | Description |
| ---------------------- | -------------------------------------------------------------------------- |

View File

@@ -68,7 +68,7 @@ export const MyCodeField: Field = {
}
```
The Code Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Code Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Option | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

View File

@@ -58,7 +58,7 @@ export const MyCollapsibleField: Field = {
}
```
The Collapsible Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Collapsible Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Option | Description |
| ------------------- | ------------------------------- |

View File

@@ -65,7 +65,7 @@ export const MyDateField: Field = {
}
```
The Date Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Date Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Property | Description |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |

View File

@@ -65,7 +65,7 @@ export const MyEmailField: Field = {
}
```
The Email Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Email Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Property | Description |
| ------------------ | ------------------------------------------------------------------------- |

View File

@@ -69,7 +69,7 @@ export const MyGroupField: Field = {
}
```
The Group Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Group Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Option | Description |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |

View File

@@ -67,7 +67,7 @@ export const MyJSONField: Field = {
}
```
The JSON Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The JSON Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Option | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |

View File

@@ -70,7 +70,7 @@ export const MyNumberField: Field = {
}
```
The Number Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Number Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Property | Description |
| ------------------ | --------------------------------------------------------------------------------- |

View File

@@ -100,7 +100,7 @@ Here are the available Presentational Fields:
### Virtual Fields
Virtual fields are used to display data that is not stored in the database. They are useful for displaying computed values that populate within the API response through hooks, etc.
Virtual fields are used to display data that is not stored in the database. They are useful for displaying computed values that populate within the APi response through hooks, etc.
Here are the available Virtual Fields:

View File

@@ -36,7 +36,7 @@ export const MyRadioField: Field = {
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`options`** \* | Array of options to allow the field to store. Can either be an array of strings, or an array of objects containing a `label` string and a `value` string. |
| **`options`** \* | Array of options to allow the field to store. Can either be an array of strings, or an array of objects containing an `label` string and a `value` string. |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
@@ -82,7 +82,7 @@ export const MyRadioField: Field = {
}
```
The Radio Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Radio Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Property | Description |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------- |

View File

@@ -86,7 +86,7 @@ export const MyRelationshipField: Field = {
}
```
The Relationship Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Relationship Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Property | Description |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |

View File

@@ -54,7 +54,6 @@ export const MySelectField: Field = {
| **`enumName`** | Custom enum name for this field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
| **`dbName`** | Custom table name (if `hasMany` set to `true`) for this field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
| **`filterOptions`** | Dynamically filter which options are available based on the user, data, etc. [More details](#filterOptions) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](/docs/fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
@@ -68,61 +67,6 @@ _\* An asterisk denotes that a property is required._
used as a GraphQL enum.
</Banner>
### filterOptions
Used to dynamically filter which options are available based on the user, data, etc.
Some examples of this might include:
- Restricting options based on a user's role, e.g. admin-only options
- Displaying different options based on the value of another field, e.g. a city/state selector
The result of `filterOptions` will determine:
- Which options are displayed in the Admin Panel
- Which options can be saved to the database
To do this, use the `filterOptions` property in your [Field Config](./overview):
```ts
import type { Field } from 'payload'
export const MySelectField: Field = {
// ...
// highlight-start
type: 'select',
options: [
{
label: 'One',
value: 'one',
},
{
label: 'Two',
value: 'two',
},
{
label: 'Three',
value: 'three',
},
],
filterOptions: ({ options, data }) =>
data.disallowOption1
? options.filter(
(option) =>
(typeof option === 'string' ? options : option.value) !== 'one',
)
: options,
// highlight-end
}
```
<Banner type="warning">
**Note:** This property is similar to `filterOptions` in
[Relationship](./relationship) or [Upload](./upload) fields, except that the
return value of this function is simply an array of options, not a query
constraint.
</Banner>
## Admin Options
To customize the appearance and behavior of the Select Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
@@ -139,7 +83,7 @@ export const MySelectField: Field = {
}
```
The Select Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Select Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Property | Description |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |

View File

@@ -70,7 +70,7 @@ export const MyTextField: Field = {
}
```
The Text Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Text Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Option | Description |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------- |

View File

@@ -67,7 +67,7 @@ export const MyTextareaField: Field = {
}
```
The Textarea Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Textarea Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Option | Description |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------- |

View File

@@ -1,103 +0,0 @@
---
title: Folders
label: Overview
order: 10
desc: Folders allow you to group documents across collections, and are a great way to organize your content.
keywords: folders, folder, content organization
---
Folders allow you to group documents across collections, and are a great way to organize your content. Folders are built on top of relationship fields, when you enable folders on a collection, Payload adds a hidden relationship field `folders`, that relates to a folder — or no folder. Folders also have the `folder` field, allowing folders to be nested within other folders.
The configuration for folders is done in two places, the collection config and the Payload config. The collection config is where you enable folders, and the Payload config is where you configure the global folder settings.
<Banner type="warning">
**Note:** The Folders feature is currently in beta and may be subject to
change in minor versions updates prior to being stable.
</Banner>
## Folder Configuration
On the payload config, you can configure the following settings under the `folders` property:
```ts
// Type definition
type RootFoldersConfiguration = {
/**
* An array of functions to be ran when the folder collection is initialized
* This allows plugins to modify the collection configuration
*/
collectionOverrides?: (({
collection,
}: {
collection: CollectionConfig
}) => CollectionConfig | Promise<CollectionConfig>)[]
/**
* Ability to view hidden fields and collections related to folders
*
* @default false
*/
debug?: boolean
/**
* The Folder field name
*
* @default "folder"
*/
fieldName?: string
/**
* Slug for the folder collection
*
* @default "payload-folders"
*/
slug?: string
}
```
```ts
// Example usage
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
folders: {
// highlight-start
debug: true, // optional
collectionOverrides: [
async ({ collection }) => {
return collection
},
], // optional
fieldName: 'folder', // optional
slug: 'payload-folders', // optional
// highlight-end
},
})
```
## Collection Configuration
To enable folders on a collection, you need to set the `admin.folders` property to `true` on the collection config. This will add a hidden relationship field to the collection that relates to a folder — or no folder.
```ts
// Type definition
type CollectionFoldersConfiguration = boolean
```
```ts
// Example usage
import { buildConfig } from 'payload'
const config = buildConfig({
collections: [
{
slug: 'pages',
// highlight-start
folders: true, // defaults to false
// highlight-end
},
],
})
```

View File

@@ -23,7 +23,7 @@ Let's see examples on how context can be used in the first two scenarios mention
### Passing Data Between Hooks
To pass data between hooks, you can assign values to context in an earlier hook in the lifecycle of a request and expect it in the context of a later hook.
To pass data between hooks, you can assign values to context in an earlier hook in the lifecycle of a request and expect it the context in a later hook.
For example:

View File

@@ -33,7 +33,7 @@ Simply add a task to the `jobs.tasks` array in your Payload config. A task consi
| `onSuccess` | Function to be executed if the task succeeds. |
| `retries` | Specify the number of times that this step should be retried if it fails. If this is undefined, the task will either inherit the retries from the workflow or have no retries. If this is 0, the task will not be retried. By default, this is undefined. |
The logic for the Task is defined in the `handler` - which can be defined as a function, or a path to a function. The `handler` will run once a worker picks up a Job that includes this task.
The logic for the Task is defined in the `handler` - which can be defined as a function, or a path to a function. The `handler` will run once a worker picks picks up a Job that includes this task.
It should return an object with an `output` key, which should contain the output of the task as you've defined.
@@ -213,7 +213,7 @@ export default buildConfig({
## Nested tasks
You can run sub-tasks within an existing task, by using the `tasks` or `inlineTask` arguments passed to the task `handler` function:
You can run sub-tasks within an existing task, by using the `tasks` or `ìnlineTask` arguments passed to the task `handler` function:
```ts
export default buildConfig({

View File

@@ -260,7 +260,7 @@ If you are using relationships or uploads in your front-end application, and you
{
// ...
// If your site is running on a different domain than your Payload server,
// This will allow requests to be made between the two domains
// This will allows requests to be made between the two domains
cors: [
'http://localhost:3001' // Your front-end application
],

View File

@@ -85,7 +85,6 @@ formBuilderPlugin({
checkbox: true,
number: true,
message: true,
date: false,
payment: false,
},
})
@@ -350,18 +349,6 @@ Maps to a `checkbox` input on your front-end. Used to collect a boolean value.
| `width` | string | The width of the field on the front-end. |
| `required` | checkbox | Whether or not the field is required when submitted. |
### Date
Maps to a `date` input on your front-end. Used to collect a date value.
| Property | Type | Description |
| -------------- | -------- | ---------------------------------------------------- |
| `name` | string | The name of the field. |
| `label` | string | The label of the field. |
| `defaultValue` | date | The default value of the field. |
| `width` | string | The width of the field on the front-end. |
| `required` | checkbox | Whether or not the field is required when submitted. |
### Number
Maps to a `number` input on your front-end. Used to collect a number.
@@ -434,42 +421,6 @@ formBuilderPlugin({
})
```
### Customizing the date field default value
You can custommise the default value of the date field and any other aspects of the date block in this way.
Note that the end submission source will be responsible for the timezone of the date. Payload only stores the date in UTC format.
```ts
import { fields as formFields } from '@payloadcms/plugin-form-builder'
// payload.config.ts
formBuilderPlugin({
fields: {
// date: true, // just enable it without any customizations
date: {
...formFields.date,
fields: [
...(formFields.date && 'fields' in formFields.date
? formFields.date.fields.map((field) => {
if ('name' in field && field.name === 'defaultValue') {
return {
...field,
timezone: true, // optionally enable timezone
admin: {
...field.admin,
description: 'This is a date field',
},
}
}
return field
})
: []),
],
},
},
})
```
## Email
This plugin relies on the [email configuration](../email/overview) defined in your Payload configuration. It will read from your config and attempt to send your emails using the credentials provided.

View File

@@ -6,14 +6,14 @@ desc: Converting between lexical richtext and HTML
keywords: lexical, richtext, html
---
## Rich Text to HTML
## Converting Rich Text to HTML
There are two main approaches to convert your Lexical-based rich text to HTML:
1. **Generate HTML on-demand (Recommended)**: Convert JSON to HTML wherever you need it, on-demand.
2. **Generate HTML within your Collection**: Create a new field that automatically converts your saved JSON content to HTML. This is not recommended because it adds overhead to the Payload API and may not work well with live preview.
### On-demand
### Generating HTML on-demand (Recommended)
To convert JSON to HTML on-demand, use the `convertLexicalToHTML` function from `@payloadcms/richtext-lexical/html`. Here's an example of how to use it in a React component in your frontend:
@@ -32,81 +32,61 @@ export const MyComponent = ({ data }: { data: SerializedEditorState }) => {
}
```
#### Dynamic Population (Advanced)
### Converting Lexical Blocks
By default, `convertLexicalToHTML` expects fully populated data (e.g. uploads, links, etc.). If you need to dynamically fetch and populate those nodes, use the async variant, `convertLexicalToHTMLAsync`, from `@payloadcms/richtext-lexical/html-async`. You must provide a `populate` function:
If your rich text includes Lexical blocks, you need to provide a way to convert them to HTML. For example:
```tsx
'use client'
import type { MyInlineBlock, MyTextBlock } from '@/payload-types'
import type {
DefaultNodeTypes,
SerializedBlockNode,
SerializedInlineBlockNode,
} from '@payloadcms/richtext-lexical'
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import { getRestPopulateFn } from '@payloadcms/richtext-lexical/client'
import { convertLexicalToHTMLAsync } from '@payloadcms/richtext-lexical/html-async'
import React, { useEffect, useState } from 'react'
export const MyComponent = ({ data }: { data: SerializedEditorState }) => {
const [html, setHTML] = useState<null | string>(null)
useEffect(() => {
async function convert() {
const html = await convertLexicalToHTMLAsync({
data,
populate: getRestPopulateFn({
apiURL: `http://localhost:3000/api`,
}),
})
setHTML(html)
}
void convert()
}, [data])
return html && <div dangerouslySetInnerHTML={{ __html: html }} />
}
```
Using the REST populate function will send a separate request for each node. If you need to populate a large number of nodes, this may be slow. For improved performance on the server, you can use the `getPayloadPopulateFn` function:
```tsx
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import { getPayloadPopulateFn } from '@payloadcms/richtext-lexical'
import { convertLexicalToHTMLAsync } from '@payloadcms/richtext-lexical/html-async'
import { getPayload } from 'payload'
import {
convertLexicalToHTML,
type HTMLConvertersFunction,
} from '@payloadcms/richtext-lexical/html'
import React from 'react'
import config from '../../config.js'
type NodeTypes =
| DefaultNodeTypes
| SerializedBlockNode<MyTextBlock>
| SerializedInlineBlockNode<MyInlineBlock>
export const MyRSCComponent = async ({
data,
}: {
data: SerializedEditorState
}) => {
const payload = await getPayload({
config,
})
const htmlConverters: HTMLConvertersFunction<NodeTypes> = ({
defaultConverters,
}) => ({
...defaultConverters,
blocks: {
// Each key should match your block's slug
myTextBlock: ({ node, providedCSSString }) =>
`<div style="background-color: red;${providedCSSString}">${node.fields.text}</div>`,
},
inlineBlocks: {
// Each key should match your inline block's slug
myInlineBlock: ({ node, providedStyleTag }) =>
`<span${providedStyleTag}>${node.fields.text}</span$>`,
},
})
const html = await convertLexicalToHTMLAsync({
export const MyComponent = ({ data }: { data: SerializedEditorState }) => {
const html = convertLexicalToHTML({
converters: htmlConverters,
data,
populate: await getPayloadPopulateFn({
currentDepth: 0,
depth: 1,
payload,
}),
})
return html && <div dangerouslySetInnerHTML={{ __html: html }} />
return <div dangerouslySetInnerHTML={{ __html: html }} />
}
```
### HTML field
### Outputting HTML from the Collection
The `lexicalHTMLField()` helper converts JSON to HTML and saves it in a field that is updated every time you read it via an `afterRead` hook. It's generally not recommended for two reasons:
1. It creates a column with duplicate content in another format.
2. In [client-side live preview](/docs/live-preview/client), it makes it not "live".
Consider using the [on-demand HTML converter above](/docs/rich-text/converting-html#on-demand-recommended) or the [JSX converter](/docs/rich-text/converting-jsx) unless you have a good reason.
To automatically generate HTML from the saved richText field in your Collection, use the `lexicalHTMLField()` helper. This approach converts the JSON to HTML using an `afterRead` hook. For instance:
```ts
import type { HTMLConvertersFunction } from '@payloadcms/richtext-lexical/html'
@@ -174,59 +154,74 @@ const Pages: CollectionConfig = {
}
```
## Blocks to HTML
### Generating HTML in Your Frontend with Dynamic Population (Advanced)
If your rich text includes Lexical blocks, you need to provide a way to convert them to HTML. For example:
By default, `convertLexicalToHTML` expects fully populated data (e.g. uploads, links, etc.). If you need to dynamically fetch and populate those nodes, use the async variant, `convertLexicalToHTMLAsync`, from `@payloadcms/richtext-lexical/html-async`. You must provide a `populate` function:
```tsx
'use client'
import type { MyInlineBlock, MyTextBlock } from '@/payload-types'
import type {
DefaultNodeTypes,
SerializedBlockNode,
SerializedInlineBlockNode,
} from '@payloadcms/richtext-lexical'
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import {
convertLexicalToHTML,
type HTMLConvertersFunction,
} from '@payloadcms/richtext-lexical/html'
import React from 'react'
type NodeTypes =
| DefaultNodeTypes
| SerializedBlockNode<MyTextBlock>
| SerializedInlineBlockNode<MyInlineBlock>
const htmlConverters: HTMLConvertersFunction<NodeTypes> = ({
defaultConverters,
}) => ({
...defaultConverters,
blocks: {
// Each key should match your block's slug
myTextBlock: ({ node, providedCSSString }) =>
`<div style="background-color: red;${providedCSSString}">${node.fields.text}</div>`,
},
inlineBlocks: {
// Each key should match your inline block's slug
myInlineBlock: ({ node, providedStyleTag }) =>
`<span${providedStyleTag}>${node.fields.text}</span$>`,
},
})
import { getRestPopulateFn } from '@payloadcms/richtext-lexical/client'
import { convertLexicalToHTMLAsync } from '@payloadcms/richtext-lexical/html-async'
import React, { useEffect, useState } from 'react'
export const MyComponent = ({ data }: { data: SerializedEditorState }) => {
const html = convertLexicalToHTML({
converters: htmlConverters,
data,
})
const [html, setHTML] = useState<null | string>(null)
useEffect(() => {
async function convert() {
const html = await convertLexicalToHTMLAsync({
data,
populate: getRestPopulateFn({
apiURL: `http://localhost:3000/api`,
}),
})
setHTML(html)
}
return <div dangerouslySetInnerHTML={{ __html: html }} />
void convert()
}, [data])
return html && <div dangerouslySetInnerHTML={{ __html: html }} />
}
```
## HTML to Richtext
Using the REST populate function will send a separate request for each node. If you need to populate a large number of nodes, this may be slow. For improved performance on the server, you can use the `getPayloadPopulateFn` function:
```tsx
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import { getPayloadPopulateFn } from '@payloadcms/richtext-lexical'
import { convertLexicalToHTMLAsync } from '@payloadcms/richtext-lexical/html-async'
import { getPayload } from 'payload'
import React from 'react'
import config from '../../config.js'
export const MyRSCComponent = async ({
data,
}: {
data: SerializedEditorState
}) => {
const payload = await getPayload({
config,
})
const html = await convertLexicalToHTMLAsync({
data,
populate: await getPayloadPopulateFn({
currentDepth: 0,
depth: 1,
payload,
}),
})
return html && <div dangerouslySetInnerHTML={{ __html: html }} />
}
```
## Converting HTML to Richtext
If you need to convert raw HTML into a Lexical editor state, use `convertHTMLToLexical` from `@payloadcms/richtext-lexical`, along with the [editorConfigFactory to retrieve the editor config](/docs/rich-text/converters#retrieving-the-editor-config):

View File

@@ -6,7 +6,7 @@ desc: Converting between lexical richtext and JSX
keywords: lexical, richtext, jsx
---
## Richtext to JSX
## Converting Richtext to JSX
To convert richtext to JSX, import the `RichText` component from `@payloadcms/richtext-lexical/react` and pass the richtext content to it:
@@ -28,7 +28,7 @@ The `RichText` component includes built-in converters for common Lexical nodes.
populated data to work correctly.
</Banner>
### Internal Links
### Converting Internal Links
By default, Payload doesn't know how to convert **internal** links to JSX, as it doesn't know what the corresponding URL of the internal link is. You'll notice that you get a "found internal link, but internalDocToHref is not provided" error in the console when you try to render content with internal links.
@@ -81,7 +81,7 @@ export const MyComponent: React.FC<{
}
```
### Lexical Blocks
### Converting Lexical Blocks
If your rich text includes custom Blocks or Inline Blocks, you must supply custom converters that match each block's slug. This converter is not included by default, as Payload doesn't know how to render your custom blocks.
@@ -133,9 +133,9 @@ export const MyComponent: React.FC<{
}
```
### Overriding Converters
### Overriding Default JSX Converters
You can override any of the default JSX converters by passing your custom converter, keyed to the node type, to the `converters` prop / the converters function.
You can override any of the default JSX converters by passing passing your custom converter, keyed to the node type, to the `converters` prop / the converters function.
Example - overriding the upload node converter to use next/image:

View File

@@ -6,7 +6,7 @@ desc: Converting between lexical richtext and Markdown / MDX
keywords: lexical, richtext, markdown, md, mdx
---
## Richtext to Markdown
## Converting Richtext to Markdown
If you have access to the Payload Config and the [lexical editor config](/docs/rich-text/converters#retrieving-the-editor-config), you can convert the lexical editor state to Markdown with the following:
@@ -91,7 +91,7 @@ const Pages: CollectionConfig = {
}
```
## Markdown to Richtext
## Converting Markdown to Richtext
If you have access to the Payload Config and the [lexical editor config](/docs/rich-text/converters#retrieving-the-editor-config), you can convert Markdown to the lexical editor state with the following:

View File

@@ -6,7 +6,7 @@ desc: Converting between lexical richtext and plaintext
keywords: lexical, richtext, plaintext, text
---
## Richtext to Plaintext
## Converting Richtext to Plaintext
Here's how you can convert richtext data to plaintext using `@payloadcms/richtext-lexical/plaintext`.

View File

@@ -142,33 +142,32 @@ import { CallToAction } from '../blocks/CallToAction'
Here's an overview of all the included features:
| Feature Name | Included by default | Description |
| ----------------------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`BoldFeature`** | Yes | Handles the bold text format |
| **`ItalicFeature`** | Yes | Handles the italic text format |
| **`UnderlineFeature`** | Yes | Handles the underline text format |
| **`StrikethroughFeature`** | Yes | Handles the strikethrough text format |
| **`SubscriptFeature`** | Yes | Handles the subscript text format |
| **`SuperscriptFeature`** | Yes | Handles the superscript text format |
| **`InlineCodeFeature`** | Yes | Handles the inline-code text format |
| **`ParagraphFeature`** | Yes | Handles paragraphs. Since they are already a key feature of lexical itself, this Feature mainly handles the Slash and Add-Block menu entries for paragraphs |
| **`HeadingFeature`** | Yes | Adds Heading Nodes (by default, H1 - H6, but that can be customized) |
| **`AlignFeature`** | Yes | Allows you to align text left, centered and right |
| **`IndentFeature`** | Yes | Allows you to indent text with the tab key |
| **`UnorderedListFeature`** | Yes | Adds unordered lists (ul) |
| **`OrderedListFeature`** | Yes | Adds ordered lists (ol) |
| **`ChecklistFeature`** | Yes | Adds checklists |
| **`LinkFeature`** | Yes | Allows you to create internal and external links |
| **`RelationshipFeature`** | Yes | Allows you to create block-level (not inline) relationships to other documents |
| **`BlockquoteFeature`** | Yes | Allows you to create block-level quotes |
| **`UploadFeature`** | Yes | Allows you to create block-level upload nodes - this supports all kinds of uploads, not just images |
| **`HorizontalRuleFeature`** | Yes | Horizontal rules / separators. Basically displays an `<hr>` element |
| **`InlineToolbarFeature`** | Yes | The inline toolbar is the floating toolbar which appears when you select text. This toolbar only contains actions relevant for selected text |
| **`FixedToolbarFeature`** | No | This classic toolbar is pinned to the top and always visible. Both inline and fixed toolbars can be enabled at the same time. |
| **`BlocksFeature`** | No | Allows you to use Payload's [Blocks Field](../fields/blocks) directly inside your editor. In the feature props, you can specify the allowed blocks - just like in the Blocks field. |
| **`TreeViewFeature`** | No | Adds a debug box under the editor, which allows you to see the current editor state live, the dom, as well as time travel. Very useful for debugging |
| **`EXPERIMENTAL_TableFeature`** | No | Adds support for tables. This feature may be removed or receive breaking changes in the future - even within a stable lexical release, without needing a major release. |
| **`EXPERIMENTAL_TextStateFeature`** | No | Allows you to store key-value attributes within TextNodes and assign them inline styles. |
| Feature Name | Included by default | Description |
| ------------------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`BoldFeature`** | Yes | Handles the bold text format |
| **`ItalicFeature`** | Yes | Handles the italic text format |
| **`UnderlineFeature`** | Yes | Handles the underline text format |
| **`StrikethroughFeature`** | Yes | Handles the strikethrough text format |
| **`SubscriptFeature`** | Yes | Handles the subscript text format |
| **`SuperscriptFeature`** | Yes | Handles the superscript text format |
| **`InlineCodeFeature`** | Yes | Handles the inline-code text format |
| **`ParagraphFeature`** | Yes | Handles paragraphs. Since they are already a key feature of lexical itself, this Feature mainly handles the Slash and Add-Block menu entries for paragraphs |
| **`HeadingFeature`** | Yes | Adds Heading Nodes (by default, H1 - H6, but that can be customized) |
| **`AlignFeature`** | Yes | Allows you to align text left, centered and right |
| **`IndentFeature`** | Yes | Allows you to indent text with the tab key |
| **`UnorderedListFeature`** | Yes | Adds unordered lists (ul) |
| **`OrderedListFeature`** | Yes | Adds ordered lists (ol) |
| **`ChecklistFeature`** | Yes | Adds checklists |
| **`LinkFeature`** | Yes | Allows you to create internal and external links |
| **`RelationshipFeature`** | Yes | Allows you to create block-level (not inline) relationships to other documents |
| **`BlockquoteFeature`** | Yes | Allows you to create block-level quotes |
| **`UploadFeature`** | Yes | Allows you to create block-level upload nodes - this supports all kinds of uploads, not just images |
| **`HorizontalRuleFeature`** | Yes | Horizontal rules / separators. Basically displays an `<hr>` element |
| **`InlineToolbarFeature`** | Yes | The inline toolbar is the floating toolbar which appears when you select text. This toolbar only contains actions relevant for selected text |
| **`FixedToolbarFeature`** | No | This classic toolbar is pinned to the top and always visible. Both inline and fixed toolbars can be enabled at the same time. |
| **`BlocksFeature`** | No | Allows you to use Payload's [Blocks Field](../fields/blocks) directly inside your editor. In the feature props, you can specify the allowed blocks - just like in the Blocks field. |
| **`TreeViewFeature`** | No | Adds a debug box under the editor, which allows you to see the current editor state live, the dom, as well as time travel. Very useful for debugging |
| **`EXPERIMENTAL_TableFeature`** | No | Adds support for tables. This feature may be removed or receive breaking changes in the future - even within a stable lexical release, without needing a major release. |
Notice how even the toolbars are features? That's how extensible our lexical editor is - you could theoretically create your own toolbar if you wanted to!

View File

@@ -3,7 +3,7 @@ title: Autosave
label: Autosave
order: 30
desc: Using Payload's Draft functionality, you can configure your collections and globals to autosave changes as drafts, and publish only you're ready.
keywords: version history, revisions, audit log, draft, publish, autosave, Content Management System, cms, headless, javascript, node, react, nextjs
keywords: version history, revisions, audit log, draft, publish, autosave, Content Management System, cms, headless, javascript, node, react, nextjss
---
Extending on Payload's [Draft](/docs/versions/drafts) functionality, you can configure your collections and globals to autosave changes as drafts, and publish only you're ready. The Admin UI will automatically adapt to autosaving progress at an interval that you define, and will store all autosaved changes as a new Draft version. Never lose your work - and publish changes to the live document only when you're ready.

View File

@@ -1,7 +1,7 @@
import type { CollectionConfig } from 'payload/types'
import { admins } from './access/admins'
import { adminsAndUser } from './access/adminsAndUser'
import adminsAndUser from './access/adminsAndUser'
import { anyone } from './access/anyone'
import { checkRole } from './access/checkRole'
import { loginAfterCreate } from './hooks/loginAfterCreate'
@@ -25,7 +25,6 @@ export const Users: CollectionConfig = {
create: anyone,
update: adminsAndUser,
delete: admins,
unlock: admins,
admin: ({ req: { user } }) => checkRole(['admin'], user),
},
hooks: {

View File

@@ -1,4 +1,4 @@
import type { Access } from 'payload'
import type { Access } from 'payload/config'
import { checkRole } from './checkRole'

View File

@@ -1,17 +1,19 @@
import type { Access } from 'payload'
import type { Access } from 'payload/config'
import { checkRole } from './checkRole'
export const adminsAndUser: Access = ({ req: { user } }) => {
const adminsAndUser: Access = ({ req: { user } }) => {
if (user) {
if (checkRole(['admin'], user)) {
return true
}
return {
id: { equals: user.id },
id: user.id,
}
}
return false
}
export default adminsAndUser

View File

@@ -1,3 +1,3 @@
import type { Access } from 'payload'
import type { Access } from 'payload/config'
export const anyone: Access = () => true

View File

@@ -1,6 +1,6 @@
import type { User } from '../../payload-types'
export const checkRole = (allRoles: User['roles'] = [], user: User | null = null): boolean => {
export const checkRole = (allRoles: User['roles'] = [], user: User = undefined): boolean => {
if (user) {
if (
allRoles.some((role) => {
@@ -8,9 +8,8 @@ export const checkRole = (allRoles: User['roles'] = [], user: User | null = null
return individualRole === role
})
})
) {
return true
}
)
{return true}
}
return false

View File

@@ -1,4 +1,4 @@
import type { FieldHook } from 'payload'
import type { FieldHook } from 'payload/types'
import type { User } from '../../payload-types'

View File

@@ -1,6 +1,7 @@
import { mongooseAdapter } from '@payloadcms/db-mongodb'
import { lexicalEditor } from '@payloadcms/richtext-lexical'
import path from 'path'
import express from 'express'
import { buildConfig } from 'payload'
import { fileURLToPath } from 'url'

View File

@@ -1,4 +1,5 @@
import express from 'express'
import type { Request, Response } from 'express'
import { parse } from 'url'
import next from 'next'

View File

@@ -1,6 +1,6 @@
{
"name": "payload-monorepo",
"version": "3.39.1",
"version": "3.38.0",
"private": true,
"type": "module",
"scripts": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/admin-bar",
"version": "3.39.1",
"version": "3.38.0",
"description": "An admin bar for React apps using Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "create-payload-app",
"version": "3.39.1",
"version": "3.38.0",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -0,0 +1,8 @@
import { readFileSync } from 'fs'
import { fileURLToPath } from 'node:url'
import path from 'path'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
const packageJson = JSON.parse(readFileSync(path.resolve(dirname, '../../package.json'), 'utf-8'))
export const PACKAGE_VERSION = packageJson.version

View File

@@ -1,8 +1,9 @@
import type { ProjectTemplate } from '../types.js'
import { error, info } from '../utils/log.js'
import { PACKAGE_VERSION } from './constants.js'
export function validateTemplate({ templateName }: { templateName: string }): boolean {
export function validateTemplate(templateName: string): boolean {
const validTemplates = getValidTemplates()
if (!validTemplates.map((t) => t.name).includes(templateName)) {
error(`'${templateName}' is not a valid template.`)
@@ -19,13 +20,13 @@ export function getValidTemplates(): ProjectTemplate[] {
name: 'blank',
type: 'starter',
description: 'Blank 3.0 Template',
url: `https://github.com/payloadcms/payload/templates/blank#main`,
url: `https://github.com/payloadcms/payload/templates/blank#v${PACKAGE_VERSION}`,
},
{
name: 'website',
type: 'starter',
description: 'Website Template',
url: `https://github.com/payloadcms/payload/templates/website#main`,
url: `https://github.com/payloadcms/payload/templates/website#v${PACKAGE_VERSION}`,
},
{
name: 'plugin',

View File

@@ -1,3 +1,4 @@
import execa from 'execa'
import fse from 'fs-extra'
import { fileURLToPath } from 'node:url'
import path from 'path'
@@ -8,7 +9,6 @@ const dirname = path.dirname(filename)
import type { NextAppDetails } from '../types.js'
import { copyRecursiveSync } from '../utils/copy-recursive-sync.js'
import { getLatestPackageVersion } from '../utils/getLatestPackageVersion.js'
import { info } from '../utils/log.js'
import { getPackageManager } from './get-package-manager.js'
import { installPackages } from './install-packages.js'
@@ -36,8 +36,15 @@ export async function updatePayloadInProject(
const packageManager = await getPackageManager({ projectDir })
// Fetch latest Payload version
const latestPayloadVersion = await getLatestPackageVersion({ packageName: 'payload' })
// Fetch latest Payload version from npm
const { exitCode: getLatestVersionExitCode, stdout: latestPayloadVersion } = await execa('npm', [
'show',
'payload',
'version',
])
if (getLatestVersionExitCode !== 0) {
throw new Error('Failed to fetch latest Payload version')
}
if (payloadVersion === latestPayloadVersion) {
return { message: `Payload v${payloadVersion} is already up to date.`, success: true }

View File

@@ -8,6 +8,7 @@ import path from 'path'
import type { CliArgs } from './types.js'
import { configurePayloadConfig } from './lib/configure-payload-config.js'
import { PACKAGE_VERSION } from './lib/constants.js'
import { createProject } from './lib/create-project.js'
import { parseExample } from './lib/examples.js'
import { generateSecret } from './lib/generate-secret.js'
@@ -19,7 +20,6 @@ import { parseTemplate } from './lib/parse-template.js'
import { selectDb } from './lib/select-db.js'
import { getValidTemplates, validateTemplate } from './lib/templates.js'
import { updatePayloadInProject } from './lib/update-payload-in-project.js'
import { getLatestPackageVersion } from './utils/getLatestPackageVersion.js'
import { debug, error, info } from './utils/log.js'
import {
feedbackOutro,
@@ -78,18 +78,13 @@ export class Main {
async init(): Promise<void> {
try {
const debugFlag = this.args['--debug']
const LATEST_VERSION = await getLatestPackageVersion({
debug: debugFlag,
packageName: 'payload',
})
if (this.args['--help']) {
helpMessage()
process.exit(0)
}
const debugFlag = this.args['--debug']
// eslint-disable-next-line no-console
console.log('\n')
p.intro(chalk.bgCyan(chalk.black(' create-payload-app ')))
@@ -205,7 +200,7 @@ export class Main {
const templateArg = this.args['--template']
if (templateArg) {
const valid = validateTemplate({ templateName: templateArg })
const valid = validateTemplate(templateArg)
if (!valid) {
helpMessage()
process.exit(1)
@@ -235,7 +230,7 @@ export class Main {
}
if (debugFlag) {
debug(`Using ${exampleArg ? 'examples' : 'templates'} from git tag: v${LATEST_VERSION}`)
debug(`Using ${exampleArg ? 'examples' : 'templates'} from git tag: v${PACKAGE_VERSION}`)
}
if (!exampleArg) {

View File

@@ -1,34 +0,0 @@
/**
* Fetches the latest version of a package from the NPM registry.
*
* Used in determining the latest version of Payload to use in the generated templates.
*/
export async function getLatestPackageVersion({
debug = false,
packageName = 'payload',
}: {
debug?: boolean
/**
* Package name to fetch the latest version for based on the NPM registry URL
*
* Eg. for `'payload'`, it will fetch the version from `https://registry.npmjs.org/payload`
*
* @default 'payload'
*/
packageName?: string
}) {
try {
const response = await fetch(`https://registry.npmjs.org/${packageName}`)
const data = await response.json()
const latestVersion = data['dist-tags'].latest
if (debug) {
console.log(`Found latest version of ${packageName}: ${latestVersion}`)
}
return latestVersion
} catch (error) {
console.error('Error fetching Payload version:', error)
throw error
}
}

View File

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

View File

@@ -20,6 +20,7 @@ type SearchParam = {
const subQueryOptions = {
lean: true,
limit: 50,
}
/**
@@ -183,7 +184,7 @@ export async function buildSearchParam({
select[joinPath] = true
}
const result = await SubModel.find(subQuery).lean().select(select)
const result = await SubModel.find(subQuery).lean().limit(50).select(select)
const $in: unknown[] = []

View File

@@ -150,18 +150,6 @@ export const buildSortParam = ({
sort = [sort]
}
// In the case of Mongo, when sorting by a field that is not unique, the results are not guaranteed to be in the same order each time.
// So we add a fallback sort to ensure that the results are always in the same order.
let fallbackSort = '-id'
if (timestamps) {
fallbackSort = '-createdAt'
}
if (!(sort.includes(fallbackSort) || sort.includes(fallbackSort.replace('-', '')))) {
sort.push(fallbackSort)
}
const sorting = sort.reduce<Record<string, string>>((acc, item) => {
let sortProperty: string
let sortDirection: SortDirection

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "3.39.1",
"version": "3.38.0",
"description": "The officially supported Postgres database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

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

View File

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

View File

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

View File

@@ -28,8 +28,6 @@ export async function migrateReset(this: DrizzleAdapter): Promise<void> {
const req = await createLocalReq({}, payload)
existingMigrations.reverse()
// Rollback all migrations in order
for (const migration of existingMigrations) {
const migrationFile = migrationFiles.find((m) => m.name === migration.name)

View File

@@ -39,9 +39,8 @@ export const buildOrderBy = ({
}: Args): BuildQueryResult['orderBy'] => {
const orderBy: BuildQueryResult['orderBy'] = []
const createdAt = adapter.tables[tableName]?.createdAt
if (!sort) {
const createdAt = adapter.tables[tableName]?.createdAt
if (createdAt) {
sort = '-createdAt'
} else {
@@ -53,18 +52,6 @@ export const buildOrderBy = ({
sort = [sort]
}
// In the case of Mongo, when sorting by a field that is not unique, the results are not guaranteed to be in the same order each time.
// So we add a fallback sort to ensure that the results are always in the same order.
let fallbackSort = '-id'
if (createdAt) {
fallbackSort = '-createdAt'
}
if (!(sort.includes(fallbackSort) || sort.includes(fallbackSort.replace('-', '')))) {
sort.push(fallbackSort)
}
for (const sortItem of sort) {
let sortProperty: string
let sortDirection: 'asc' | 'desc'

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/graphql",
"version": "3.39.1",
"version": "3.38.0",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/next",
"version": "3.39.1",
"version": "3.38.0",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -59,7 +59,6 @@ export const DocumentTabLink: React.FC<{
className={[baseClass, isActive && `${baseClass}--active`].filter(Boolean).join(' ')}
disabled={isActive}
el={!isActive || href !== pathname ? 'link' : 'div'}
margin={false}
newTab={newTab}
size="medium"
to={!isActive || href !== pathname ? hrefWithLocale : undefined}

View File

@@ -21,22 +21,13 @@ export const DefaultNavClient: React.FC<{
const {
config: {
admin: {
routes: { browseByFolder: foldersRoute },
routes: { folders: foldersRoute },
},
collections,
folders: { collections: folderCollections = {}, enabled } = {},
routes: { admin: adminRoute },
},
} = useConfig()
const [folderCollectionSlugs] = React.useState<string[]>(() => {
return collections.reduce<string[]>((acc, collection) => {
if (collection.folders) {
acc.push(collection.slug)
}
return acc
}, [])
})
const { i18n } = useTranslation()
const folderURL = formatAdminURL({
@@ -48,7 +39,9 @@ export const DefaultNavClient: React.FC<{
return (
<Fragment>
{folderCollectionSlugs.length > 0 && <BrowseByFolderButton active={viewingRootFolderView} />}
{enabled && Object.keys(folderCollections).length > 0 && (
<BrowseByFolderButton active={viewingRootFolderView} />
)}
{groups.map(({ entities, label }, key) => {
return (
<NavGroup isOpen={navPreferences?.groups?.[label]?.open} key={key} label={label}>

View File

@@ -13,8 +13,6 @@ import { redirect } from 'next/navigation.js'
import { getFolderData, parseDocumentID } from 'payload'
import React from 'react'
import { getPreferences } from '../../utilities/getPreferences.js'
// import { renderFolderViewSlots } from './renderFolderViewSlots.js'
export type BuildCollectionFolderViewStateArgs = {
@@ -37,7 +35,6 @@ export const buildCollectionFolderView = async (
disableBulkDelete,
disableBulkEdit,
enableRowSelections,
folderCollectionSlugs,
folderID,
initPageResult,
isInDrawer,
@@ -69,12 +66,12 @@ export const buildCollectionFolderView = async (
if (collectionConfig) {
const query = queryFromArgs || queryFromReq
const collectionFolderPreferences = await getPreferences<{ viewPreference: string }>(
`${collectionSlug}-collection-folder`,
payload,
user.id,
user.collection,
)
// const collectionFolderPreferences = await upsertPreferences<ListPreferences>({
// key: `${collectionSlug}-collection-folder`,
// req,
// value: {
// },
// })
const {
routes: { admin: adminRoute },
@@ -82,7 +79,7 @@ export const buildCollectionFolderView = async (
if (
(!visibleEntities.collections.includes(collectionSlug) && !overrideEntityVisibility) ||
!folderCollectionSlugs.includes(collectionSlug)
!Object.keys(config.folders.collections).includes(collectionSlug)
) {
throw new Error('not-found')
}
@@ -97,13 +94,13 @@ export const buildCollectionFolderView = async (
if (folderID) {
whereConstraints.push({
[config.folders.fieldName]: {
_folder: {
equals: parseDocumentID({ id: folderID, collectionSlug, payload }),
},
})
} else {
whereConstraints.push({
[config.folders.fieldName]: {
_folder: {
exists: false,
},
})
@@ -112,21 +109,22 @@ export const buildCollectionFolderView = async (
const { breadcrumbs, documents, subfolders } = await getFolderData({
collectionSlug,
folderID,
req: initPageResult.req,
payload: initPageResult.req.payload,
search: query?.search as string,
user: initPageResult.req.user,
})
const resolvedFolderID = breadcrumbs[breadcrumbs.length - 1]?.id
if (
!isInDrawer &&
((resolvedFolderID && folderID && folderID !== resolvedFolderID) ||
((resolvedFolderID && folderID && folderID !== String(resolvedFolderID)) ||
(folderID && !resolvedFolderID))
) {
redirect(
return redirect(
formatAdminURL({
adminRoute,
path: `/collections/${collectionSlug}/${config.folders.slug}`,
path: `/collections/${collectionSlug}/folders`,
serverURL: config.serverURL,
}),
)
@@ -175,7 +173,6 @@ export const buildCollectionFolderView = async (
breadcrumbs={breadcrumbs}
collectionSlug={collectionSlug}
documents={documents}
folderCollectionSlugs={folderCollectionSlugs}
folderID={folderID}
search={search}
subfolders={subfolders}
@@ -190,7 +187,6 @@ export const buildCollectionFolderView = async (
enableRowSelections,
hasCreatePermission,
newDocumentURL,
viewPreference: collectionFolderPreferences?.value?.viewPreference,
},
Component: collectionConfig?.admin?.components?.views?.list?.Component,
Fallback: DefaultCollectionFolderView,

View File

@@ -11,9 +11,6 @@ export const CollectionFolderView: React.FC<BuildCollectionFolderViewStateArgs>
const { View } = await buildCollectionFolderView(args)
return View
} catch (error) {
if (error?.message === 'NEXT_REDIRECT') {
throw error
}
if (error.message === 'not-found') {
notFound()
} else {

View File

@@ -5,15 +5,13 @@ import type {
ListQuery,
} from 'payload'
import { DefaultBrowseByFolderView, FolderProvider, HydrateAuthProvider } from '@payloadcms/ui'
import { DefaultFolderView, FolderProvider, HydrateAuthProvider } from '@payloadcms/ui'
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
import { formatAdminURL } from '@payloadcms/ui/shared'
import { redirect } from 'next/navigation.js'
import { getFolderData } from 'payload'
import React from 'react'
import { getPreferences } from '../../utilities/getPreferences.js'
export type BuildFolderViewArgs = {
customCellProps?: Record<string, any>
disableBulkDelete?: boolean
@@ -25,14 +23,13 @@ export type BuildFolderViewArgs = {
query: ListQuery
} & AdminViewServerProps
export const buildBrowseByFolderView = async (
export const buildFolderView = async (
args: BuildFolderViewArgs,
): Promise<BuildCollectionFolderViewResult> => {
const {
disableBulkDelete,
disableBulkEdit,
enableRowSelections,
folderCollectionSlugs,
folderID,
initPageResult,
isInDrawer,
@@ -54,7 +51,9 @@ export const buildBrowseByFolderView = async (
visibleEntities,
} = initPageResult
const collections = folderCollectionSlugs.filter(
const allFolderCollectionSlugs = Object.keys(config?.folders?.collections || {})
const collections = allFolderCollectionSlugs.filter(
(collectionSlug) =>
permissions?.collections?.[collectionSlug]?.read &&
visibleEntities.collections.includes(collectionSlug),
@@ -65,10 +64,11 @@ export const buildBrowseByFolderView = async (
}
const query = queryFromArgs || queryFromReq
// get relationTo filter from query params
const selectedCollectionSlugs: string[] =
Array.isArray(query?.relationTo) && query.relationTo.length
? query.relationTo
: [...folderCollectionSlugs, config.folders.slug]
: [...allFolderCollectionSlugs, config.folders.slug]
const {
routes: { admin: adminRoute },
@@ -76,33 +76,27 @@ export const buildBrowseByFolderView = async (
const { breadcrumbs, documents, subfolders } = await getFolderData({
folderID,
req: initPageResult.req,
payload: initPageResult.req.payload,
search: query?.search as string,
user: initPageResult.req.user,
})
const resolvedFolderID = breadcrumbs[breadcrumbs.length - 1]?.id
if (
!isInDrawer &&
((resolvedFolderID && folderID && folderID !== resolvedFolderID) ||
((resolvedFolderID && folderID && folderID !== String(resolvedFolderID)) ||
(folderID && !resolvedFolderID))
) {
redirect(
return redirect(
formatAdminURL({
adminRoute,
path: config.admin.routes.browseByFolder,
path: config.admin.routes.folders,
serverURL: config.serverURL,
}),
)
}
const browseByFolderPreferences = await getPreferences<{ viewPreference: string }>(
'browse-by-folder',
payload,
user.id,
user.collection,
)
const serverProps: Omit<FolderListViewServerPropsOnly, 'collectionConfig' | 'listPreferences'> = {
documents,
i18n,
@@ -125,7 +119,7 @@ export const buildBrowseByFolderView = async (
// documents cannot be created without a parent folder in this view
const hasCreatePermissionCollectionSlugs = folderID
? [config.folders.slug, ...folderCollectionSlugs]
? [config.folders.slug, ...allFolderCollectionSlugs]
: [config.folders.slug]
return {
@@ -134,7 +128,6 @@ export const buildBrowseByFolderView = async (
breadcrumbs={breadcrumbs}
documents={documents}
filteredCollectionSlugs={selectedCollectionSlugs}
folderCollectionSlugs={folderCollectionSlugs}
folderID={folderID}
subfolders={subfolders}
>
@@ -147,10 +140,9 @@ export const buildBrowseByFolderView = async (
enableRowSelections,
hasCreatePermissionCollectionSlugs,
selectedCollectionSlugs,
viewPreference: browseByFolderPreferences?.value?.viewPreference,
},
// Component:config.folders?.components?.views?.list?.Component,
Fallback: DefaultBrowseByFolderView,
Fallback: DefaultFolderView,
importMap: payload.importMap,
serverProps,
})}

View File

@@ -4,16 +4,13 @@ import { notFound } from 'next/navigation.js'
import type { BuildFolderViewArgs } from './buildView.js'
import { buildBrowseByFolderView } from './buildView.js'
import { buildFolderView } from './buildView.js'
export const BrowseByFolder: React.FC<BuildFolderViewArgs> = async (args) => {
export const FolderView: React.FC<BuildFolderViewArgs> = async (args) => {
try {
const { View } = await buildBrowseByFolderView(args)
const { View } = await buildFolderView(args)
return View
} catch (error) {
if (error?.message === 'NEXT_REDIRECT') {
throw error
}
if (error.message === 'not-found') {
notFound()
} else {

View File

@@ -20,11 +20,7 @@ export const resolveAllFilterOptions = async ({
return
}
if (
(field.type === 'relationship' || field.type === 'upload') &&
'filterOptions' in field &&
field.filterOptions
) {
if ('name' in field && 'filterOptions' in field && field.filterOptions) {
const options = await resolveFilterOptions(field.filterOptions, {
id: undefined,
blockData: undefined,

View File

@@ -1,6 +1,5 @@
import type {
AdminViewServerProps,
CollectionSlug,
DocumentSubViewTypes,
ImportMap,
PayloadComponent,
@@ -15,11 +14,11 @@ import { formatAdminURL } from 'payload/shared'
import type { initPage } from '../../utilities/initPage/index.js'
import { Account } from '../Account/index.js'
import { BrowseByFolder } from '../BrowseByFolder/index.js'
import { CollectionFolderView } from '../CollectionFolders/index.js'
import { CreateFirstUserView } from '../CreateFirstUser/index.js'
import { Dashboard } from '../Dashboard/index.js'
import { Document as DocumentView } from '../Document/index.js'
import { FolderView } from '../Folders/index.js'
import { forgotPasswordBaseClass, ForgotPasswordView } from '../ForgotPassword/index.js'
import { ListView } from '../List/index.js'
import { loginBaseClass, LoginView } from '../Login/index.js'
@@ -52,8 +51,8 @@ export type ViewFromConfig = {
const oneSegmentViews: OneSegmentViews = {
account: Account,
browseByFolder: BrowseByFolder,
createFirstUser: CreateFirstUserView,
folders: FolderView,
forgot: ForgotPasswordView,
inactivity: LogoutInactivity,
login: LoginView,
@@ -75,7 +74,6 @@ type GetRouteDataArgs = {
type GetRouteDataResult = {
DefaultView: ViewFromConfig
documentSubViewType?: DocumentSubViewTypes
folderCollectionSlugs: CollectionSlug[]
folderID?: string
initPageOptions: Parameters<typeof initPage>[0]
serverProps: ServerPropsFromView
@@ -112,13 +110,7 @@ export const getRouteData = ({
const isCollection = segmentOne === 'collections'
let matchedCollection: SanitizedConfig['collections'][number] = undefined
let matchedGlobal: SanitizedConfig['globals'][number] = undefined
const folderCollectionSlugs = config.collections.reduce((acc, { slug, folders }) => {
if (folders) {
return [...acc, slug]
}
return acc
}, [])
const isFolderViewEnabled = config.folders?.enabled
const serverProps: ServerPropsFromView = {
viewActions: config?.admin?.components?.actions || [],
@@ -168,7 +160,7 @@ export const getRouteData = ({
if (oneSegmentViews[viewKey]) {
// --> /account
// --> /create-first-user
// --> /browse-by-folder
// --> /folders
// --> /forgot
// --> /login
// --> /logout
@@ -182,15 +174,15 @@ export const getRouteData = ({
templateClassName = baseClasses[viewKey]
templateType = 'minimal'
if (isFolderViewEnabled && viewKey === 'folders') {
templateType = 'default'
viewType = 'folders'
}
if (viewKey === 'account') {
templateType = 'default'
viewType = 'account'
}
if (folderCollectionSlugs.length && viewKey === 'browseByFolder') {
templateType = 'default'
viewType = 'folders'
}
}
break
}
@@ -203,18 +195,17 @@ export const getRouteData = ({
templateClassName = baseClasses[segmentTwo]
templateType = 'minimal'
viewType = 'reset'
} else if (
folderCollectionSlugs.length &&
`/${segmentOne}` === config.admin.routes.browseByFolder
) {
// --> /browse-by-folder/:folderID
ViewToRender = {
Component: oneSegmentViews.browseByFolder,
} else if (`/${segmentOne}` === config.admin.routes.folders) {
if (isFolderViewEnabled) {
// --> /folders/:folderID
ViewToRender = {
Component: oneSegmentViews.folders,
}
templateClassName = baseClasses.folders
templateType = 'default'
viewType = 'folders'
folderID = segmentTwo
}
templateClassName = baseClasses.folders
templateType = 'default'
viewType = 'folders'
folderID = segmentTwo
} else if (isCollection && matchedCollection) {
// --> /collections/:collectionSlug
@@ -260,21 +251,23 @@ export const getRouteData = ({
templateType = 'minimal'
viewType = 'verify'
} else if (isCollection && matchedCollection) {
if (
segmentThree === config.folders.slug &&
folderCollectionSlugs.includes(matchedCollection.slug)
) {
// Collection Folder Views
// --> /collections/:collectionSlug/:folderCollectionSlug
// --> /collections/:collectionSlug/:folderCollectionSlug/:folderID
ViewToRender = {
Component: CollectionFolderView,
}
if (segmentThree === 'folders') {
if (
isFolderViewEnabled &&
Object.keys(config.folders.collections).includes(matchedCollection.slug)
) {
// Collection Folder Views
// --> /collections/:collectionSlug/folders
// --> /collections/:collectionSlug/folders/:folderID
ViewToRender = {
Component: CollectionFolderView,
}
templateClassName = `collection-folders`
templateType = 'default'
viewType = 'collection-folders'
folderID = segmentFour
templateClassName = `collection-folders`
templateType = 'default'
viewType = 'collection-folders'
folderID = segmentFour
}
} else {
// Collection Edit Views
// --> /collections/:collectionSlug/:id
@@ -335,7 +328,6 @@ export const getRouteData = ({
return {
DefaultView: ViewToRender,
documentSubViewType,
folderCollectionSlugs,
folderID,
initPageOptions,
serverProps,

View File

@@ -1,16 +1,15 @@
import type { I18nClient } from '@payloadcms/translations'
import type { Metadata } from 'next'
import type {
AdminViewClientProps,
AdminViewServerPropsOnly,
ImportMap,
SanitizedConfig,
} from 'payload'
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
import { getClientConfig } from '@payloadcms/ui/utilities/getClientConfig'
import { notFound, redirect } from 'next/navigation.js'
import {
type AdminViewClientProps,
type AdminViewServerPropsOnly,
type ImportMap,
parseDocumentID,
type SanitizedConfig,
} from 'payload'
import { formatAdminURL } from 'payload/shared'
import React from 'react'
@@ -65,8 +64,7 @@ export const RootPage = async ({
const {
DefaultView,
documentSubViewType,
folderCollectionSlugs,
folderID: folderIDParam,
folderID,
initPageOptions,
serverProps,
templateClassName,
@@ -139,20 +137,8 @@ export const RootPage = async ({
importMap,
})
const payload = initPageResult?.req.payload
const folderID = parseDocumentID({
id: folderIDParam,
collectionSlug: payload.config.folders.slug,
payload,
})
const RenderedView = RenderServerComponent({
clientProps: {
clientConfig,
documentSubViewType,
folderCollectionSlugs,
viewType,
} satisfies AdminViewClientProps,
clientProps: { clientConfig, documentSubViewType, viewType } satisfies AdminViewClientProps,
Component: DefaultView.payloadComponent,
Fallback: DefaultView.Component,
importMap,

View File

@@ -3,11 +3,11 @@ import type { SanitizedConfig } from 'payload'
import { getNextRequestI18n } from '../../utilities/getNextRequestI18n.js'
import { generateAccountViewMetadata } from '../Account/metadata.js'
import { generateBrowseByFolderMetadata } from '../BrowseByFolder/metadata.js'
import { generateCollectionFolderMetadata } from '../CollectionFolders/metadata.js'
import { generateCreateFirstUserViewMetadata } from '../CreateFirstUser/metadata.js'
import { generateDashboardViewMetadata } from '../Dashboard/metadata.js'
import { generateDocumentViewMetadata } from '../Document/metadata.js'
import { generateBrowseByFolderMetadata } from '../Folders/metadata.js'
import { generateForgotPasswordViewMetadata } from '../ForgotPassword/metadata.js'
import { generateListViewMetadata } from '../List/metadata.js'
import { generateLoginViewMetadata } from '../Login/metadata.js'
@@ -43,14 +43,8 @@ export const generatePageMetadata = async ({
params: paramsPromise,
}: Args) => {
const config = await configPromise
const params = await paramsPromise
const folderCollectionSlugs = config.collections.reduce((acc, { slug, folders }) => {
if (folders) {
return [...acc, slug]
}
return acc
}, [])
const params = await paramsPromise
const segments = Array.isArray(params.segments) ? params.segments : []
const currentRoute = `/${segments.join('/')}`
@@ -81,22 +75,20 @@ export const generatePageMetadata = async ({
break
}
case 1: {
if (folderCollectionSlugs.length && `/${segmentOne}` === config.admin.routes.browseByFolder) {
// --> /:folderCollectionSlug
meta = await oneSegmentMeta.folders({ config, i18n })
} else if (segmentOne === 'account') {
// --> /account
meta = await generateAccountViewMetadata({ config, i18n })
break
} else if (oneSegmentMeta[segmentOne]) {
if (oneSegmentMeta[segmentOne] && segmentOne !== 'account') {
// --> /create-first-user
// --> /forgot
// --> /folders
// --> /login
// --> /logout
// --> /logout-inactivity
// --> /unauthorized
meta = await oneSegmentMeta[segmentOne]({ config, i18n })
break
} else if (segmentOne === 'account') {
// --> /account
meta = await generateAccountViewMetadata({ config, i18n })
break
}
break
}
@@ -104,11 +96,8 @@ export const generatePageMetadata = async ({
if (`/${segmentOne}` === config.admin.routes.reset) {
// --> /reset/:token
meta = await generateResetPasswordViewMetadata({ config, i18n })
} else if (
folderCollectionSlugs.length &&
`/${segmentOne}` === config.admin.routes.browseByFolder
) {
// --> /browse-by-folder/:folderID
} else if (`/${segmentOne}` === config.admin.routes.folders) {
// --> /folders/:folderID
meta = await generateBrowseByFolderMetadata({ config, i18n })
} else if (isCollection) {
// --> /collections/:collectionSlug
@@ -129,11 +118,11 @@ export const generatePageMetadata = async ({
// --> /:collectionSlug/verify/:token
meta = await generateVerifyViewMetadata({ config, i18n })
} else if (isCollection) {
if (segmentThree === config.folders.slug) {
if (folderCollectionSlugs.includes(collectionConfig.slug)) {
if (segmentThree === 'folders') {
if (Object.keys(config.folders.collections).includes(collectionConfig.slug)) {
// Collection Folder Views
// --> /collections/:collectionSlug/:folderCollectionSlug
// --> /collections/:collectionSlug/:folderCollectionSlug/:id
// --> /collections/:collectionSlug/folders
// --> /collections/:collectionSlug/folders/:id
meta = await generateCollectionFolderMetadata({
collectionConfig,
config,

View File

@@ -1,6 +1,6 @@
import type { ArrayFieldClient, BlocksFieldClient, ClientConfig, ClientField } from 'payload'
import { fieldShouldBeLocalized, groupHasName } from 'payload/shared'
import { fieldShouldBeLocalized } from 'payload/shared'
import { fieldHasChanges } from './fieldHasChanges.js'
import { getFieldsForRowComparison } from './getFieldsForRowComparison.js'
@@ -114,37 +114,25 @@ export function countChangedFields({
// Fields that have nested fields and nest their fields' data.
case 'group': {
if (groupHasName(field)) {
if (locales && fieldShouldBeLocalized({ field, parentIsLocalized })) {
locales.forEach((locale) => {
count += countChangedFields({
comparison: comparison?.[field.name]?.[locale],
config,
fields: field.fields,
locales,
parentIsLocalized: parentIsLocalized || field.localized,
version: version?.[field.name]?.[locale],
})
})
} else {
if (locales && fieldShouldBeLocalized({ field, parentIsLocalized })) {
locales.forEach((locale) => {
count += countChangedFields({
comparison: comparison?.[field.name],
comparison: comparison?.[field.name]?.[locale],
config,
fields: field.fields,
locales,
parentIsLocalized: parentIsLocalized || field.localized,
version: version?.[field.name],
version: version?.[field.name]?.[locale],
})
}
})
} else {
// Unnamed group field: data is NOT nested under `field.name`
count += countChangedFields({
comparison,
comparison: comparison?.[field.name],
config,
fields: field.fields,
locales,
parentIsLocalized: parentIsLocalized || field.localized,
version,
version: version?.[field.name],
})
}
break

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/payload-cloud",
"version": "3.39.1",
"version": "3.38.0",
"description": "The official Payload Cloud plugin",
"homepage": "https://payloadcms.com",
"repository": {

View File

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

View File

@@ -1,8 +1,7 @@
import type { MarkOptional } from 'ts-essentials'
import type { Option, SelectField, SelectFieldClient } from '../../fields/config/types.js'
import type { SelectField, SelectFieldClient } from '../../fields/config/types.js'
import type { SelectFieldValidation } from '../../fields/validations.js'
import type { PayloadRequest } from '../../types/index.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
ClientFieldBase,

View File

@@ -1,7 +1,7 @@
import { type SupportedLanguages } from '@payloadcms/translations'
import type { SanitizedDocumentPermissions } from '../../auth/types.js'
import type { Field, Option, Validate } from '../../fields/config/types.js'
import type { Field, Validate } from '../../fields/config/types.js'
import type { TypedLocale } from '../../index.js'
import type { DocumentPreferences } from '../../preferences/types.js'
import type { PayloadRequest, SelectType, Where } from '../../types/index.js'
@@ -67,10 +67,6 @@ export type FieldState = {
lastRenderedPath?: string
passesCondition?: boolean
rows?: Row[]
/**
* The result of running `field.filterOptions` on select fields.
*/
selectFilterOptions?: Option[]
valid?: boolean
validate?: Validate
value?: unknown

View File

@@ -27,7 +27,6 @@ export type FolderListViewClientProps = {
enableRowSelections?: boolean
hasCreatePermission: boolean
newDocumentURL: string
viewPreference: 'grid' | 'list'
} & FolderListViewSlots
export type FolderListViewSlotSharedClientProps = {

View File

@@ -31,7 +31,6 @@ export type AdminViewConfig = {
export type AdminViewClientProps = {
clientConfig: ClientConfig
documentSubViewType?: DocumentSubViewTypes
folderCollectionSlugs?: SanitizedCollectionConfig['slug'][]
viewType: ViewTypes
}
@@ -42,7 +41,7 @@ export type AdminViewServerPropsOnly = {
* @todo remove `docID` here as it is already contained in `initPageResult`
*/
readonly docID?: number | string
readonly folderID?: number | string
readonly folderID?: string
readonly importMap: ImportMap
readonly initialData?: Data
readonly initPageResult: InitPageResult

View File

@@ -82,7 +82,6 @@ export const addDefaultsToCollectionConfig = (collection: CollectionConfig): Col
collection.custom = collection.custom ?? {}
collection.endpoints = collection.endpoints ?? []
collection.fields = collection.fields ?? []
collection.folders = collection.folders ?? false
collection.hooks = {
afterChange: [],

View File

@@ -32,7 +32,6 @@ import type {
RelationshipField,
UploadField,
} from '../../fields/config/types.js'
import type { CollectionFoldersConfiguration } from '../../folders/types.js'
import type {
CollectionSlug,
JsonObject,
@@ -440,10 +439,6 @@ export type CollectionConfig<TSlug extends CollectionSlug = any> = {
*/
endpoints?: false | Omit<Endpoint, 'root'>[]
fields: Field[]
/**
* Enables folders for this collection
*/
folders?: CollectionFoldersConfiguration
/**
* Specify which fields should be selected always, regardless of the `select` query which can be useful that the field exists for access control / hooks
*/

View File

@@ -2,7 +2,6 @@ import type { JobsConfig } from '../queues/config/types/index.js'
import type { Config } from './types.js'
import defaultAccess from '../auth/defaultAccess.js'
import { foldersSlug, parentFolderFieldName } from '../folders/constants.js'
/**
* @deprecated - remove in 4.0. This is error-prone, as mutating this object will affect any objects that use the defaults as a base.
@@ -24,8 +23,8 @@ export const defaults: Omit<Config, 'db' | 'editor' | 'secret'> = {
},
routes: {
account: '/account',
browseByFolder: '/browse-by-folder',
createFirstUser: '/create-first-user',
folders: '/folders',
forgot: '/forgot',
inactivity: '/logout-inactivity',
login: '/login',
@@ -100,8 +99,8 @@ export const addDefaultsToConfig = (config: Config): Config => {
},
routes: {
account: '/account',
browseByFolder: '/browse-by-folder',
createFirstUser: '/create-first-user',
folders: '/folders',
forgot: '/forgot',
inactivity: '/logout-inactivity',
login: '/login',
@@ -113,9 +112,9 @@ export const addDefaultsToConfig = (config: Config): Config => {
}
config.folders = {
slug: foldersSlug,
collections: {},
debug: false,
fieldName: parentFolderFieldName,
enabled: false,
...(config.folders || {}),
}

View File

@@ -40,10 +40,10 @@ import type {
import type { DatabaseAdapterResult } from '../database/types.js'
import type { EmailAdapter, SendEmailOptions } from '../email/types.js'
import type { ErrorName } from '../errors/types.js'
import type { RootFoldersConfiguration } from '../folders/types.js'
import type { GlobalConfig, Globals, SanitizedGlobalConfig } from '../globals/config/types.js'
import type {
Block,
CollectionSlug,
FlattenedBlock,
JobsConfig,
Payload,
@@ -847,16 +847,16 @@ export type Config = {
* @default '/account'
*/
account?: `/${string}`
/** The route for the browse by folder view.
*
* @default '/browse-by-folder'
*/
browseByFolder?: `/${string}`
/** The route for the create first user page.
*
* @default '/create-first-user'
*/
createFirstUser?: `/${string}`
/** The route for folder view.
*
* @default '/folders'
*/
folders: `/${string}`
/** The route for the forgot password page.
*
* @default '/forgot'
@@ -985,9 +985,42 @@ export type Config = {
endpoints?: Endpoint[]
/**
* Options for folder view within the admin panel
* @experimental this feature may change in minor versions until it is fully stable
*/
folders?: RootFoldersConfiguration
folders?: {
/**
* An array of functions to be ran when the folder collection is initialized
* This allows plugins to modify the collection configuration
*/
collectionOverrides?: (({
collection,
}: {
collection: CollectionConfig
}) => CollectionConfig | Promise<CollectionConfig>)[]
/**
* Collections that you would like organize within folders
*/
collections: {
[key: CollectionSlug]: any
}
/**
* Ability to view hidden fields and collections related to folders
*
* @default false
*/
debug?: boolean
/**
* Enable folders in the admin panel
*
* @default false
*/
enabled?: boolean
/**
* Slug for the folder collection
*
* @default "_folders"
*/
slug?: string
}
/**
* @see https://payloadcms.com/docs/configuration/globals#global-configs
*/

View File

@@ -21,8 +21,6 @@ export async function migrateReset(this: BaseDatabaseAdapter): Promise<void> {
const req = await createLocalReq({}, payload)
migrationFiles.reverse()
// Rollback all migrations in order
for (const migration of migrationFiles) {
// Create or update migration in database

View File

@@ -29,7 +29,6 @@ export {
fieldIsVirtual,
fieldShouldBeLocalized,
fieldSupportsMany,
groupHasName,
optionIsObject,
optionIsValue,
optionsAreObjects,
@@ -55,9 +54,10 @@ export { formatFolderOrDocumentItem } from '../folders/utils/formatFolderOrDocum
export { validOperators, validOperatorSet } from '../types/constants.js'
export { formatFilesize } from '../uploads/formatFilesize.js'
export { isImage } from '../uploads/isImage.js'
export { isImage } from '../uploads/isImage.js'
export { combineWhereConstraints } from '../utilities/combineWhereConstraints.js'
export {
deepCopyObject,
deepCopyObjectComplex,
@@ -73,14 +73,12 @@ export {
} from '../utilities/deepMerge.js'
export { extractID } from '../utilities/extractID.js'
export { fieldSchemaToJSON } from '../utilities/fieldSchemaToJSON.js'
export { flattenAllFields } from '../utilities/flattenAllFields.js'
export { default as flattenTopLevelFields } from '../utilities/flattenTopLevelFields.js'
export { default as flattenTopLevelFields } from '../utilities/flattenTopLevelFields.js'
export { formatAdminURL } from '../utilities/formatAdminURL.js'
export { formatLabels, toWords } from '../utilities/formatLabels.js'
export { getBestFitFromSizes } from '../utilities/getBestFitFromSizes.js'
export { getDataByPath } from '../utilities/getDataByPath.js'
export { getFieldPermissions } from '../utilities/getFieldPermissions.js'

View File

@@ -36,7 +36,7 @@ export type ServerOnlyFieldProperties =
| 'dbName' // can be a function
| 'editor' // This is a `richText` only property
| 'enumName' // can be a function
| 'filterOptions' // This is a `relationship`, `upload`, and `select` only property
| 'filterOptions' // This is a `relationship` and `upload` only property
| 'graphQL'
| 'label'
| 'typescriptSchema'
@@ -53,7 +53,7 @@ const serverOnlyFieldProperties: Partial<ServerOnlyFieldProperties>[] = [
'access',
'validate',
'defaultValue',
'filterOptions', // This is a `relationship`, `upload`, and `select` only property
'filterOptions', // This is a `relationship` and `upload` only property
'editor', // This is a `richText` only property
'custom',
'typescriptSchema',
@@ -67,12 +67,10 @@ const serverOnlyFieldProperties: Partial<ServerOnlyFieldProperties>[] = [
// `tabs`
// `admin`
]
const serverOnlyFieldAdminProperties: Partial<ServerOnlyFieldAdminProperties>[] = [
'condition',
'components',
]
type FieldWithDescription = {
admin: AdminClient
} & ClientField

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