Compare commits
354 Commits
db-postgre
...
v2.0.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10ebd76fcf | ||
|
|
36d6eb0a69 | ||
|
|
cd1f8dc332 | ||
|
|
e4275aa228 | ||
|
|
ddfcb2f12e | ||
|
|
09f33eae2c | ||
|
|
d3b7c9feec | ||
|
|
5fd3d43000 | ||
|
|
7767679caa | ||
|
|
fdf2e32005 | ||
|
|
773be8744d | ||
|
|
40d5bc0c4a | ||
|
|
fd33c790f2 | ||
|
|
ae7aac7639 | ||
|
|
05cc2873b4 | ||
|
|
171ee121e9 | ||
|
|
4af1d7d812 | ||
|
|
d229fc391a | ||
|
|
46a24a9822 | ||
|
|
06a51b3c9b | ||
|
|
1d4142ccc0 | ||
|
|
f1741beba2 | ||
|
|
9103277a10 | ||
|
|
d84673f400 | ||
|
|
15e23a3adc | ||
|
|
565929adcf | ||
|
|
8bbac60e60 | ||
|
|
15c7f0dbf3 | ||
|
|
05eba56d7d | ||
|
|
ab984b3ea9 | ||
|
|
a54638eb47 | ||
|
|
69af8d9c83 | ||
|
|
64864686c4 | ||
|
|
32c0bef05e | ||
|
|
a77513e94f | ||
|
|
aaf883909c | ||
|
|
cc56da11d6 | ||
|
|
41d9c28073 | ||
|
|
a071b97607 | ||
|
|
cfd9231403 | ||
|
|
71dce62646 | ||
|
|
db376f24ba | ||
|
|
2752483ac7 | ||
|
|
860f867c62 | ||
|
|
5b0adbe9c3 | ||
|
|
fb7d1be2f3 | ||
|
|
687a2e85d0 | ||
|
|
df80483afe | ||
|
|
8781770d83 | ||
|
|
ed1d5a60f7 | ||
|
|
50a0965561 | ||
|
|
c31fa5dd83 | ||
|
|
440eb8d9c6 | ||
|
|
1523b2be41 | ||
|
|
68f55c4064 | ||
|
|
0d3544ea04 | ||
|
|
f152f451dc | ||
|
|
5d92436e39 | ||
|
|
63edecddd8 | ||
|
|
65cdf6bfd0 | ||
|
|
9e831a6a00 | ||
|
|
0b64c5fb66 | ||
|
|
40426a25df | ||
|
|
c91b1e8310 | ||
|
|
06e2fa9d11 | ||
|
|
72249d1ecd | ||
|
|
dc22496103 | ||
|
|
c6925ec29f | ||
|
|
8f46b31249 | ||
|
|
af1c2e924e | ||
|
|
1487250752 | ||
|
|
e3c776523a | ||
|
|
c09e9d96cf | ||
|
|
aabc0650f8 | ||
|
|
4dd4e9aaae | ||
|
|
3b63b7fc3c | ||
|
|
f0e2e78b82 | ||
|
|
a154adf066 | ||
|
|
d86bcc1495 | ||
|
|
2b7043c6e6 | ||
|
|
e0afeeca97 | ||
|
|
76e306ddd8 | ||
|
|
cfc78ed4f5 | ||
|
|
64b0db5a7a | ||
|
|
48600f0c66 | ||
|
|
1ebb9f3915 | ||
|
|
eab04d9b4d | ||
|
|
1758b6c449 | ||
|
|
bca1be8cb6 | ||
|
|
1e197933dd | ||
|
|
4eb929d57c | ||
|
|
198209c2a4 | ||
|
|
54f19ce1e3 | ||
|
|
d32f3ade1b | ||
|
|
bf189abc91 | ||
|
|
69a379e49f | ||
|
|
493fc3ed68 | ||
|
|
138e495e1a | ||
|
|
8fe619a221 | ||
|
|
5195a80dba | ||
|
|
909cf90fa2 | ||
|
|
c1d1a00d4a | ||
|
|
ae68093f35 | ||
|
|
0f2f355a01 | ||
|
|
0101aa60d9 | ||
|
|
c823ee07cd | ||
|
|
1f12f9b480 | ||
|
|
ec31ab3a2c | ||
|
|
a7bea35d69 | ||
|
|
64a4f19539 | ||
|
|
c35661e16e | ||
|
|
69b6179521 | ||
|
|
3d2e167e78 | ||
|
|
aa1955221c | ||
|
|
7a9b11e2c4 | ||
|
|
a82c0d0e50 | ||
|
|
35a6daa10d | ||
|
|
bf5db4e44a | ||
|
|
a87e8aa82b | ||
|
|
e00d87a791 | ||
|
|
b61babca73 | ||
|
|
e403a0492e | ||
|
|
54a76e1401 | ||
|
|
91f6e36420 | ||
|
|
9e74fe558f | ||
|
|
ac8bcfac23 | ||
|
|
91b0a691ed | ||
|
|
3ced6ec2a0 | ||
|
|
650fe159ee | ||
|
|
b92657fb39 | ||
|
|
17dbe066c1 | ||
|
|
5acc88ee9a | ||
|
|
d4983af3fc | ||
|
|
6f2dcfd44e | ||
|
|
3df6435353 | ||
|
|
3ef03364be | ||
|
|
5396af9bfc | ||
|
|
56eddf3a93 | ||
|
|
6dd900e6b9 | ||
|
|
441a26b79c | ||
|
|
471d5ca17f | ||
|
|
536f995ab4 | ||
|
|
bb3c97828e | ||
|
|
c4d32d5418 | ||
|
|
8522fd9f27 | ||
|
|
ee93118688 | ||
|
|
47f9b89175 | ||
|
|
891fc55e25 | ||
|
|
988755f202 | ||
|
|
b5483a46f6 | ||
|
|
04fd2d6e82 | ||
|
|
4cdc7cf3c4 | ||
|
|
fbaa1028e6 | ||
|
|
df26e19c16 | ||
|
|
98501cf4c0 | ||
|
|
d0ac142871 | ||
|
|
d60c66ebd6 | ||
|
|
a515bdae56 | ||
|
|
cc40853903 | ||
|
|
121d69faf9 | ||
|
|
0a2d7c858a | ||
|
|
0962e1e563 | ||
|
|
a324768b3f | ||
|
|
0973ee512e | ||
|
|
f74e492448 | ||
|
|
e567627809 | ||
|
|
56965bc0ed | ||
|
|
e43d6520c5 | ||
|
|
1123909960 | ||
|
|
bc1853c2e7 | ||
|
|
318b734f96 | ||
|
|
2734b1d54a | ||
|
|
82510c1574 | ||
|
|
0a2b02f206 | ||
|
|
41ee127de8 | ||
|
|
9ddec59ddd | ||
|
|
b72b22c628 | ||
|
|
a685f30245 | ||
|
|
173ec6f0f8 | ||
|
|
349ab5343e | ||
|
|
cf97adab7c | ||
|
|
b6fc940f18 | ||
|
|
2fb685c0fe | ||
|
|
54be5847f7 | ||
|
|
bf4f37b514 | ||
|
|
9e577e7214 | ||
|
|
69185c06c2 | ||
|
|
e561016d07 | ||
|
|
9db4dadce3 | ||
|
|
fa40d511c2 | ||
|
|
ebfb86866f | ||
|
|
be853a0657 | ||
|
|
c880342099 | ||
|
|
d09bbd2171 | ||
|
|
c1823f719a | ||
|
|
39686e3f05 | ||
|
|
482973559d | ||
|
|
600dbd72f4 | ||
|
|
785337dc5d | ||
|
|
14a35f35c3 | ||
|
|
ed2e176285 | ||
|
|
2d79280999 | ||
|
|
8b5084ab43 | ||
|
|
b19356597b | ||
|
|
0bd412edbd | ||
|
|
1a6ba25e5d | ||
|
|
26d0cd18a1 | ||
|
|
31653fe76e | ||
|
|
a6d52223d5 | ||
|
|
1bf7c4084c | ||
|
|
419a3eef53 | ||
|
|
f62531bafd | ||
|
|
dc815dad14 | ||
|
|
bde2ce9b53 | ||
|
|
041af0100c | ||
|
|
2e18c5b8cf | ||
|
|
53f0c526f7 | ||
|
|
dc5c4eced0 | ||
|
|
0d8b6d03ed | ||
|
|
cfa364280f | ||
|
|
7a293563fb | ||
|
|
9359954233 | ||
|
|
9fcc05676e | ||
|
|
1e95f5de49 | ||
|
|
f8983e9e5c | ||
|
|
aab71f03b3 | ||
|
|
447d88bf82 | ||
|
|
897e94f2f4 | ||
|
|
87936e5b52 | ||
|
|
5e02762715 | ||
|
|
0785820539 | ||
|
|
bb6d545aae | ||
|
|
4cdc94d92f | ||
|
|
c28dca6fc0 | ||
|
|
95e630201a | ||
|
|
84100be7eb | ||
|
|
0c7007ae9a | ||
|
|
3e7e3669fe | ||
|
|
1d3bb9c287 | ||
|
|
cacc624f5a | ||
|
|
04dd824f0a | ||
|
|
dc929732b1 | ||
|
|
a47fd23199 | ||
|
|
d205da0aa4 | ||
|
|
c414f12527 | ||
|
|
d9418c9fe3 | ||
|
|
65e2ba9bd0 | ||
|
|
b3f808644f | ||
|
|
1e30435525 | ||
|
|
156d25741b | ||
|
|
de3ee812cd | ||
|
|
234fb33864 | ||
|
|
c168bb5201 | ||
|
|
0ce5d774cb | ||
|
|
d2c2bbd711 | ||
|
|
88193adebb | ||
|
|
eac44f9496 | ||
|
|
6400095f1f | ||
|
|
b57267e60a | ||
|
|
79541b6ba7 | ||
|
|
0420098e94 | ||
|
|
9f80634be4 | ||
|
|
25ecb27aed | ||
|
|
2ff2efd4b2 | ||
|
|
ff7a29179d | ||
|
|
8403f8ac2a | ||
|
|
df0d4fa726 | ||
|
|
2a4bb5a11d | ||
|
|
2b6c5e42b5 | ||
|
|
a1a4765a94 | ||
|
|
64d0bc7a16 | ||
|
|
b1fb43baf5 | ||
|
|
bb309ca843 | ||
|
|
760662263f | ||
|
|
bce5205cf1 | ||
|
|
0b21726af6 | ||
|
|
04a7d256c5 | ||
|
|
8a9915b58a | ||
|
|
820e867804 | ||
|
|
699314a781 | ||
|
|
86552e62ff | ||
|
|
13769d3cdc | ||
|
|
158ae0de30 | ||
|
|
04056513d7 | ||
|
|
2b7e6dda2f | ||
|
|
b11464542a | ||
|
|
b97568f394 | ||
|
|
18e8839b8c | ||
|
|
f5e5bfae81 | ||
|
|
b27ab75e07 | ||
|
|
5799e4015f | ||
|
|
2f72ed78e1 | ||
|
|
5f3f038a6b | ||
|
|
5b29852c0a | ||
|
|
9616e43035 | ||
|
|
b918425e72 | ||
|
|
9ed5f5b6fc | ||
|
|
1721d118a8 | ||
|
|
8dc400c65a | ||
|
|
a5ac793443 | ||
|
|
8d6d995d78 | ||
|
|
4652255d4f | ||
|
|
a274f2e5ca | ||
|
|
ed4528096a | ||
|
|
f7946af404 | ||
|
|
26ead270a2 | ||
|
|
c45c784c58 | ||
|
|
6b6977cc00 | ||
|
|
41a6abd2e4 | ||
|
|
d59ccc0f34 | ||
|
|
870946d01b | ||
|
|
3bf68ef9d4 | ||
|
|
60d7d51a0a | ||
|
|
61deb2c873 | ||
|
|
0ae27d4212 | ||
|
|
3c96622313 | ||
|
|
8066ce6f49 | ||
|
|
b7f9ffc51a | ||
|
|
4a873a5ae3 | ||
|
|
c080deb0b8 | ||
|
|
8cefa8181c | ||
|
|
a34dd651b1 | ||
|
|
a86041836f | ||
|
|
6dbd760a2e | ||
|
|
4181a84e9b | ||
|
|
b4d5168409 | ||
|
|
74756c0703 | ||
|
|
dce57d6fdd | ||
|
|
d55df67642 | ||
|
|
769d9063d5 | ||
|
|
8f95a23df9 | ||
|
|
9816c33015 | ||
|
|
1d14c976f2 | ||
|
|
63c436e0ac | ||
|
|
a4700d7a9d | ||
|
|
e5a1fe0771 | ||
|
|
b101ff86a9 | ||
|
|
0c5a6044a0 | ||
|
|
6fc7c0b9ad | ||
|
|
9459e82161 | ||
|
|
62501eb3b8 | ||
|
|
df000b7508 | ||
|
|
c04bde6725 | ||
|
|
1fe8ae39cb | ||
|
|
e2049b9564 | ||
|
|
49d9836ab4 | ||
|
|
4ed38575bf | ||
|
|
ef166cd70d | ||
|
|
d45665f092 | ||
|
|
10bae6dab7 | ||
|
|
ad25c86fdd | ||
|
|
f064ff35f3 | ||
|
|
55b44b41bf | ||
|
|
1f3e9b22f4 |
@@ -10,3 +10,9 @@ cdaa0acd61d3001407609915bd573b78565d5571
|
||||
|
||||
# prettier write again
|
||||
dfac7395fed95fc5d8ebca21b786ce70821942bb
|
||||
|
||||
# lint and format plugin-cloud
|
||||
fb7d1be2f3325d076b7c967b1730afcef37922c2
|
||||
|
||||
# lint and format create-payload-app
|
||||
5fd3d430001efe86515262ded5e26f00c1451181
|
||||
|
||||
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -2,7 +2,7 @@
|
||||
|
||||
<!-- Please include a summary of the pull request and any related issues it fixes. Please also include relevant motivation and context. -->
|
||||
|
||||
- [ ] I have read and understand the [CONTRIBUTING.md](../CONTRIBUTING.md) document in this repository.
|
||||
- [ ] I have read and understand the [CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md) document in this repository.
|
||||
|
||||
## Type of change
|
||||
|
||||
|
||||
36
.github/workflows/main.yml
vendored
36
.github/workflows/main.yml
vendored
@@ -170,7 +170,6 @@ jobs:
|
||||
run: pnpm dev:generate-graphql-schema graphql-schema-gen
|
||||
|
||||
build-packages:
|
||||
name: Build Packages
|
||||
runs-on: ubuntu-latest
|
||||
needs: core-build
|
||||
strategy:
|
||||
@@ -206,3 +205,38 @@ jobs:
|
||||
|
||||
- name: Build ${{ matrix.pkg }}
|
||||
run: pnpm turbo run build --filter=${{ matrix.pkg }}
|
||||
|
||||
plugins:
|
||||
runs-on: ubuntu-latest
|
||||
needs: core-build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pkg:
|
||||
- plugin-cloud
|
||||
- create-payload-app
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Build ${{ matrix.pkg }}
|
||||
run: pnpm turbo run build --filter=${{ matrix.pkg }}
|
||||
|
||||
- name: Test ${{ matrix.pkg }}
|
||||
run: pnpm --filter ${{ matrix.pkg }} run test
|
||||
if: matrix.pkg != 'create-payload-app' # degit doesn't work within GitHub Actions
|
||||
|
||||
@@ -121,6 +121,12 @@ This means that in some fringe cases, if you are creating a doc and then instant
|
||||
|
||||
To avoid any issues, you can pass the `req.transactionID` through to your Local API calls, so that your Local API calls are included as part of the parent transaction.
|
||||
|
||||
### ⚠️ Locales now have more functionality, and in some places, you might need to update custom code
|
||||
|
||||
Payload's locales have become more powerful and now allow you to customize more aspects per locale such as a human-friendly label and if the locale is RTL or not.
|
||||
|
||||
This means that certain functions now return a different shape, such as `useLocale`. This hook used to return a string of the locale code you are currently editing, but it now returns an object with type of `Locale`.
|
||||
|
||||
### ⚠️ Admin panel CSS classes may have changed
|
||||
|
||||
The revisions we've made in 2.0 required changes to both HTML and CSS within the admin panel. For this reason, if you were loading custom CSS into the admin panel to customize the look and feel, your stylesheets may need to be updated. If your CSS is targeting elements on the page using HTML selectors or class names, you may need to update these selectors based on the current markup. It may also be necessary to update your style definitions if the core Payload component you are targeting has undergone significant change.
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
</h4>
|
||||
<hr/>
|
||||
|
||||
### 🎉 Payload 2.0 is now available! Read more in the [announcement post](https://payloadcms.com/blog/payload-2-0).
|
||||
> [!IMPORTANT]
|
||||
> 🎉 <strong>Payload 2.0 is now available!<strong> Read more in the <a target="_blank" href="https://payloadcms.com/blog/payload-2-0" rel="dofollow"><strong>announcement post</strong></a>.
|
||||
|
||||
<h3>Benefits over a regular CMS</h3>
|
||||
<ul>
|
||||
|
||||
@@ -28,25 +28,25 @@ When bundled, it is code-split, highly performant (even with 100+ fields), and w
|
||||
|
||||
All options for the Admin panel are defined in your base Payload config file.
|
||||
|
||||
| Option | Description |
|
||||
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `user` | The `slug` of a Collection that you want be used to log in to the Admin dashboard. [More](/docs/admin/overview#the-admin-user-collection) |
|
||||
| `buildPath` | Specify an absolute path for where to store the built Admin panel bundle used in production. Defaults to `path.resolve(process.cwd(), 'build')`. |
|
||||
| `meta` | Base meta data to use for the Admin panel. Included properties are `titleSuffix`, `ogImage`, and `favicon`. |
|
||||
| `disable` | If set to `true`, the entire Admin panel will be disabled. |
|
||||
| `indexHTML` | Optionally replace the entirety of the `index.html` file used by the Admin panel. Reference the [base index.html file](https://github.com/payloadcms/payload/blob/main/packages/payload/src/admin/index.html) to ensure your replacement has the appropriate HTML elements. |
|
||||
| `css` | Absolute path to a stylesheet that you can use to override / customize the Admin panel styling. [More](/docs/admin/customizing-css). |
|
||||
| `scss` | Absolute path to a Sass variables / mixins stylesheet meant to override Payload styles to make for an easy re-skinning of the Admin panel. [More](/docs/admin/customizing-css#overriding-scss-variables). |
|
||||
| `dateFormat` | Global date format that will be used for all dates in the Admin panel. Any valid [date-fns](https://date-fns.org/) format pattern can be used. |
|
||||
| `avatar` | Set account profile picture. Options: `gravatar`, `default` or a custom React component. |
|
||||
| `autoLogin` | Used to automate admin log-in for dev and demonstration convenience. [More](/docs/authentication/config). |
|
||||
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More](/docs/live-preview/overview). |
|
||||
| `components` | Component overrides that affect the entirety of the Admin panel. [More](/docs/admin/components) |
|
||||
| `webpack` | Customize the Webpack config that's used to generate the Admin panel. [More](/docs/admin/webpack) |
|
||||
| `vite` | Customize the Vite config that's used to generate the Admin panel. [More](/docs/admin/vite) |
|
||||
| **`bundler`** | The bundler that you would like to use to bundle the admin panel. Officially supported bundlers: [Webpack](/docs/admin/webpack) and [Vite](/docs/admin/vite). |
|
||||
| **`logoutRoute`** | The route for the `logout` page. |
|
||||
| **`inactivityRoute`** | The route for the `logout` inactivity page. |
|
||||
| Option | Description |
|
||||
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `bundler` | The bundler that you would like to use to bundle the admin panel. Officially supported bundlers: [Webpack](/docs/admin/webpack) and [Vite](/docs/admin/vite). |
|
||||
| `user` | The `slug` of a Collection that you want be used to log in to the Admin dashboard. [More](/docs/admin/overview#the-admin-user-collection) |
|
||||
| `buildPath` | Specify an absolute path for where to store the built Admin panel bundle used in production. Defaults to `path.resolve(process.cwd(), 'build')`. |
|
||||
| `meta` | Base meta data to use for the Admin panel. Included properties are `titleSuffix`, `ogImage`, and `favicon`. |
|
||||
| `disable` | If set to `true`, the entire Admin panel will be disabled. |
|
||||
| `indexHTML` | Optionally replace the entirety of the `index.html` file used by the Admin panel. Reference the [base index.html file](https://github.com/payloadcms/payload/blob/main/packages/payload/src/admin/index.html) to ensure your replacement has the appropriate HTML elements. |
|
||||
| `css` | Absolute path to a stylesheet that you can use to override / customize the Admin panel styling. [More](/docs/admin/customizing-css). |
|
||||
| `scss` | Absolute path to a Sass variables / mixins stylesheet meant to override Payload styles to make for an easy re-skinning of the Admin panel. [More](/docs/admin/customizing-css#overriding-scss-variables). |
|
||||
| `dateFormat` | Global date format that will be used for all dates in the Admin panel. Any valid [date-fns](https://date-fns.org/) format pattern can be used. |
|
||||
| `avatar` | Set account profile picture. Options: `gravatar`, `default` or a custom React component. |
|
||||
| `autoLogin` | Used to automate admin log-in for dev and demonstration convenience. [More](/docs/authentication/config). |
|
||||
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More](/docs/live-preview/overview). |
|
||||
| `components` | Component overrides that affect the entirety of the Admin panel. [More](/docs/admin/components) |
|
||||
| `webpack` | Customize the Webpack config that's used to generate the Admin panel. [More](/docs/admin/webpack) |
|
||||
| `vite` | Customize the Vite config that's used to generate the Admin panel. [More](/docs/admin/vite) |
|
||||
| `logoutRoute` | The route for the `logout` page. |
|
||||
| `inactivityRoute` | The route for the `logout` inactivity page. |
|
||||
|
||||
### The Admin User Collection
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ After a user logs in, they can change their language selection in the `/account`
|
||||
<strong>Note:</strong>
|
||||
<br />
|
||||
If there is a language that Payload does not yet support, we accept code
|
||||
[contributions](https://github.com/payloadcms/payload/blob/main/contributing.md).
|
||||
[contributions](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md).
|
||||
</Banner>
|
||||
|
||||
### Node Express
|
||||
|
||||
@@ -19,48 +19,53 @@ Payload is a _config-based_, code-first CMS and application framework. The Paylo
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Description |
|
||||
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `serverURL` | A string used to define the absolute URL of your app including the protocol, for example `https://example.com`. No paths allowed, only protocol, domain and (optionally) port |
|
||||
| `collections` | An array of all Collections that Payload will manage. To read more about how to define your collection configs, [click here](/docs/configuration/collections). |
|
||||
| `cors` | Either a whitelist array of URLS to allow CORS requests from, or a wildcard string (`'*'`) to accept incoming requests from any domain. |
|
||||
| `globals` | An array of all Globals that Payload will manage. For more on Globals and their configs, [click here](/docs/configuration/globals). |
|
||||
| `admin` | Base Payload admin configuration. Specify custom components, control metadata, set the Admin user collection, and [more](/docs/admin/overview#admin-options). |
|
||||
| `editor` | Default richText editor which will be used by richText fields. |
|
||||
| `localization` | Opt-in and control how Payload handles the translation of your content into multiple locales. [More](/docs/configuration/localization) |
|
||||
| `graphQL` | Manage GraphQL-specific functionality here. Define your own queries and mutations, manage query complexity limits, and [more](/docs/graphql/overview#graphql-options). |
|
||||
| `cookiePrefix` | A string that will be prefixed to all cookies that Payload sets. |
|
||||
| `csrf` | A whitelist array of URLs to allow Payload cookies to be accepted from as a form of CSRF protection. [More](/docs/authentication/overview#csrf-protection) |
|
||||
| `defaultDepth` | If a user does not specify `depth` while requesting a resource, this depth will be used. [More](/docs/getting-started/concepts#depth) |
|
||||
| `maxDepth` | The maximum allowed depth to be permitted application-wide. This setting helps prevent against malicious queries. Defaults to `10`. |
|
||||
| `indexSortableFields` | Automatically index all sortable top-level fields in the database to improve sort performance and add database compatibility for Azure Cosmos and similar. |
|
||||
| `upload` | Base Payload upload configuration. [More](/docs/upload/overview#payload-wide-upload-options). |
|
||||
| `routes` | Control the routing structure that Payload binds itself to. Specify `admin`, `api`, `graphQL`, and `graphQLPlayground`. |
|
||||
| `email` | Base email settings to allow Payload to generate email such as Forgot Password requests and other requirements. [More](/docs/email/overview#configuration) |
|
||||
| `express` | Express-specific middleware options such as compression and JSON parsing. [More](/docs/configuration/express) |
|
||||
| `debug` | Enable to expose more detailed error information. |
|
||||
| `telemetry` | Disable Payload telemetry by passing `false`. [More](/docs/configuration/overview#telemetry) |
|
||||
| `rateLimit` | Control IP-based rate limiting for all Payload resources. Used to prevent DDoS attacks and [more](/docs/production/preventing-abuse#rate-limiting-requests). |
|
||||
| `hooks` | Tap into Payload-wide hooks. [More](/docs/hooks/overview) |
|
||||
| `plugins` | An array of Payload plugins. [More](/docs/plugins/overview) |
|
||||
| `endpoints` | An array of custom API endpoints added to the Payload router. [More](/docs/rest-api/overview#custom-endpoints) |
|
||||
| `custom` | Extension point for adding custom data (e.g. for plugins) |
|
||||
| Option | Description |
|
||||
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `admin` \* | Base Payload admin configuration. Specify bundler*, custom components, control metadata, set the Admin user collection, and [more](/docs/admin/overview#admin-options). Required. |
|
||||
| `editor` \* | Rich Text Editor which will be used by richText fields. Required. |
|
||||
| `db` \* | Database Adapter which will be used by Payload. Read more [here](/docs/database/overview). Required. |
|
||||
| `serverURL` | A string used to define the absolute URL of your app including the protocol, for example `https://example.com`. No paths allowed, only protocol, domain and (optionally) port |
|
||||
| `collections` | An array of all Collections that Payload will manage. To read more about how to define your collection configs, [click here](/docs/configuration/collections). |
|
||||
| `globals` | An array of all Globals that Payload will manage. For more on Globals and their configs, [click here](/docs/configuration/globals). |
|
||||
| `cors` | Either a whitelist array of URLS to allow CORS requests from, or a wildcard string (`'*'`) to accept incoming requests from any domain. |
|
||||
| `localization` | Opt-in and control how Payload handles the translation of your content into multiple locales. [More](/docs/configuration/localization) |
|
||||
| `graphQL` | Manage GraphQL-specific functionality here. Define your own queries and mutations, manage query complexity limits, and [more](/docs/graphql/overview#graphql-options). |
|
||||
| `cookiePrefix` | A string that will be prefixed to all cookies that Payload sets. |
|
||||
| `csrf` | A whitelist array of URLs to allow Payload cookies to be accepted from as a form of CSRF protection. [More](/docs/authentication/overview#csrf-protection) |
|
||||
| `defaultDepth` | If a user does not specify `depth` while requesting a resource, this depth will be used. [More](/docs/getting-started/concepts#depth) |
|
||||
| `maxDepth` | The maximum allowed depth to be permitted application-wide. This setting helps prevent against malicious queries. Defaults to `10`. |
|
||||
| `indexSortableFields` | Automatically index all sortable top-level fields in the database to improve sort performance and add database compatibility for Azure Cosmos and similar. |
|
||||
| `upload` | Base Payload upload configuration. [More](/docs/upload/overview#payload-wide-upload-options). |
|
||||
| `routes` | Control the routing structure that Payload binds itself to. Specify `admin`, `api`, `graphQL`, and `graphQLPlayground`. |
|
||||
| `email` | Base email settings to allow Payload to generate email such as Forgot Password requests and other requirements. [More](/docs/email/overview#configuration) |
|
||||
| `express` | Express-specific middleware options such as compression and JSON parsing. [More](/docs/configuration/express) |
|
||||
| `debug` | Enable to expose more detailed error information. |
|
||||
| `telemetry` | Disable Payload telemetry by passing `false`. [More](/docs/configuration/overview#telemetry) |
|
||||
| `rateLimit` | Control IP-based rate limiting for all Payload resources. Used to prevent DDoS attacks and [more](/docs/production/preventing-abuse#rate-limiting-requests). |
|
||||
| `hooks` | Tap into Payload-wide hooks. [More](/docs/hooks/overview) |
|
||||
| `plugins` | An array of Payload plugins. [More](/docs/plugins/overview) |
|
||||
| `endpoints` | An array of custom API endpoints added to the Payload router. [More](/docs/rest-api/overview#custom-endpoints) |
|
||||
| `custom` | Extension point for adding custom data (e.g. for plugins) |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
#### Simple example
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload/config'
|
||||
import { mongooseAdapter } from '@payloadcms/db-mongodb'
|
||||
import { postgresAdapter } from '@payloadcms/db-postgres'
|
||||
import { postgresAdapter } from '@payloadcms/db-postgres' // beta
|
||||
|
||||
import { viteBundler } from '@payloadcms/bundler-vite'
|
||||
import { webpackBundler } from '@payloadcms/bundler-webpack'
|
||||
|
||||
import { lexicalEditor } from '@payloadcms/richtext-lexical'
|
||||
import { lexicalEditor } from '@payloadcms/richtext-lexical' // beta
|
||||
import { slateEditor } from '@payloadcms/richtext-slate'
|
||||
|
||||
export default buildConfig({
|
||||
bundler: webpackBundler() // or viteBundler(),
|
||||
admin: {
|
||||
bundler: webpackBundler(), // or viteBundler()
|
||||
},
|
||||
db: mongooseAdapter({}) // or postgresAdapter({}),
|
||||
editor: lexicalEditor({}) // or slateEditor({})
|
||||
collections: [
|
||||
|
||||
@@ -8,7 +8,7 @@ keywords: headless cms, typescript, documentation, Content Management System, cm
|
||||
|
||||
When working with GraphQL it is useful to have the schema for development of other projects that need to call on your GraphQL endpoint. In Payload the schema is controlled by your collections and globals and is made available to the developer or third parties, it is not necessary for developers using Payload to write schema types manually.
|
||||
|
||||
### Schema generatation script
|
||||
### Schema generation script
|
||||
|
||||
Run the following command in a Payload project to generate your project's GraphQL schema from Payload:
|
||||
|
||||
|
||||
@@ -88,13 +88,14 @@ This package provides the following functions:
|
||||
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`subscribe`** | Subscribes to the Admin panel's `window.postMessage` events and calls the provided callback function. |
|
||||
| **`unsubscribe`** | Unsubscribes from the Admin panel's `window.postMessage` events. |
|
||||
| **`ready`** | Sends a `window.postMessage` event to the Admin panel to indicate that the front-end is ready to receive messages. |
|
||||
|
||||
The `subscribe` function takes the following args:
|
||||
|
||||
| Path | Description |
|
||||
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`callback`** \* | A callback function that is called with `data` every time a change is made to the document. |
|
||||
| **`serverURL`** \* | The URL of your Payload server. git s |
|
||||
| **`serverURL`** \* | The URL of your Payload server. |
|
||||
| **`initialData`** | The initial data of the document. The live data will be merged in as changes are made. |
|
||||
| **`depth`** | The depth of the relationships to fetch. Defaults to `0`. |
|
||||
|
||||
@@ -103,18 +104,23 @@ With these functions, you can build your own hook using your front-end framework
|
||||
```tsx
|
||||
import { subscribe, unsubscribe } from '@payloadcms/live-preview';
|
||||
|
||||
// Build your own hook to subscribe to the live preview events
|
||||
// This function will handle everything for you like
|
||||
// 1. subscribing to `window.postMessage` events
|
||||
// 2. merging initial page data with incoming form state
|
||||
// 3. populating relationships and uploads
|
||||
// To build your own hook, subscribe to Live Preview events using the`subscribe` function
|
||||
// It handles everything from:
|
||||
// 1. Listening to `window.postMessage` events
|
||||
// 2. Merging initial data with active form state
|
||||
// 3. Populating relationships and uploads
|
||||
// 4. Calling the `onChange` callback with the result
|
||||
// Your hook should also:
|
||||
// 1. Tell the Admin panel when it is ready to receive messages
|
||||
// 2. Handle the results of the `onChange` callback to update the UI
|
||||
// 3. Unsubscribe from the `window.postMessage` events when it unmounts
|
||||
```
|
||||
|
||||
Here is an example of what the same `useLivePreview` React hook from above looks like under the hood:
|
||||
|
||||
```tsx
|
||||
import { subscribe, unsubscribe } from '@payloadcms/live-preview'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { subscribe, unsubscribe, ready } from '@payloadcms/live-preview'
|
||||
import { useCallback, useEffect, useState, useRef } from 'react'
|
||||
|
||||
export const useLivePreview = <T extends any>(props: {
|
||||
depth?: number
|
||||
@@ -127,13 +133,18 @@ export const useLivePreview = <T extends any>(props: {
|
||||
const { depth = 0, initialData, serverURL } = props
|
||||
const [data, setData] = useState<T>(initialData)
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true)
|
||||
const hasSentReadyMessage = useRef<boolean>(false)
|
||||
|
||||
const onChange = useCallback((mergedData) => {
|
||||
// When a change is made, the `onChange` callback will be called with the merged data
|
||||
// Set this merged data into state so that React will re-render the UI
|
||||
setData(mergedData)
|
||||
setIsLoading(false)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
// Listen for `window.postMessage` events from the Admin panel
|
||||
// When a change is made, the `onChange` callback will be called with the merged data
|
||||
const subscription = subscribe({
|
||||
callback: onChange,
|
||||
depth,
|
||||
@@ -141,6 +152,17 @@ export const useLivePreview = <T extends any>(props: {
|
||||
serverURL,
|
||||
})
|
||||
|
||||
// Once subscribed, send a `ready` message back up to the Admin panel
|
||||
// This will indicate that the front-end is ready to receive messages
|
||||
if (!hasSentReadyMessage.current) {
|
||||
hasSentReadyMessage.current = true
|
||||
|
||||
ready({
|
||||
serverURL
|
||||
})
|
||||
}
|
||||
|
||||
// When the component unmounts, unsubscribe from the `window.postMessage` events
|
||||
return () => {
|
||||
unsubscribe(subscription)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ keywords: live preview, preview, live, iframe, iframe preview, visual editing, d
|
||||
|
||||
**With Live Preview you can render your front-end application directly within the Admin panel. As you type, your changes take effect in real-time. No need to save a draft or publish your changes.**
|
||||
|
||||
Live Preview works by rendering an iframe on the page that loads your front-end application. The Admin panel communicates with your app through `window.postMessage` events. These events are emitted every time a change is made to the document. Your app then listens for these events and re-renders itself with the data it receives.
|
||||
Live Preview works by rendering an iframe on the page that loads your front-end application. The Admin panel communicates with your app through [`window.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) events. These events are emitted every time a change is made to the document. Your app then listens for these events and re-renders itself with the data it receives.
|
||||
|
||||
{/* IMAGE OF LIVE PREVIEW HERE */}
|
||||
|
||||
@@ -84,8 +84,8 @@ Here is an example of using a function that returns a dynamic URL:
|
||||
documentInfo,
|
||||
locale
|
||||
}) => `${data.tenant.url}${ // Multi-tenant top-level domain
|
||||
documentInfo.slug === 'posts' ? `/posts/${data.slug}` : `/${data.slug}
|
||||
`}?locale=${locale}`, // Localization query param
|
||||
documentInfo.slug === 'posts' ? `/posts/${data.slug}` : `${data.slug !== 'home' : `/${data.slug}` : ''}`
|
||||
}${locale ? `?locale=${locale?.code}` : ''}`, // Localization query param
|
||||
collections: ['pages'],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"ext": "ts",
|
||||
"exec": "ts-node src/server.ts"
|
||||
"exec": "ts-node src/server.ts -- -I",
|
||||
"stdin": false
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"watch": ["server.ts"],
|
||||
"exec": "ts-node --project tsconfig.server.json src/server.ts",
|
||||
"ext": "js ts"
|
||||
"exec": "ts-node --project tsconfig.server.json src/server.ts -- -I",
|
||||
"ext": "js ts",
|
||||
"stdin": false
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"ext": "ts",
|
||||
"exec": "ts-node src/server.ts"
|
||||
"exec": "ts-node src/server.ts -- -I",
|
||||
"stdin": false
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"ext": "ts",
|
||||
"exec": "ts-node src/server.ts"
|
||||
"exec": "ts-node src/server.ts -- -I",
|
||||
"stdin": false
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"ext": "ts",
|
||||
"exec": "ts-node src/server.ts"
|
||||
"exec": "ts-node src/server.ts -- -I",
|
||||
"stdin": false
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ export const Pages: CollectionConfig = {
|
||||
useAsTitle: 'title',
|
||||
defaultColumns: ['title', 'slug', 'updatedAt'],
|
||||
livePreview: {
|
||||
url: ({ data }) => `${process.env.PAYLOAD_PUBLIC_SITE_URL}/${data.slug}`,
|
||||
url: ({ data }) =>
|
||||
`${process.env.PAYLOAD_PUBLIC_SITE_URL}${data.slug !== 'home' ? `/${data.slug}` : ''}`,
|
||||
},
|
||||
},
|
||||
access: {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"ext": "ts",
|
||||
"exec": "ts-node src/server.ts"
|
||||
"exec": "ts-node src/server.ts -- -I",
|
||||
"stdin": false
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"ext": "ts",
|
||||
"exec": "ts-node src/server.ts"
|
||||
"exec": "ts-node src/server.ts -- -I",
|
||||
"stdin": false
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"ext": "ts",
|
||||
"exec": "ts-node src/server.ts"
|
||||
"exec": "ts-node src/server.ts -- -I",
|
||||
"stdin": false
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"ext": "ts",
|
||||
"exec": "ts-node src/server.ts"
|
||||
"exec": "ts-node src/server.ts -- -I",
|
||||
"stdin": false
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"@types/shelljs": "0.8.12",
|
||||
"@types/testing-library__jest-dom": "5.14.8",
|
||||
"chalk": "^5.3.0",
|
||||
"chalk-template": "1.1.0",
|
||||
"copyfiles": "2.4.1",
|
||||
"cross-env": "7.0.3",
|
||||
"dotenv": "8.6.0",
|
||||
@@ -58,7 +59,6 @@
|
||||
"fs-extra": "10.1.0",
|
||||
"get-port": "5.1.1",
|
||||
"glob": "8.1.0",
|
||||
"graphql-request": "6.1.0",
|
||||
"husky": "^8.0.3",
|
||||
"isomorphic-fetch": "3.0.0",
|
||||
"jest": "29.6.4",
|
||||
@@ -74,9 +74,11 @@
|
||||
"qs": "6.11.2",
|
||||
"rimraf": "3.0.2",
|
||||
"shelljs": "0.8.5",
|
||||
"simple-git": "^3.20.0",
|
||||
"slash": "3.0.0",
|
||||
"slate": "0.91.4",
|
||||
"ts-node": "10.9.1",
|
||||
"tsx": "^3.13.0",
|
||||
"turbo": "^1.10.15",
|
||||
"typescript": "5.2.2",
|
||||
"uuid": "^9.0.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/bundler-webpack",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"description": "The officially supported Webpack bundler adapter for Payload",
|
||||
"repository": "https://github.com/payloadcms/payload",
|
||||
"license": "MIT",
|
||||
@@ -25,6 +25,7 @@
|
||||
"css-loader": "5.2.7",
|
||||
"css-minimizer-webpack-plugin": "^5.0.0",
|
||||
"file-loader": "6.2.0",
|
||||
"find-node-modules": "^2.1.3",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"md5": "2.3.0",
|
||||
"mini-css-extract-plugin": "1.6.2",
|
||||
@@ -48,6 +49,7 @@
|
||||
"devDependencies": {
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@types/extract-text-webpack-plugin": "^3.0.7",
|
||||
"@types/find-node-modules": "^2.1.0",
|
||||
"@types/html-webpack-plugin": "^3.2.6",
|
||||
"@types/mini-css-extract-plugin": "^1.4.3",
|
||||
"@types/optimize-css-assets-webpack-plugin": "^5.0.5",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { SanitizedConfig } from 'payload/config'
|
||||
import type { Configuration } from 'webpack'
|
||||
|
||||
import findNodeModules from 'find-node-modules'
|
||||
import HtmlWebpackPlugin from 'html-webpack-plugin'
|
||||
import path from 'path'
|
||||
import webpack from 'webpack'
|
||||
@@ -8,7 +9,8 @@ import webpack from 'webpack'
|
||||
const mockModulePath = path.resolve(__dirname, '../mocks/emptyModule.js')
|
||||
const mockDotENVPath = path.resolve(__dirname, '../mocks/dotENV.js')
|
||||
|
||||
const nodeModulesPath = path.resolve(__dirname, '../../../../')
|
||||
const nodeModulesPaths = findNodeModules({ cwd: process.cwd() })
|
||||
const nodeModulesPath = path.resolve(nodeModulesPaths[0])
|
||||
const adminFolderPath = path.resolve(nodeModulesPath, 'payload/dist/admin')
|
||||
|
||||
export const getBaseConfig = (payloadConfig: SanitizedConfig): Configuration => ({
|
||||
|
||||
@@ -23,7 +23,9 @@ export const getDevConfig = (payloadConfig: SanitizedConfig): Configuration => {
|
||||
entry: {
|
||||
...baseConfig.entry,
|
||||
main: [
|
||||
`webpack-hot-middleware/client?path=${payloadConfig.routes.admin}/__webpack_hmr`,
|
||||
`${require.resolve('webpack-hot-middleware/client')}?path=${
|
||||
payloadConfig.routes.admin
|
||||
}/__webpack_hmr`,
|
||||
...(baseConfig.entry.main as string[]),
|
||||
],
|
||||
},
|
||||
|
||||
44
packages/create-payload-app/.eslintrc.js
Normal file
44
packages/create-payload-app/.eslintrc.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/** @type {import('prettier').Config} */
|
||||
module.exports = {
|
||||
extends: ['@payloadcms'],
|
||||
ignorePatterns: ['README.md', '**/*.spec.ts'],
|
||||
overrides: [
|
||||
{
|
||||
extends: ['plugin:@typescript-eslint/disable-type-checked'],
|
||||
files: ['*.js', '*.cjs', '*.json', '*.md', '*.yml', '*.yaml'],
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['package.json', 'tsconfig.json'],
|
||||
rules: {
|
||||
'perfectionist/sort-array-includes': 'off',
|
||||
'perfectionist/sort-astro-attributes': 'off',
|
||||
'perfectionist/sort-classes': 'off',
|
||||
'perfectionist/sort-enums': 'off',
|
||||
'perfectionist/sort-exports': 'off',
|
||||
'perfectionist/sort-imports': 'off',
|
||||
'perfectionist/sort-interfaces': 'off',
|
||||
'perfectionist/sort-jsx-props': 'off',
|
||||
'perfectionist/sort-keys': 'off',
|
||||
'perfectionist/sort-maps': 'off',
|
||||
'perfectionist/sort-named-exports': 'off',
|
||||
'perfectionist/sort-named-imports': 'off',
|
||||
'perfectionist/sort-object-types': 'off',
|
||||
'perfectionist/sort-objects': 'off',
|
||||
'perfectionist/sort-svelte-attributes': 'off',
|
||||
'perfectionist/sort-union-types': 'off',
|
||||
'perfectionist/sort-vue-attributes': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.json'],
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
root: true,
|
||||
}
|
||||
34
packages/create-payload-app/README.md
Normal file
34
packages/create-payload-app/README.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Create Payload App
|
||||
|
||||
CLI for easily starting new Payload project
|
||||
|
||||
## Usage
|
||||
|
||||
```text
|
||||
|
||||
USAGE
|
||||
|
||||
$ npx create-payload-app
|
||||
$ npx create-payload-app my-project
|
||||
$ npx create-payload-app -n my-project -t blog
|
||||
|
||||
OPTIONS
|
||||
|
||||
-n my-payload-app Set project name
|
||||
-t template_name Choose specific template
|
||||
|
||||
Available templates:
|
||||
|
||||
blank Blank Template
|
||||
website Website Template
|
||||
ecommerce E-commerce Template
|
||||
plugin Template for creating a Payload plugin
|
||||
payload-demo Payload demo site at https://demo.payloadcms.com
|
||||
payload-website Payload website CMS at https://payloadcms.com
|
||||
|
||||
--use-npm Use npm to install dependencies
|
||||
--use-yarn Use yarn to install dependencies
|
||||
--use-pnpm Use pnpm to install dependencies
|
||||
--no-deps Do not install any dependencies
|
||||
-h Show help
|
||||
```
|
||||
2
packages/create-payload-app/bin/cli.js
Executable file
2
packages/create-payload-app/bin/cli.js
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env node
|
||||
require('../dist/index.js')
|
||||
9
packages/create-payload-app/jest.config.js
Normal file
9
packages/create-payload-app/jest.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
module.exports = {
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/src/**/?(*.)+(spec|test|it-test).[tj]s?(x)'],
|
||||
testTimeout: 10000,
|
||||
transform: {
|
||||
'^.+\\.(ts|tsx)?$': 'ts-jest',
|
||||
},
|
||||
verbose: true,
|
||||
}
|
||||
47
packages/create-payload-app/package.json
Normal file
47
packages/create-payload-app/package.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "0.5.2",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"create-payload-app": "bin/cli.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc && pnpm copyfiles",
|
||||
"copyfiles": "copyfiles -u 1 \"src/templates/**\" \"src/lib/common-files/**\" dist",
|
||||
"clean": "rimraf dist",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "eslint \"src/**/*.ts\"",
|
||||
"lint:fix": "eslint \"src/**/*.ts\" --fix",
|
||||
"lint-staged": "lint-staged --quiet",
|
||||
"test": "jest",
|
||||
"prepublishOnly": "pnpm test && pnpm clean && pnpm build"
|
||||
},
|
||||
"files": [
|
||||
"package.json",
|
||||
"dist",
|
||||
"bin"
|
||||
],
|
||||
"dependencies": {
|
||||
"@sindresorhus/slugify": "^1.1.0",
|
||||
"arg": "^5.0.0",
|
||||
"chalk": "^4.1.0",
|
||||
"command-exists": "^1.2.9",
|
||||
"degit": "^2.8.4",
|
||||
"execa": "^5.0.0",
|
||||
"figures": "^3.2.0",
|
||||
"fs-extra": "^9.0.1",
|
||||
"handlebars": "^4.7.7",
|
||||
"ora": "^5.1.0",
|
||||
"prompts": "^2.4.2",
|
||||
"terminal-link": "^2.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/command-exists": "^1.2.0",
|
||||
"@types/degit": "^2.8.3",
|
||||
"@types/fs-extra": "^9.0.12",
|
||||
"@types/jest": "^27.0.3",
|
||||
"@types/node": "^16.6.2",
|
||||
"@types/prompts": "^2.4.1",
|
||||
"ts-jest": "^29.1.0"
|
||||
}
|
||||
}
|
||||
8
packages/create-payload-app/src/index.ts
Normal file
8
packages/create-payload-app/src/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Main } from './main'
|
||||
import { error } from './utils/log'
|
||||
|
||||
async function main(): Promise<void> {
|
||||
await new Main().init()
|
||||
}
|
||||
|
||||
main().catch((e) => error(`An error has occurred: ${e instanceof Error ? e.message : e}`))
|
||||
117
packages/create-payload-app/src/lib/configure-payload-config.ts
Normal file
117
packages/create-payload-app/src/lib/configure-payload-config.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import fse from 'fs-extra'
|
||||
import path from 'path'
|
||||
|
||||
import type { DbDetails } from '../types'
|
||||
|
||||
import { warning } from '../utils/log'
|
||||
import { bundlerPackages, dbPackages, editorPackages } from './packages'
|
||||
|
||||
/** Update payload config with necessary imports and adapters */
|
||||
export async function configurePayloadConfig(args: {
|
||||
dbDetails: DbDetails | undefined
|
||||
projectDir: string
|
||||
}): Promise<void> {
|
||||
if (!args.dbDetails) {
|
||||
return
|
||||
}
|
||||
|
||||
// Update package.json
|
||||
const packageJsonPath = path.resolve(args.projectDir, 'package.json')
|
||||
try {
|
||||
const packageObj = await fse.readJson(packageJsonPath)
|
||||
|
||||
packageObj.dependencies['payload'] = '^2.0.0'
|
||||
|
||||
const dbPackage = dbPackages[args.dbDetails.type]
|
||||
const bundlerPackage = bundlerPackages['webpack']
|
||||
const editorPackage = editorPackages['slate']
|
||||
|
||||
// Delete all other db adapters
|
||||
Object.values(dbPackages).forEach((p) => {
|
||||
if (p.packageName !== dbPackage.packageName) {
|
||||
delete packageObj.dependencies[p.packageName]
|
||||
}
|
||||
})
|
||||
|
||||
packageObj.dependencies[dbPackage.packageName] = dbPackage.version
|
||||
packageObj.dependencies[bundlerPackage.packageName] = bundlerPackage.version
|
||||
packageObj.dependencies[editorPackage.packageName] = editorPackage.version
|
||||
|
||||
await fse.writeJson(packageJsonPath, packageObj, { spaces: 2 })
|
||||
} catch (err: unknown) {
|
||||
warning('Unable to update name in package.json')
|
||||
}
|
||||
|
||||
try {
|
||||
const possiblePaths = [
|
||||
path.resolve(args.projectDir, 'src/payload.config.ts'),
|
||||
path.resolve(args.projectDir, 'src/payload/payload.config.ts'),
|
||||
]
|
||||
|
||||
let payloadConfigPath: string | undefined
|
||||
|
||||
possiblePaths.forEach((p) => {
|
||||
if (fse.pathExistsSync(p) && !payloadConfigPath) {
|
||||
payloadConfigPath = p
|
||||
}
|
||||
})
|
||||
|
||||
if (!payloadConfigPath) {
|
||||
warning('Unable to update payload.config.ts with plugins')
|
||||
return
|
||||
}
|
||||
|
||||
const configContent = fse.readFileSync(payloadConfigPath, 'utf-8')
|
||||
const configLines = configContent.split('\n')
|
||||
|
||||
const dbReplacement = dbPackages[args.dbDetails.type]
|
||||
const bundlerReplacement = bundlerPackages['webpack']
|
||||
const editorReplacement = editorPackages['slate']
|
||||
|
||||
let dbConfigStartLineIndex: number | undefined
|
||||
let dbConfigEndLineIndex: number | undefined
|
||||
|
||||
configLines.forEach((l, i) => {
|
||||
if (l.includes('// database-adapter-import')) {
|
||||
configLines[i] = dbReplacement.importReplacement
|
||||
}
|
||||
if (l.includes('// bundler-import')) {
|
||||
configLines[i] = bundlerReplacement.importReplacement
|
||||
}
|
||||
|
||||
if (l.includes('// bundler-config')) {
|
||||
configLines[i] = bundlerReplacement.configReplacement
|
||||
}
|
||||
|
||||
if (l.includes('// editor-import')) {
|
||||
configLines[i] = editorReplacement.importReplacement
|
||||
}
|
||||
|
||||
if (l.includes('// editor-config')) {
|
||||
configLines[i] = editorReplacement.configReplacement
|
||||
}
|
||||
|
||||
if (l.includes('// database-adapter-config-start')) {
|
||||
dbConfigStartLineIndex = i
|
||||
}
|
||||
if (l.includes('// database-adapter-config-end')) {
|
||||
dbConfigEndLineIndex = i
|
||||
}
|
||||
})
|
||||
|
||||
if (!dbConfigStartLineIndex || !dbConfigEndLineIndex) {
|
||||
warning('Unable to update payload.config.ts with database adapter import')
|
||||
} else {
|
||||
// Replaces lines between `// database-adapter-config-start` and `// database-adapter-config-end`
|
||||
configLines.splice(
|
||||
dbConfigStartLineIndex,
|
||||
dbConfigEndLineIndex - dbConfigStartLineIndex + 1,
|
||||
...dbReplacement.configReplacement,
|
||||
)
|
||||
}
|
||||
|
||||
fse.writeFileSync(payloadConfigPath, configLines.join('\n'))
|
||||
} catch (err: unknown) {
|
||||
warning('Unable to update payload.config.ts with plugins')
|
||||
}
|
||||
}
|
||||
151
packages/create-payload-app/src/lib/create-project.spec.ts
Normal file
151
packages/create-payload-app/src/lib/create-project.spec.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import fse from 'fs-extra'
|
||||
import path from 'path'
|
||||
import type { BundlerType, CliArgs, DbType, ProjectTemplate } from '../types'
|
||||
import { createProject } from './create-project'
|
||||
import { bundlerPackages, dbPackages, editorPackages } from './packages'
|
||||
import exp from 'constants'
|
||||
|
||||
const projectDir = path.resolve(__dirname, './tmp')
|
||||
describe('createProject', () => {
|
||||
beforeAll(() => {
|
||||
console.log = jest.fn()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
if (fse.existsSync(projectDir)) {
|
||||
fse.rmdirSync(projectDir, { recursive: true })
|
||||
}
|
||||
})
|
||||
afterEach(() => {
|
||||
if (fse.existsSync(projectDir)) {
|
||||
fse.rmSync(projectDir, { recursive: true })
|
||||
}
|
||||
})
|
||||
|
||||
describe('#createProject', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const args = {
|
||||
_: ['project-name'],
|
||||
'--db': 'mongodb',
|
||||
'--no-deps': true,
|
||||
} as CliArgs
|
||||
const packageManager = 'yarn'
|
||||
|
||||
it('creates starter project', async () => {
|
||||
const projectName = 'starter-project'
|
||||
const template: ProjectTemplate = {
|
||||
name: 'blank',
|
||||
type: 'starter',
|
||||
url: 'https://github.com/payloadcms/payload/templates/blank',
|
||||
description: 'Blank Template',
|
||||
}
|
||||
await createProject({
|
||||
cliArgs: args,
|
||||
projectName,
|
||||
projectDir,
|
||||
template,
|
||||
packageManager,
|
||||
})
|
||||
|
||||
const packageJsonPath = path.resolve(projectDir, 'package.json')
|
||||
const packageJson = fse.readJsonSync(packageJsonPath)
|
||||
|
||||
// Check package name and description
|
||||
expect(packageJson.name).toEqual(projectName)
|
||||
})
|
||||
|
||||
it('creates plugin template', async () => {
|
||||
const projectName = 'plugin'
|
||||
const template: ProjectTemplate = {
|
||||
name: 'plugin',
|
||||
type: 'plugin',
|
||||
url: 'https://github.com/payloadcms/payload-plugin-template',
|
||||
description: 'Template for creating a Payload plugin',
|
||||
}
|
||||
await createProject({
|
||||
cliArgs: args,
|
||||
projectName,
|
||||
projectDir,
|
||||
template,
|
||||
packageManager,
|
||||
})
|
||||
|
||||
const packageJsonPath = path.resolve(projectDir, 'package.json')
|
||||
const packageJson = fse.readJsonSync(packageJsonPath)
|
||||
|
||||
// Check package name and description
|
||||
expect(packageJson.name).toEqual(projectName)
|
||||
})
|
||||
|
||||
describe('db adapters and bundlers', () => {
|
||||
it.each([
|
||||
['mongodb', 'webpack'],
|
||||
['postgres', 'webpack'],
|
||||
])('update config and deps: %s, %s', async (db, bundler) => {
|
||||
const projectName = 'starter-project'
|
||||
const template: ProjectTemplate = {
|
||||
name: 'blank',
|
||||
type: 'starter',
|
||||
url: 'https://github.com/payloadcms/payload/templates/blank',
|
||||
description: 'Blank Template',
|
||||
}
|
||||
await createProject({
|
||||
cliArgs: args,
|
||||
projectName,
|
||||
projectDir,
|
||||
template,
|
||||
packageManager,
|
||||
dbDetails: {
|
||||
dbUri: `${db}://localhost:27017/create-project-test`,
|
||||
type: db as DbType,
|
||||
},
|
||||
})
|
||||
|
||||
const dbReplacement = dbPackages[db as DbType]
|
||||
const bundlerReplacement = bundlerPackages[bundler as BundlerType]
|
||||
const editorReplacement = editorPackages['slate']
|
||||
|
||||
const packageJsonPath = path.resolve(projectDir, 'package.json')
|
||||
const packageJson = fse.readJsonSync(packageJsonPath)
|
||||
|
||||
// Check deps
|
||||
expect(packageJson.dependencies['payload']).toEqual('^2.0.0')
|
||||
expect(packageJson.dependencies[dbReplacement.packageName]).toEqual(dbReplacement.version)
|
||||
|
||||
// Should only have one db adapter
|
||||
expect(
|
||||
Object.keys(packageJson.dependencies).filter((n) => n.startsWith('@payloadcms/db-')),
|
||||
).toHaveLength(1)
|
||||
|
||||
expect(packageJson.dependencies[bundlerReplacement.packageName]).toEqual(
|
||||
bundlerReplacement.version,
|
||||
)
|
||||
expect(packageJson.dependencies[editorReplacement.packageName]).toEqual(
|
||||
editorReplacement.version,
|
||||
)
|
||||
|
||||
const payloadConfigPath = path.resolve(projectDir, 'src/payload.config.ts')
|
||||
const content = fse.readFileSync(payloadConfigPath, 'utf-8')
|
||||
|
||||
// Check payload.config.ts
|
||||
expect(content).not.toContain('// database-adapter-import')
|
||||
expect(content).toContain(dbReplacement.importReplacement)
|
||||
|
||||
expect(content).not.toContain('// database-adapter-config-start')
|
||||
expect(content).not.toContain('// database-adapter-config-end')
|
||||
expect(content).toContain(dbReplacement.configReplacement.join('\n'))
|
||||
|
||||
expect(content).not.toContain('// bundler-config-import')
|
||||
expect(content).toContain(bundlerReplacement.importReplacement)
|
||||
|
||||
expect(content).not.toContain('// bundler-config')
|
||||
expect(content).toContain(bundlerReplacement.configReplacement)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Templates', () => {
|
||||
it.todo('Verify that all templates are valid')
|
||||
// Loop through all templates.ts that should have replacement comments, and verify that they are present
|
||||
})
|
||||
})
|
||||
102
packages/create-payload-app/src/lib/create-project.ts
Normal file
102
packages/create-payload-app/src/lib/create-project.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import chalk from 'chalk'
|
||||
import degit from 'degit'
|
||||
import execa from 'execa'
|
||||
import fse from 'fs-extra'
|
||||
import ora from 'ora'
|
||||
import path from 'path'
|
||||
|
||||
import type { CliArgs, DbDetails, PackageManager, ProjectTemplate } from '../types'
|
||||
|
||||
import { error, success, warning } from '../utils/log'
|
||||
import { configurePayloadConfig } from './configure-payload-config'
|
||||
|
||||
async function createOrFindProjectDir(projectDir: string): Promise<void> {
|
||||
const pathExists = await fse.pathExists(projectDir)
|
||||
if (!pathExists) {
|
||||
await fse.mkdir(projectDir)
|
||||
}
|
||||
}
|
||||
|
||||
async function installDeps(args: {
|
||||
cliArgs: CliArgs
|
||||
packageManager: PackageManager
|
||||
projectDir: string
|
||||
}): Promise<boolean> {
|
||||
const { cliArgs, packageManager, projectDir } = args
|
||||
if (cliArgs['--no-deps']) {
|
||||
return true
|
||||
}
|
||||
let installCmd = 'npm install --legacy-peer-deps'
|
||||
|
||||
if (packageManager === 'yarn') {
|
||||
installCmd = 'yarn'
|
||||
} else if (packageManager === 'pnpm') {
|
||||
installCmd = 'pnpm install'
|
||||
}
|
||||
|
||||
try {
|
||||
await execa.command(installCmd, {
|
||||
cwd: path.resolve(projectDir),
|
||||
})
|
||||
return true
|
||||
} catch (err: unknown) {
|
||||
console.log({ err })
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function createProject(args: {
|
||||
cliArgs: CliArgs
|
||||
dbDetails?: DbDetails
|
||||
packageManager: PackageManager
|
||||
projectDir: string
|
||||
projectName: string
|
||||
template: ProjectTemplate
|
||||
}): Promise<void> {
|
||||
const { cliArgs, dbDetails, packageManager, projectDir, projectName, template } = args
|
||||
|
||||
await createOrFindProjectDir(projectDir)
|
||||
|
||||
console.log(`\n Creating project in ${chalk.green(path.resolve(projectDir))}\n`)
|
||||
|
||||
if ('url' in template) {
|
||||
const emitter = degit(template.url)
|
||||
await emitter.clone(projectDir)
|
||||
}
|
||||
|
||||
const spinner = ora('Checking latest Payload version...').start()
|
||||
|
||||
await updatePackageJSON({ projectDir, projectName })
|
||||
await configurePayloadConfig({ dbDetails, projectDir })
|
||||
|
||||
// Remove yarn.lock file. This is only desired in Payload Cloud.
|
||||
const lockPath = path.resolve(projectDir, 'yarn.lock')
|
||||
if (fse.existsSync(lockPath)) {
|
||||
await fse.remove(lockPath)
|
||||
}
|
||||
|
||||
spinner.text = 'Installing dependencies...'
|
||||
const result = await installDeps({ cliArgs, packageManager, projectDir })
|
||||
spinner.stop()
|
||||
spinner.clear()
|
||||
if (result) {
|
||||
success('Dependencies installed')
|
||||
} else {
|
||||
error('Error installing dependencies')
|
||||
}
|
||||
}
|
||||
|
||||
export async function updatePackageJSON(args: {
|
||||
projectDir: string
|
||||
projectName: string
|
||||
}): Promise<void> {
|
||||
const { projectDir, projectName } = args
|
||||
const packageJsonPath = path.resolve(projectDir, 'package.json')
|
||||
try {
|
||||
const packageObj = await fse.readJson(packageJsonPath)
|
||||
packageObj.name = projectName
|
||||
await fse.writeJson(packageJsonPath, packageObj, { spaces: 2 })
|
||||
} catch (err: unknown) {
|
||||
warning('Unable to update name in package.json')
|
||||
}
|
||||
}
|
||||
5
packages/create-payload-app/src/lib/generate-secret.ts
Normal file
5
packages/create-payload-app/src/lib/generate-secret.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { randomBytes } from 'crypto'
|
||||
|
||||
export function generateSecret(): string {
|
||||
return randomBytes(32).toString('hex').slice(0, 24)
|
||||
}
|
||||
83
packages/create-payload-app/src/lib/packages.ts
Normal file
83
packages/create-payload-app/src/lib/packages.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import type { BundlerType, DbType, EditorType } from '../types'
|
||||
|
||||
type DbAdapterReplacement = {
|
||||
configReplacement: string[]
|
||||
importReplacement: string
|
||||
packageName: string
|
||||
version: string
|
||||
}
|
||||
|
||||
type BundlerReplacement = {
|
||||
configReplacement: string
|
||||
importReplacement: string
|
||||
packageName: string
|
||||
version: string
|
||||
}
|
||||
|
||||
type EditorReplacement = {
|
||||
configReplacement: string
|
||||
importReplacement: string
|
||||
packageName: string
|
||||
version: string
|
||||
}
|
||||
|
||||
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,', ' }),'],
|
||||
version: '^1.0.0',
|
||||
}
|
||||
|
||||
const postgresReplacement: DbAdapterReplacement = {
|
||||
configReplacement: [
|
||||
' db: postgresAdapter({',
|
||||
' pool: {',
|
||||
' connectionString: process.env.DATABASE_URI,',
|
||||
' },',
|
||||
' }),',
|
||||
],
|
||||
importReplacement: "import { postgresAdapter } from '@payloadcms/db-postgres'",
|
||||
packageName: '@payloadcms/db-postgres',
|
||||
version: '^0.x', // up to, not including 1.0.0
|
||||
}
|
||||
|
||||
export const dbPackages: Record<DbType, DbAdapterReplacement> = {
|
||||
mongodb: mongodbReplacement,
|
||||
postgres: postgresReplacement,
|
||||
}
|
||||
|
||||
const webpackReplacement: BundlerReplacement = {
|
||||
importReplacement: "import { webpackBundler } from '@payloadcms/bundler-webpack'",
|
||||
packageName: '@payloadcms/bundler-webpack',
|
||||
// Replacement of line containing `// bundler-config`
|
||||
configReplacement: ' bundler: webpackBundler(),',
|
||||
version: '^1.0.0',
|
||||
}
|
||||
|
||||
const viteReplacement: BundlerReplacement = {
|
||||
configReplacement: ' bundler: viteBundler(),',
|
||||
importReplacement: "import { viteBundler } from '@payloadcms/bundler-vite'",
|
||||
packageName: '@payloadcms/bundler-vite',
|
||||
version: '^0.x', // up to, not including 1.0.0
|
||||
}
|
||||
|
||||
export const bundlerPackages: Record<BundlerType, BundlerReplacement> = {
|
||||
vite: viteReplacement,
|
||||
webpack: webpackReplacement,
|
||||
}
|
||||
|
||||
export const editorPackages: Record<EditorType, EditorReplacement> = {
|
||||
lexical: {
|
||||
configReplacement: ' editor: lexicalEditor({}),',
|
||||
importReplacement: "import { lexicalEditor } from '@payloadcms/richtext-lexical'",
|
||||
packageName: '@payloadcms/richtext-lexical',
|
||||
version: '^0.x', // up to, not including 1.0.0
|
||||
},
|
||||
slate: {
|
||||
configReplacement: ' editor: slateEditor({}),',
|
||||
importReplacement: "import { slateEditor } from '@payloadcms/richtext-slate'",
|
||||
packageName: '@payloadcms/richtext-slate',
|
||||
version: '^1.0.0',
|
||||
},
|
||||
}
|
||||
24
packages/create-payload-app/src/lib/parse-project-name.ts
Normal file
24
packages/create-payload-app/src/lib/parse-project-name.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import prompts from 'prompts'
|
||||
|
||||
import type { CliArgs } from '../types'
|
||||
|
||||
export async function parseProjectName(args: CliArgs): Promise<string> {
|
||||
if (args['--name']) return args['--name']
|
||||
if (args._[0]) return args._[0]
|
||||
|
||||
const response = await prompts(
|
||||
{
|
||||
name: 'value',
|
||||
message: 'Project name?',
|
||||
type: 'text',
|
||||
validate: (value: string) => !!value.length,
|
||||
},
|
||||
{
|
||||
onCancel: () => {
|
||||
process.exit(0)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
return response.value
|
||||
}
|
||||
41
packages/create-payload-app/src/lib/parse-template.ts
Normal file
41
packages/create-payload-app/src/lib/parse-template.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import prompts from 'prompts'
|
||||
|
||||
import type { CliArgs, ProjectTemplate } from '../types'
|
||||
|
||||
export async function parseTemplate(
|
||||
args: CliArgs,
|
||||
validTemplates: ProjectTemplate[],
|
||||
): Promise<ProjectTemplate> {
|
||||
if (args['--template']) {
|
||||
const templateName = args['--template']
|
||||
const template = validTemplates.find((t) => t.name === templateName)
|
||||
if (!template) throw new Error('Invalid template given')
|
||||
return template
|
||||
}
|
||||
|
||||
const response = await prompts(
|
||||
{
|
||||
name: 'value',
|
||||
choices: validTemplates.map((p) => {
|
||||
return {
|
||||
description: p.description,
|
||||
title: p.name,
|
||||
value: p.name,
|
||||
}
|
||||
}),
|
||||
message: 'Choose project template',
|
||||
type: 'select',
|
||||
validate: (value: string) => !!value.length,
|
||||
},
|
||||
{
|
||||
onCancel: () => {
|
||||
process.exit(0)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
const template = validTemplates.find((t) => t.name === response.value)
|
||||
if (!template) throw new Error('Template is undefined')
|
||||
|
||||
return template
|
||||
}
|
||||
86
packages/create-payload-app/src/lib/select-db.ts
Normal file
86
packages/create-payload-app/src/lib/select-db.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import slugify from '@sindresorhus/slugify'
|
||||
import prompts from 'prompts'
|
||||
|
||||
import type { CliArgs, DbDetails, DbType } from '../types'
|
||||
|
||||
type DbChoice = {
|
||||
dbConnectionPrefix: `${string}/`
|
||||
title: string
|
||||
value: DbType
|
||||
}
|
||||
|
||||
const dbChoiceRecord: Record<DbType, DbChoice> = {
|
||||
mongodb: {
|
||||
dbConnectionPrefix: 'mongodb://127.0.0.1/',
|
||||
title: 'MongoDB',
|
||||
value: 'mongodb',
|
||||
},
|
||||
postgres: {
|
||||
dbConnectionPrefix: 'postgres://127.0.0.1:5432/',
|
||||
title: 'PostgreSQL (beta)',
|
||||
value: 'postgres',
|
||||
},
|
||||
}
|
||||
|
||||
export async function selectDb(args: CliArgs, projectName: string): Promise<DbDetails> {
|
||||
let dbType: DbType | undefined = undefined
|
||||
if (args['--db']) {
|
||||
if (!Object.values(dbChoiceRecord).some((dbChoice) => dbChoice.value === args['--db'])) {
|
||||
throw new Error(
|
||||
`Invalid database type given. Valid types are: ${Object.values(dbChoiceRecord)
|
||||
.map((dbChoice) => dbChoice.value)
|
||||
.join(', ')}`,
|
||||
)
|
||||
}
|
||||
dbType = args['--db'] as DbType
|
||||
} else {
|
||||
const dbTypeRes = await prompts(
|
||||
{
|
||||
name: 'value',
|
||||
choices: Object.values(dbChoiceRecord).map((dbChoice) => {
|
||||
return {
|
||||
title: dbChoice.title,
|
||||
value: dbChoice.value,
|
||||
}
|
||||
}),
|
||||
message: 'Select a database',
|
||||
type: 'select',
|
||||
validate: (value: string) => !!value.length,
|
||||
},
|
||||
{
|
||||
onCancel: () => {
|
||||
process.exit(0)
|
||||
},
|
||||
},
|
||||
)
|
||||
dbType = dbTypeRes.value
|
||||
}
|
||||
|
||||
const dbChoice = dbChoiceRecord[dbType]
|
||||
|
||||
const dbUriRes = await prompts(
|
||||
{
|
||||
name: 'value',
|
||||
initial: `${dbChoice.dbConnectionPrefix}${
|
||||
projectName === '.' ? `payload-${getRandomDigitSuffix()}` : slugify(projectName)
|
||||
}`,
|
||||
message: `Enter ${dbChoice.title.split(' ')[0]} connection string`, // strip beta from title
|
||||
type: 'text',
|
||||
validate: (value: string) => !!value.length,
|
||||
},
|
||||
{
|
||||
onCancel: () => {
|
||||
process.exit(0)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
return {
|
||||
dbUri: dbUriRes.value,
|
||||
type: dbChoice.value,
|
||||
}
|
||||
}
|
||||
|
||||
function getRandomDigitSuffix(): string {
|
||||
return (Math.random() * Math.pow(10, 6)).toFixed(0)
|
||||
}
|
||||
54
packages/create-payload-app/src/lib/templates.ts
Normal file
54
packages/create-payload-app/src/lib/templates.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import type { ProjectTemplate } from '../types'
|
||||
|
||||
import { error, info } from '../utils/log'
|
||||
|
||||
export function validateTemplate(templateName: string): boolean {
|
||||
const validTemplates = getValidTemplates()
|
||||
if (!validTemplates.map((t) => t.name).includes(templateName)) {
|
||||
error(`'${templateName}' is not a valid template.`)
|
||||
info(`Valid templates: ${validTemplates.map((t) => t.name).join(', ')}`)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export function getValidTemplates(): ProjectTemplate[] {
|
||||
return [
|
||||
{
|
||||
name: 'blank',
|
||||
description: 'Blank Template',
|
||||
type: 'starter',
|
||||
url: 'https://github.com/payloadcms/payload/templates/blank',
|
||||
},
|
||||
{
|
||||
name: 'website',
|
||||
description: 'Website Template',
|
||||
type: 'starter',
|
||||
url: 'https://github.com/payloadcms/payload/templates/website',
|
||||
},
|
||||
{
|
||||
name: 'ecommerce',
|
||||
description: 'E-commerce Template',
|
||||
type: 'starter',
|
||||
url: 'https://github.com/payloadcms/payload/templates/ecommerce',
|
||||
},
|
||||
{
|
||||
name: 'plugin',
|
||||
description: 'Template for creating a Payload plugin',
|
||||
type: 'plugin',
|
||||
url: 'https://github.com/payloadcms/payload-plugin-template',
|
||||
},
|
||||
{
|
||||
name: 'payload-demo',
|
||||
description: 'Payload demo site at https://demo.payloadcms.com',
|
||||
type: 'starter',
|
||||
url: 'https://github.com/payloadcms/public-demo',
|
||||
},
|
||||
{
|
||||
name: 'payload-website',
|
||||
description: 'Payload website CMS at https://payloadcms.com',
|
||||
type: 'starter',
|
||||
url: 'https://github.com/payloadcms/website-cms',
|
||||
},
|
||||
]
|
||||
}
|
||||
55
packages/create-payload-app/src/lib/write-env-file.ts
Normal file
55
packages/create-payload-app/src/lib/write-env-file.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
|
||||
import type { ProjectTemplate } from '../types'
|
||||
|
||||
import { error, success } from '../utils/log'
|
||||
|
||||
/** Parse and swap .env.example values and write .env */
|
||||
export async function writeEnvFile(args: {
|
||||
databaseUri: string
|
||||
payloadSecret: string
|
||||
projectDir: string
|
||||
template: ProjectTemplate
|
||||
}): Promise<void> {
|
||||
const { databaseUri, payloadSecret, projectDir, template } = args
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
return `${key}=${value}`
|
||||
})
|
||||
|
||||
// Write new .env file
|
||||
await fs.writeFile(path.join(projectDir, '.env'), envWithValues.join('\n'))
|
||||
} else {
|
||||
const content = `MONGODB_URI=${databaseUri}\nPAYLOAD_SECRET=${payloadSecret}`
|
||||
await fs.outputFile(`${projectDir}/.env`, content)
|
||||
}
|
||||
|
||||
success('.env file created')
|
||||
} catch (err: unknown) {
|
||||
error('Unable to write .env file')
|
||||
if (err instanceof Error) {
|
||||
error(err.message)
|
||||
}
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
133
packages/create-payload-app/src/main.ts
Normal file
133
packages/create-payload-app/src/main.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import slugify from '@sindresorhus/slugify'
|
||||
import arg from 'arg'
|
||||
import commandExists from 'command-exists'
|
||||
|
||||
import type { CliArgs, PackageManager } from './types'
|
||||
|
||||
import { createProject } from './lib/create-project'
|
||||
import { generateSecret } from './lib/generate-secret'
|
||||
import { parseProjectName } from './lib/parse-project-name'
|
||||
import { parseTemplate } from './lib/parse-template'
|
||||
import { selectDb } from './lib/select-db'
|
||||
import { getValidTemplates, validateTemplate } from './lib/templates'
|
||||
import { writeEnvFile } from './lib/write-env-file'
|
||||
import { success } from './utils/log'
|
||||
import { helpMessage, successMessage, welcomeMessage } from './utils/messages'
|
||||
|
||||
export class Main {
|
||||
args: CliArgs
|
||||
|
||||
constructor() {
|
||||
// @ts-expect-error bad typings
|
||||
this.args = arg(
|
||||
{
|
||||
'--db': String,
|
||||
'--help': Boolean,
|
||||
'--name': String,
|
||||
'--secret': String,
|
||||
'--template': String,
|
||||
|
||||
// Package manager
|
||||
'--no-deps': Boolean,
|
||||
'--use-npm': Boolean,
|
||||
'--use-pnpm': Boolean,
|
||||
'--use-yarn': Boolean,
|
||||
|
||||
// Flags
|
||||
'--beta': Boolean,
|
||||
'--dry-run': Boolean,
|
||||
|
||||
// Aliases
|
||||
'-d': '--db',
|
||||
'-h': '--help',
|
||||
'-n': '--name',
|
||||
'-t': '--template',
|
||||
},
|
||||
{ permissive: true },
|
||||
)
|
||||
}
|
||||
|
||||
async init(): Promise<void> {
|
||||
try {
|
||||
if (this.args['--help']) {
|
||||
console.log(helpMessage())
|
||||
process.exit(0)
|
||||
}
|
||||
const templateArg = this.args['--template']
|
||||
if (templateArg) {
|
||||
const valid = validateTemplate(templateArg)
|
||||
if (!valid) {
|
||||
console.log(helpMessage())
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
console.log(welcomeMessage)
|
||||
const projectName = await parseProjectName(this.args)
|
||||
const validTemplates = getValidTemplates()
|
||||
const template = await parseTemplate(this.args, validTemplates)
|
||||
|
||||
const projectDir = projectName === '.' ? process.cwd() : `./${slugify(projectName)}`
|
||||
const packageManager = await getPackageManager(this.args)
|
||||
|
||||
if (template.type !== 'plugin') {
|
||||
const dbDetails = await selectDb(this.args, projectName)
|
||||
const payloadSecret = generateSecret()
|
||||
if (!this.args['--dry-run']) {
|
||||
await createProject({
|
||||
cliArgs: this.args,
|
||||
dbDetails,
|
||||
packageManager,
|
||||
projectDir,
|
||||
projectName,
|
||||
template,
|
||||
})
|
||||
await writeEnvFile({
|
||||
databaseUri: dbDetails.dbUri,
|
||||
payloadSecret,
|
||||
projectDir,
|
||||
template,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (!this.args['--dry-run']) {
|
||||
await createProject({
|
||||
cliArgs: this.args,
|
||||
packageManager,
|
||||
projectDir,
|
||||
projectName,
|
||||
template,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
success('Payload project successfully created')
|
||||
console.log(successMessage(projectDir, packageManager))
|
||||
} catch (error: unknown) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getPackageManager(args: CliArgs): Promise<PackageManager> {
|
||||
let packageManager: PackageManager = 'npm'
|
||||
|
||||
if (args['--use-npm']) {
|
||||
packageManager = 'npm'
|
||||
} else if (args['--use-yarn']) {
|
||||
packageManager = 'yarn'
|
||||
} else if (args['--use-pnpm']) {
|
||||
packageManager = 'pnpm'
|
||||
} else {
|
||||
try {
|
||||
if (await commandExists('yarn')) {
|
||||
packageManager = 'yarn'
|
||||
} else if (await commandExists('pnpm')) {
|
||||
packageManager = 'pnpm'
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
packageManager = 'npm'
|
||||
}
|
||||
}
|
||||
return packageManager
|
||||
}
|
||||
58
packages/create-payload-app/src/types.ts
Normal file
58
packages/create-payload-app/src/types.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import type arg from 'arg'
|
||||
|
||||
export interface Args extends arg.Spec {
|
||||
'--beta': BooleanConstructor
|
||||
'--db': StringConstructor
|
||||
'--dry-run': BooleanConstructor
|
||||
'--help': BooleanConstructor
|
||||
'--name': StringConstructor
|
||||
'--no-deps': BooleanConstructor
|
||||
'--secret': StringConstructor
|
||||
'--template': StringConstructor
|
||||
'--use-npm': BooleanConstructor
|
||||
'--use-pnpm': BooleanConstructor
|
||||
'--use-yarn': BooleanConstructor
|
||||
'-h': string
|
||||
'-n': string
|
||||
'-t': string
|
||||
}
|
||||
|
||||
export type CliArgs = arg.Result<Args>
|
||||
|
||||
export type ProjectTemplate = GitTemplate | PluginTemplate
|
||||
|
||||
/**
|
||||
* Template that is cloned verbatim from a git repo
|
||||
* Performs .env manipulation based upon input
|
||||
*/
|
||||
export interface GitTemplate extends Template {
|
||||
type: 'starter'
|
||||
url: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Type specifically for the plugin template
|
||||
* No .env manipulation is done
|
||||
*/
|
||||
export interface PluginTemplate extends Template {
|
||||
type: 'plugin'
|
||||
url: string
|
||||
}
|
||||
|
||||
interface Template {
|
||||
description?: string
|
||||
name: string
|
||||
type: ProjectTemplate['type']
|
||||
}
|
||||
|
||||
export type PackageManager = 'npm' | 'pnpm' | 'yarn'
|
||||
|
||||
export type DbType = 'mongodb' | 'postgres'
|
||||
|
||||
export type DbDetails = {
|
||||
dbUri: string
|
||||
type: DbType
|
||||
}
|
||||
|
||||
export type BundlerType = 'vite' | 'webpack'
|
||||
export type EditorType = 'lexical' | 'slate'
|
||||
18
packages/create-payload-app/src/utils/log.ts
Normal file
18
packages/create-payload-app/src/utils/log.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import chalk from 'chalk'
|
||||
import figures from 'figures'
|
||||
|
||||
export const success = (message: string): void => {
|
||||
console.log(`${chalk.green(figures.tick)} ${chalk.bold(message)}`)
|
||||
}
|
||||
|
||||
export const warning = (message: string): void => {
|
||||
console.log(chalk.yellow('? ') + chalk.bold(message))
|
||||
}
|
||||
|
||||
export const info = (message: string): void => {
|
||||
console.log(`${chalk.yellow(figures.info)} ${chalk.bold(message)}`)
|
||||
}
|
||||
|
||||
export const error = (message: string): void => {
|
||||
console.log(`${chalk.red(figures.cross)} ${chalk.bold(message)}`)
|
||||
}
|
||||
76
packages/create-payload-app/src/utils/messages.ts
Normal file
76
packages/create-payload-app/src/utils/messages.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import chalk from 'chalk'
|
||||
import figures from 'figures'
|
||||
import path from 'path'
|
||||
import terminalLink from 'terminal-link'
|
||||
|
||||
import type { ProjectTemplate } from '../types'
|
||||
|
||||
import { getValidTemplates } from '../lib/templates'
|
||||
|
||||
const header = (message: string): string => `${chalk.yellow(figures.star)} ${chalk.bold(message)}`
|
||||
|
||||
export const welcomeMessage = chalk`
|
||||
{green Welcome to Payload. Let's create a project! }
|
||||
`
|
||||
|
||||
const spacer = ' '.repeat(8)
|
||||
|
||||
export function helpMessage(): string {
|
||||
const validTemplates = getValidTemplates()
|
||||
return chalk`
|
||||
{bold USAGE}
|
||||
|
||||
{dim $} {bold npx create-payload-app}
|
||||
{dim $} {bold npx create-payload-app} my-project
|
||||
{dim $} {bold npx create-payload-app} -n my-project -t blog
|
||||
|
||||
{bold OPTIONS}
|
||||
|
||||
-n {underline my-payload-app} Set project name
|
||||
-t {underline template_name} Choose specific template
|
||||
|
||||
{dim Available templates: ${formatTemplates(validTemplates)}}
|
||||
|
||||
--use-npm Use npm to install dependencies
|
||||
--use-yarn Use yarn to install dependencies
|
||||
--use-pnpm Use pnpm to install dependencies
|
||||
--no-deps Do not install any dependencies
|
||||
-h Show help
|
||||
`
|
||||
}
|
||||
|
||||
function formatTemplates(templates: ProjectTemplate[]) {
|
||||
return `\n\n${spacer}${templates
|
||||
.map((t) => `${t.name}${' '.repeat(28 - t.name.length)}${t.description}`)
|
||||
.join(`\n${spacer}`)}`
|
||||
}
|
||||
|
||||
export function successMessage(projectDir: string, packageManager: string): string {
|
||||
return `
|
||||
${header('Launch Application:')}
|
||||
|
||||
- cd ${projectDir}
|
||||
- ${
|
||||
packageManager === 'yarn' ? 'yarn' : 'npm run'
|
||||
} dev or follow directions in ${createTerminalLink(
|
||||
'README.md',
|
||||
`file://${path.resolve(projectDir, 'README.md')}`,
|
||||
)}
|
||||
|
||||
${header('Documentation:')}
|
||||
|
||||
- ${createTerminalLink(
|
||||
'Getting Started',
|
||||
'https://payloadcms.com/docs/getting-started/what-is-payload',
|
||||
)}
|
||||
- ${createTerminalLink('Configuration', 'https://payloadcms.com/docs/configuration/overview')}
|
||||
|
||||
`
|
||||
}
|
||||
|
||||
// Create terminalLink with fallback for unsupported terminals
|
||||
function createTerminalLink(text: string, url: string) {
|
||||
return terminalLink(text, url, {
|
||||
fallback: (text, url) => `${text}: ${url}`,
|
||||
})
|
||||
}
|
||||
24
packages/create-payload-app/tsconfig.json
Normal file
24
packages/create-payload-app/tsconfig.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true, // Make sure typescript knows that this module depends on their references
|
||||
"noEmit": false /* Do not emit outputs. */,
|
||||
"emitDeclarationOnly": true,
|
||||
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
|
||||
"rootDir": "./src" /* Specify the root folder within your source files. */
|
||||
},
|
||||
"exclude": [
|
||||
"dist",
|
||||
"build",
|
||||
"tests",
|
||||
"test",
|
||||
"node_modules",
|
||||
".eslintrc.js",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.spec.jsx",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.spec.tsx"
|
||||
],
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts", "src/**/*.json"],
|
||||
"references": [{ "path": "../payload" }]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"repository": "https://github.com/payloadcms/payload",
|
||||
"license": "MIT",
|
||||
@@ -23,7 +23,7 @@
|
||||
"bson-objectid": "2.0.4",
|
||||
"deepmerge": "4.3.1",
|
||||
"get-port": "5.1.1",
|
||||
"mongoose": "6.11.4",
|
||||
"mongoose": "6.12.0",
|
||||
"mongoose-aggregate-paginate-v2": "1.0.6",
|
||||
"mongoose-paginate-v2": "1.7.22",
|
||||
"prompts": "2.4.2",
|
||||
|
||||
@@ -150,7 +150,10 @@ export const findMany = async function find({
|
||||
const countResult = await chainMethods({
|
||||
methods: selectCountMethods,
|
||||
query: db
|
||||
.select({ count: sql<number>`count(*)` })
|
||||
.select({
|
||||
count: sql<number>`count
|
||||
(*)`,
|
||||
})
|
||||
.from(table)
|
||||
.where(where),
|
||||
})
|
||||
|
||||
@@ -24,6 +24,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
||||
buildTable({
|
||||
adapter: this,
|
||||
buildRelationships: true,
|
||||
disableNotNull: !!collection?.versions?.drafts,
|
||||
disableUnique: false,
|
||||
fields: collection.fields,
|
||||
tableName,
|
||||
@@ -37,6 +38,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
||||
buildTable({
|
||||
adapter: this,
|
||||
buildRelationships: true,
|
||||
disableNotNull: !!collection.versions?.drafts,
|
||||
disableUnique: true,
|
||||
fields: versionFields,
|
||||
tableName: versionsTableName,
|
||||
@@ -51,6 +53,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
||||
buildTable({
|
||||
adapter: this,
|
||||
buildRelationships: true,
|
||||
disableNotNull: !!global?.versions?.drafts,
|
||||
disableUnique: false,
|
||||
fields: global.fields,
|
||||
tableName,
|
||||
@@ -64,6 +67,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
||||
buildTable({
|
||||
adapter: this,
|
||||
buildRelationships: true,
|
||||
disableNotNull: !!global.versions?.drafts,
|
||||
disableUnique: true,
|
||||
fields: versionFields,
|
||||
tableName: versionsTableName,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { SQL } from 'drizzle-orm'
|
||||
import type { Field, Operator, Where } from 'payload/types'
|
||||
|
||||
import { and, ilike, isNotNull, isNull, ne, or, sql } from 'drizzle-orm'
|
||||
import { and, ilike, isNotNull, isNull, ne, notInArray, or, sql } from 'drizzle-orm'
|
||||
import { QueryError } from 'payload/errors'
|
||||
import { validOperators } from 'payload/types'
|
||||
|
||||
@@ -147,6 +147,7 @@ export async function parseParams({
|
||||
const { operator: queryOperator, value: queryValue } = sanitizeQueryValue({
|
||||
field,
|
||||
operator,
|
||||
relationOrPath,
|
||||
val,
|
||||
})
|
||||
|
||||
@@ -158,6 +159,17 @@ export async function parseParams({
|
||||
ne<any>(rawColumn || table[columnName], queryValue),
|
||||
),
|
||||
)
|
||||
} else if (
|
||||
(field.type === 'relationship' || field.type === 'upload') &&
|
||||
Array.isArray(queryValue) &&
|
||||
operator === 'not_in'
|
||||
) {
|
||||
constraints.push(
|
||||
sql`${notInArray(table[columnName], queryValue)} OR
|
||||
${table[columnName]}
|
||||
IS
|
||||
NULL`,
|
||||
)
|
||||
} else {
|
||||
constraints.push(
|
||||
operatorMap[queryOperator](rawColumn || table[columnName], queryValue),
|
||||
|
||||
@@ -5,12 +5,14 @@ import { createArrayFromCommaDelineated } from 'payload/utilities'
|
||||
type SanitizeQueryValueArgs = {
|
||||
field: Field | TabAsField
|
||||
operator: string
|
||||
relationOrPath: string
|
||||
val: any
|
||||
}
|
||||
|
||||
export const sanitizeQueryValue = ({
|
||||
field,
|
||||
operator: operatorArg,
|
||||
relationOrPath,
|
||||
val,
|
||||
}: SanitizeQueryValueArgs): { operator: string; value: unknown } => {
|
||||
let operator = operatorArg
|
||||
@@ -18,6 +20,22 @@ export const sanitizeQueryValue = ({
|
||||
|
||||
if (!fieldAffectsData(field)) return { operator, value: formattedValue }
|
||||
|
||||
if (
|
||||
(field.type === 'relationship' || field.type === 'upload') &&
|
||||
!relationOrPath.endsWith('relationTo') &&
|
||||
Array.isArray(formattedValue)
|
||||
) {
|
||||
const allPossibleIDTypes: (number | string)[] = []
|
||||
formattedValue.forEach((val) => {
|
||||
if (typeof val === 'string') {
|
||||
allPossibleIDTypes.push(val, parseInt(val))
|
||||
} else {
|
||||
allPossibleIDTypes.push(val, String(val))
|
||||
}
|
||||
})
|
||||
formattedValue = allPossibleIDTypes
|
||||
}
|
||||
|
||||
// Cast incoming values as proper searchable types
|
||||
if (field.type === 'checkbox' && typeof val === 'string') {
|
||||
if (val.toLowerCase() === 'true') formattedValue = true
|
||||
|
||||
@@ -27,6 +27,7 @@ type Args = {
|
||||
baseColumns?: Record<string, PgColumnBuilder>
|
||||
baseExtraConfig?: Record<string, (cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder>
|
||||
buildRelationships?: boolean
|
||||
disableNotNull: boolean
|
||||
disableUnique: boolean
|
||||
fields: Field[]
|
||||
rootRelationsToBuild?: Map<string, string>
|
||||
@@ -46,6 +47,7 @@ export const buildTable = ({
|
||||
baseColumns = {},
|
||||
baseExtraConfig = {},
|
||||
buildRelationships,
|
||||
disableNotNull,
|
||||
disableUnique = false,
|
||||
fields,
|
||||
rootRelationsToBuild,
|
||||
@@ -102,6 +104,7 @@ export const buildTable = ({
|
||||
adapter,
|
||||
buildRelationships,
|
||||
columns,
|
||||
disableNotNull,
|
||||
disableUnique,
|
||||
fields,
|
||||
indexes,
|
||||
|
||||
@@ -34,6 +34,7 @@ type Args = {
|
||||
buildRelationships: boolean
|
||||
columnPrefix?: string
|
||||
columns: Record<string, PgColumnBuilder>
|
||||
disableNotNull: boolean
|
||||
disableUnique?: boolean
|
||||
fieldPrefix?: string
|
||||
fields: (Field | TabAsField)[]
|
||||
@@ -62,6 +63,7 @@ export const traverseFields = ({
|
||||
buildRelationships,
|
||||
columnPrefix,
|
||||
columns,
|
||||
disableNotNull,
|
||||
disableUnique = false,
|
||||
fieldPrefix,
|
||||
fields,
|
||||
@@ -174,7 +176,7 @@ export const traverseFields = ({
|
||||
|
||||
case 'radio':
|
||||
case 'select': {
|
||||
const enumName = `enum_${newTableName}_${columnPrefix || ''}${toSnakeCase(field.name)}`
|
||||
const enumName = `enum_${newTableName}_${toSnakeCase(field.name)}`
|
||||
|
||||
adapter.enums[enumName] = pgEnum(
|
||||
enumName,
|
||||
@@ -188,7 +190,7 @@ export const traverseFields = ({
|
||||
)
|
||||
|
||||
if (field.type === 'select' && field.hasMany) {
|
||||
const selectTableName = `${newTableName}_${toSnakeCase(fieldName)}`
|
||||
const selectTableName = `${newTableName}_${toSnakeCase(field.name)}`
|
||||
const baseColumns: Record<string, PgColumnBuilder> = {
|
||||
order: integer('order').notNull(),
|
||||
parent: parentIDColumnMap[parentIDColType]('parent_id')
|
||||
@@ -218,6 +220,7 @@ export const traverseFields = ({
|
||||
adapter,
|
||||
baseColumns,
|
||||
baseExtraConfig,
|
||||
disableNotNull,
|
||||
disableUnique,
|
||||
fields: [],
|
||||
tableName: selectTableName,
|
||||
@@ -274,6 +277,7 @@ export const traverseFields = ({
|
||||
adapter,
|
||||
baseColumns,
|
||||
baseExtraConfig,
|
||||
disableNotNull,
|
||||
disableUnique,
|
||||
fields: field.fields,
|
||||
rootRelationsToBuild,
|
||||
@@ -339,6 +343,7 @@ export const traverseFields = ({
|
||||
adapter,
|
||||
baseColumns,
|
||||
baseExtraConfig,
|
||||
disableNotNull,
|
||||
disableUnique,
|
||||
fields: block.fields,
|
||||
rootRelationsToBuild,
|
||||
@@ -399,6 +404,7 @@ export const traverseFields = ({
|
||||
buildRelationships,
|
||||
columnPrefix,
|
||||
columns,
|
||||
disableNotNull,
|
||||
disableUnique,
|
||||
fieldPrefix,
|
||||
fields: field.fields,
|
||||
@@ -432,6 +438,7 @@ export const traverseFields = ({
|
||||
buildRelationships,
|
||||
columnPrefix: `${columnName}_`,
|
||||
columns,
|
||||
disableNotNull,
|
||||
disableUnique,
|
||||
fieldPrefix: `${fieldName}_`,
|
||||
fields: field.fields,
|
||||
@@ -466,6 +473,7 @@ export const traverseFields = ({
|
||||
buildRelationships,
|
||||
columnPrefix,
|
||||
columns,
|
||||
disableNotNull,
|
||||
disableUnique,
|
||||
fieldPrefix,
|
||||
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
||||
@@ -502,6 +510,7 @@ export const traverseFields = ({
|
||||
buildRelationships,
|
||||
columnPrefix,
|
||||
columns,
|
||||
disableNotNull,
|
||||
disableUnique,
|
||||
fieldPrefix,
|
||||
fields: field.fields,
|
||||
@@ -544,7 +553,13 @@ export const traverseFields = ({
|
||||
|
||||
const condition = field.admin && field.admin.condition
|
||||
|
||||
if (targetTable[fieldName] && 'required' in field && field.required && !condition) {
|
||||
if (
|
||||
!disableNotNull &&
|
||||
targetTable[fieldName] &&
|
||||
'required' in field &&
|
||||
field.required &&
|
||||
!condition
|
||||
) {
|
||||
targetTable[fieldName].notNull()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { subscribe, unsubscribe } from '@payloadcms/live-preview'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { ready, subscribe, unsubscribe } from '@payloadcms/live-preview'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
|
||||
// To prevent the flicker of missing data on initial load,
|
||||
// you can pass in the initial page data from the server
|
||||
@@ -17,6 +17,7 @@ export const useLivePreview = <T extends any>(props: {
|
||||
const { depth = 0, initialData, serverURL } = props
|
||||
const [data, setData] = useState<T>(initialData)
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true)
|
||||
const hasSentReadyMessage = useRef<boolean>(false)
|
||||
|
||||
const onChange = useCallback((mergedData) => {
|
||||
setData(mergedData)
|
||||
@@ -31,6 +32,14 @@ export const useLivePreview = <T extends any>(props: {
|
||||
serverURL,
|
||||
})
|
||||
|
||||
if (!hasSentReadyMessage.current) {
|
||||
hasSentReadyMessage.current = true
|
||||
|
||||
ready({
|
||||
serverURL,
|
||||
})
|
||||
}
|
||||
|
||||
return () => {
|
||||
unsubscribe(subscription)
|
||||
}
|
||||
|
||||
@@ -7,11 +7,10 @@ export const handleMessage = async <T>(args: {
|
||||
serverURL: string
|
||||
}): Promise<T> => {
|
||||
const { depth, event, initialData, serverURL } = args
|
||||
|
||||
if (event.origin === serverURL && event.data) {
|
||||
const eventData = JSON.parse(event?.data)
|
||||
|
||||
if (eventData.type === 'livePreview') {
|
||||
if (eventData.type === 'payload-live-preview') {
|
||||
const mergedData = await mergeData<T>({
|
||||
depth,
|
||||
fieldSchema: eventData.fieldSchemaJSON,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export { handleMessage } from './handleMessage'
|
||||
export { mergeData } from './mergeData'
|
||||
export { ready } from './ready'
|
||||
export { subscribe } from './subscribe'
|
||||
export { unsubscribe } from './unsubscribe'
|
||||
|
||||
15
packages/live-preview/src/ready.ts
Normal file
15
packages/live-preview/src/ready.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export const ready = (args: { serverURL: string }): void => {
|
||||
const { serverURL } = args
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
// This subscription may have been from either an iframe `src` or `window.open()`
|
||||
// i.e. `window?.opener` || `window?.parent`
|
||||
window?.opener?.postMessage(
|
||||
JSON.stringify({
|
||||
popupReady: true,
|
||||
type: 'payload-live-preview',
|
||||
}),
|
||||
serverURL,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -8,15 +8,14 @@ export const subscribe = <T>(args: {
|
||||
}): ((event: MessageEvent) => void) => {
|
||||
const { callback, depth, initialData, serverURL } = args
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
const handleMessageCallback = async (event: MessageEvent) => {
|
||||
const mergedData = await handleMessage({ depth, event, initialData, serverURL })
|
||||
callback(mergedData)
|
||||
}
|
||||
|
||||
window.addEventListener('message', handleMessageCallback)
|
||||
window.parent.postMessage('ready', serverURL)
|
||||
|
||||
return handleMessageCallback
|
||||
const onMessage = async (event: MessageEvent) => {
|
||||
const mergedData = await handleMessage({ depth, event, initialData, serverURL })
|
||||
callback(mergedData)
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('message', onMessage)
|
||||
}
|
||||
|
||||
return onMessage
|
||||
}
|
||||
|
||||
@@ -26,7 +26,9 @@
|
||||
</h4>
|
||||
<hr/>
|
||||
|
||||
### 🎉 Payload 2.0 is now available! Read more in the [announcement post](https://payloadcms.com/blog/payload-2-0).
|
||||
<h3>
|
||||
🎉 Payload 2.0 is now available! Read more in the <a target="_blank" href="https://payloadcms.com/blog/payload-2-0" rel="dofollow"><strong>announcement post</strong></a>
|
||||
</h3>
|
||||
|
||||
<h3>Benefits over a regular CMS</h3>
|
||||
<ul>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload",
|
||||
"version": "2.0.3",
|
||||
"version": "2.0.5",
|
||||
"description": "Node, React and MongoDB Headless CMS and Application Framework",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
@@ -77,7 +77,7 @@
|
||||
"flatley": "5.2.0",
|
||||
"fs-extra": "10.1.0",
|
||||
"get-tsconfig": "4.6.2",
|
||||
"graphql": "16.7.1",
|
||||
"graphql": "16.8.1",
|
||||
"graphql-http": "1.21.0",
|
||||
"graphql-playground-middleware-express": "1.7.23",
|
||||
"graphql-query-complexity": "0.12.0",
|
||||
@@ -187,7 +187,7 @@
|
||||
"file-loader": "6.2.0",
|
||||
"form-data": "3.0.1",
|
||||
"get-port": "5.1.1",
|
||||
"graphql-request": "3.7.0",
|
||||
"graphql-request": "6.1.0",
|
||||
"mini-css-extract-plugin": "1.6.2",
|
||||
"node-fetch": "2.6.12",
|
||||
"nodemon": "3.0.1",
|
||||
|
||||
@@ -167,7 +167,10 @@ export const DocumentControls: React.FC<{
|
||||
<div className={`${baseClass}__controls`}>
|
||||
{showPreviewButton && (
|
||||
<PreviewButton
|
||||
CustomComponent={collection?.admin?.components?.edit?.PreviewButton}
|
||||
CustomComponent={
|
||||
collection?.admin?.components?.edit?.PreviewButton ||
|
||||
global?.admin?.components?.elements?.PreviewButton
|
||||
}
|
||||
generatePreviewURL={collection?.admin?.preview || global?.admin?.preview}
|
||||
/>
|
||||
)}
|
||||
@@ -178,13 +181,26 @@ export const DocumentControls: React.FC<{
|
||||
{((collection?.versions?.drafts && !collection?.versions?.drafts?.autosave) ||
|
||||
(global?.versions?.drafts && !global?.versions?.drafts?.autosave)) && (
|
||||
<SaveDraft
|
||||
CustomComponent={collection?.admin?.components?.edit?.SaveDraftButton}
|
||||
CustomComponent={
|
||||
collection?.admin?.components?.edit?.SaveDraftButton ||
|
||||
global?.admin?.components?.elements?.SaveDraftButton
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Publish CustomComponent={collection?.admin?.components?.edit?.PublishButton} />
|
||||
<Publish
|
||||
CustomComponent={
|
||||
collection?.admin?.components?.edit?.PublishButton ||
|
||||
global?.admin?.components?.elements?.PublishButton
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<Save CustomComponent={collection?.admin?.components?.edit?.SaveButton} />
|
||||
<Save
|
||||
CustomComponent={
|
||||
collection?.admin?.components?.edit?.SaveButton ||
|
||||
global?.admin?.components?.elements?.SaveButton
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
@@ -54,13 +54,12 @@ const StepNav: React.FC<{
|
||||
)}
|
||||
{stepNav.map((item, i) => {
|
||||
const StepLabel = <span key={i}>{getTranslation(item.label, i18n)}</span>
|
||||
|
||||
const Step =
|
||||
stepNav.length === i + 1 ? (
|
||||
StepLabel
|
||||
) : (
|
||||
<Fragment key={i}>
|
||||
<Link to={item.url}>{StepLabel}</Link>
|
||||
{item.url ? <Link to={item.url}>{StepLabel}</Link> : StepLabel}
|
||||
<span>/</span>
|
||||
</Fragment>
|
||||
)
|
||||
|
||||
@@ -2,24 +2,24 @@ import type { PayloadRequest } from '../../../../../express/types'
|
||||
import type { RichTextField, Validate } from '../../../../../fields/config/types'
|
||||
import type { CellComponentProps } from '../../../views/collections/List/Cell/types'
|
||||
|
||||
export type RichTextFieldProps<AdapterProps = object> = Omit<
|
||||
RichTextField<AdapterProps>,
|
||||
export type RichTextFieldProps<Value extends object, AdapterProps> = Omit<
|
||||
RichTextField<Value, AdapterProps>,
|
||||
'type'
|
||||
> & {
|
||||
path?: string
|
||||
}
|
||||
|
||||
export type RichTextAdapter<AdapterProps = object> = {
|
||||
CellComponent: React.FC<CellComponentProps<RichTextField<AdapterProps>>>
|
||||
FieldComponent: React.FC<RichTextFieldProps<AdapterProps>>
|
||||
export type RichTextAdapter<Value extends object = object, AdapterProps = any> = {
|
||||
CellComponent: React.FC<CellComponentProps<RichTextField<Value, AdapterProps>>>
|
||||
FieldComponent: React.FC<RichTextFieldProps<Value, AdapterProps>>
|
||||
afterReadPromise?: (data: {
|
||||
currentDepth?: number
|
||||
depth: number
|
||||
field: RichTextField<AdapterProps>
|
||||
field: RichTextField<Value, AdapterProps>
|
||||
overrideAccess?: boolean
|
||||
req: PayloadRequest
|
||||
showHiddenFields: boolean
|
||||
siblingDoc: Record<string, unknown>
|
||||
}) => Promise<void> | null
|
||||
validate: Validate<unknown, unknown, RichTextField<AdapterProps>>
|
||||
validate: Validate<Value, Value, unknown, RichTextField<Value, AdapterProps>>
|
||||
}
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
gap: calc(var(--base) * 2);
|
||||
align-items: flex-start;
|
||||
|
||||
ul {
|
||||
padding-left: calc(var(--base) * 1);
|
||||
}
|
||||
|
||||
&--fullscreen {
|
||||
padding-left: 0;
|
||||
.query-inspector__configuration {
|
||||
@@ -173,6 +177,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__row-line--nested {
|
||||
margin-left: 25px;
|
||||
}
|
||||
|
||||
&__bracket {
|
||||
position: relative;
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import type { EditViewProps } from '../types'
|
||||
|
||||
import { Chevron } from '../..'
|
||||
import { requests } from '../../../api'
|
||||
import CopyToClipboard from '../../elements/CopyToClipboard'
|
||||
@@ -11,6 +13,7 @@ import { MinimizeMaximize } from '../../icons/MinimizeMaximize'
|
||||
import { useConfig } from '../../utilities/Config'
|
||||
import { useDocumentInfo } from '../../utilities/DocumentInfo'
|
||||
import { useLocale } from '../../utilities/Locale'
|
||||
import { SetStepNav } from '../collections/Edit/SetStepNav'
|
||||
import './index.scss'
|
||||
|
||||
const chars = {
|
||||
@@ -119,12 +122,19 @@ const RecursivelyRenderObjectData = ({
|
||||
}
|
||||
|
||||
if (type === 'date' || type === 'string' || type === 'null' || type === 'number') {
|
||||
const parentHasKey = Boolean(parentType === 'object' && key)
|
||||
|
||||
const rowClasses = [
|
||||
`${baseClass}__row-line`,
|
||||
`${baseClass}__value-type--${type}`,
|
||||
`${baseClass}__row-line--${objectKey ? 'nested' : 'top'}`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
|
||||
return (
|
||||
<li
|
||||
className={`${baseClass}__row-line ${baseClass}__value-type--${type}`}
|
||||
key={`${key}-${keyIndex}`}
|
||||
>
|
||||
{parentType === 'object' ? <span>{`"${key}": `}</span> : null}
|
||||
<li className={rowClasses} key={`${key}-${keyIndex}`}>
|
||||
{parentHasKey ? <span>{`"${key}": `}</span> : null}
|
||||
|
||||
{type === 'string' ? (
|
||||
<span className={`${baseClass}__value`}>{`"${value}"`}</span>
|
||||
@@ -156,7 +166,8 @@ function createURL(url: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export const API = ({ apiURL }) => {
|
||||
export const API: React.FC<EditViewProps> = (props) => {
|
||||
const { apiURL } = props
|
||||
const { i18n } = useTranslation()
|
||||
const {
|
||||
localization,
|
||||
@@ -201,8 +212,21 @@ export const API = ({ apiURL }) => {
|
||||
|
||||
const classes = [baseClass, fullscreen && `${baseClass}--fullscreen`].filter(Boolean).join(' ')
|
||||
|
||||
let isEditing: boolean
|
||||
|
||||
if ('collection' in props) {
|
||||
isEditing = props?.isEditing
|
||||
}
|
||||
|
||||
return (
|
||||
<Gutter className={classes} right={false}>
|
||||
<SetStepNav
|
||||
collection={collection}
|
||||
global={global}
|
||||
id={id}
|
||||
isEditing={isEditing}
|
||||
view="API"
|
||||
/>
|
||||
<div className={`${baseClass}__configuration`}>
|
||||
<div className={`${baseClass}__api-url`}>
|
||||
<span className={`${baseClass}__label`}>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { DocumentHeader } from '../../elements/DocumentHeader'
|
||||
import { FormLoadingOverlayToggle } from '../../elements/Loading'
|
||||
import Form from '../../forms/Form'
|
||||
import { OperationContext } from '../../utilities/OperationProvider'
|
||||
import { SetStepNav } from '../collections/Edit/SetStepNav'
|
||||
import { GlobalRoutes } from './Routes'
|
||||
import { CustomGlobalComponent } from './Routes/CustomComponent'
|
||||
import './index.scss'
|
||||
@@ -40,6 +41,7 @@ const DefaultGlobalView: React.FC<
|
||||
return (
|
||||
<main className={baseClass}>
|
||||
<OperationContext.Provider value="update">
|
||||
<SetStepNav global={global} />
|
||||
<Form
|
||||
action={action}
|
||||
className={`${baseClass}__form`}
|
||||
|
||||
@@ -49,8 +49,9 @@
|
||||
|
||||
&__sidebar-wrap {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
flex-grow: 1;
|
||||
top: var(--doc-controls-height);
|
||||
width: 33.33%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&__sidebar {
|
||||
|
||||
@@ -8,7 +8,6 @@ import type { SizeReducerAction } from './sizeReducer'
|
||||
export interface LivePreviewContextType {
|
||||
breakpoint: LivePreviewConfig['breakpoints'][number]['name']
|
||||
breakpoints: LivePreviewConfig['breakpoints']
|
||||
deviceFrameRef: React.RefObject<HTMLDivElement>
|
||||
iframeHasLoaded: boolean
|
||||
iframeRef: React.RefObject<HTMLIFrameElement>
|
||||
measuredDeviceSize: {
|
||||
@@ -18,6 +17,7 @@ export interface LivePreviewContextType {
|
||||
setBreakpoint: (breakpoint: LivePreviewConfig['breakpoints'][number]['name']) => void
|
||||
setHeight: (height: number) => void
|
||||
setIframeHasLoaded: (loaded: boolean) => void
|
||||
setMeasuredDeviceSize: (size: { height: number; width: number }) => void
|
||||
setSize: Dispatch<SizeReducerAction>
|
||||
setToolbarPosition: (position: { x: number; y: number }) => void
|
||||
setWidth: (width: number) => void
|
||||
@@ -36,7 +36,6 @@ export interface LivePreviewContextType {
|
||||
export const LivePreviewContext = createContext<LivePreviewContextType>({
|
||||
breakpoint: undefined,
|
||||
breakpoints: undefined,
|
||||
deviceFrameRef: undefined,
|
||||
iframeHasLoaded: false,
|
||||
iframeRef: undefined,
|
||||
measuredDeviceSize: {
|
||||
@@ -46,6 +45,7 @@ export const LivePreviewContext = createContext<LivePreviewContextType>({
|
||||
setBreakpoint: () => {},
|
||||
setHeight: () => {},
|
||||
setIframeHasLoaded: () => {},
|
||||
setMeasuredDeviceSize: () => {},
|
||||
setSize: () => {},
|
||||
setToolbarPosition: () => {},
|
||||
setWidth: () => {},
|
||||
|
||||
@@ -5,7 +5,6 @@ import type { LivePreviewConfig } from '../../../../../exports/config'
|
||||
import type { EditViewProps } from '../../types'
|
||||
import type { usePopupWindow } from '../usePopupWindow'
|
||||
|
||||
import { useResize } from '../../../../utilities/useResize'
|
||||
import { customCollisionDetection } from './collisionDetection'
|
||||
import { LivePreviewContext } from './context'
|
||||
import { sizeReducer } from './sizeReducer'
|
||||
@@ -26,8 +25,6 @@ export const LivePreviewProvider: React.FC<ToolbarProviderProps> = (props) => {
|
||||
|
||||
const iframeRef = React.useRef<HTMLIFrameElement>(null)
|
||||
|
||||
const deviceFrameRef = React.useRef<HTMLDivElement>(null)
|
||||
|
||||
const [iframeHasLoaded, setIframeHasLoaded] = React.useState(false)
|
||||
|
||||
const [zoom, setZoom] = React.useState(1)
|
||||
@@ -36,6 +33,11 @@ export const LivePreviewProvider: React.FC<ToolbarProviderProps> = (props) => {
|
||||
|
||||
const [size, setSize] = React.useReducer(sizeReducer, { height: 0, width: 0 })
|
||||
|
||||
const [measuredDeviceSize, setMeasuredDeviceSize] = React.useState({
|
||||
height: 0,
|
||||
width: 0,
|
||||
})
|
||||
|
||||
const [breakpoint, setBreakpoint] =
|
||||
React.useState<LivePreviewConfig['breakpoints'][0]['name']>('responsive')
|
||||
|
||||
@@ -92,22 +94,18 @@ export const LivePreviewProvider: React.FC<ToolbarProviderProps> = (props) => {
|
||||
}
|
||||
}, [breakpoint, breakpoints])
|
||||
|
||||
// keep an accurate measurement of the actual device size as it is truly rendered
|
||||
// this is helpful when `sizes` are non-number units like percentages, etc.
|
||||
const { size: measuredDeviceSize } = useResize(deviceFrameRef)
|
||||
|
||||
return (
|
||||
<LivePreviewContext.Provider
|
||||
value={{
|
||||
breakpoint,
|
||||
breakpoints,
|
||||
deviceFrameRef,
|
||||
iframeHasLoaded,
|
||||
iframeRef,
|
||||
measuredDeviceSize,
|
||||
setBreakpoint,
|
||||
setHeight,
|
||||
setIframeHasLoaded,
|
||||
setMeasuredDeviceSize,
|
||||
setSize,
|
||||
setToolbarPosition: setPosition,
|
||||
setWidth,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
|
||||
import { useResize } from '../../../../utilities/useResize'
|
||||
import { useLivePreviewContext } from '../Context/context'
|
||||
|
||||
export const DeviceContainer: React.FC<{
|
||||
@@ -7,7 +8,22 @@ export const DeviceContainer: React.FC<{
|
||||
}> = (props) => {
|
||||
const { children } = props
|
||||
|
||||
const { breakpoint, deviceFrameRef, size, zoom } = useLivePreviewContext()
|
||||
const deviceFrameRef = React.useRef<HTMLDivElement>(null)
|
||||
|
||||
const { breakpoint, setMeasuredDeviceSize, size, zoom } = useLivePreviewContext()
|
||||
|
||||
// Keep an accurate measurement of the actual device size as it is truly rendered
|
||||
// This is helpful when `sizes` are non-number units like percentages, etc.
|
||||
const { size: measuredDeviceSize } = useResize(deviceFrameRef)
|
||||
|
||||
// Sync the measured device size with the context so that other components can use it
|
||||
// This happens from the bottom up so that as this component mounts and unmounts,
|
||||
// Its size is freshly populated again upon re-mounting, i.e. going from iframe->popup->iframe
|
||||
useEffect(() => {
|
||||
if (measuredDeviceSize) {
|
||||
setMeasuredDeviceSize(measuredDeviceSize)
|
||||
}
|
||||
}, [measuredDeviceSize, setMeasuredDeviceSize])
|
||||
|
||||
let x = '0'
|
||||
let margin = '0'
|
||||
|
||||
@@ -7,7 +7,7 @@ export const DeviceContainer: React.FC<{
|
||||
}> = (props) => {
|
||||
const { children } = props
|
||||
|
||||
const { breakpoint, breakpoints, deviceFrameRef, size, zoom } = useLivePreviewContext()
|
||||
const { breakpoint, breakpoints, size, zoom } = useLivePreviewContext()
|
||||
|
||||
const foundBreakpoint = breakpoint && breakpoints?.find((bp) => bp.name === breakpoint)
|
||||
|
||||
@@ -31,7 +31,6 @@ export const DeviceContainer: React.FC<{
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={deviceFrameRef}
|
||||
style={{
|
||||
height:
|
||||
foundBreakpoint && foundBreakpoint?.name !== 'responsive'
|
||||
|
||||
@@ -58,13 +58,16 @@ const Preview: React.FC<
|
||||
const values = reduceFieldsToValues(fields, true)
|
||||
|
||||
// TODO: only send `fieldSchemaToJSON` one time
|
||||
const message = JSON.stringify({ data: values, fieldSchemaJSON, type: 'livePreview' })
|
||||
const message = JSON.stringify({
|
||||
data: values,
|
||||
fieldSchemaJSON,
|
||||
type: 'payload-live-preview',
|
||||
})
|
||||
|
||||
// external window
|
||||
if (isPopupOpen) {
|
||||
setIframeHasLoaded(false)
|
||||
|
||||
if (popupHasLoaded && popupRef.current) {
|
||||
if (popupRef.current) {
|
||||
popupRef.current.postMessage(message, url)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,8 +55,8 @@ export const LivePreviewView: React.FC<EditViewProps> = (props) => {
|
||||
: livePreviewConfig?.url
|
||||
|
||||
const popupState = usePopupWindow({
|
||||
eventType: 'livePreview',
|
||||
href: url,
|
||||
eventType: 'payload-live-preview',
|
||||
url,
|
||||
})
|
||||
|
||||
const { apiURL, data, permissions } = props
|
||||
@@ -94,7 +94,13 @@ export const LivePreviewView: React.FC<EditViewProps> = (props) => {
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<SetStepNav collection={collection} global={global} id={id} isEditing={isEditing} />
|
||||
<SetStepNav
|
||||
collection={collection}
|
||||
global={global}
|
||||
id={id}
|
||||
isEditing={isEditing}
|
||||
view={t('livePreview')}
|
||||
/>
|
||||
<DocumentControls
|
||||
apiURL={apiURL}
|
||||
collection={collection}
|
||||
|
||||
@@ -14,28 +14,29 @@ export interface PopupMessage {
|
||||
|
||||
export const usePopupWindow = (props: {
|
||||
eventType?: string
|
||||
href: string
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
onMessage?: (searchParams: PopupMessage['searchParams']) => Promise<void>
|
||||
url: string
|
||||
}): {
|
||||
isPopupOpen: boolean
|
||||
openPopupWindow: (e: React.MouseEvent<HTMLAnchorElement>) => void
|
||||
popupHasLoaded: boolean
|
||||
popupRef?: React.MutableRefObject<Window | null>
|
||||
} => {
|
||||
const { eventType, href, onMessage } = props
|
||||
const { eventType, onMessage, url } = props
|
||||
const isReceivingMessage = useRef(false)
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [popupHasLoaded, setPopupHasLoaded] = useState(false)
|
||||
const { serverURL } = useConfig()
|
||||
const popupRef = useRef<Window | null>(null)
|
||||
const hasAttachedMessageListener = useRef(false)
|
||||
|
||||
// Optionally broadcast messages back out to the parent component
|
||||
useEffect(() => {
|
||||
const receiveMessage = async (event: MessageEvent): Promise<void> => {
|
||||
if (
|
||||
event.origin !== window.location.origin ||
|
||||
event.origin !== href ||
|
||||
event.origin !== url ||
|
||||
event.origin !== serverURL
|
||||
) {
|
||||
// console.warn(`Message received by ${event.origin}; IGNORED.`) // eslint-disable-line no-console
|
||||
@@ -53,12 +54,14 @@ export const usePopupWindow = (props: {
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('message', receiveMessage, false)
|
||||
if (isOpen && popupRef.current) {
|
||||
window.addEventListener('message', receiveMessage, false)
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('message', receiveMessage)
|
||||
}
|
||||
}, [onMessage, eventType, href, serverURL])
|
||||
}, [onMessage, eventType, url, serverURL, isOpen])
|
||||
|
||||
// Customize the size, position, and style of the popup window
|
||||
const openPopupWindow = useCallback(
|
||||
@@ -93,23 +96,36 @@ export const usePopupWindow = (props: {
|
||||
return strCopy
|
||||
}, '')
|
||||
.slice(0, -1) // remove last ',' (comma)
|
||||
const newWindow = window.open(href, '_blank', popupOptions)
|
||||
|
||||
const newWindow = window.open(url, '_blank', popupOptions)
|
||||
|
||||
popupRef.current = newWindow
|
||||
|
||||
setIsOpen(true)
|
||||
},
|
||||
[href],
|
||||
[url],
|
||||
)
|
||||
|
||||
// the only cross-origin way of detecting when a popup window has loaded
|
||||
// we catch a message event that the site rendered within the popup window fires
|
||||
// there is no way in js to add an event listener to a popup window across domains
|
||||
useEffect(() => {
|
||||
if (hasAttachedMessageListener.current) return
|
||||
hasAttachedMessageListener.current = true
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event.origin === href && event.data === 'ready') {
|
||||
const data = JSON.parse(event.data)
|
||||
|
||||
if (
|
||||
url.startsWith(event.origin) &&
|
||||
data.type === eventType &&
|
||||
data.popupReady &&
|
||||
!popupHasLoaded
|
||||
) {
|
||||
setPopupHasLoaded(true)
|
||||
}
|
||||
})
|
||||
}, [href])
|
||||
}, [url, eventType, popupHasLoaded])
|
||||
|
||||
// this is the most stable and widely supported way to check if a popup window is no longer open
|
||||
// we poll its ref every x ms and use the popup window's `closed` property
|
||||
|
||||
@@ -1,88 +1,30 @@
|
||||
import React, { useEffect } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import type { StepNavItem } from '../../elements/StepNav/types'
|
||||
import type { Props } from './types'
|
||||
|
||||
import { getTranslation } from '../../../../utilities/getTranslation'
|
||||
import { Gutter } from '../../elements/Gutter'
|
||||
import { LoadingOverlayToggle } from '../../elements/Loading'
|
||||
import Paginator from '../../elements/Paginator'
|
||||
import PerPage from '../../elements/PerPage'
|
||||
import { useStepNav } from '../../elements/StepNav'
|
||||
import { Table } from '../../elements/Table'
|
||||
import { useConfig } from '../../utilities/Config'
|
||||
import Meta from '../../utilities/Meta'
|
||||
import { useSearchParams } from '../../utilities/SearchParams'
|
||||
import { SetStepNav } from '../collections/Edit/SetStepNav'
|
||||
import { buildVersionColumns } from './columns'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'versions'
|
||||
|
||||
export const DefaultVersionsView: React.FC<Props> = (props) => {
|
||||
const { id, collection, data, editURL, entityLabel, global, isLoadingVersions, versionsData } =
|
||||
props
|
||||
const { id, collection, data, entityLabel, global, isLoadingVersions, versionsData } = props
|
||||
|
||||
const {
|
||||
routes: { admin },
|
||||
} = useConfig()
|
||||
|
||||
const { setStepNav } = useStepNav()
|
||||
|
||||
const { i18n, t } = useTranslation('version')
|
||||
const { t } = useTranslation('version')
|
||||
|
||||
const { limit } = useSearchParams()
|
||||
|
||||
const useAsTitle = collection?.admin?.useAsTitle || 'id'
|
||||
|
||||
useEffect(() => {
|
||||
let nav: StepNavItem[] = []
|
||||
|
||||
if (collection) {
|
||||
let docLabel = ''
|
||||
|
||||
if (data) {
|
||||
if (useAsTitle) {
|
||||
if (data[useAsTitle]) {
|
||||
docLabel = data[useAsTitle]
|
||||
} else {
|
||||
docLabel = `[${t('general:untitled')}]`
|
||||
}
|
||||
} else {
|
||||
docLabel = data.id
|
||||
}
|
||||
}
|
||||
|
||||
nav = [
|
||||
{
|
||||
label: getTranslation(collection.labels.plural, i18n),
|
||||
url: `${admin}/collections/${collection.slug}`,
|
||||
},
|
||||
{
|
||||
label: docLabel,
|
||||
url: editURL,
|
||||
},
|
||||
{
|
||||
label: t('versions'),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
if (global) {
|
||||
nav = [
|
||||
{
|
||||
label: getTranslation(global.label, i18n),
|
||||
url: editURL,
|
||||
},
|
||||
{
|
||||
label: t('versions'),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
setStepNav(nav)
|
||||
}, [setStepNav, collection, global, useAsTitle, data, admin, id, editURL, t, i18n])
|
||||
|
||||
let metaDesc: string
|
||||
let metaTitle: string
|
||||
|
||||
@@ -100,6 +42,7 @@ export const DefaultVersionsView: React.FC<Props> = (props) => {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<SetStepNav collection={collection} global={global} id={id} isEditing view={t('versions')} />
|
||||
<LoadingOverlayToggle name="versions" show={isLoadingVersions} />
|
||||
<main className={baseClass}>
|
||||
<Meta description={metaDesc} title={metaTitle} />
|
||||
|
||||
@@ -53,8 +53,9 @@
|
||||
|
||||
&__sidebar-wrap {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
flex-grow: 1;
|
||||
top: var(--doc-controls-height);
|
||||
width: 33.33%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&__sidebar {
|
||||
|
||||
@@ -9,15 +9,18 @@ import { getTranslation } from '../../../../../utilities/getTranslation'
|
||||
import useTitle from '../../../../hooks/useTitle'
|
||||
import { useStepNav } from '../../../elements/StepNav'
|
||||
import { useConfig } from '../../../utilities/Config'
|
||||
import { useEditDepth } from '../../../utilities/EditDepth'
|
||||
|
||||
export const SetStepNav: React.FC<
|
||||
| {
|
||||
collection: SanitizedCollectionConfig
|
||||
id: string
|
||||
id: number | string
|
||||
isEditing: boolean
|
||||
view?: string
|
||||
}
|
||||
| {
|
||||
global: SanitizedGlobalConfig
|
||||
view?: string
|
||||
}
|
||||
> = (props) => {
|
||||
let collection: SanitizedCollectionConfig | undefined
|
||||
@@ -27,7 +30,8 @@ export const SetStepNav: React.FC<
|
||||
let pluralLabel: SanitizedCollectionConfig['labels']['plural']
|
||||
let slug: string
|
||||
let isEditing = false
|
||||
let id: string | undefined
|
||||
let id: number | string | undefined
|
||||
const view: string | undefined = props?.view || undefined
|
||||
|
||||
if ('collection' in props) {
|
||||
const {
|
||||
@@ -35,18 +39,23 @@ export const SetStepNav: React.FC<
|
||||
collection: collectionFromProps,
|
||||
isEditing: isEditingFromProps,
|
||||
} = props
|
||||
collection = collectionFromProps
|
||||
useAsTitle = collection.admin.useAsTitle
|
||||
pluralLabel = collection.labels.plural
|
||||
slug = collection.slug
|
||||
isEditing = isEditingFromProps
|
||||
id = idFromProps
|
||||
|
||||
if (collectionFromProps) {
|
||||
collection = collectionFromProps
|
||||
useAsTitle = collection.admin.useAsTitle
|
||||
pluralLabel = collection.labels.plural
|
||||
slug = collection.slug
|
||||
isEditing = isEditingFromProps
|
||||
id = idFromProps
|
||||
}
|
||||
}
|
||||
|
||||
if ('global' in props) {
|
||||
const { global: globalFromProps } = props
|
||||
global = globalFromProps
|
||||
slug = globalFromProps?.slug
|
||||
if (globalFromProps) {
|
||||
global = globalFromProps
|
||||
slug = globalFromProps?.slug
|
||||
}
|
||||
}
|
||||
|
||||
const title = useTitle({ collection, global })
|
||||
@@ -59,6 +68,8 @@ export const SetStepNav: React.FC<
|
||||
routes: { admin },
|
||||
} = useConfig()
|
||||
|
||||
const drawerDepth = useEditDepth()
|
||||
|
||||
useEffect(() => {
|
||||
const nav: StepNavItem[] = []
|
||||
|
||||
@@ -70,7 +81,8 @@ export const SetStepNav: React.FC<
|
||||
|
||||
if (isEditing) {
|
||||
nav.push({
|
||||
label: useAsTitle && useAsTitle !== 'id' ? title || `[${t('untitled')}]` : id,
|
||||
label: (useAsTitle && useAsTitle !== 'id' && title) || `${id}`,
|
||||
url: `${admin}/collections/${slug}/${id}`,
|
||||
})
|
||||
} else {
|
||||
nav.push({
|
||||
@@ -84,7 +96,13 @@ export const SetStepNav: React.FC<
|
||||
})
|
||||
}
|
||||
|
||||
setStepNav(nav)
|
||||
if (view) {
|
||||
nav.push({
|
||||
label: view,
|
||||
})
|
||||
}
|
||||
|
||||
if (drawerDepth <= 1) setStepNav(nav)
|
||||
}, [
|
||||
setStepNav,
|
||||
isEditing,
|
||||
@@ -98,6 +116,8 @@ export const SetStepNav: React.FC<
|
||||
title,
|
||||
global,
|
||||
collection,
|
||||
view,
|
||||
drawerDepth,
|
||||
])
|
||||
|
||||
return null
|
||||
|
||||
@@ -139,6 +139,19 @@ async function deleteOperation<TSlug extends keyof GeneratedTypes['collections']
|
||||
t,
|
||||
})
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Delete versions
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (collectionConfig.versions) {
|
||||
await deleteCollectionVersions({
|
||||
id,
|
||||
payload,
|
||||
req,
|
||||
slug: collectionConfig.slug,
|
||||
})
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Delete document
|
||||
// /////////////////////////////////////
|
||||
@@ -153,19 +166,6 @@ async function deleteOperation<TSlug extends keyof GeneratedTypes['collections']
|
||||
},
|
||||
})
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Delete versions
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (collectionConfig.versions) {
|
||||
await deleteCollectionVersions({
|
||||
id,
|
||||
payload,
|
||||
req,
|
||||
slug: collectionConfig.slug,
|
||||
})
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterRead - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -109,6 +109,19 @@ async function deleteByID<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
t,
|
||||
})
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Delete versions
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (collectionConfig.versions) {
|
||||
await deleteCollectionVersions({
|
||||
id,
|
||||
payload,
|
||||
req,
|
||||
slug: collectionConfig.slug,
|
||||
})
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Delete document
|
||||
// /////////////////////////////////////
|
||||
@@ -130,19 +143,6 @@ async function deleteByID<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
req,
|
||||
})
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Delete versions
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (collectionConfig.versions) {
|
||||
await deleteCollectionVersions({
|
||||
id,
|
||||
payload,
|
||||
req,
|
||||
slug: collectionConfig.slug,
|
||||
})
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterRead - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -12,6 +12,7 @@ import { initTransaction } from '../../utilities/initTransaction'
|
||||
import { killTransaction } from '../../utilities/killTransaction'
|
||||
import { buildVersionCollectionFields } from '../../versions/buildCollectionFields'
|
||||
import { appendVersionToQueryKey } from '../../versions/drafts/appendVersionToQueryKey'
|
||||
import { getQueryDraftsSort } from '../../versions/drafts/getQueryDraftsSort'
|
||||
import { buildAfterOperation } from './utils'
|
||||
|
||||
export type Arguments = {
|
||||
@@ -127,7 +128,7 @@ async function find<T extends TypeWithID & Record<string, unknown>>(
|
||||
page: sanitizedPage,
|
||||
pagination: usePagination,
|
||||
req,
|
||||
sort,
|
||||
sort: getQueryDraftsSort(sort),
|
||||
where: fullWhere,
|
||||
})
|
||||
} else {
|
||||
|
||||
@@ -243,8 +243,8 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
const { hash, salt } = await generatePasswordSaltHash({ password })
|
||||
dataToUpdate.salt = salt
|
||||
dataToUpdate.hash = hash
|
||||
delete dataToUpdate.password
|
||||
delete data.password
|
||||
delete result.password
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -517,7 +517,7 @@ export type Config = {
|
||||
*/
|
||||
defaultMaxTextLength?: number
|
||||
/** Default richtext editor to use for richText fields */
|
||||
editor: RichTextAdapter
|
||||
editor: RichTextAdapter<any, any>
|
||||
/**
|
||||
* Email configuration options. This value is overridden by `email` in Payload.init if passed.
|
||||
*
|
||||
|
||||
@@ -398,11 +398,13 @@ export type RelationshipValue =
|
||||
| ValueWithRelation[]
|
||||
| (number | string)
|
||||
|
||||
export type RichTextField<AdapterProps = object> = FieldBase & {
|
||||
type IsAny<T> = 0 extends 1 & T ? true : false
|
||||
|
||||
export type RichTextField<Value extends object = any, AdapterProps = any> = FieldBase & {
|
||||
admin?: Admin
|
||||
editor?: RichTextAdapter<AdapterProps>
|
||||
editor?: RichTextAdapter<Value, AdapterProps>
|
||||
type: 'richText'
|
||||
} & AdapterProps
|
||||
} & (IsAny<AdapterProps> extends true ? {} : AdapterProps)
|
||||
|
||||
export type ArrayField = FieldBase & {
|
||||
admin?: Admin & {
|
||||
|
||||
@@ -211,7 +211,7 @@ export const date: Validate<unknown, unknown, DateField> = (value, { required, t
|
||||
return true
|
||||
}
|
||||
|
||||
export const richText: Validate<unknown, unknown, RichTextField, RichTextField> = async (
|
||||
export const richText: Validate<object, unknown, RichTextField, RichTextField> = async (
|
||||
value,
|
||||
options,
|
||||
) => {
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
"addFilter": "Добави филтър",
|
||||
"adminTheme": "Цветова тема",
|
||||
"and": "И",
|
||||
"applyChanges": "Приложете промените",
|
||||
"applyChanges": "Приложи промените",
|
||||
"ascending": "Възходящ",
|
||||
"automatic": "Автоматична",
|
||||
"backToDashboard": "Обратно към таблото",
|
||||
@@ -176,7 +176,7 @@
|
||||
"deletedSuccessfully": "Изтрито успешно.",
|
||||
"deleting": "Изтриване...",
|
||||
"descending": "Низходящо",
|
||||
"deselectAllRows": "Деселектирайте всички редове",
|
||||
"deselectAllRows": "Деселектирай всички редове",
|
||||
"duplicate": "Дупликирай",
|
||||
"duplicateWithoutSaving": "Дупликирай без да запазваш промените",
|
||||
"edit": "Редактирай",
|
||||
@@ -231,7 +231,7 @@
|
||||
"saving": "Запазване...",
|
||||
"searchBy": "Търси по {{label}}",
|
||||
"selectAll": "Избери всички {{count}} {{label}}",
|
||||
"selectAllRows": "Изберете всички редове",
|
||||
"selectAllRows": "Избери всички редове",
|
||||
"selectValue": "Избери стойност",
|
||||
"selectedCount": "{{count}} {{label}} избрани",
|
||||
"showAllLabel": "Покажи всички {{label}}",
|
||||
@@ -273,23 +273,23 @@
|
||||
"near": "близко"
|
||||
},
|
||||
"upload": {
|
||||
"crop": "Реколта",
|
||||
"cropToolDescription": "Плъзнете ъглите на избраната област, нарисувайте нова област или коригирайте стойностите по-долу.",
|
||||
"crop": "Изрязване",
|
||||
"cropToolDescription": "Плъзни ъглите на избраната област, избери нова област или коригирай стойностите по-долу.",
|
||||
"dragAndDrop": "Дръпни и пусни файл",
|
||||
"dragAndDropHere": "или дръпни и пусни файла тук",
|
||||
"editImage": "Редактирай изображение",
|
||||
"fileName": "Име на файла",
|
||||
"fileSize": "Големина на файла",
|
||||
"focalPoint": "Фокусна точка",
|
||||
"focalPointDescription": "Преместете фокусната точка директно върху визуализацията или регулирайте стойностите по-долу.",
|
||||
"focalPointDescription": "Премести фокусната точка директно върху визуализацията или регулирай стойностите по-долу.",
|
||||
"height": "Височина",
|
||||
"lessInfo": "По-малко информация",
|
||||
"moreInfo": "Повече информация",
|
||||
"previewSizes": "Преглед на размери",
|
||||
"selectCollectionToBrowse": "Избери колекция, която да разгледаш",
|
||||
"selectFile": "Избери файл",
|
||||
"setCropArea": "Задайте област за изрязване",
|
||||
"setFocalPoint": "Задайте фокусна точка",
|
||||
"setCropArea": "Задай област за изрязване",
|
||||
"setFocalPoint": "Задай фокусна точка",
|
||||
"sizes": "Големини",
|
||||
"sizesFor": "Размери за {{label}}",
|
||||
"width": "Ширина"
|
||||
@@ -368,4 +368,4 @@
|
||||
"viewingVersions": "Гледане на версии за {{entityLabel}} {{documentTitle}}",
|
||||
"viewingVersionsGlobal": "Гледане на версии за глобалния документ {{entityLabel}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,7 +273,7 @@
|
||||
"near": "in der Nähe"
|
||||
},
|
||||
"upload": {
|
||||
"crop": "Ernte",
|
||||
"crop": "Zuschneiden",
|
||||
"cropToolDescription": "Ziehen Sie die Ecken des ausgewählten Bereichs, zeichnen Sie einen neuen Bereich oder passen Sie die Werte unten an.",
|
||||
"dragAndDrop": "Ziehen Sie eine Datei per Drag-and-Drop",
|
||||
"dragAndDropHere": "oder ziehe eine Datei hier",
|
||||
@@ -368,4 +368,4 @@
|
||||
"viewingVersions": "Betrachte Versionen für {{entityLabel}} {{documentTitle}}",
|
||||
"viewingVersionsGlobal": "`Betrachte Versionen für das Globale Dokument {{entityLabel}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,7 +273,7 @@
|
||||
"near": "рядом"
|
||||
},
|
||||
"upload": {
|
||||
"crop": "Урожай",
|
||||
"crop": "Обрезать",
|
||||
"cropToolDescription": "Перетащите углы выбранной области, нарисуйте новую область или отрегулируйте значения ниже.",
|
||||
"dragAndDrop": "Перетащите файл",
|
||||
"dragAndDropHere": "или перетащите файл сюда",
|
||||
@@ -368,4 +368,4 @@
|
||||
"viewingVersions": "Просмотр версий для {{entityLabel}} {{documentTitle}}",
|
||||
"viewingVersionsGlobal": "`Просмотр версии для глобальной Коллекции {{entityLabel}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,7 +273,7 @@
|
||||
"near": "поруч"
|
||||
},
|
||||
"upload": {
|
||||
"crop": "Врожай",
|
||||
"crop": "Обрізати",
|
||||
"cropToolDescription": "Перетягніть кути обраної області, намалюйте нову область або скоригуйте значення нижче.",
|
||||
"dragAndDrop": "Перемістіть файл",
|
||||
"dragAndDropHere": "або перемістіть сюди файл",
|
||||
@@ -368,4 +368,4 @@
|
||||
"viewingVersions": "Огляд версій для {{entityLabel}} {{documentTitle}}",
|
||||
"viewingVersionsGlobal": "Огляд версій для глобальної колекції {{entityLabel}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
17
packages/payload/src/versions/drafts/getQueryDraftsSort.ts
Normal file
17
packages/payload/src/versions/drafts/getQueryDraftsSort.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Takes the incoming sort argument and prefixes it with `versions.` and preserves any `-` prefixes for descending order
|
||||
* @param sort
|
||||
*/
|
||||
export const getQueryDraftsSort = (sort: string): string => {
|
||||
if (!sort) return sort
|
||||
|
||||
let direction = ''
|
||||
let orderBy = sort
|
||||
|
||||
if (sort[0] === '-') {
|
||||
direction = '-'
|
||||
orderBy = sort.substring(1)
|
||||
}
|
||||
|
||||
return `${direction}version.${orderBy}`
|
||||
}
|
||||
37
packages/plugin-cloud/.eslintrc.cjs
Normal file
37
packages/plugin-cloud/.eslintrc.cjs
Normal file
@@ -0,0 +1,37 @@
|
||||
/** @type {import('prettier').Config} */
|
||||
module.exports = {
|
||||
extends: ['@payloadcms'],
|
||||
overrides: [
|
||||
{
|
||||
extends: ['plugin:@typescript-eslint/disable-type-checked'],
|
||||
files: ['*.js', '*.cjs', '*.json', '*.md', '*.yml', '*.yaml'],
|
||||
},
|
||||
{
|
||||
files: ['package.json', 'tsconfig.json'],
|
||||
rules: {
|
||||
'perfectionist/sort-array-includes': 'off',
|
||||
'perfectionist/sort-astro-attributes': 'off',
|
||||
'perfectionist/sort-classes': 'off',
|
||||
'perfectionist/sort-enums': 'off',
|
||||
'perfectionist/sort-exports': 'off',
|
||||
'perfectionist/sort-imports': 'off',
|
||||
'perfectionist/sort-interfaces': 'off',
|
||||
'perfectionist/sort-jsx-props': 'off',
|
||||
'perfectionist/sort-keys': 'off',
|
||||
'perfectionist/sort-maps': 'off',
|
||||
'perfectionist/sort-named-exports': 'off',
|
||||
'perfectionist/sort-named-imports': 'off',
|
||||
'perfectionist/sort-object-types': 'off',
|
||||
'perfectionist/sort-objects': 'off',
|
||||
'perfectionist/sort-svelte-attributes': 'off',
|
||||
'perfectionist/sort-union-types': 'off',
|
||||
'perfectionist/sort-vue-attributes': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.json'],
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
root: true,
|
||||
}
|
||||
248
packages/plugin-cloud/.gitignore
vendored
Normal file
248
packages/plugin-cloud/.gitignore
vendored
Normal file
@@ -0,0 +1,248 @@
|
||||
dev/tmp
|
||||
dev/yarn.lock
|
||||
|
||||
# Created by https://www.gitignore.io/api/node,macos,windows,webstorm,sublimetext,visualstudiocode
|
||||
|
||||
### macOS ###
|
||||
*.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# Yarn Berry
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
.pnp.*
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
|
||||
### SublimeText ###
|
||||
# cache files for sublime text
|
||||
*.tmlanguage.cache
|
||||
*.tmPreferences.cache
|
||||
*.stTheme.cache
|
||||
|
||||
# workspace files are user-specific
|
||||
*.sublime-workspace
|
||||
|
||||
# project files should be checked into the repository, unless a significant
|
||||
# proportion of contributors will probably not be using SublimeText
|
||||
# *.sublime-project
|
||||
|
||||
# sftp configuration file
|
||||
sftp-config.json
|
||||
|
||||
# Package control specific files
|
||||
Package Control.last-run
|
||||
Package Control.ca-list
|
||||
Package Control.ca-bundle
|
||||
Package Control.system-ca-bundle
|
||||
Package Control.cache/
|
||||
Package Control.ca-certs/
|
||||
Package Control.merged-ca-bundle
|
||||
Package Control.user-ca-bundle
|
||||
oscrypto-ca-bundle.crt
|
||||
bh_unicode_properties.cache
|
||||
|
||||
# Sublime-github package stores a github token in this file
|
||||
# https://packagecontrol.io/packages/sublime-github
|
||||
GitHub.sublime-settings
|
||||
|
||||
### VisualStudioCode ###
|
||||
.vscode/*
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history
|
||||
|
||||
### WebStorm ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
.idea/*
|
||||
# User-specific stuff:
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/dictionaries
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.xml
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
|
||||
# Gradle:
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# CMake
|
||||
cmake-build-debug/
|
||||
|
||||
# Mongo Explorer plugin:
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
## File-based project format:
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
/out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Ruby plugin and RubyMine
|
||||
/.rakeTasks
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
### WebStorm Patch ###
|
||||
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
|
||||
|
||||
# *.iml
|
||||
# modules.xml
|
||||
# .idea/misc.xml
|
||||
# *.ipr
|
||||
|
||||
# Sonarlint plugin
|
||||
.idea/sonarlint
|
||||
|
||||
### Windows ###
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# End of https://www.gitignore.io/api/node,macos,windows,webstorm,sublimetext,visualstudiocode
|
||||
|
||||
# Ignore all uploads
|
||||
demo/upload
|
||||
demo/media
|
||||
demo/files
|
||||
|
||||
# Ignore build folder
|
||||
build
|
||||
|
||||
# Ignore built components
|
||||
components/index.js
|
||||
components/styles.css
|
||||
|
||||
# Ignore generated
|
||||
demo/generated-types.ts
|
||||
demo/generated-schema.graphql
|
||||
|
||||
# Ignore dist, no need for git
|
||||
dist
|
||||
|
||||
# Ignore emulator volumes
|
||||
src/adapters/s3/emulator/.localstack/
|
||||
10
packages/plugin-cloud/.prettierignore
Normal file
10
packages/plugin-cloud/.prettierignore
Normal file
@@ -0,0 +1,10 @@
|
||||
.tmp
|
||||
**/.git
|
||||
**/.hg
|
||||
**/.pnp.*
|
||||
**/.svn
|
||||
**/.yarn/**
|
||||
**/build
|
||||
**/dist/**
|
||||
**/node_modules
|
||||
**/temp
|
||||
22
packages/plugin-cloud/LICENSE.md
Normal file
22
packages/plugin-cloud/LICENSE.md
Normal file
@@ -0,0 +1,22 @@
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2018-2022 Payload CMS, LLC <info@payloadcms.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
99
packages/plugin-cloud/README.md
Normal file
99
packages/plugin-cloud/README.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# Payload Cloud Plugin
|
||||
|
||||
This is the official Payload Cloud plugin that connects your Payload instance to the resources that Payload Cloud provides.
|
||||
|
||||
## File storage
|
||||
|
||||
Payload Cloud gives you S3 file storage backed by Cloudflare as a CDN, and this plugin extends Payload so that all of your media will be stored in S3 rather than locally.
|
||||
|
||||
## Email delivery
|
||||
|
||||
Payload Cloud provides an email delivery service out-of-the-box for all Payload Cloud customers. Powered by [Resend](https://resend.com).
|
||||
|
||||
## Upload caching
|
||||
|
||||
Payload Cloud provides a caching for all upload collections by default through Cloudflare's CDN.
|
||||
|
||||
## How to use
|
||||
|
||||
Add the plugin to your Payload config
|
||||
|
||||
`yarn add @payloadcms/plugin-cloud`
|
||||
|
||||
```ts
|
||||
import { payloadCloud } from '@payloadcms/plugin-cloud'
|
||||
import { buildConfig } from 'payload/config'
|
||||
|
||||
export default buildConfig({
|
||||
plugins: [payloadCloud()],
|
||||
// rest of config
|
||||
})
|
||||
```
|
||||
|
||||
NOTE: If your Payload config already has an email with transport, this will take precedence over Payload Cloud's email service.
|
||||
|
||||
### Optional configuration
|
||||
|
||||
If you wish to opt-out of any Payload cloud features, the plugin also accepts options to do so.
|
||||
|
||||
```ts
|
||||
payloadCloud({
|
||||
storage: false, // Disable file storage
|
||||
email: false, // Disable email delivery
|
||||
uploadCaching: false, // Disable upload caching
|
||||
})
|
||||
```
|
||||
|
||||
#### Upload Caching Configuration
|
||||
|
||||
If you wish to configure upload caching on a per-collection basis, you can do so by passing in a keyed object of collection names. By default, all collections will be cached for 24 hours (86400 seconds). The cache is invalidated when an item is updated or deleted.
|
||||
|
||||
```ts
|
||||
payloadCloud({
|
||||
uploadCaching: {
|
||||
maxAge: 604800, // Override default maxAge for all collections
|
||||
collection1Slug: {
|
||||
maxAge: 10, // Collection-specific maxAge, takes precedence over others
|
||||
},
|
||||
collection2Slug: {
|
||||
enabled: false, // Disable caching for this collection
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Accessing File Storage from Local Environment
|
||||
|
||||
This plugin works off of a specific set of environment variables in order to access your file resources. Here is this list with some prefilled.
|
||||
|
||||
```txt
|
||||
PORT=3000
|
||||
MONGODB_URI=
|
||||
PAYLOAD_CLOUD=true
|
||||
PAYLOAD_CLOUD_ENVIRONMENT=prod
|
||||
PAYLOAD_CLOUD_BUCKET=
|
||||
PAYLOAD_CLOUD_COGNITO_IDENTITY_POOL_ID=
|
||||
PAYLOAD_CLOUD_COGNITO_USER_POOL_CLIENT_ID=
|
||||
PAYLOAD_CLOUD_COGNITO_USER_POOL_ID=
|
||||
PAYLOAD_CLOUD_PROJECT_ID=
|
||||
PAYLOAD_CLOUD_COGNITO_PASSWORD=
|
||||
PAYLOAD_CLOUD_BUCKET_REGION=
|
||||
|
||||
PAYLOAD_SECRET=
|
||||
```
|
||||
|
||||
- `MONGODB_URI` is on the the Database tab.
|
||||
- `PAYLOAD_CLOUD_PROJECT_ID` is from Settings -> Billing.
|
||||
- `PAYLOAD_SECRET` is from Settings -> Environment Variables
|
||||
|
||||
The remaining values can be seen on your project's File Storage tab. You'll have to match up the values appropriately. We plan on adding the ability to easily copy these values in the near future.
|
||||
|
||||
## Future enhancements
|
||||
|
||||
### API CDN
|
||||
|
||||
In the future, this plugin will also ship with a way to dynamically cache API requests as well as purge them whenever a resource is updated.
|
||||
|
||||
## When it executes
|
||||
|
||||
This plugin will only execute if the required environment variables set by Payload Cloud are in place. If they are not, the plugin will not execute and your Payload instance will behave as normal.
|
||||
14
packages/plugin-cloud/dev/.env.example
Normal file
14
packages/plugin-cloud/dev/.env.example
Normal file
@@ -0,0 +1,14 @@
|
||||
MONGODB_URI=mongodb://localhost/payload-plugin-cloud
|
||||
PAYLOAD_PUBLIC_SERVER_URL=http://localhost:3000
|
||||
PAYLOAD_SECRET=45ligj345ligj4wl5igj4lw5igj45ligj45wlijl
|
||||
PAYLOAD_CONFIG_PATH=src/payload.config.ts
|
||||
|
||||
PAYLOAD_CLOUD=true
|
||||
PAYLOAD_CLOUD_BUCKET=
|
||||
PAYLOAD_CLOUD_COGNITO_IDENTITY_POOL_ID=
|
||||
PAYLOAD_CLOUD_COGNITO_USER_POOL_CLIENT_ID=
|
||||
PAYLOAD_CLOUD_COGNITO_USER_POOL_ID=
|
||||
PAYLOAD_CLOUD_ENVIRONMENT=
|
||||
PAYLOAD_CLOUD_PROJECT_ID=
|
||||
PAYLOAD_CLOUD_COGNITO_PASSWORD=
|
||||
PAYLOAD_CLOUD_BUCKET_REGION=
|
||||
5
packages/plugin-cloud/dev/nodemon.json
Normal file
5
packages/plugin-cloud/dev/nodemon.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"exec": "ts-node src/server.ts",
|
||||
"ext": "ts",
|
||||
"watch": ["src/**/*.ts", "../src/**/*.ts"]
|
||||
}
|
||||
28
packages/plugin-cloud/dev/package.json
Normal file
28
packages/plugin-cloud/dev/package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "payload-plugin-cloud-demo",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/server.js",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts nodemon",
|
||||
"build:payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload build",
|
||||
"build:server": "tsc",
|
||||
"build": "yarn build:payload && yarn build:server",
|
||||
"serve": "cross-env PAYLOAD_CONFIG_PATH=dist/payload.config.js NODE_ENV=production node dist/server.js",
|
||||
"generate:types": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.142.0",
|
||||
"dotenv": "^8.2.0",
|
||||
"express": "^4.17.1",
|
||||
"payload": "^1.8.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.9",
|
||||
"cross-env": "^7.0.3",
|
||||
"nodemon": "^2.0.6",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.1.3"
|
||||
}
|
||||
}
|
||||
56
packages/plugin-cloud/dev/src/collections/Media.ts
Normal file
56
packages/plugin-cloud/dev/src/collections/Media.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
/* eslint-disable no-console */
|
||||
import type { CollectionConfig, Field } from 'payload/types'
|
||||
|
||||
const urlField: Field = {
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
hooks: {
|
||||
afterRead: [
|
||||
({ value }) => {
|
||||
console.log('hello from hook')
|
||||
return value
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export const Media: CollectionConfig = {
|
||||
slug: 'media',
|
||||
upload: {
|
||||
imageSizes: [
|
||||
{
|
||||
height: 400,
|
||||
width: 400,
|
||||
crop: 'center',
|
||||
name: 'square',
|
||||
},
|
||||
{
|
||||
width: 900,
|
||||
height: 450,
|
||||
crop: 'center',
|
||||
name: 'sixteenByNineMedium',
|
||||
},
|
||||
],
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'alt',
|
||||
label: 'Alt Text',
|
||||
type: 'text',
|
||||
},
|
||||
|
||||
// The following fields should be able to be merged in to default upload fields
|
||||
urlField,
|
||||
{
|
||||
name: 'sizes',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
name: 'square',
|
||||
type: 'group',
|
||||
fields: [urlField],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
23
packages/plugin-cloud/dev/src/collections/Users.ts
Normal file
23
packages/plugin-cloud/dev/src/collections/Users.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { CollectionConfig } from 'payload/types'
|
||||
|
||||
const Users: CollectionConfig = {
|
||||
slug: 'users',
|
||||
auth: true,
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'avatar',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
},
|
||||
{
|
||||
name: 'background',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default Users
|
||||
49
packages/plugin-cloud/dev/src/payload.config.ts
Normal file
49
packages/plugin-cloud/dev/src/payload.config.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { buildConfig } from 'payload/config'
|
||||
import path from 'path'
|
||||
import Users from './collections/Users'
|
||||
import { payloadCloud } from '../../src'
|
||||
import { Media } from './collections/Media'
|
||||
|
||||
export default buildConfig({
|
||||
serverURL: 'http://localhost:3000',
|
||||
collections: [Media, Users],
|
||||
admin: {
|
||||
// NOTE - these webpack extensions are only required
|
||||
// for development of this plugin.
|
||||
// No need to use these aliases within your own projects.
|
||||
webpack: (config) => {
|
||||
return {
|
||||
...config,
|
||||
resolve: {
|
||||
...(config.resolve || {}),
|
||||
alias: {
|
||||
...(config.resolve.alias || {}),
|
||||
[path.resolve(__dirname, '../../src')]: path.resolve(__dirname, '../../src/admin.js'),
|
||||
react: path.resolve(__dirname, '../node_modules/react'),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
typescript: {
|
||||
outputFile: path.resolve(__dirname, 'payload-types.ts'),
|
||||
},
|
||||
// @ts-expect-error local reference
|
||||
plugins: [payloadCloud()],
|
||||
onInit: async (payload) => {
|
||||
const users = await payload.find({
|
||||
collection: 'users',
|
||||
limit: 1,
|
||||
})
|
||||
|
||||
if (!users.docs.length) {
|
||||
await payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: 'dev@payloadcms.com',
|
||||
password: 'test',
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user