Compare commits

..

153 Commits

Author SHA1 Message Date
Elliot DeNolf
ea4203bb32 chore(release): v3.0.0-beta.10 [skip ci] 2024-04-12 14:58:12 -04:00
Elliot DeNolf
568b5073c8 chore(deps): sync pnpm-lock.yaml 2024-04-12 14:56:42 -04:00
Elliot DeNolf
471e1f4827 chore: specify next canary peer dep 2024-04-12 14:56:04 -04:00
Elliot DeNolf
b9185a6fcd chore: fix more publish config exports 2024-04-12 14:48:34 -04:00
Elliot DeNolf
80496aa94c fix: remove all exports null (#5830) 2024-04-12 14:35:36 -04:00
Alessio Gravili
5fd6e3c1a8 fix!: upgrade minimum required node version from 18.17.0 to v18.20.2. Some old node versions have issues with our loader (#5829) 2024-04-12 14:01:33 -04:00
Alessio Gravili
54590c1700 fix(richtext-lexical)!: fix output of internal list HTML converter (#5827)
BREAKING: Changes the classnames of the converted HTML
2024-04-12 12:10:44 -04:00
Elliot DeNolf
b1259be8f2 chore(release): v3.0.0-beta.9 [skip ci] 2024-04-12 12:06:41 -04:00
Elliot DeNolf
cd161e4b16 feat!: remove pointer files (#5826) 2024-04-12 11:58:34 -04:00
Alessio Gravili
cb4214fe6e fix(richtext-lexical)!: fix output of internal list HTML converter
BREAKING: Changes the classnames of the converted HTML
2024-04-12 11:58:05 -04:00
Elliot DeNolf
9d42751a42 feat!: remove more pointer files 2024-04-12 11:39:08 -04:00
Elliot DeNolf
c2c637b359 chore: clean up unused files 2024-04-12 11:35:25 -04:00
Paul
2f446e11d6 chore: bump nextjs dependencies to ^14.2 (#5820)
fix(plugin-seo): overriding existing endpoints
2024-04-12 12:32:45 -03:00
Elliot DeNolf
4f566b088c feat!: remove pointer files 2024-04-12 11:31:35 -04:00
Dan Ribbens
0d40d87b31 fix(db-postgres): relationship query pagination (#5803) 2024-04-12 11:18:40 -04:00
Jacob Fletcher
94c0095b3b chore(ui): removes all static font assets (#5821) 2024-04-12 09:57:31 -04:00
Elliot DeNolf
4328060637 feat(pcs): vercel blob storage adapter (#5811) 2024-04-12 09:37:35 -04:00
Elliot DeNolf
d98d0fd5bd chore(pcs): use proper getFilePrefix 2024-04-12 09:30:19 -04:00
Elliot DeNolf
5db2863d08 feat(pcs): export utilities 2024-04-12 09:30:10 -04:00
Jacob Fletcher
ff5e438d6d chore(ui): replaces suisse-intl font with system fallbacks 2024-04-12 09:16:43 -04:00
Paul
bfd5f13ee9 chore: add types for local api find/update operations (#5808) 2024-04-12 10:15:57 -03:00
Elliot DeNolf
8043188f36 chore(pcs): update README 2024-04-12 09:12:34 -04:00
Elliot DeNolf
e3e0998772 chore: ignore vercelBlob pointers, adjust peer deps 2024-04-12 00:34:41 -04:00
Elliot DeNolf
86adc6f282 chore: add vercelBlob to exports 2024-04-11 23:05:22 -04:00
Elliot DeNolf
b51b519d30 feat(plugin-cloud-storage): vercel blob storage adapter 2024-04-11 22:58:55 -04:00
Alessio Gravili
c70dcb6a59 feat(richtext-lexical): add HorizontalRuleFeature, improve block handle positioning (#5806) 2024-04-11 16:51:54 -04:00
Alessio Gravili
2486c7dba0 fix(richtext-lexical): incorrect margin for nested unordered lists 2024-04-11 16:42:35 -04:00
Paul
1456fcdcad chore: type locale from localization config on the payload request (#5801) 2024-04-11 17:38:35 -03:00
Alessio Gravili
a216800c72 chore(richtext-lexical): fix build error 2024-04-11 16:31:40 -04:00
Alessio Gravili
c3d8597c13 feat(richtext-lexical): add HorizontalRuleFeature 2024-04-11 16:24:04 -04:00
Alessio Gravili
844663ce1a fix(richtext-lexical): limit unnecessary floating handle positioning updates 2024-04-11 15:55:55 -04:00
Alessio Gravili
055e6af7b7 feat(richtext-lexical): improve floating handle y-positioning by positioning it in the center for smaller elements. 2024-04-11 15:55:43 -04:00
Alessio Gravili
479e6ecddc fix(richtext-lexical): incorrect floating handle y-position calculation next to certain kinds of HTML elements like HR 2024-04-11 15:55:26 -04:00
Jacob Fletcher
b9456e8244 fix(next): safely handles missing json body in post requests (#5797) 2024-04-11 15:53:50 -04:00
Jacob Fletcher
432dfef435 chore(next): installs merriweather as google font and removes static assets 2024-04-11 15:31:19 -04:00
Elliot DeNolf
429c6f7a48 chore(release): v3.0.0-beta.6 [skip ci] 2024-04-11 15:20:10 -04:00
Elliot DeNolf
6d41f6c56d fix(ui): actual scss paths [skip ci] 2024-04-11 15:17:33 -04:00
Elliot DeNolf
20ac2b86cf chore(release): v3.0.0-beta.5 [skip ci] 2024-04-11 15:11:14 -04:00
Elliot DeNolf
b88455166a fix: improve config finding (#5800) 2024-04-11 15:08:04 -04:00
Elliot DeNolf
9b86de1f9d fix(ui): scss paths 2024-04-11 15:06:27 -04:00
Elliot DeNolf
1393c72281 fix: improve config finding 2024-04-11 15:06:09 -04:00
Jacob Fletcher
bcb538aee2 fix(next): awaits logout operation in api route handler 2024-04-11 14:57:35 -04:00
Jacob Fletcher
01e8f8c649 Merge branch 'beta' into fix/post-body-parse 2024-04-11 14:15:56 -04:00
Elliot DeNolf
1119cf3af9 chore(release): v3.0.0-beta.4 [skip ci] 2024-04-11 14:02:17 -04:00
Elliot DeNolf
216934145c fix: default baseUrl in loader if not set (#5798) 2024-04-11 13:52:35 -04:00
James Mikrut
40f952cac3 chore: type local API auth function and PayloadRequest (#5791) 2024-04-11 13:45:24 -04:00
Jacob Fletcher
330e4a7724 fix(next): safely handles missing json body in post requests 2024-04-11 13:42:21 -04:00
Paul Popus
e676503e02 update further types 2024-04-11 14:12:03 -03:00
James Mikrut
06233fbb2f fix: Generated ids for an array items are the same as global id if it's created (#5795) 2024-04-11 13:06:24 -04:00
Paul Popus
17298695b1 fix: provide request to the previewFunction and fix type 2024-04-11 14:01:22 -03:00
Jarrod Flesch
c1081ccfe2 chore(tests): flakey drawer, tab, navigation tests (#5792) 2024-04-11 12:57:19 -04:00
Ritsu
30da5a8643 fix: generated ids for array items the same as global id 2024-04-11 19:46:14 +03:00
Paul Popus
55bf5436e4 fix type issues 2024-04-11 13:09:39 -03:00
Elliot DeNolf
a4956dc649 fix(cpa): ast parse error handling (#5793) 2024-04-11 12:01:16 -04:00
James Mikrut
512b7bd429 fix: number ids were not sanitized to number in rest api (#5778) 2024-04-11 11:19:22 -04:00
Paul Popus
5119c51439 chore: type request too 2024-04-11 12:19:11 -03:00
James
f3e25f3277 more de-flake 2024-04-11 11:17:59 -04:00
Jacob Fletcher
1275c70187 feat(ui): provides payload as prop to all custom server components (#5775) 2024-04-11 11:16:15 -04:00
James
be69fc448d chore: de-flake 2024-04-11 10:59:26 -04:00
Paul Popus
bcccefe98e chore: add int test for local API login function 2024-04-11 11:56:52 -03:00
Paul Popus
2061f38d9e feat: add new type generation for the auth operation 2024-04-11 11:56:42 -03:00
Elliot DeNolf
d0869d9087 chore: unused file [skip ci] 2024-04-11 10:22:06 -04:00
Elliot DeNolf
1eabf316d6 chore(templates): add generate:types to blank 2024-04-10 22:30:56 -04:00
Elliot DeNolf
a0dd750a52 chore(cpa): add or operator to process.env.DATABASE_URI [skip ci] 2024-04-10 22:17:52 -04:00
Elliot DeNolf
2fc9885abc chore(scripts): add getPackageRegistryVersions script 2024-04-10 21:26:50 -04:00
Elliot DeNolf
14d683fb9a chore(scripts): update release script 2024-04-10 21:26:31 -04:00
Elliot DeNolf
e286519cb1 chore(release): v3.0.0-beta.3 [skip ci] 2024-04-10 20:46:22 -04:00
Elliot DeNolf
b1e78a3562 feat(cpa): improvements (#5783) 2024-04-10 20:35:19 -04:00
Elliot DeNolf
0bc103658a chore(cpa): improve move message 2024-04-10 20:29:57 -04:00
Elliot DeNolf
9037b9b4fa chore(cpa): remove 2.0 templates until updated 2024-04-10 20:25:20 -04:00
Elliot DeNolf
d194493e9a chore(cpa): update help 2024-04-10 20:24:51 -04:00
Elliot DeNolf
aa22344cdb fix(cpa): append to existing .env 2024-04-10 20:16:59 -04:00
Elliot DeNolf
736e7b822e chore(release): v3.0.0-beta.2 [skip ci] 2024-04-10 17:32:32 -04:00
Elliot DeNolf
2ebda95036 fix(next): proper named export of withPayload 2024-04-10 17:30:47 -04:00
Elliot DeNolf
d8783eaad4 chore(release): v3.0.0-beta.1 [skip ci] 2024-04-10 17:16:20 -04:00
Elliot DeNolf
cddb08de1a chore: ignore payload/i18n 2024-04-10 17:11:20 -04:00
James
d4e5d3df54 chore: fixes to unit tests 2024-04-10 17:05:44 -04:00
James
414b03ce74 chore: fix to unit tests 2024-04-10 17:03:37 -04:00
James
96dbab8834 chore: misc fixes 2024-04-10 16:58:08 -04:00
Elliot DeNolf
03a110a750 feat(next)!: cjs support (#5772) 2024-04-10 16:44:20 -04:00
Elliot DeNolf
6accc705be chore(next): cjs build 2024-04-10 16:32:36 -04:00
Elliot DeNolf
312dca003b feat(cpa): CJS next config AST parsing 2024-04-10 16:32:19 -04:00
James
4f9fdb6c14 fix: number ids were not sanitized to number in rest api 2024-04-10 16:01:28 -04:00
Jarrod Flesch
94af06466b chore: re-exports languages in payload (#5771) 2024-04-10 15:55:01 -04:00
Jacob Fletcher
7cf2686097 fix: optionally types req in auth operation (#5769) 2024-04-10 14:08:47 -04:00
Elliot DeNolf
f14883aa11 chore: update blank 3.0 template for withPayload change 2024-04-10 13:41:11 -04:00
Elliot DeNolf
9df8de2386 chore: adjust exports 2024-04-10 13:41:11 -04:00
James
8b2cf4705e chore: withPayload CJS 2024-04-10 13:41:10 -04:00
Jarrod Flesch
364e9832ac fix(next): ensures requested lang header is supported (#5765) 2024-04-10 13:05:56 -04:00
Jarrod Flesch
2deeb61f17 fix: locale switcher flakey test (#5761) 2024-04-10 13:05:30 -04:00
Patrik
14498e8a9c fix(ui): avoids getting and setting doc preferences when creating new (#5758) 2024-04-10 11:42:03 -04:00
Alessio Gravili
eb78022387 fix: undo changing baseBlockFields type to FieldWithRichTextRequiredEditor (#5767) 2024-04-10 11:38:27 -04:00
Elliot DeNolf
3677a59a78 fix(cpa): dependency tag (#5768) 2024-04-10 11:25:54 -04:00
Alessio Gravili
7c1c840a59 chore: increase admin e2e beforeAll timeout, as prebuild sometimes takes longer than the timeout 2024-04-10 11:22:17 -04:00
Alessio Gravili
a73eaf5d37 chore: fix test suite types, add LexicalBlock type 2024-04-10 11:07:01 -04:00
Alessio Gravili
68989a58a8 fix: undo changing baseBlockFields types to FieldWithRichTextRequiredEditor 2024-04-10 10:56:07 -04:00
Alessio Gravili
9841731ae7 feat: properly type withPayload (#5756) 2024-04-10 09:51:46 -04:00
Elliot DeNolf
ba7ac5d439 test: fix unit tests (#5760) 2024-04-09 23:08:20 -04:00
Elliot DeNolf
7c60772b26 ci: alpha -> beta branch push list 2024-04-09 23:07:09 -04:00
Alessio Gravili
1141a5d3af fix(richtext-lexical): do not render uploads extra fields drawer if no extra fields are provided (#5755) 2024-04-09 16:30:43 -04:00
Elliot DeNolf
6f74fd1f98 chore(release): v3.0.0-beta.0 [skip ci] 2024-04-09 15:00:39 -04:00
Alessio Gravili
75873bfcfa fix(richtext-lexical): catch errors that may occur during HTML generation (#5752) 2024-04-09 14:53:17 -04:00
Jarrod Flesch
1faf621f17 fix: persist locale when navigating (#5753) 2024-04-09 14:49:26 -04:00
Elliot DeNolf
1d1c73dfcc chore(release): v3.0.0-alpha.61 [skip ci] 2024-04-09 14:36:04 -04:00
Patrik
d057ce0a85 fix(next): removes global slug from collectionSlug prop in SetStepNav (#5744) 2024-04-09 14:26:09 -04:00
James Mikrut
0ce26d2c08 Feat/config i18n (#5735) 2024-04-09 14:13:17 -04:00
Alessio Gravili
abf285d713 chore: get dev:generate-types to work again (#5750) 2024-04-09 14:12:23 -04:00
James
c2ee8e3999 chore: de-flakes fields/index tests 2024-04-09 13:56:32 -04:00
James
167ba0c68f chore: adds back all tests 2024-04-09 13:50:54 -04:00
James
9ad1cbe920 chore: adjusts test snapshot logic to restore uploads properly 2024-04-09 13:33:42 -04:00
James
313ea52e3d chore: getRequestLanguage now defaults 2024-04-09 13:17:43 -04:00
James
3acfb7a83f chore: corrects imports 2024-04-09 12:43:36 -04:00
James
783dae2bbb Merge branch 'alpha' of github.com:payloadcms/payload into feat/config-i18n 2024-04-09 12:35:57 -04:00
Alessio Gravili
59681b211b fix(richtext-lexical): upload nodes weren't visible (#5746) 2024-04-09 12:32:24 -04:00
James
98438175cf chore: handles server errors 2024-04-09 12:31:25 -04:00
Alessio Gravili
af40302e5f fix(richtext-lexical): get links to work again (#5745) 2024-04-09 12:29:45 -04:00
Alessio Gravili
ec0e0ae449 chore(richtext-lexical): add e2e test to ensure that pre-seeded upload nodes are visible 2024-04-09 12:24:54 -04:00
Alessio Gravili
607ff17033 fix(richtext-lexical): upload nodes weren't visible due to incorrect relationships condition 2024-04-09 12:24:30 -04:00
Alessio Gravili
e73e610669 chore: fields test suite: clearAndSeedEverything instead of seed for dev as well, to ensure same state as test runs (most importantly, this gets rid of leftover uploads) 2024-04-09 12:22:42 -04:00
Jarrod Flesch
30fddde066 chore: corrects type for getLocalI18n 2024-04-09 11:51:40 -04:00
Jarrod Flesch
a56d2842fb chore: converts ua to uk 2024-04-09 11:44:34 -04:00
Jarrod Flesch
35f59a47cc chore: corrects dateFNS keys, stricter types 2024-04-09 11:38:38 -04:00
Jarrod Flesch
817d57bd12 chore: migrate langs 2024-04-09 11:05:35 -04:00
Elliot DeNolf
5826048e7b ci: publish script throttling 2024-04-09 09:50:23 -04:00
Elliot DeNolf
1a975b31cf chore(release): v3.0.0-alpha.60 [skip ci] 2024-04-09 09:36:41 -04:00
James
73298a80f0 chore: adds more de-flake to tabs 2024-04-09 09:34:11 -04:00
James
a5d14ef4c1 chore: de-flakes tabs 2024-04-09 09:34:11 -04:00
James
d0c79b65f8 chore: skips index to see what remains 2024-04-09 09:34:11 -04:00
James
2fc50b1a1f chore: de-flakes array 2024-04-09 09:34:11 -04:00
James
0ddeedb0b3 chore: de-flakes relationship suite 2024-04-09 09:34:11 -04:00
James
09c2fb10f3 chore: bug in ci 2024-04-09 09:34:11 -04:00
James
5bfff5b7ba chore: ensures artifacts work 2024-04-09 09:34:11 -04:00
James
702088375c chore: attempts to de-flake 2024-04-09 09:34:11 -04:00
James
ea507fbcc4 chore: moves lexical tests into collection folder 2024-04-09 09:34:11 -04:00
James
0ff1e6632b chore: splits out relationship 2024-04-09 09:34:11 -04:00
James
996ee47f96 chore: splits out blocks and array into their own suites 2024-04-09 09:34:11 -04:00
James
3e9bd5bb62 chore: uses ci env var to set max retries 2024-04-09 09:34:11 -04:00
James
ee7221c986 chore: sets maxRetries 2024-04-09 09:34:11 -04:00
James
318c126ae3 chore: turns off prebuild for fields 2024-04-09 09:34:11 -04:00
Jarrod Flesch
2154aea89f chore: comment out all other test suites for now 2024-04-09 09:34:11 -04:00
Jarrod Flesch
4d3ad1af35 chore: run fields e2e on ci 2024-04-09 09:34:11 -04:00
Alessio Gravili
75cab7688f chore: fix incorrect next tsconfig paths breaking monorepo setup (#5743) 2024-04-09 09:26:09 -04:00
James
5084d6dd97 chore: attempts to de-flake live preview 2024-04-08 22:34:23 -04:00
James
518f80cbb6 chore: es 2024-04-08 22:29:41 -04:00
James
c9399efa65 chore: fixes access control test 2024-04-08 22:25:35 -04:00
James
d3016b7eb5 chore: merge 2024-04-08 22:13:16 -04:00
James
69e884f5b7 chore: dynamically loads date-fns locales 2024-04-08 22:12:05 -04:00
James
3e7925e33f chore: removes async nature from a few lexical things 2024-04-08 16:48:56 -04:00
James
7a2ccba63c Merge branch 'feat/config-i18n' of github.com:payloadcms/payload into feat/config-i18n 2024-04-08 16:39:03 -04:00
James
be2134eb69 chore: requires languages to be passed to config 2024-04-08 16:38:12 -04:00
James Mikrut
95e422b0e1 Merge branch 'alpha' into feat/config-i18n 2024-04-08 16:26:38 -04:00
James
53d9c4ca95 chore: dynamically loads translations 2024-04-08 16:25:24 -04:00
James
30948ab545 chore: dynamically loads translations 2024-04-08 16:25:21 -04:00
493 changed files with 19767 additions and 30974 deletions

View File

@@ -4,7 +4,7 @@ on:
pull_request:
types: [opened, reopened, synchronize]
push:
branches: ['main', 'alpha']
branches: ['main', 'beta']
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -88,7 +88,6 @@ jobs:
tests-unit:
runs-on: ubuntu-latest
needs: build
if: false # Disable until tests are updated for 3.0
steps:
- name: Use Node.js 18
@@ -221,8 +220,11 @@ jobs:
- email
- field-error-states
- fields-relationship
# - fields
- fields/lexical
- fields
- fields__collections__Blocks
- fields__collections__Array
- fields__collections__Relationship
- fields__collections__Lexical
- live-preview
- localization
- plugin-form-builder

View File

@@ -1 +1 @@
v18.19.1
v18.20.2

2
.nvmrc
View File

@@ -1 +1 @@
v18.19.1
v18.20.2

View File

@@ -3,7 +3,7 @@ import type { Metadata } from 'next'
import config from '@payload-config'
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import { NotFoundPage, generatePageMetadata } from '@payloadcms/next/views/NotFound/index.js'
import { NotFoundPage, generatePageMetadata } from '@payloadcms/next/views'
type Args = {
params: {
@@ -14,8 +14,8 @@ type Args = {
}
}
export const generateMetadata = ({ params }: Args): Promise<Metadata> =>
generatePageMetadata({ config, params })
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
generatePageMetadata({ config, params, searchParams })
const NotFound = ({ params, searchParams }: Args) => NotFoundPage({ config, params, searchParams })

View File

@@ -3,7 +3,7 @@ import type { Metadata } from 'next'
import config from '@payload-config'
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import { RootPage, generatePageMetadata } from '@payloadcms/next/views/Root/index.js'
import { RootPage, generatePageMetadata } from '@payloadcms/next/views'
type Args = {
params: {

View File

@@ -1,7 +1,7 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY it because it could be re-written at any time. */
import config from '@payload-config'
import { REST_DELETE, REST_GET, REST_PATCH, REST_POST } from '@payloadcms/next/routes/index.js'
import { REST_DELETE, REST_GET, REST_PATCH, REST_POST } from '@payloadcms/next/routes'
export const GET = REST_GET(config)
export const POST = REST_POST(config)

View File

@@ -1,6 +1,6 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY it because it could be re-written at any time. */
import config from '@payload-config'
import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes/index.js'
import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes'
export const GET = GRAPHQL_PLAYGROUND_GET(config)

View File

@@ -1,6 +1,6 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY it because it could be re-written at any time. */
import config from '@payload-config'
import { GRAPHQL_POST } from '@payloadcms/next/routes/index.js'
import { GRAPHQL_POST } from '@payloadcms/next/routes'
export const POST = GRAPHQL_POST(config)

View File

@@ -1,6 +1,6 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
import configPromise from '@payload-config'
import { RootLayout } from '@payloadcms/next/layouts/Root/index.js'
import { RootLayout } from '@payloadcms/next/layouts'
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import React from 'react'

View File

@@ -138,7 +138,7 @@ import { CallToAction } from '../blocks/CallToAction'
Here's an overview of all the included features:
| Feature Name | Included by default | Description |
| ------------------------------ | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|--------------------------------|---------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **`BoldTextFeature`** | Yes | Handles the bold text format |
| **`ItalicTextFeature`** | Yes | Handles the italic text format |
| **`UnderlineTextFeature`** | Yes | Handles the underline text format |
@@ -157,7 +157,8 @@ Here's an overview of all the included features:
| **`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 |
| **`BlocksFeature`** | No | Allows you to use Payload's [Blocks Field](/docs/fields/blocks) directly inside your editor. In the feature props, you can specify the allowed blocks - just like in the Blocks field. |
| **`HorizontalRuleFeature`** | Yes | Horizontal rules / separators. Basically displays an <hr> element |
| **`BlocksFeature`** | No | Allows you to use Payload's [Blocks Field](/docs/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 |
## Creating your own, custom Feature
@@ -234,6 +235,19 @@ This method employs `convertLexicalToHTML` from `@payloadcms/richtext-lexical`,
Because every `Feature` is able to provide html converters, and because the `htmlFeature` can modify those or provide their own, we need to consolidate them with the default html Converters using the `consolidateHTMLConverters` function.
#### CSS
Payload's lexical HTML converter does not generate CSS for you, but it does add classes to the generated HTML. You can use these classes to style the HTML in your frontend.
Here is some "base" CSS you can use to ensure that nested lists render correctly:
```css
/* Base CSS for Lexical HTML */
.nestedListItem, .list-check {
list-style-type: none;
}
```
#### Creating your own HTML Converter
HTML Converters are typed as `HTMLConverter`, which contains the node type it should handle, and a function that accepts the serialized node from the lexical editor, and outputs the HTML string. Here's the HTML Converter of the Upload node as an example:

View File

@@ -1 +0,0 @@
export default {}

View File

@@ -1,6 +1,6 @@
{
"name": "payload-monorepo",
"version": "3.0.0-alpha.60",
"version": "3.0.0-beta.10",
"private": true,
"type": "module",
"workspaces:": [
@@ -127,7 +127,7 @@
"lint-staged": "^14.0.1",
"minimist": "1.2.8",
"mongodb-memory-server": "^9.0",
"next": "14.2.0-canary.22",
"next": "^14.2.0-canary.23",
"node-mocks-http": "^1.14.1",
"nodemon": "3.0.3",
"open": "^10.1.0",
@@ -163,7 +163,7 @@
"react": "18.2.0"
},
"engines": {
"node": ">=18.17.0",
"node": ">=18.20.2",
"pnpm": ">=8"
},
"lint-staged": {

View File

@@ -1,6 +1,6 @@
{
"name": "create-payload-app",
"version": "3.0.0-alpha.60",
"version": "3.0.0-beta.10",
"license": "MIT",
"type": "module",
"homepage": "https://payloadcms.com",
@@ -35,7 +35,7 @@
"comment-json": "^4.2.3",
"degit": "^2.8.4",
"detect-package-manager": "^3.0.1",
"esprima": "^4.0.1",
"esprima-next": "^6.0.3",
"execa": "^5.0.0",
"figures": "^6.1.0",
"fs-extra": "^9.0.1",

View File

@@ -4,6 +4,7 @@ import * as p from '@clack/prompts'
import { parse, stringify } from 'comment-json'
import execa from 'execa'
import fs from 'fs'
import fse from 'fs-extra'
import globby from 'globby'
import path from 'path'
import { promisify } from 'util'
@@ -31,6 +32,8 @@ type InitNextArgs = Pick<CliArgs, '--debug'> & {
useDistFiles?: boolean
}
type NextConfigType = 'cjs' | 'esm'
type InitNextResult =
| {
isSrcDir: boolean
@@ -45,11 +48,22 @@ export async function initNext(args: InitNextArgs): Promise<InitNextResult> {
const nextAppDetails = args.nextAppDetails || (await getNextAppDetails(projectDir))
const { hasTopLevelLayout, isSrcDir, nextAppDir } =
nextAppDetails || (await getNextAppDetails(projectDir))
if (!nextAppDetails.nextAppDir) {
warning(`Could not find app directory in ${projectDir}, creating...`)
const createdAppDir = path.resolve(projectDir, nextAppDetails.isSrcDir ? 'src/app' : 'app')
fse.mkdirSync(createdAppDir, { recursive: true })
nextAppDetails.nextAppDir = createdAppDir
}
if (!nextAppDir) {
return { isSrcDir, reason: `Could not find app directory in ${projectDir}`, success: false }
const { hasTopLevelLayout, isSrcDir, nextAppDir, nextConfigType } = nextAppDetails
if (!nextConfigType) {
return {
isSrcDir,
nextAppDir,
reason: `Could not determine Next Config type in ${projectDir}. Possibly try renaming next.config.js to next.config.cjs or next.config.mjs.`,
success: false,
}
}
if (hasTopLevelLayout) {
@@ -69,6 +83,7 @@ export async function initNext(args: InitNextArgs): Promise<InitNextResult> {
const configurationResult = installAndConfigurePayload({
...args,
nextAppDetails,
nextConfigType,
useDistFiles: true, // Requires running 'pnpm pack-template-files' in cpa
})
@@ -96,6 +111,13 @@ export async function initNext(args: InitNextArgs): Promise<InitNextResult> {
async function addPayloadConfigToTsConfig(projectDir: string, isSrcDir: boolean) {
const tsConfigPath = path.resolve(projectDir, 'tsconfig.json')
// Check if tsconfig.json exists
if (!fs.existsSync(tsConfigPath)) {
warning(`Could not find tsconfig.json to add @payload-config path.`)
return
}
const userTsConfigContent = await readFile(tsConfigPath, {
encoding: 'utf8',
})
@@ -119,13 +141,18 @@ async function addPayloadConfigToTsConfig(projectDir: string, isSrcDir: boolean)
}
function installAndConfigurePayload(
args: InitNextArgs & { nextAppDetails: NextAppDetails; useDistFiles?: boolean },
args: InitNextArgs & {
nextAppDetails: NextAppDetails
nextConfigType: NextConfigType
useDistFiles?: boolean
},
):
| { payloadConfigPath: string; success: true }
| { payloadConfigPath?: string; reason: string; success: false } {
const {
'--debug': debug,
nextAppDetails: { isSrcDir, nextAppDir, nextConfigPath } = {},
nextConfigType,
projectDir,
useDistFiles,
} = args
@@ -172,6 +199,7 @@ function installAndConfigurePayload(
logDebug(`nextAppDir: ${nextAppDir}`)
logDebug(`projectDir: ${projectDir}`)
logDebug(`nextConfigPath: ${nextConfigPath}`)
logDebug(`payloadConfigPath: ${path.resolve(projectDir, 'payload.config.ts')}`)
logDebug(
`isSrcDir: ${isSrcDir}. source: ${templateSrcDir}. dest: ${path.dirname(nextConfigPath)}`,
@@ -181,7 +209,7 @@ function installAndConfigurePayload(
copyRecursiveSync(templateSrcDir, path.dirname(nextConfigPath), debug)
// Wrap next.config.js with withPayload
wrapNextConfig({ nextConfigPath })
wrapNextConfig({ nextConfigPath, nextConfigType })
return {
payloadConfigPath: path.resolve(nextAppDir, '../payload.config.ts'),
@@ -191,10 +219,10 @@ function installAndConfigurePayload(
async function installDeps(projectDir: string, packageManager: PackageManager, dbType: DbType) {
const packagesToInstall = ['payload', '@payloadcms/next', '@payloadcms/richtext-lexical'].map(
(pkg) => `${pkg}@alpha`,
(pkg) => `${pkg}@beta`,
)
packagesToInstall.push(`@payloadcms/db-${dbType}@alpha`)
packagesToInstall.push(`@payloadcms/db-${dbType}@beta`)
let exitCode = 0
switch (packageManager) {
@@ -226,6 +254,7 @@ type NextAppDetails = {
isSrcDir: boolean
nextAppDir?: string
nextConfigPath?: string
nextConfigType?: NextConfigType
}
export async function getNextAppDetails(projectDir: string): Promise<NextAppDetails> {
@@ -246,6 +275,7 @@ export async function getNextAppDetails(projectDir: string): Promise<NextAppDeta
await globby(['**/app'], {
absolute: true,
cwd: projectDir,
ignore: ['**/node_modules/**'],
onlyDirectories: true,
})
)?.[0]
@@ -254,9 +284,31 @@ export async function getNextAppDetails(projectDir: string): Promise<NextAppDeta
nextAppDir = undefined
}
const configType = await getProjectType(projectDir, nextConfigPath)
const hasTopLevelLayout = nextAppDir
? fs.existsSync(path.resolve(nextAppDir, 'layout.tsx'))
: false
return { hasTopLevelLayout, isSrcDir, nextAppDir, nextConfigPath }
return { hasTopLevelLayout, isSrcDir, nextAppDir, nextConfigPath, nextConfigType: configType }
}
async function getProjectType(projectDir: string, nextConfigPath: string): Promise<'cjs' | 'esm'> {
if (nextConfigPath.endsWith('.mjs')) {
return 'esm'
}
if (nextConfigPath.endsWith('.cjs')) {
return 'cjs'
}
const packageObj = await fse.readJson(path.resolve(projectDir, 'package.json'))
const packageJsonType = packageObj.type
if (packageJsonType === 'module') {
return 'esm'
}
if (packageJsonType === 'commonjs') {
return 'cjs'
}
return 'cjs'
}

View File

@@ -10,14 +10,18 @@ const mongodbReplacement: DbAdapterReplacement = {
importReplacement: "import { mongooseAdapter } from '@payloadcms/db-mongodb'",
packageName: '@payloadcms/db-mongodb',
// Replacement between `// database-adapter-config-start` and `// database-adapter-config-end`
configReplacement: [' db: mongooseAdapter({', ' url: process.env.DATABASE_URI,', ' }),'],
configReplacement: [
' db: mongooseAdapter({',
" url: process.env.DATABASE_URI || '',",
' }),',
],
}
const postgresReplacement: DbAdapterReplacement = {
configReplacement: [
' db: postgresAdapter({',
' pool: {',
' connectionString: process.env.DATABASE_URI,',
" connectionString: process.env.DATABASE_URI || '',",
' },',
' }),',
],

View File

@@ -18,43 +18,46 @@ export function getValidTemplates(): ProjectTemplate[] {
name: 'blank-3.0',
type: 'starter',
description: 'Blank 3.0 Template',
url: 'https://github.com/payloadcms/payload/templates/blank-3.0',
},
{
name: 'blank',
type: 'starter',
description: 'Blank Template',
url: 'https://github.com/payloadcms/payload/templates/blank',
},
{
name: 'website',
type: 'starter',
description: 'Website Template',
url: 'https://github.com/payloadcms/payload/templates/website',
},
{
name: 'ecommerce',
type: 'starter',
description: 'E-commerce Template',
url: 'https://github.com/payloadcms/payload/templates/ecommerce',
},
{
name: 'plugin',
type: 'plugin',
description: 'Template for creating a Payload plugin',
url: 'https://github.com/payloadcms/payload-plugin-template',
},
{
name: 'payload-demo',
type: 'starter',
description: 'Payload demo site at https://demo.payloadcms.com',
url: 'https://github.com/payloadcms/public-demo',
},
{
name: 'payload-website',
type: 'starter',
description: 'Payload website CMS at https://payloadcms.com',
url: 'https://github.com/payloadcms/website-cms',
url: 'https://github.com/payloadcms/payload/templates/blank-3.0#beta',
},
// Remove these until they have been updated for 3.0
// {
// name: 'blank',
// type: 'starter',
// description: 'Blank Template',
// url: 'https://github.com/payloadcms/payload/templates/blank',
// },
// {
// name: 'website',
// type: 'starter',
// description: 'Website Template',
// url: 'https://github.com/payloadcms/payload/templates/website',
// },
// {
// name: 'ecommerce',
// type: 'starter',
// description: 'E-commerce Template',
// url: 'https://github.com/payloadcms/payload/templates/ecommerce',
// },
// {
// name: 'plugin',
// type: 'plugin',
// description: 'Template for creating a Payload plugin',
// url: 'https://github.com/payloadcms/payload-plugin-template',
// },
// {
// name: 'payload-demo',
// type: 'starter',
// description: 'Payload demo site at https://demo.payloadcms.com',
// url: 'https://github.com/payloadcms/public-demo',
// },
// {
// name: 'payload-website',
// type: 'starter',
// description: 'Payload website CMS at https://payloadcms.com',
// url: 'https://github.com/payloadcms/website-cms',
// },
]
}

View File

@@ -1,61 +1,159 @@
import { parseAndModifyConfigContent, withPayloadImportStatement } from './wrap-next-config.js'
import { parseAndModifyConfigContent, withPayloadStatement } from './wrap-next-config.js'
import * as p from '@clack/prompts'
const defaultNextConfig = `/** @type {import('next').NextConfig} */
const esmConfigs = {
defaultNextConfig: `/** @type {import('next').NextConfig} */
const nextConfig = {};
export default nextConfig;
`
const nextConfigWithFunc = `const nextConfig = {
// Your Next.js config here
}
export default someFunc(nextConfig)
`
const nextConfigWithFuncMultiline = `const nextConfig = {
// Your Next.js config here
}
`,
nextConfigWithFunc: `const nextConfig = {};
export default someFunc(nextConfig);
`,
nextConfigWithFuncMultiline: `const nextConfig = {};;
export default someFunc(
nextConfig
)
`
const nextConfigExportNamedDefault = `const nextConfig = {
// Your Next.js config here
);
`,
nextConfigExportNamedDefault: `const nextConfig = {};
const wrapped = someFunc(asdf);
export { wrapped as default };
`,
nextConfigWithSpread: `const nextConfig = {
...someConfig,
};
export default nextConfig;
`,
}
const cjsConfigs = {
defaultNextConfig: `
/** @type {import('next').NextConfig} */
const nextConfig = {};
module.exports = nextConfig;
`,
anonConfig: `module.exports = {};`,
nextConfigWithFunc: `const nextConfig = {};
module.exports = someFunc(nextConfig);
`,
nextConfigWithFuncMultiline: `const nextConfig = {};
module.exports = someFunc(
nextConfig
);
`,
nextConfigExportNamedDefault: `const nextConfig = {};
const wrapped = someFunc(asdf);
module.exports = wrapped;
`,
nextConfigWithSpread: `const nextConfig = { ...someConfig };
module.exports = nextConfig;
`,
}
const wrapped = someFunc(asdf)
export { wrapped as default }
`
describe('parseAndInsertWithPayload', () => {
it('should parse the default next config', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(defaultNextConfig)
expect(modifiedConfigContent).toContain(withPayloadImportStatement)
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
})
it('should parse the config with a function', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(nextConfigWithFunc)
expect(modifiedConfigContent).toContain('withPayload(someFunc(nextConfig))')
describe('esm', () => {
const configType = 'esm'
const importStatement = withPayloadStatement[configType]
it('should parse the default next config', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(
esmConfigs.defaultNextConfig,
configType,
)
expect(modifiedConfigContent).toContain(importStatement)
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
})
it('should parse the config with a function', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(
esmConfigs.nextConfigWithFunc,
configType,
)
expect(modifiedConfigContent).toContain('withPayload(someFunc(nextConfig))')
})
it('should parse the config with a function on a new line', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(
esmConfigs.nextConfigWithFuncMultiline,
configType,
)
expect(modifiedConfigContent).toContain(importStatement)
expect(modifiedConfigContent).toMatch(/withPayload\(someFunc\(\n nextConfig\n\)\)/)
})
it('should parse the config with a spread', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(
esmConfigs.nextConfigWithSpread,
configType,
)
expect(modifiedConfigContent).toContain(importStatement)
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
})
// Unsupported: export { wrapped as default }
it('should give warning with a named export as default', () => {
const warnLogSpy = jest.spyOn(p.log, 'warn').mockImplementation(() => {})
const { modifiedConfigContent, success } = parseAndModifyConfigContent(
esmConfigs.nextConfigExportNamedDefault,
configType,
)
expect(modifiedConfigContent).toContain(importStatement)
expect(success).toBe(false)
expect(warnLogSpy).toHaveBeenCalledWith(
expect.stringContaining('Could not automatically wrap'),
)
})
})
it('should parse the config with a function on a new line', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(nextConfigWithFuncMultiline)
expect(modifiedConfigContent).toContain(withPayloadImportStatement)
expect(modifiedConfigContent).toMatch(/withPayload\(someFunc\(\n nextConfig\n\)\)/)
})
describe('cjs', () => {
const configType = 'cjs'
const requireStatement = withPayloadStatement[configType]
it('should parse the default next config', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(
cjsConfigs.defaultNextConfig,
configType,
)
expect(modifiedConfigContent).toContain(requireStatement)
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
})
it('should parse anonymous default config', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(
cjsConfigs.anonConfig,
configType,
)
expect(modifiedConfigContent).toContain(requireStatement)
expect(modifiedConfigContent).toContain('withPayload({})')
})
it('should parse the config with a function', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(
cjsConfigs.nextConfigWithFunc,
configType,
)
expect(modifiedConfigContent).toContain('withPayload(someFunc(nextConfig))')
})
it('should parse the config with a function on a new line', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(
cjsConfigs.nextConfigWithFuncMultiline,
configType,
)
expect(modifiedConfigContent).toContain(requireStatement)
expect(modifiedConfigContent).toMatch(/withPayload\(someFunc\(\n nextConfig\n\)\)/)
})
it('should parse the config with a named export as default', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(
cjsConfigs.nextConfigExportNamedDefault,
configType,
)
expect(modifiedConfigContent).toContain(requireStatement)
expect(modifiedConfigContent).toContain('withPayload(wrapped)')
})
// Unsupported: export { wrapped as default }
it('should give warning with a named export as default', () => {
const warnLogSpy = jest.spyOn(p.log, 'warn').mockImplementation(() => {})
const { modifiedConfigContent, success } = parseAndModifyConfigContent(
nextConfigExportNamedDefault,
)
expect(modifiedConfigContent).toContain(withPayloadImportStatement)
expect(success).toBe(false)
expect(warnLogSpy).toHaveBeenCalledWith(expect.stringContaining('Could not automatically wrap'))
it('should parse the config with a spread', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(
cjsConfigs.nextConfigWithSpread,
configType,
)
expect(modifiedConfigContent).toContain(requireStatement)
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
})
})
})

View File

@@ -1,16 +1,29 @@
import type { Program } from 'esprima-next'
import chalk from 'chalk'
import { parseModule } from 'esprima'
import { Syntax, parseModule } from 'esprima-next'
import fs from 'fs'
import { warning } from '../utils/log.js'
import { log } from '../utils/log.js'
export const withPayloadImportStatement = `import { withPayload } from '@payloadcms/next'\n`
export const withPayloadStatement = {
cjs: `const { withPayload } = require('@payloadcms/next/withPayload')\n`,
esm: `import { withPayload } from '@payloadcms/next/withPayload'\n`,
}
export const wrapNextConfig = (args: { nextConfigPath: string }) => {
const { nextConfigPath } = args
type NextConfigType = 'cjs' | 'esm'
export const wrapNextConfig = (args: {
nextConfigPath: string
nextConfigType: NextConfigType
}) => {
const { nextConfigPath, nextConfigType: configType } = args
const configContent = fs.readFileSync(nextConfigPath, 'utf8')
const { modifiedConfigContent: newConfig, success } = parseAndModifyConfigContent(configContent)
const { modifiedConfigContent: newConfig, success } = parseAndModifyConfigContent(
configContent,
configType,
)
if (!success) {
return
@@ -22,72 +35,121 @@ export const wrapNextConfig = (args: { nextConfigPath: string }) => {
/**
* Parses config content with AST and wraps it with withPayload function
*/
export function parseAndModifyConfigContent(content: string): {
modifiedConfigContent: string
success: boolean
} {
content = withPayloadImportStatement + content
const ast = parseModule(content, { loc: true })
const exportDefaultDeclaration = ast.body.find((p) => p.type === 'ExportDefaultDeclaration') as
| Directive
| undefined
export function parseAndModifyConfigContent(
content: string,
configType: NextConfigType,
): { modifiedConfigContent: string; success: boolean } {
content = withPayloadStatement[configType] + content
const exportNamedDeclaration = ast.body.find((p) => p.type === 'ExportNamedDeclaration') as
| ExportNamedDeclaration
| undefined
if (!exportDefaultDeclaration && !exportNamedDeclaration) {
throw new Error('Could not find ExportDefaultDeclaration in next.config.js')
}
if (exportDefaultDeclaration && exportDefaultDeclaration.declaration?.loc) {
const modifiedConfigContent = insertBeforeAndAfter(
content,
exportDefaultDeclaration.declaration.loc,
)
return { modifiedConfigContent, success: true }
} else if (exportNamedDeclaration) {
const exportSpecifier = exportNamedDeclaration.specifiers.find(
(s) =>
s.type === 'ExportSpecifier' &&
s.exported?.name === 'default' &&
s.local?.type === 'Identifier' &&
s.local?.name,
)
if (exportSpecifier) {
warning('Could not automatically wrap next.config.js with withPayload.')
warning('Automatic wrapping of named exports as default not supported yet.')
warnUserWrapNotSuccessful()
return {
modifiedConfigContent: content,
success: false,
}
let ast: Program | undefined
try {
ast = parseModule(content, { loc: true })
} catch (error: unknown) {
if (error instanceof Error) {
warning(`Unable to parse Next config. Error: ${error.message} `)
warnUserWrapNotSuccessful(configType)
}
return {
modifiedConfigContent: content,
success: false,
}
}
warning('Could not automatically wrap next.config.js with withPayload.')
warnUserWrapNotSuccessful()
if (configType === 'esm') {
const exportDefaultDeclaration = ast.body.find(
(p) => p.type === Syntax.ExportDefaultDeclaration,
) as Directive | undefined
const exportNamedDeclaration = ast.body.find(
(p) => p.type === Syntax.ExportNamedDeclaration,
) as ExportNamedDeclaration | undefined
if (!exportDefaultDeclaration && !exportNamedDeclaration) {
throw new Error('Could not find ExportDefaultDeclaration in next.config.js')
}
if (exportDefaultDeclaration && exportDefaultDeclaration.declaration?.loc) {
const modifiedConfigContent = insertBeforeAndAfter(
content,
exportDefaultDeclaration.declaration.loc,
)
return { modifiedConfigContent, success: true }
} else if (exportNamedDeclaration) {
const exportSpecifier = exportNamedDeclaration.specifiers.find(
(s) =>
s.type === 'ExportSpecifier' &&
s.exported?.name === 'default' &&
s.local?.type === 'Identifier' &&
s.local?.name,
)
if (exportSpecifier) {
warning('Could not automatically wrap next.config.js with withPayload.')
warning('Automatic wrapping of named exports as default not supported yet.')
warnUserWrapNotSuccessful(configType)
return {
modifiedConfigContent: content,
success: false,
}
}
}
warning('Could not automatically wrap Next config with withPayload.')
warnUserWrapNotSuccessful(configType)
return {
modifiedConfigContent: content,
success: false,
}
} else if (configType === 'cjs') {
// Find `module.exports = X`
const moduleExports = ast.body.find(
(p) =>
p.type === Syntax.ExpressionStatement &&
p.expression?.type === Syntax.AssignmentExpression &&
p.expression.left?.type === Syntax.MemberExpression &&
p.expression.left.object?.type === Syntax.Identifier &&
p.expression.left.object.name === 'module' &&
p.expression.left.property?.type === Syntax.Identifier &&
p.expression.left.property.name === 'exports',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) as any
if (moduleExports && moduleExports.expression.right?.loc) {
const modifiedConfigContent = insertBeforeAndAfter(
content,
moduleExports.expression.right.loc,
)
return { modifiedConfigContent, success: true }
}
return {
modifiedConfigContent: content,
success: false,
}
}
warning('Could not automatically wrap Next config with withPayload.')
warnUserWrapNotSuccessful(configType)
return {
modifiedConfigContent: content,
success: false,
}
}
function warnUserWrapNotSuccessful() {
function warnUserWrapNotSuccessful(configType: NextConfigType) {
// Output directions for user to update next.config.js
const withPayloadMessage = `
${chalk.bold(`Please manually wrap your existing next.config.js with the withPayload function. Here is an example:`)}
import withPayload from '@payloadcms/next/withPayload'
${withPayloadStatement[configType]}
const nextConfig = {
// Your Next.js config here
}
export default withPayload(nextConfig)
${configType === 'esm' ? 'export default withPayload(nextConfig)' : 'module.exports = withPayload(nextConfig)'}
`

View File

@@ -20,32 +20,41 @@ export async function writeEnvFile(args: {
return
}
const envOutputPath = path.join(projectDir, '.env')
try {
if (template?.type === 'starter' && fs.existsSync(path.join(projectDir, '.env.example'))) {
// Parse .env file into key/value pairs
const envFile = await fs.readFile(path.join(projectDir, '.env.example'), 'utf8')
const envWithValues: string[] = envFile
.split('\n')
.filter((e) => e)
.map((line) => {
if (line.startsWith('#') || !line.includes('=')) return line
if (fs.existsSync(envOutputPath)) {
if (template?.type === 'starter') {
// Parse .env file into key/value pairs
const envFile = await fs.readFile(path.join(projectDir, '.env.example'), 'utf8')
const envWithValues: string[] = envFile
.split('\n')
.filter((e) => e)
.map((line) => {
if (line.startsWith('#') || !line.includes('=')) return line
const split = line.split('=')
const key = split[0]
let value = split[1]
const split = line.split('=')
const key = split[0]
let value = split[1]
if (key === 'MONGODB_URI' || key === 'MONGO_URL' || key === 'DATABASE_URI') {
value = databaseUri
}
if (key === 'PAYLOAD_SECRET' || key === 'PAYLOAD_SECRET_KEY') {
value = payloadSecret
}
if (key === 'MONGODB_URI' || key === 'MONGO_URL' || key === 'DATABASE_URI') {
value = databaseUri
}
if (key === 'PAYLOAD_SECRET' || key === 'PAYLOAD_SECRET_KEY') {
value = payloadSecret
}
return `${key}=${value}`
})
return `${key}=${value}`
})
// Write new .env file
await fs.writeFile(path.join(projectDir, '.env'), envWithValues.join('\n'))
// Write new .env file
await fs.writeFile(envOutputPath, envWithValues.join('\n'))
} else {
const existingEnv = await fs.readFile(envOutputPath, 'utf8')
const newEnv =
existingEnv + `\nDATABASE_URI=${databaseUri}\nPAYLOAD_SECRET=${payloadSecret}\n`
await fs.writeFile(envOutputPath, newEnv)
}
} else {
const content = `DATABASE_URI=${databaseUri}\nPAYLOAD_SECRET=${payloadSecret}`
await fs.outputFile(`${projectDir}/.env`, content)

View File

@@ -21,6 +21,12 @@ export function helpMessage(): void {
console.log(chalk`
{bold USAGE}
{dim Inside of an existing Next.js project}
{dim $} {bold npx create-payload-app}
{dim Create a new project from scratch}
{dim $} {bold npx create-payload-app}
{dim $} {bold npx create-payload-app} my-project
{dim $} {bold npx create-payload-app} -n my-project -t template-name
@@ -80,7 +86,7 @@ export function successfulNextInit(): string {
}
export function moveMessage(args: { nextAppDir: string; projectDir: string }): string {
const relativePath = path.relative(process.cwd(), args.nextAppDir)
const relativeAppDir = path.relative(process.cwd(), args.nextAppDir)
return `
${header('Next Steps:')}
@@ -88,7 +94,10 @@ Payload does not support a top-level layout.tsx file in the app directory.
${chalk.bold('To continue:')}
Move all files from ./${relativePath} to a named directory such as ./${relativePath}/${chalk.bold('(app)')}
- Create a new directory in ./${relativeAppDir} such as ./${relativeAppDir}/${chalk.bold('(app)')}
- Move all files from ./${relativeAppDir} into that directory
It is recommended to do this from your IDE if your app has existing file references.
Once moved, rerun the create-payload-app command again.
`

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-mongodb",
"version": "3.0.0-alpha.60",
"version": "3.0.0-beta.10",
"description": "The officially supported MongoDB database adapter for Payload",
"repository": {
"type": "git",

View File

@@ -56,12 +56,6 @@ export const init: Init = function init(this: MongooseAdapter) {
this.autoPluralization === true ? undefined : collection.slug,
) as CollectionModel
this.collections[collection.slug] = model
// TS expect error only needed until we launch 2.0.0
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
this.payload.collections[collection.slug] = {
config: collection,
}
})
const model = buildGlobalModel(this.payload.config)

View File

@@ -59,17 +59,12 @@ export async function buildSearchParam({
let hasCustomID = false
if (sanitizedPath === '_id') {
const customIDfield = payload.collections[collectionSlug]?.config.fields.find(
(field) => fieldAffectsData(field) && field.name === 'id',
)
const customIDFieldType = payload.collections[collectionSlug]?.customIDType
let idFieldType: 'number' | 'text' = 'text'
if (customIDfield) {
if (customIDfield?.type === 'text' || customIDfield?.type === 'number') {
idFieldType = customIDfield.type
}
if (customIDFieldType) {
idFieldType = customIDFieldType
hasCustomID = true
}
@@ -213,18 +208,11 @@ export async function buildSearchParam({
} else {
;(Array.isArray(field.relationTo) ? field.relationTo : [field.relationTo]).forEach(
(relationTo) => {
const isRelatedToCustomNumberID = payload.collections[
relationTo
]?.config?.fields.find((relatedField) => {
return (
fieldAffectsData(relatedField) &&
relatedField.name === 'id' &&
relatedField.type === 'number'
)
})
const isRelatedToCustomNumberID =
payload.collections[relationTo]?.customIDType === 'number'
if (isRelatedToCustomNumberID) {
if (isRelatedToCustomNumberID.type === 'number') hasNumberIDRelation = true
hasNumberIDRelation = true
}
},
)

View File

@@ -1,18 +1,20 @@
import { sanitizeConfig } from 'payload/config'
import { SanitizedConfig, sanitizeConfig } from 'payload/config'
import { Config } from 'payload/config'
import { getLocalizedSortProperty } from './getLocalizedSortProperty.js'
const config = {
const config = sanitizeConfig({
localization: {
locales: ['en', 'es'],
defaultLocale: 'en',
fallback: true,
},
} as Config
} as Config) as SanitizedConfig
describe('get localized sort property', () => {
it('passes through a non-localized sort property', () => {
const result = getLocalizedSortProperty({
segments: ['title'],
config: sanitizeConfig(config),
config,
fields: [
{
name: 'title',
@@ -28,7 +30,7 @@ describe('get localized sort property', () => {
it('properly localizes an un-localized sort property', () => {
const result = getLocalizedSortProperty({
segments: ['title'],
config: sanitizeConfig(config),
config,
fields: [
{
name: 'title',
@@ -45,7 +47,7 @@ describe('get localized sort property', () => {
it('keeps specifically asked-for localized sort properties', () => {
const result = getLocalizedSortProperty({
segments: ['title', 'es'],
config: sanitizeConfig(config),
config,
fields: [
{
name: 'title',
@@ -62,7 +64,7 @@ describe('get localized sort property', () => {
it('properly localizes nested sort properties', () => {
const result = getLocalizedSortProperty({
segments: ['group', 'title'],
config: sanitizeConfig(config),
config,
fields: [
{
name: 'group',
@@ -85,7 +87,7 @@ describe('get localized sort property', () => {
it('keeps requested locale with nested sort properties', () => {
const result = getLocalizedSortProperty({
segments: ['group', 'title', 'es'],
config: sanitizeConfig(config),
config,
fields: [
{
name: 'group',
@@ -108,7 +110,7 @@ describe('get localized sort property', () => {
it('properly localizes field within row', () => {
const result = getLocalizedSortProperty({
segments: ['title'],
config: sanitizeConfig(config),
config,
fields: [
{
type: 'row',
@@ -130,7 +132,7 @@ describe('get localized sort property', () => {
it('properly localizes field within named tab', () => {
const result = getLocalizedSortProperty({
segments: ['tab', 'title'],
config: sanitizeConfig(config),
config,
fields: [
{
type: 'tabs',
@@ -157,7 +159,7 @@ describe('get localized sort property', () => {
it('properly localizes field within unnamed tab', () => {
const result = getLocalizedSortProperty({
segments: ['title'],
config: sanitizeConfig(config),
config,
fields: [
{
type: 'tabs',

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "3.0.0-alpha.60",
"version": "3.0.0-beta.10",
"description": "The officially supported Postgres database adapter for Payload",
"repository": {
"type": "git",

View File

@@ -120,7 +120,7 @@ export const findMany = async function find({
const findPromise = db.query[tableName].findMany(findManyArgs)
if (pagination !== false && (orderedIDs ? orderedIDs?.length >= limit : true)) {
if (pagination !== false && (orderedIDs ? orderedIDs?.length <= limit : true)) {
const selectCountMethods: ChainedMethods = []
joinAliases.forEach(({ condition, table }) => {

View File

@@ -298,11 +298,12 @@ export const buildTable = ({
throwValidationError: true,
})
let colType = adapter.idType === 'uuid' ? 'uuid' : 'integer'
const relatedCollectionCustomID = relationshipConfig.fields.find(
(field) => fieldAffectsData(field) && field.name === 'id',
)
if (relatedCollectionCustomID?.type === 'number') colType = 'numeric'
if (relatedCollectionCustomID?.type === 'text') colType = 'varchar'
const relatedCollectionCustomIDType =
adapter.payload.collections[relationshipConfig.slug]?.customIDType
if (relatedCollectionCustomIDType === 'number') colType = 'numeric'
if (relatedCollectionCustomIDType === 'text') colType = 'varchar'
relationshipColumns[`${relationTo}ID`] = parentIDColumnMap[colType](
`${formattedRelationTo}_id`,

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/graphql",
"version": "3.0.0-alpha.60",
"version": "3.0.0-beta.10",
"main": "./src/index.ts",
"types": "./src/index.d.ts",
"type": "module",

View File

@@ -39,7 +39,13 @@
}
},
"publishConfig": {
"exports": null,
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"main": "./dist/index.js",
"registry": "https://registry.npmjs.org/",
"types": "./dist/index.d.ts"

View File

@@ -32,7 +32,13 @@
}
},
"publishConfig": {
"exports": null,
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"main": "./dist/index.js",
"registry": "https://registry.npmjs.org/",
"types": "./dist/index.d.ts"

15
packages/next/.swcrc-cjs Normal file
View File

@@ -0,0 +1,15 @@
{
"$schema": "https://json.schemastore.org/swcrc",
"sourceMaps": true,
"jsc": {
"target": "esnext",
"parser": {
"syntax": "typescript",
"tsx": true,
"dts": true
}
},
"module": {
"type": "commonjs"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/next",
"version": "3.0.0-alpha.60",
"version": "3.0.0-beta.10",
"main": "./src/index.js",
"types": "./src/index.js",
"type": "module",
@@ -11,7 +11,8 @@
"directory": "packages/next"
},
"scripts": {
"build": "pnpm copyfiles && pnpm build:swc && pnpm build:types && pnpm build:webpack && rm dist/prod/index.js",
"build:cjs": "swc ./src/withPayload.js -o ./dist/cjs/withPayload.cjs --config-file .swcrc-cjs",
"build": "pnpm copyfiles && pnpm build:swc && pnpm build:cjs && pnpm build:types && pnpm build:webpack && rm dist/prod/index.js",
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
"build:types": "tsc --emitDeclarationOnly --outDir dist",
"build:webpack": "webpack --config webpack.config.js",
@@ -27,6 +28,10 @@
"require": "./src/index.js",
"types": "./src/index.js"
},
"./withPayload": {
"import": "./src/withPayload.js",
"require": "./src/withPayload.js"
},
"./*": {
"import": "./src/exports/*.ts",
"require": "./src/exports/*.ts",
@@ -72,7 +77,7 @@
},
"peerDependencies": {
"http-status": "1.6.2",
"next": "14.2.0-canary.23",
"next": "^14.2.0-canary.23",
"payload": "workspace:*"
},
"publishConfig": {
@@ -84,9 +89,9 @@
"require": "./dist/prod/styles.css",
"default": "./dist/prod/styles.css"
},
".": {
"import": "./dist/index.js",
"require": "./dist/index.js"
"./withPayload": {
"import": "./dist/withPayload.js",
"require": "./dist/cjs/withPayload.cjs"
},
"./*": {
"import": "./dist/exports/*.js",
@@ -97,7 +102,7 @@
"registry": "https://registry.npmjs.org/"
},
"engines": {
"node": ">=18.17.0"
"node": ">=18.20.2"
},
"files": [
"dist"

View File

@@ -1,2 +1,2 @@
export { getNextI18n } from '../utilities/getNextI18n.js'
export { getNextRequestI18n } from '../utilities/getNextRequestI18n.js'
export { getPayloadHMR } from '../utilities/getPayloadHMR.js'

View File

@@ -1,16 +1,18 @@
import type { AcceptedLanguages } from '@payloadcms/translations'
import type { SanitizedConfig } from 'payload/types'
import { translations } from '@payloadcms/translations/client'
import { rtlLanguages } from '@payloadcms/translations'
import { initI18n } from '@payloadcms/translations'
import { RootProvider } from '@payloadcms/ui/providers/Root'
import '@payloadcms/ui/scss/app.scss'
import { buildComponentMap } from '@payloadcms/ui/utilities/buildComponentMap'
import { headers as getHeaders, cookies as nextCookies } from 'next/headers.js'
import { parseCookies } from 'payload/auth'
import { createClientConfig } from 'payload/config'
import { deepMerge } from 'payload/utilities'
import React from 'react'
import 'react-toastify/dist/ReactToastify.css'
import { getPayloadHMR } from '../../utilities/getPayloadHMR.js'
import { getRequestLanguage } from '../../utilities/getRequestLanguage.js'
import { DefaultEditView } from '../../views/Edit/Default/index.js'
import { DefaultListView } from '../../views/List/Default/index.js'
@@ -20,7 +22,15 @@ export const metadata = {
title: 'Next.js',
}
const rtlLanguages = ['ar', 'fa', 'ha', 'ku', 'ur', 'ps', 'dv', 'ks', 'khw', 'he', 'yi']
import { Merriweather } from 'next/font/google'
const merriweather = Merriweather({
display: 'swap',
style: ['normal', 'italic'],
subsets: ['latin'],
variable: '--font-serif',
weight: ['400', '900'],
})
export const RootLayout = async ({
children,
@@ -30,26 +40,37 @@ export const RootLayout = async ({
config: Promise<SanitizedConfig>
}) => {
const config = await configPromise
const clientConfig = await createClientConfig(config)
const headers = getHeaders()
const cookies = parseCookies(headers)
const lang =
getRequestLanguage({
config,
cookies,
headers,
}) ?? clientConfig.i18n.fallbackLanguage
const languageCode = getRequestLanguage({
config,
cookies,
headers,
})
const dir = rtlLanguages.includes(lang) ? 'RTL' : 'LTR'
const payload = await getPayloadHMR({ config })
const i18n = await initI18n({ config: config.i18n, context: 'client', language: languageCode })
const clientConfig = await createClientConfig({ config, t: i18n.t })
const mergedTranslations = deepMerge(translations, clientConfig.i18n.translations)
const dir = (rtlLanguages as unknown as AcceptedLanguages[]).includes(languageCode)
? 'RTL'
: 'LTR'
const languageOptions = Object.entries(translations || {}).map(([language, translations]) => ({
label: translations.general.thisLanguage,
value: language,
}))
const languageOptions = Object.entries(config.i18n.supportedLanguages || {}).reduce(
(acc, [language, languageConfig]) => {
if (Object.keys(config.i18n.supportedLanguages).includes(language)) {
acc.push({
label: languageConfig.translations.general.thisLanguage,
value: language,
})
}
return acc
},
[],
)
// eslint-disable-next-line @typescript-eslint/require-await
async function switchLanguageServerAction(lang: string): Promise<void> {
@@ -66,20 +87,23 @@ export const RootLayout = async ({
DefaultListView,
children,
config,
i18n,
payload,
})
return (
<html dir={dir} lang={lang}>
<html className={merriweather.variable} dir={dir} lang={languageCode}>
<body>
<RootProvider
componentMap={componentMap}
config={clientConfig}
dateFNSKey={i18n.dateFNSKey}
fallbackLang={clientConfig.i18n.fallbackLanguage}
lang={lang}
languageCode={languageCode}
languageOptions={languageOptions}
// eslint-disable-next-line react/jsx-no-bind
switchLanguageServerAction={switchLanguageServerAction}
translations={mergedTranslations[lang]}
translations={i18n.translations}
>
{wrappedChildren}
</RootProvider>

View File

@@ -5,7 +5,7 @@ import { logoutOperation } from 'payload/operations'
import type { CollectionRouteHandler } from '../types.js'
export const logout: CollectionRouteHandler = async ({ collection, req }) => {
const result = logoutOperation({
const result = await logoutOperation({
collection,
req,
})

View File

@@ -1,11 +1,5 @@
import type { BuildFormStateArgs } from '@payloadcms/ui/forms/buildStateFromSchema'
import type {
DocumentPreferences,
Field,
PayloadRequest,
SanitizedConfig,
TypeWithID,
} from 'payload/types'
import type { DocumentPreferences, Field, PayloadRequest, TypeWithID } from 'payload/types'
import { buildStateFromSchema } from '@payloadcms/ui/forms/buildStateFromSchema'
import { reduceFieldsToValues } from '@payloadcms/ui/utilities/reduceFieldsToValues'
@@ -22,12 +16,12 @@ if (!cached) {
cached = global._payload_fieldSchemaMap = null
}
export const getFieldSchemaMap = (config: SanitizedConfig): FieldSchemaMap => {
export const getFieldSchemaMap = (req: PayloadRequest): FieldSchemaMap => {
if (cached && process.env.NODE_ENV !== 'development') {
return cached
}
cached = buildFieldSchemaMap(config)
cached = buildFieldSchemaMap(req)
return cached
}
@@ -65,7 +59,7 @@ export const buildFormState = async ({ req }: { req: PayloadRequest }) => {
})
}
const fieldSchemaMap = getFieldSchemaMap(req.payload.config)
const fieldSchemaMap = getFieldSchemaMap(req)
const id = collectionSlug ? reqData.id : undefined
const schemaPathSegments = schemaPath.split('.')
@@ -162,7 +156,7 @@ export const buildFormState = async ({ req }: { req: PayloadRequest }) => {
})
}
if (globalSlug) {
if (globalSlug && schemaPath === globalSlug) {
resolvedData = await req.payload.findGlobal({
slug: globalSlug,
depth: 0,

View File

@@ -4,9 +4,22 @@ import { isNumber } from 'payload/utilities'
import type { CollectionRouteHandlerWithID } from '../types.js'
export const deleteByID: CollectionRouteHandlerWithID = async ({ id, collection, req }) => {
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
export const deleteByID: CollectionRouteHandlerWithID = async ({
id: incomingID,
collection,
req,
}) => {
const { searchParams } = req
const depth = searchParams.get('depth')
const id = sanitizeCollectionID({
id: incomingID,
collectionSlug: collection.config.slug,
payload: req.payload,
})
const doc = await deleteByIDOperation({
id,
collection,

View File

@@ -5,12 +5,24 @@ import { isNumber } from 'payload/utilities'
import type { CollectionRouteHandlerWithID } from '../types.js'
export const duplicate: CollectionRouteHandlerWithID = async ({ id, collection, req }) => {
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
export const duplicate: CollectionRouteHandlerWithID = async ({
id: incomingID,
collection,
req,
}) => {
const { searchParams } = req
const depth = searchParams.get('depth')
// draft defaults to true, unless explicitly set requested as false to prevent the newly duplicated document from being published
const draft = searchParams.get('draft') !== 'false'
const id = sanitizeCollectionID({
id: incomingID,
collectionSlug: collection.config.slug,
payload: req.payload,
})
const doc = await duplicateOperation({
id,
collection,

View File

@@ -4,10 +4,22 @@ import { isNumber } from 'payload/utilities'
import type { CollectionRouteHandlerWithID } from '../types.js'
export const findByID: CollectionRouteHandlerWithID = async ({ id, collection, req }) => {
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
export const findByID: CollectionRouteHandlerWithID = async ({
id: incomingID,
collection,
req,
}) => {
const { searchParams } = req
const depth = searchParams.get('depth')
const id = sanitizeCollectionID({
id: incomingID,
collectionSlug: collection.config.slug,
payload: req.payload,
})
const result = await findByIDOperation({
id,
collection,

View File

@@ -4,10 +4,22 @@ import { isNumber } from 'payload/utilities'
import type { CollectionRouteHandlerWithID } from '../types.js'
export const findVersionByID: CollectionRouteHandlerWithID = async ({ id, collection, req }) => {
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
export const findVersionByID: CollectionRouteHandlerWithID = async ({
id: incomingID,
collection,
req,
}) => {
const { searchParams } = req
const depth = searchParams.get('depth')
const id = sanitizeCollectionID({
id: incomingID,
collectionSlug: collection.config.slug,
payload: req.payload,
})
const result = await findVersionByIDOperation({
id,
collection,

View File

@@ -1,4 +1,5 @@
import httpStatus from 'http-status'
import { extractJWT } from 'payload/auth'
import { findByIDOperation } from 'payload/operations'
import { isNumber } from 'payload/utilities'
@@ -24,11 +25,14 @@ export const preview: CollectionRouteHandlerWithID = async ({ id, collection, re
(config) => config.slug === collection.config.slug,
)?.admin?.preview
const token = extractJWT(req)
if (typeof generatePreviewURL === 'function') {
try {
previewURL = await generatePreviewURL(result, {
locale: req.locale,
token: req.user?.token,
req,
token,
})
} catch (err) {
routeError({

View File

@@ -4,10 +4,22 @@ import { isNumber } from 'payload/utilities'
import type { CollectionRouteHandlerWithID } from '../types.js'
export const restoreVersion: CollectionRouteHandlerWithID = async ({ id, collection, req }) => {
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
export const restoreVersion: CollectionRouteHandlerWithID = async ({
id: incomingID,
collection,
req,
}) => {
const { searchParams } = req
const depth = searchParams.get('depth')
const id = sanitizeCollectionID({
id: incomingID,
collectionSlug: collection.config.slug,
payload: req.payload,
})
const result = await restoreVersionOperation({
id,
collection,

View File

@@ -4,12 +4,24 @@ import { isNumber } from 'payload/utilities'
import type { CollectionRouteHandlerWithID } from '../types.js'
export const updateByID: CollectionRouteHandlerWithID = async ({ id, collection, req }) => {
import { sanitizeCollectionID } from '../utilities/sanitizeCollectionID.js'
export const updateByID: CollectionRouteHandlerWithID = async ({
id: incomingID,
collection,
req,
}) => {
const { searchParams } = req
const depth = searchParams.get('depth')
const autosave = searchParams.get('autosave') === 'true'
const draft = searchParams.get('draft') === 'true'
const id = sanitizeCollectionID({
id: incomingID,
collectionSlug: collection.config.slug,
payload: req.payload,
})
const doc = await updateByIDOperation({
id,
autosave,

View File

@@ -1,4 +1,5 @@
import httpStatus from 'http-status'
import { extractJWT } from 'payload/auth'
import { findOneOperation } from 'payload/operations'
import { isNumber } from 'payload/utilities'
@@ -24,11 +25,14 @@ export const preview: GlobalRouteHandler = async ({ globalConfig, req }) => {
(config) => config.slug === globalConfig.slug,
)?.admin?.preview
const token = extractJWT(req)
if (typeof generatePreviewURL === 'function') {
try {
previewURL = await generatePreviewURL(result, {
locale: req.locale,
token: req.user?.token,
req,
token,
})
} catch (err) {
routeError({

View File

@@ -1,7 +1,7 @@
import type { Collection, PayloadRequest } from 'payload/types'
import httpStatus from 'http-status'
import { APIError, ValidationError } from 'payload/errors'
import { APIError } from 'payload/errors'
export type ErrorResponse = { data?: any; errors: unknown[]; stack?: string }

View File

@@ -0,0 +1,23 @@
import type { Payload } from 'payload/types'
type Args = {
collectionSlug: string
id: string
payload: Payload
}
export const sanitizeCollectionID = ({ id, collectionSlug, payload }: Args): number | string => {
let sanitizedID: number | string = id
const collection = payload.collections[collectionSlug]
// If default db ID type is a number, we should sanitize
let shouldSanitize = Boolean(payload.db.defaultIDType === 'number')
// UNLESS the customIDType for this collection is text.... then we leave it
if (shouldSanitize && collection.customIDType === 'text') shouldSanitize = false
// If we still should sanitize, parse float
if (shouldSanitize) sanitizedID = parseFloat(sanitizedID)
return sanitizedID
}

View File

@@ -1,4 +1,3 @@
@import './fonts.scss';
@import './styles.scss';
@import './toastify.scss';
@import './colors.scss';
@@ -20,9 +19,9 @@
--theme-overlay: rgba(5, 5, 5, 0.5);
--theme-baseline: #{$baseline-px};
--theme-baseline-body-size: #{$baseline-body-size};
--font-body: 'Suisse Intl', system-ui;
--font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
sans-serif;
--font-mono: monospace;
--font-serif: 'Merriweather', serif;
--style-radius-s: #{$style-radius-s};
--style-radius-m: #{$style-radius-m};

View File

@@ -1,75 +0,0 @@
@font-face {
font-family: 'Suisse Intl';
src:
url('../assets/fonts/SuisseIntl.woff2') format('woff2'),
url('../assets/fonts/SuisseIntl.woff') format('woff');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Suisse Intl';
src:
url('../assets/fonts/SuisseIntl-Medium.woff2') format('woff2'),
url('../assets/fonts/SuisseIntl-Medium.woff') format('woff');
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: 'Suisse Intl';
src:
url('../assets/fonts/SuisseIntl-SemiBold.woff2') format('woff2'),
url('../assets/fonts/SuisseIntl-SemiBold.woff') format('woff');
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: 'Suisse Intl';
src:
url('../assets/fonts/SuisseIntl-Bold.woff2') format('woff2'),
url('../assets/fonts/SuisseIntl-Bold.woff') format('woff');
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: 'Merriweather';
font-style: normal;
font-weight: 400;
src:
local(''),
url('../assets/fonts/merriweather-v30-latin-regular.woff2') format('woff2'),
url('../assets/fonts/merriweather-v30-latin-regular.woff') format('woff');
}
@font-face {
font-family: 'Merriweather';
font-style: italic;
font-weight: 400;
src:
local(''),
url('../assets/fonts/merriweather-v30-latin-italic.woff2') format('woff2'),
url('../assets/fonts/merriweather-v30-latin-italic.woff') format('woff');
}
@font-face {
font-family: 'Merriweather';
font-style: normal;
font-weight: 900;
src:
local(''),
url('../assets/fonts/merriweather-v30-latin-900.woff2') format('woff2'),
url('../assets/fonts/merriweather-v30-latin-900.woff') format('woff');
}
@font-face {
font-family: 'Merriweather';
font-style: italic;
font-weight: 900;
src:
local(''),
url('../assets/fonts/merriweather-v30-latin-900italic.woff2') format('woff2'),
url('../assets/fonts/merriweather-v30-latin-900italic.woff') format('woff');
}

View File

@@ -21,13 +21,6 @@ $baseline: math.div($baseline-px, $baseline-body-size) + rem;
@return (math.div($baseline-px, $baseline-body-size) * $multiplier) + rem;
}
//////////////////////////////
// FONTS (DEPRECATED. DO NOT USE. PREFER CSS VARIABLES)
//////////////////////////////
$font-body: 'Suisse Intl' !default;
$font-mono: monospace !default;
//////////////////////////////
// COLORS (DEPRECATED. DO NOT USE. PREFER CSS VARIABLES)
//////////////////////////////

View File

@@ -1,10 +1,13 @@
import type { SanitizedConfig } from 'payload/types'
import type { PayloadRequest } from 'payload/types'
import type { FieldSchemaMap } from './types.js'
import { traverseFields } from './traverseFields.js'
export const buildFieldSchemaMap = (config: SanitizedConfig): FieldSchemaMap => {
export const buildFieldSchemaMap = ({
i18n,
payload: { config },
}: PayloadRequest): FieldSchemaMap => {
const result: FieldSchemaMap = new Map()
const validRelationships = config.collections.map((c) => c.slug) || []
@@ -13,6 +16,7 @@ export const buildFieldSchemaMap = (config: SanitizedConfig): FieldSchemaMap =>
traverseFields({
config,
fields: collection.fields,
i18n,
schemaMap: result,
schemaPath: collection.slug,
validRelationships,
@@ -23,6 +27,7 @@ export const buildFieldSchemaMap = (config: SanitizedConfig): FieldSchemaMap =>
traverseFields({
config,
fields: global.fields,
i18n,
schemaMap: result,
schemaPath: global.slug,
validRelationships,

View File

@@ -1,3 +1,4 @@
import type { I18n } from '@payloadcms/translations'
import type { Field, SanitizedConfig } from 'payload/types'
import { tabHasName } from 'payload/types'
@@ -7,6 +8,7 @@ import type { FieldSchemaMap } from './types.js'
type Args = {
config: SanitizedConfig
fields: Field[]
i18n: I18n
schemaMap: FieldSchemaMap
schemaPath: string
validRelationships: string[]
@@ -15,6 +17,7 @@ type Args = {
export const traverseFields = ({
config,
fields,
i18n,
schemaMap,
schemaPath,
validRelationships,
@@ -28,6 +31,7 @@ export const traverseFields = ({
traverseFields({
config,
fields: field.fields,
i18n,
schemaMap,
schemaPath: `${schemaPath}.${field.name}`,
validRelationships,
@@ -39,6 +43,7 @@ export const traverseFields = ({
traverseFields({
config,
fields: field.fields,
i18n,
schemaMap,
schemaPath,
validRelationships,
@@ -54,6 +59,7 @@ export const traverseFields = ({
traverseFields({
config,
fields: block.fields,
i18n,
schemaMap,
schemaPath: blockSchemaPath,
validRelationships,
@@ -65,6 +71,7 @@ export const traverseFields = ({
if (typeof field.editor.generateSchemaMap === 'function') {
field.editor.generateSchemaMap({
config,
i18n,
schemaMap,
schemaPath: `${schemaPath}.${field.name}`,
})
@@ -83,6 +90,7 @@ export const traverseFields = ({
traverseFields({
config,
fields: tab.fields,
i18n,
schemaMap,
schemaPath: tabSchemaPath,
validRelationships,

View File

@@ -6,7 +6,6 @@ import type {
} from 'payload/types'
import { initI18n } from '@payloadcms/translations'
import { translations } from '@payloadcms/translations/api'
import { executeAuthStrategies } from 'payload/auth'
import { parseCookies } from 'payload/auth'
import { getDataLoader } from 'payload/utilities'
@@ -72,11 +71,10 @@ export const createPayloadRequest = async ({
headers: request.headers,
})
const i18n = initI18n({
const i18n = await initI18n({
config: config.i18n,
context: 'api',
language,
translations,
})
const customRequest: CustomPayloadRequest = {

View File

@@ -18,7 +18,11 @@ export const getDataAndFile: GetDataAndFile = async ({ collection, config, reque
const [contentType] = (request.headers.get('Content-Type') || '').split(';')
if (contentType === 'application/json') {
data = await request.json()
try {
data = await request.json()
} catch (error) {
data = {}
}
} else if (contentType === 'multipart/form-data') {
// possible upload request
if (collection?.config?.upload) {

View File

@@ -1,22 +0,0 @@
import type { I18n } from '@payloadcms/translations'
import type { SanitizedConfig } from 'payload/types'
import { initI18n } from '@payloadcms/translations'
import { translations } from '@payloadcms/translations/client'
import { cookies, headers } from 'next/headers.js'
import { getRequestLanguage } from './getRequestLanguage.js'
export const getNextI18n = ({
config,
language,
}: {
config: SanitizedConfig
language?: string
}): I18n =>
initI18n({
config: config.i18n,
context: 'client',
language: language || getRequestLanguage({ config, cookies: cookies(), headers: headers() }),
translations,
})

View File

@@ -0,0 +1,19 @@
import type { I18n } from '@payloadcms/translations'
import type { SanitizedConfig } from 'payload/types'
import { initI18n } from '@payloadcms/translations'
import { cookies, headers } from 'next/headers.js'
import { getRequestLanguage } from './getRequestLanguage.js'
/**
* In the context of NextJS, this function initializes the i18n object for the current request.
*
* It must be called on the server side, and within the lifecycle of a request since it relies on the request headers and cookies.
*/
export const getNextRequestI18n = async ({ config }: { config: SanitizedConfig }): Promise<I18n> =>
initI18n({
config: config.i18n,
context: 'client',
language: getRequestLanguage({ config, cookies: cookies(), headers: headers() }),
})

View File

@@ -35,7 +35,10 @@ export const getPayloadHMR = async (options: InitOptions): Promise<Payload> => {
cached.payload.config = config
cached.payload.collections = config.collections.reduce((collections, collection) => {
collections[collection.slug] = { config: collection }
collections[collection.slug] = {
config: collection,
customIDType: cached.payload.collections[collection.slug]?.customIDType,
}
return collections
}, {})

View File

@@ -1,12 +1,13 @@
import type { AcceptedLanguages } from '@payloadcms/translations'
import type { ReadonlyRequestCookies } from 'next/dist/server/web/spec-extension/adapters/request-cookies.js'
import type { SanitizedConfig } from 'payload/config'
import { matchLanguage } from '@payloadcms/translations'
import { extractHeaderLanguage } from '@payloadcms/translations'
type GetRequestLanguageArgs = {
config: SanitizedConfig
cookies: Map<string, string> | ReadonlyRequestCookies
defaultLanguage?: string
defaultLanguage?: AcceptedLanguages
headers: Request['headers']
}
@@ -15,14 +16,23 @@ export const getRequestLanguage = ({
cookies,
defaultLanguage = 'en',
headers,
}: GetRequestLanguageArgs): string => {
const acceptLanguage = headers.get('Accept-Language')
const cookieLanguage = cookies.get(`${config.cookiePrefix || 'payload'}-lng`)
}: GetRequestLanguageArgs): AcceptedLanguages => {
const langCookie = cookies.get(`${config.cookiePrefix || 'payload'}-lng`)
const languageFromCookie = typeof langCookie === 'string' ? langCookie : langCookie?.value
const languageFromHeader = headers.get('Accept-Language')
? extractHeaderLanguage(headers.get('Accept-Language'))
: undefined
const fallbackLang = config?.i18n?.fallbackLanguage || defaultLanguage
const reqLanguage =
(typeof cookieLanguage === 'string' ? cookieLanguage : cookieLanguage?.value) ||
acceptLanguage ||
defaultLanguage
const supportedLanguageKeys = Object.keys(config?.i18n?.supportedLanguages || {})
return matchLanguage(reqLanguage)
if (languageFromCookie && supportedLanguageKeys.includes(languageFromCookie)) {
return languageFromCookie as AcceptedLanguages
}
if (languageFromHeader && supportedLanguageKeys.includes(languageFromHeader)) {
return languageFromHeader
}
return supportedLanguageKeys.includes(fallbackLang) ? (fallbackLang as AcceptedLanguages) : 'en'
}

View File

@@ -8,7 +8,6 @@ import type {
} from 'payload/types'
import { initI18n } from '@payloadcms/translations'
import { translations } from '@payloadcms/translations/client'
import { findLocaleFromCode } from '@payloadcms/ui/utilities/findLocaleFromCode'
import { headers as getHeaders } from 'next/headers.js'
import { notFound, redirect } from 'next/navigation.js'
@@ -45,14 +44,13 @@ export const initPage = async ({
const cookies = parseCookies(headers)
const language = getRequestLanguage({ config: payload.config, cookies, headers })
const i18n = initI18n({
const i18n = await initI18n({
config: payload.config.i18n,
context: 'client',
language,
translations,
})
const req = createLocalReq(
const req = await createLocalReq(
{
fallbackLocale: null,
locale: locale.code,

View File

@@ -1,9 +1,10 @@
import type { Field } from 'payload/types'
import type { Field, WithServerSideProps as WithServerSidePropsType } from 'payload/types'
import type { AdminViewProps } from 'payload/types'
import { Form } from '@payloadcms/ui/forms/Form'
import { FormSubmit } from '@payloadcms/ui/forms/Submit'
import { buildStateFromSchema } from '@payloadcms/ui/forms/buildStateFromSchema'
import { WithServerSideProps as WithServerSidePropsGeneric } from '@payloadcms/ui/providers/ComponentMap'
import { mapFields } from '@payloadcms/ui/utilities/buildComponentMap'
import React from 'react'
@@ -16,6 +17,8 @@ export const CreateFirstUserView: React.FC<AdminViewProps> = async ({ initPageRe
const {
req,
req: {
i18n,
payload,
payload: {
config,
config: {
@@ -48,9 +51,15 @@ export const CreateFirstUserView: React.FC<AdminViewProps> = async ({ initPageRe
},
]
const WithServerSideProps: WithServerSidePropsType = ({ Component, ...rest }) => {
return <WithServerSidePropsGeneric Component={Component} payload={payload} {...rest} />
}
const createFirstUserFieldMap = mapFields({
WithServerSideProps,
config,
fieldSchema: fields,
i18n,
parentPath: userSlug,
})

View File

@@ -3,7 +3,7 @@ import type { SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload/t
import type { GenerateViewMetadata } from '../Root/index.js'
import { getNextI18n } from '../../utilities/getNextI18n.js'
import { getNextRequestI18n } from '../../utilities/getNextRequestI18n.js'
import { generateMetadata as apiMeta } from '../API/meta.js'
import { generateMetadata as editMeta } from '../Edit/meta.js'
import { generateMetadata as livePreviewMeta } from '../LivePreview/meta.js'
@@ -89,7 +89,7 @@ export const getMetaBySegment: GenerateEditViewMetadata = async ({
}
}
const i18n = await getNextI18n({
const i18n = await getNextRequestI18n({
config,
})

View File

@@ -117,7 +117,10 @@ export const ListView: React.FC<AdminViewProps> = async ({ initPageResult, searc
<Fragment>
<HydrateClientUser permissions={permissions} user={user} />
<ListInfoProvider
collectionConfig={createClientCollectionConfig(collectionConfig)}
collectionConfig={createClientCollectionConfig({
collection: collectionConfig,
t: initPageResult.req.i18n.t,
})}
collectionSlug={collectionSlug}
hasCreatePermission={permissions?.collections?.[collectionSlug]?.create?.permission}
newDocumentURL={`${admin}/collections/${collectionSlug}/create`}

View File

@@ -2,11 +2,11 @@ import type { I18n } from '@payloadcms/translations'
import type { Metadata } from 'next'
import type { AdminViewComponent, SanitizedConfig } from 'payload/types'
import { getNextI18n } from '@payloadcms/next/utilities'
import { HydrateClientUser } from '@payloadcms/ui/elements/HydrateClientUser'
import { DefaultTemplate } from '@payloadcms/ui/templates/Default'
import React, { Fragment } from 'react'
import { getNextRequestI18n } from '../../utilities/getNextRequestI18n.js'
import { initPage } from '../../utilities/initPage.js'
import { NotFoundClient } from './index.client.js'
@@ -19,7 +19,7 @@ export const generatePageMetadata = async ({
}): Promise<Metadata> => {
const config = await configPromise
const i18n = getNextI18n({
const i18n = await getNextRequestI18n({
config,
})

View File

@@ -1,7 +1,7 @@
import type { Metadata } from 'next'
import type { SanitizedConfig } from 'payload/types'
import { getNextI18n } from '../../utilities/getNextI18n.js'
import { getNextRequestI18n } from '../../utilities/getNextRequestI18n.js'
import { generateAccountMetadata } from '../Account/index.js'
import { generateCreateFirstUserMetadata } from '../CreateFirstUser/index.js'
import { generateDashboardMetadata } from '../Dashboard/index.js'
@@ -49,7 +49,7 @@ export const generatePageMetadata = async ({ config: configPromise, params }: Ar
const isGlobal = segmentOne === 'globals'
const isCollection = segmentOne === 'collections'
const i18n = getNextI18n({
const i18n = await getNextRequestI18n({
config,
})

View File

@@ -85,7 +85,9 @@ export const SetStepNav: React.FC<{
url: `${adminRoute}/collections/${collectionSlug}/${id}/versions`,
},
{
label: doc?.createdAt ? formatDate(doc.createdAt, dateFormat, i18n?.language) : '',
label: doc?.createdAt
? formatDate({ date: doc.createdAt, i18n, pattern: dateFormat })
: '',
},
]
}
@@ -101,7 +103,9 @@ export const SetStepNav: React.FC<{
url: `${adminRoute}/globals/${globalConfig.slug}/versions`,
},
{
label: doc?.createdAt ? formatDate(doc.createdAt, dateFormat, i18n?.language) : '',
label: doc?.createdAt
? formatDate({ date: doc.createdAt, i18n, pattern: dateFormat })
: '',
},
]
}

View File

@@ -61,7 +61,7 @@ export const DefaultVersionView: React.FC<DefaultVersionsViewProps> = ({
} = config
const formattedCreatedAt = doc?.createdAt
? formatDate(doc.createdAt, dateFormat, i18n.language)
? formatDate({ date: doc.createdAt, i18n, pattern: dateFormat })
: ''
const originalDocFetchURL = `${serverURL}${apiRoute}/${globalSlug ? 'globals/' : ''}${

View File

@@ -88,7 +88,7 @@ export const SelectComparison: React.FC<Props> = (props) => {
setOptions((existingOptions) => [
...existingOptions,
...data.docs.map((doc) => ({
label: formatDate(doc.updatedAt, dateFormat, i18n.language),
label: formatDate({ date: doc.updatedAt, i18n, pattern: dateFormat }),
value: doc.id,
})),
])

View File

@@ -22,7 +22,7 @@ export const generateMetadata: GenerateEditViewMetadata = async ({
const doc: any = {} // TODO: figure this out
const formattedCreatedAt = doc?.createdAt
? formatDate(doc.createdAt, config?.admin?.dateFormat, i18n?.language)
? formatDate({ date: doc.createdAt, i18n, pattern: config?.admin?.dateFormat })
: ''
if (collectionConfig) {

View File

@@ -38,7 +38,8 @@ export const CreatedAtCell: React.FC<CreatedAtCellProps> = ({
return (
<Link href={to}>
{cellData && formatDate(cellData as Date | number | string, dateFormat, i18n.language)}
{cellData &&
formatDate({ date: cellData as Date | number | string, i18n, pattern: dateFormat })}
</Link>
)
}

View File

@@ -101,7 +101,7 @@ export const VersionsView: EditViewComponent = async (props) => {
return (
<React.Fragment>
<SetStepNav
collectionSlug={collectionConfig?.slug || globalConfig?.slug}
collectionSlug={collectionConfig?.slug}
globalSlug={globalConfig?.slug}
id={id}
pluralLabel={collectionConfig?.labels?.plural || globalConfig?.label}

View File

@@ -1,5 +1,9 @@
/** @type {import('next').NextConfig} */
const withPayload = (nextConfig = {}) => {
/**
* @param {import('next').NextConfig} nextConfig
*
* @returns {import('next').NextConfig}
* */
export const withPayload = (nextConfig = {}) => {
return {
...nextConfig,
experimental: {

View File

@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"module": "CommonJS",
"composite": true, // Required for references to work
"noEmit": false /* Do not emit outputs. */,
"emitDeclarationOnly": true,
"outDir": "./dist/cjs" /* Specify an output folder for all emitted files. */,
"rootDir": "./src" /* Specify the root folder within your source files. */,
"sourceMap": true
},
"include": ["src/withPayload.js" /* Include the withPayload.js file in the build */]
}

View File

@@ -1,26 +0,0 @@
/fields/
/components/
/auth.d.ts
/auth.js
/components.d.ts
/components.js
/config.d.ts
/config.js
/database.d.ts
/database.js
/errors.d.ts
/errors.js
/graphql.d.ts
/graphql.js
/types.d.ts
/types.js
/utilities.d.ts
/utilities.js
/versions.d.ts
/versions.js
/operations.js
/operations.d.ts
/node.js
/node.d.ts
/uploads.js
/uploads.d.ts

View File

@@ -1,6 +1,6 @@
{
"name": "payload",
"version": "3.0.0-alpha.60",
"version": "3.0.0-beta.10",
"description": "Node, React and MongoDB Headless CMS and Application Framework",
"license": "MIT",
"main": "./src/index.ts",
@@ -22,7 +22,7 @@
}
},
"scripts": {
"build": "pnpm copyfiles && pnpm build:swc && pnpm build:types && tsx ../../scripts/exportPointerFiles.ts ../packages/payload dist/exports",
"build": "pnpm copyfiles && pnpm build:swc && pnpm build:types",
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
"build:types": "tsc --emitDeclarationOnly --outDir dist",
"build:watch": "nodemon --watch 'src/**' --ext 'ts,tsx' --exec \"pnpm build:tsc\"",
@@ -113,7 +113,7 @@
"ts-essentials": "7.0.3"
},
"engines": {
"node": ">=18.17.0"
"node": ">=18.20.2"
},
"files": [
"bin.js",

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +0,0 @@
@import './dist/admin/scss/vars';
@import './dist/admin/scss/z-index';
@import './dist/admin/scss/type';
@import './dist/admin/scss/queries';
@import './dist/admin/scss/resets';
@import './dist/admin/scss/svg';

View File

@@ -1,8 +1,10 @@
import type { I18n } from '@payloadcms/translations'
import type { JSONSchema4 } from 'json-schema'
import type { SanitizedConfig } from '../config/types.js'
import type { Field, RichTextField, Validate } from '../fields/config/types.js'
import type { PayloadRequest, RequestContext } from '../types/index.js'
import type { WithServerSideProps } from './elements/WithServerSideProps.js'
export type RichTextFieldProps<
Value extends object,
@@ -27,11 +29,14 @@ type RichTextAdapterBase<
siblingDoc: Record<string, unknown>
}) => Promise<void> | null
generateComponentMap: (args: {
WithServerSideProps: WithServerSideProps
config: SanitizedConfig
i18n: I18n
schemaPath: string
}) => Map<string, React.ReactNode>
generateSchemaMap?: (args: {
config: SanitizedConfig
i18n: I18n
schemaMap: Map<string, Field[]>
schemaPath: string
}) => Map<string, Field[]>

View File

@@ -1 +1,3 @@
export type CustomPreviewButton = React.ComponentType
import type { CustomComponent } from '../../config/types.js'
export type CustomPreviewButton = CustomComponent

View File

@@ -1 +1,3 @@
export type CustomPublishButton = React.ComponentType
import type { CustomComponent } from '../../config/types.js'
export type CustomPublishButton = CustomComponent

View File

@@ -1 +1,3 @@
export type CustomSaveButton = React.ComponentType
import type { CustomComponent } from '../../config/types.js'
export type CustomSaveButton = CustomComponent

View File

@@ -1 +1,3 @@
export type CustomSaveDraftButton = React.ComponentType
import type { CustomComponent } from '../../config/types.js'
export type CustomSaveDraftButton = CustomComponent

View File

@@ -0,0 +1,4 @@
export type WithServerSideProps = (args: {
[key: string]: any
Component: React.ComponentType<any>
}) => React.ReactNode

View File

@@ -1,8 +1,11 @@
import type React from 'react'
export type DescriptionFunction = () => string
import type { CustomComponent, LabelFunction } from '../../config/types.js'
import type { Payload } from '../../index.js'
export type DescriptionComponent = React.ComponentType<FieldDescriptionProps>
export type DescriptionFunction = LabelFunction
export type DescriptionComponent = CustomComponent<FieldDescriptionProps>
export type Description =
| DescriptionComponent
@@ -15,4 +18,5 @@ export type FieldDescriptionProps = {
className?: string
description?: Record<string, string> | string
marginPlacement?: 'bottom' | 'top'
payload?: Payload
}

View File

@@ -1,8 +1,10 @@
import type { LabelFunction } from '../../config/types.js'
export type LabelProps = {
CustomLabel?: React.ReactNode
as?: 'label' | 'span'
htmlFor?: string
label?: Record<string, string> | false | string
label?: LabelFunction | Record<string, string> | false | string
required?: boolean
unstyled?: boolean
}

View File

@@ -1,3 +1,5 @@
export type RowLabelComponent = React.ComponentType
import type { CustomComponent } from '../../config/types.js'
export type RowLabelComponent = CustomComponent
export type RowLabel = Record<string, string> | RowLabelComponent | string

View File

@@ -13,6 +13,7 @@ export type {
DocumentTabConfig,
DocumentTabProps,
} from './elements/Tab.js'
export type { WithServerSideProps } from './elements/WithServerSideProps.js'
export type { ErrorProps } from './forms/Error.js'
export type {
Description,

View File

@@ -1,4 +1,4 @@
import type { Translations } from '@payloadcms/translations'
import type { SupportedLanguages } from '@payloadcms/translations'
import type { Permissions } from '../../auth/index.js'
import type { SanitizedCollectionConfig } from '../../collections/config/types.js'
@@ -43,7 +43,7 @@ export type InitPageResult = {
locale: Locale
permissions: Permissions
req: PayloadRequest
translations: Translations
translations: SupportedLanguages
visibleEntities: VisibleEntities
}

View File

@@ -2,15 +2,12 @@ import crypto from 'crypto'
import type { Field, FieldHook } from '../../fields/config/types.js'
import { extractTranslations } from '../../translations/extractTranslations.js'
const labels = extractTranslations(['authentication:enableAPIKey', 'authentication:apiKey'])
const encryptKey: FieldHook = ({ req, value }) =>
value ? req.payload.encrypt(value as string) : null
const decryptKey: FieldHook = ({ req, value }) =>
value ? req.payload.decrypt(value as string) : undefined
// eslint-disable-next-line no-restricted-exports
export default [
{
name: 'enableAPIKey',
@@ -21,7 +18,7 @@ export default [
},
},
defaultValue: false,
label: labels['authentication:enableAPIKey'],
label: ({ t }) => t('authentication:enableAPIKey'),
},
{
name: 'apiKey',
@@ -35,7 +32,7 @@ export default [
afterRead: [decryptKey],
beforeChange: [encryptKey],
},
label: labels['authentication:apiKey'],
label: ({ t }) => t('authentication:apiKey'),
},
{
name: 'apiKeyIndex',

View File

@@ -1,9 +1,6 @@
import type { Field } from '../../fields/config/types.js'
import { email } from '../../fields/validations.js'
import { extractTranslations } from '../../translations/extractTranslations.js'
const labels = extractTranslations(['general:email'])
const baseAuthFields: Field[] = [
{
@@ -14,7 +11,7 @@ const baseAuthFields: Field[] = [
Field: () => null,
},
},
label: labels['general:email'],
label: ({ t }) => t('general:email'),
required: true,
unique: true,
validate: email,

View File

@@ -1,9 +1,5 @@
import type { Field, FieldHook } from '../../fields/config/types.js'
import { extractTranslations } from '../../translations/extractTranslations.js'
const labels = extractTranslations(['authentication:verified'])
const autoRemoveVerificationToken: FieldHook = ({ data, operation, originalDoc, value }) => {
// If a user manually sets `_verified` to true,
// and it was `false`, set _verificationToken to `null`.
@@ -33,7 +29,7 @@ export default [
Field: () => null,
},
},
label: labels['authentication:verified'],
label: ({ t }) => t('authentication:verified'),
},
{
name: '_verificationToken',

View File

@@ -1,9 +1,5 @@
import type { CollectionConfig } from '../collections/config/types.js'
import { extractTranslations } from '../translations/extractTranslations.js'
const labels = extractTranslations(['general:user', 'general:users'])
export const defaultUserCollection: CollectionConfig = {
slug: 'users',
admin: {
@@ -14,7 +10,7 @@ export const defaultUserCollection: CollectionConfig = {
},
fields: [],
labels: {
plural: labels['general:users'],
singular: labels['general:user'],
plural: ({ t }) => t('general:users'),
singular: ({ t }) => t('general:user'),
},
}

View File

@@ -1,8 +1,9 @@
import type { GeneratedTypes } from '../index.js'
import type { AuthStrategyFunctionArgs, User } from './index.js'
export const executeAuthStrategies = async (
args: AuthStrategyFunctionArgs,
): Promise<User | null> => {
): Promise<GeneratedTypes['user'] | null> => {
return args.payload.authStrategies.reduce(async (accumulatorPromise, strategy) => {
const authUser = await accumulatorPromise
if (!authUser) {

View File

@@ -1,6 +1,7 @@
/* eslint-disable no-param-reassign */
import type { CollectionConfig } from '../collections/config/types.js'
import type { Field, TabAsField } from '../fields/config/types.js'
import type { PayloadRequest } from '../types/index.js'
import type { User } from './index.js'
import { fieldAffectsData, tabHasName } from '../fields/config/types.js'
@@ -105,7 +106,7 @@ const traverseFields = ({
export const getFieldsToSign = (args: {
collectionConfig: CollectionConfig
email: string
user: User
user: PayloadRequest['user']
}): Record<string, unknown> => {
const { collectionConfig, email, user } = args

View File

@@ -1,3 +1,4 @@
import type { GeneratedTypes } from '../../index.js'
import type { PayloadRequest } from '../../types/index.js'
import type { Permissions, User } from '../types.js'
@@ -10,16 +11,16 @@ import { getAccessResults } from '../getAccessResults.js'
export type AuthArgs = {
headers: Request['headers']
req: Omit<PayloadRequest, 'user'>
req?: Omit<PayloadRequest, 'user'>
}
export type AuthResult = {
cookies: Map<string, string>
permissions: Permissions
user: User | null
user: GeneratedTypes['user'] | null
}
export const auth = async (args: AuthArgs): Promise<AuthResult> => {
export const auth = async (args: Required<AuthArgs>): Promise<AuthResult> => {
const { headers } = args
const req = args.req as PayloadRequest
const { payload } = req

View File

@@ -10,6 +10,6 @@ export const auth = async (payload: Payload, options: AuthArgs): Promise<AuthRes
return await authOperation({
headers,
req: createLocalReq({ req: options.req as PayloadRequest }, payload),
req: await createLocalReq({ req: options.req as PayloadRequest }, payload),
})
}

View File

@@ -26,6 +26,11 @@ type ResolveFn = (...args: Required<ResolveArgs>) => Promise<ResolveResult>
const locatedConfig = getTsconfig()
const tsconfig = locatedConfig.config.compilerOptions as unknown as ts.CompilerOptions
// Ensure baseUrl is set in order to support paths
if (!tsconfig.baseUrl) {
tsconfig.baseUrl = '.'
}
// Don't resolve d.ts files, because we aren't type-checking
tsconfig.noDtsResolution = true
tsconfig.module = ts.ModuleKind.ESNext
@@ -78,7 +83,6 @@ export const resolve: ResolveFn = async (specifier, context, nextResolve) => {
// and keep going
let nextResult: ResolveResult
// First, try to
if (!isTS) {
try {
nextResult = await nextResolve(specifier, context, nextResolve)

View File

@@ -23,14 +23,22 @@ export type ClientCollectionConfig = Omit<
fields: ClientFieldConfig[]
}
import type { TFunction } from '@payloadcms/translations'
import type { ClientFieldConfig } from '../../fields/config/client.js'
import type { SanitizedCollectionConfig } from './types.js'
import { createClientFieldConfigs } from '../../fields/config/client.js'
export const createClientCollectionConfig = (collection: SanitizedCollectionConfig) => {
export const createClientCollectionConfig = ({
collection,
t,
}: {
collection: SanitizedCollectionConfig
t: TFunction
}) => {
const sanitized = { ...collection }
sanitized.fields = createClientFieldConfigs(sanitized.fields)
sanitized.fields = createClientFieldConfigs({ fields: sanitized.fields, t })
const serverOnlyCollectionProperties: Partial<ServerOnlyCollectionProperties>[] = [
'hooks',
@@ -60,6 +68,14 @@ export const createClientCollectionConfig = (collection: SanitizedCollectionConf
delete sanitized.auth.verify
}
if (sanitized.labels) {
Object.entries(sanitized.labels).forEach(([labelType, collectionLabel]) => {
if (typeof collectionLabel === 'function') {
sanitized.labels[labelType] = collectionLabel({ t })
}
})
}
if ('admin' in sanitized) {
sanitized.admin = { ...sanitized.admin }
@@ -85,7 +101,11 @@ export const createClientCollectionConfig = (collection: SanitizedCollectionConf
return sanitized
}
export const createClientCollectionConfigs = (
collections: SanitizedCollectionConfig[],
): ClientCollectionConfig[] =>
collections.map((collection) => createClientCollectionConfig(collection))
export const createClientCollectionConfigs = ({
collections,
t,
}: {
collections: SanitizedCollectionConfig[]
t: TFunction
}): ClientCollectionConfig[] =>
collections.map((collection) => createClientCollectionConfig({ collection, t }))

View File

@@ -11,15 +11,12 @@ import TimestampsRequired from '../../errors/TimestampsRequired.js'
import { sanitizeFields } from '../../fields/config/sanitize.js'
import { fieldAffectsData } from '../../fields/config/types.js'
import mergeBaseFields from '../../fields/mergeBaseFields.js'
import { extractTranslations } from '../../translations/extractTranslations.js'
import { getBaseUploadFields } from '../../uploads/getBaseFields.js'
import { formatLabels } from '../../utilities/formatLabels.js'
import { isPlainObject } from '../../utilities/isPlainObject.js'
import baseVersionFields from '../../versions/baseFields.js'
import { authDefaults, defaults } from './defaults.js'
const translations = extractTranslations(['general:createdAt', 'general:updatedAt'])
const sanitizeCollection = (
config: Config,
collection: CollectionConfig,
@@ -51,7 +48,7 @@ const sanitizeCollection = (
disableBulkEdit: true,
hidden: true,
},
label: translations['general:updatedAt'],
label: ({ t }) => t('general:updatedAt'),
})
}
if (!hasCreatedAt) {
@@ -64,7 +61,7 @@ const sanitizeCollection = (
// The default sort for list view is createdAt. Thus, enabling indexing by default, is a major performance improvement, especially for large or a large amount of collections.
type: 'date',
index: true,
label: translations['general:createdAt'],
label: ({ t }) => t('general:createdAt'),
})
}
}

View File

@@ -140,10 +140,10 @@ const collectionSchema = joi.object().keys({
labels: joi.object({
plural: joi
.alternatives()
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
.try(joi.func(), joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
singular: joi
.alternatives()
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
.try(joi.func(), joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
}),
timestamps: joi.boolean(),
typescript: joi.object().keys({

View File

@@ -10,10 +10,12 @@ import type {
import type { Auth, ClientUser, IncomingAuthType } from '../../auth/types.js'
import type {
Access,
CustomComponent,
EditConfig,
Endpoint,
EntityDescription,
GeneratePreviewURL,
LabelFunction,
LivePreviewConfig,
} from '../../config/types.js'
import type { Field } from '../../fields/config/types.js'
@@ -200,10 +202,10 @@ export type CollectionAdminOptions = {
* Custom admin components
*/
components?: {
AfterList?: React.ComponentType<any>[]
AfterListTable?: React.ComponentType<any>[]
BeforeList?: React.ComponentType<any>[]
BeforeListTable?: React.ComponentType<any>[]
AfterList?: CustomComponent[]
AfterListTable?: CustomComponent[]
BeforeList?: CustomComponent[]
BeforeListTable?: CustomComponent[]
/**
* Components within the edit view
*/
@@ -238,7 +240,7 @@ export type CollectionAdminOptions = {
List?:
| {
Component?: React.ComponentType<any>
actions?: React.ComponentType<any>[]
actions?: CustomComponent[]
}
| React.ComponentType<any>
}
@@ -360,8 +362,8 @@ export type CollectionConfig = {
* Label configuration
*/
labels?: {
plural?: Record<string, string> | string
singular?: Record<string, string> | string
plural?: LabelFunction | Record<string, string> | string
singular?: LabelFunction | Record<string, string> | string
}
slug: string
/**
@@ -407,6 +409,7 @@ export interface SanitizedCollectionConfig
export type Collection = {
config: SanitizedCollectionConfig
customIDType?: 'number' | 'text'
graphQL?: {
JWT: GraphQLObjectType
mutationInputType: GraphQLNonNull<any>

View File

@@ -5,8 +5,6 @@ import DataLoader from 'dataloader'
import type { PayloadRequest } from '../types/index.js'
import type { TypeWithID } from './config/types.js'
import { fieldAffectsData } from '../fields/config/types.js'
import { getIDType } from '../utilities/getIDType.js'
import { isValidID } from '../utilities/isValidID.js'
// Payload uses `dataloader` to solve the classic GraphQL N+1 problem.
@@ -71,15 +69,13 @@ const batchAndLoadDocs =
const batchKey = JSON.stringify(batchKeyArray)
const idField = payload.collections?.[collection].config.fields.find(
(field) => fieldAffectsData(field) && field.name === 'id',
)
const idType = payload.collections?.[collection].customIDType || payload.db.defaultIDType
let sanitizedID: number | string = id
if (idField?.type === 'number') sanitizedID = parseFloat(id)
if (idType === 'number') sanitizedID = parseFloat(id)
if (isValidID(sanitizedID, getIDType(idField, payload?.db?.defaultIDType))) {
if (isValidID(sanitizedID, idType)) {
return {
...batches,
[batchKey]: [...(batches[batchKey] || []), sanitizedID],

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