Compare commits

..

391 Commits

Author SHA1 Message Date
Elliot DeNolf
27589482dd chore(release): @payloadcms/db-postgres/0.1.7 2023-10-15 14:12:02 -04:00
James Mikrut
d7ab4b7062 Merge pull request #3642 from payloadcms/fix/#3568-postgres-relationships-in-array
fix(db-postgres): query relationship in array alias
2023-10-15 11:31:57 -04:00
James
2c8fbf1be3 chore: adds specificity to tests 2023-10-15 11:08:16 -04:00
James
eec88f8f1b Merge branch 'fix/#3568-postgres-relationships-in-array' of github.com:payloadcms/payload into fix/#3568-postgres-relationships-in-array 2023-10-15 10:43:32 -04:00
James
1481ef97b5 Merge branch 'main' of github.com:payloadcms/payload 2023-10-15 09:44:19 -04:00
James
a89e89fb80 chore: documents pagination: false 2023-10-15 09:44:00 -04:00
Alessio Gravili
7e7eeb059d chore(richtext-lexical): add 'use client' to field and cell 2023-10-15 14:25:36 +02:00
Alessio Gravili
dc2a502dcc perf(richtext-lexical): remove unnecessary prop drilling and load hooks being run for initialState 2023-10-15 14:07:24 +02:00
Elliot DeNolf
a9a5ba82d8 chore: update pnpm-lock.yaml 2023-10-14 21:09:15 -04:00
James
e6e8fae1c5 Merge branch 'main' of github.com:payloadcms/payload 2023-10-14 19:24:50 -04:00
James
a05868a7f3 chore: remove unnecessary peer dep 2023-10-14 19:24:32 -04:00
Elliot DeNolf
f27cd26575 chore(release): richtext-lexical/0.1.11 2023-10-14 17:35:44 -04:00
Elliot DeNolf
db835ea5c8 chore(release): db-postgres/0.1.6 2023-10-14 17:35:26 -04:00
Jacob Fletcher
d8f265fb94 chore(examples): bumps deps to latest (#3655) 2023-10-14 16:49:47 -04:00
Alessio Gravili
d81d4eb075 feat(richtext-lexical): LexicalPluginToLexical migration feature 2023-10-14 22:36:16 +02:00
James
52f89c0136 fix(db-postgres): ensures columns are nullable if within field with condition 2023-10-14 15:34:27 -04:00
Dan Ribbens
b0083b7c07 test: fix missing variable 2023-10-14 15:32:06 -04:00
Dan Ribbens
21649537a6 fix(db-postgres): query relationship path inside arrays 2023-10-14 15:17:42 -04:00
Elliot DeNolf
9be34c9599 chore(release): richtext-lexical/0.1.10 2023-10-14 14:38:58 -04:00
Elliot DeNolf
8ca632e541 chore(release): richtext-slate/1.0.5 2023-10-14 14:38:48 -04:00
Elliot DeNolf
2ef79145a4 chore(release): payload/2.0.6 2023-10-14 14:37:18 -04:00
James
a0641a445d Merge branch 'main' of github.com:payloadcms/payload 2023-10-14 14:34:46 -04:00
James
3a2e78f7f3 chore: add peer deps for richtext packages 2023-10-14 14:34:40 -04:00
James Mikrut
976d69d154 Merge pull request #3657 from payloadcms/chore/export-pattern
chore: properly separates server / client exports
2023-10-14 14:32:11 -04:00
James
66018362fe chore: properly separates server / client exports 2023-10-14 14:08:08 -04:00
Elliot DeNolf
647fe23d1c chore(release): richtext-lexical/0.1.9 2023-10-14 12:26:28 -04:00
Elliot DeNolf
d7c61861f6 chore(release): richtext-slate/1.0.4 2023-10-14 12:26:19 -04:00
Elliot DeNolf
7caa098023 chore(release): db-postgres/0.1.5 2023-10-14 12:25:54 -04:00
James Mikrut
fd54c40400 Merge pull request #3654 from payloadcms/chore/dynamic-drizzle-kit-import
chore: only imports drizzle-kit if it will be used
2023-10-14 12:21:14 -04:00
James
e180131314 chore: only imports drizzle-kit if it will be used 2023-10-14 12:13:13 -04:00
James
5902d4542b Merge branch 'main' of github.com:payloadcms/payload 2023-10-14 11:51:13 -04:00
James
6bc282444e chore: slate compatibility with next-payload 2023-10-14 11:49:38 -04:00
Alessio Gravili
4dc6c09347 feat(richtext-lexical): SlateToLexical migration feature 2023-10-14 13:36:32 +02:00
Elliot DeNolf
03b9ab0054 chore: cleanup scripts 2023-10-13 16:34:37 -04:00
Elliot DeNolf
3c3c93f483 chore(release): richtext-lexical/0.1.8 2023-10-13 16:05:59 -04:00
Alessio Gravili
5dbfb1a335 fix(richtext-lexical): Blocks: working population for crazy amounts of nesting 2023-10-13 21:04:56 +02:00
Alessio Gravili
d411874589 chore(richtext-lexical): Blocks: clean up population 2023-10-13 20:02:18 +02:00
Jacob Fletcher
8358e2f2d2 chore: properly scopes selector in bulk update e2e test (#3640) 2023-10-13 13:51:52 -04:00
Dan Ribbens
2c67eff059 fix(db-postgres): query relationship in array alias 2023-10-13 13:32:44 -04:00
Elliot DeNolf
012b8e6f90 chore: remove pnpm from engines, shows warning when not using pnpm 2023-10-13 13:05:25 -04:00
Jacob Fletcher
fcd4c8d830 fix: document sidebar vertical overflow (#3639) 2023-10-13 13:00:02 -04:00
Elliot DeNolf
81ec435363 chore(release): richtext-lexical/0.1.7 2023-10-13 12:49:08 -04:00
Jacob Fletcher
e116fcfbf5 docs: updates references of master to main 2023-10-13 12:44:45 -04:00
Alessio Gravili
c47632dc1d fix(richtext-lexical): Blocks: Nested Blocks having incorrect initial data (e.g. missing rows property) (#3638)
* fix(richtext-lexical): Blocks: Sub-Blocks having incorrect initial data (e.g. missing rows property)

* chore: remove unnecessary comment
2023-10-13 18:39:34 +02:00
Jacob Fletcher
0dab68b336 chore: prevents group fields from overflowing into the sidebar (#3637) 2023-10-13 12:04:39 -04:00
Jacob Fletcher
483f93bfcf chore: cleans up admin e2e tests (#3636) 2023-10-13 12:04:05 -04:00
Jessica Chowdhury
4bd01df411 fix: login form clearing out and field spacing (#3633) 2023-10-13 11:15:07 -04:00
Jessica Chowdhury
c956a85252 fix: sidebar field permissions (#3629)
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2023-10-13 10:29:22 -04:00
Jacob Fletcher
beed83b231 fix: preview button conditions (#3613) 2023-10-13 10:23:26 -04:00
James Mikrut
3b1bdcbe41 chore: de-duplicates array / block data from form state (#3607)
* chore: consolidates array manipulation tests

---------

Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2023-10-13 09:45:00 -04:00
James Mikrut
d3d0971275 Merge pull request #3630 from payloadcms/fix/duplicating-drafts
fix: allows drafts to be duplicated
2023-10-13 08:50:36 -04:00
Jessica Boezwinkle
1a99d66cd0 fix: allows drafts to be duplicated 2023-10-13 11:59:23 +01:00
Elliot DeNolf
52c4a63bf1 chore(release): richtext-slate/1.0.3 2023-10-12 23:38:38 -04:00
Elliot DeNolf
3446d28602 chore(release): richtext-lexical/0.1.6 2023-10-12 23:38:29 -04:00
Elliot DeNolf
2eb18771a1 chore(release): live-preview-react/0.1.3 2023-10-12 23:38:25 -04:00
Elliot DeNolf
f6fd5d6742 chore(release): live-preview/0.1.3 2023-10-12 23:38:20 -04:00
Elliot DeNolf
76d6c88261 chore(release): db-postgres/0.1.4 2023-10-12 23:38:12 -04:00
Elliot DeNolf
10ebd76fcf chore(release): payload/2.0.5 2023-10-12 23:35:05 -04:00
Elliot DeNolf
36d6eb0a69 Merge pull request #3619 from payloadcms/import/create-payload-app
chore: import create-payload-app
2023-10-12 23:31:44 -04:00
Elliot DeNolf
cd1f8dc332 ci: remove cpa tests, degit doesn't work in actions 2023-10-12 23:20:00 -04:00
Elliot DeNolf
e4275aa228 chore(deps): update pnpm-lock.yaml 2023-10-12 20:37:06 -04:00
Elliot DeNolf
ddfcb2f12e ci: add create-payload-app 2023-10-12 20:29:05 -04:00
Elliot DeNolf
09f33eae2c chore(create-payload-app): remove all yarn refs 2023-10-12 19:09:18 -04:00
Elliot DeNolf
d3b7c9feec chore: ignore lint/format of create-payload-app 2023-10-12 19:06:25 -04:00
Elliot DeNolf
5fd3d43000 chore(create-payload-app): lint and format 2023-10-12 19:04:28 -04:00
Elliot DeNolf
7767679caa chore(create-payload-app): cleanup after import 2023-10-12 18:37:31 -04:00
Elliot DeNolf
fdf2e32005 chore: import create-payload-app 2023-10-12 18:22:27 -04:00
Elliot DeNolf
773be8744d chore: move all files into packages/create-payload-app 2023-10-12 18:20:19 -04:00
Alessio Gravili
40d5bc0c4a Merge pull request #3617 from payloadcms/fix/will-change 2023-10-13 00:05:24 +02:00
Alessio Gravili
fd33c790f2 chore(richtext-lexical): Improve animations of draggable block indicator 2023-10-13 00:03:51 +02:00
Alessio Gravili
ae7aac7639 fix(richtext-lexical): will-change css rule applied to all top-level nodes causing various issues 2023-10-12 23:50:31 +02:00
Jacob Fletcher
05cc2873b4 fix: properly renders custom buttons for globals (#3616) 2023-10-12 17:47:55 -04:00
Elliot DeNolf
171ee121e9 chore: remove workspace file 2023-10-12 17:18:12 -04:00
Elliot DeNolf
4af1d7d812 chore(deps): update dev deps 2023-10-12 17:15:47 -04:00
Dan Ribbens
d229fc391a fix(db-postgres): sorting on versions (#3611)
* fix(db-postgres): WIP sorting on versions

* fix: sorting collections with drafts

* chore: getQueryDraftsSort readability
2023-10-12 17:11:25 -04:00
Dan Ribbens
46a24a9822 fix(db-postgres): in and not_in query operator (#3608) 2023-10-12 17:11:12 -04:00
Alessio Gravili
06a51b3c9b fix: minor type issue in richText validate function 2023-10-12 22:59:03 +02:00
Alessio Gravili
1d4142ccc0 Merge pull request #3615 from payloadcms/chore/lexical-improvements 2023-10-12 22:53:34 +02:00
Alessio Gravili
f1741beba2 fix(richtext-lexical): dropdown menu closing too early on firefox 2023-10-12 22:52:41 +02:00
Alessio Gravili
9103277a10 chore(richtext-lexical): throw error if you try to use Slate or payload-plugin-lexical data inside of richtext-lexical 2023-10-12 22:07:20 +02:00
Alessio Gravili
d84673f400 chore(richtext-lexical): improve slash menu positioning when it goes off the screen 2023-10-12 21:53:47 +02:00
Alessio Gravili
15e23a3adc chore(richtext-lexical): improve anchor handling for slash menu and floating select menu 2023-10-12 21:01:47 +02:00
Elliot DeNolf
565929adcf chore(templates): better versioning in blank 2023-10-12 14:23:10 -04:00
Jacob Fletcher
8bbac60e60 fix: live preview device size (#3606) 2023-10-12 14:20:55 -04:00
Jacob Fletcher
15c7f0dbf3 docs: updates building your own live preview hook (#3604) 2023-10-12 14:20:02 -04:00
Nikola Ganchev
05eba56d7d Fix Bulgarian translation (#3582) 2023-10-12 14:18:28 -04:00
Elliot DeNolf
ab984b3ea9 fix(templates): peer dependencies for pnpm (#3603)
* fix(templates): peer dependencies for pnpm

* chore: more concise dep versions
2023-10-12 14:16:55 -04:00
Elliot DeNolf
a54638eb47 fix(templates): bump plugin-cloud version for blank 2023-10-12 14:15:51 -04:00
Alessio Gravili
69af8d9c83 chore: remove duplicate z-index property 2023-10-12 19:43:46 +02:00
Jacob Fletcher
64864686c4 fix: properly handles nested routes for live preview (#3586) 2023-10-12 12:45:39 -04:00
Jessica Chowdhury
32c0bef05e fix: updates admin e2e test for stepnav change (#3602) 2023-10-12 12:40:35 -04:00
Jacob Fletcher
a77513e94f fix(live-preview-react): prevents duplicative ready message in strict mode (#3601) 2023-10-12 12:17:59 -04:00
Jessica Chowdhury
aaf883909c fix: various stepnav related issues (#3599) 2023-10-12 12:01:16 -04:00
Alessio Gravili
cc56da11d6 fix: database adapter types 2023-10-12 17:53:35 +02:00
Jacob Fletcher
41d9c28073 feat(live-preview): exports ready function for reuse (#3600) 2023-10-12 11:42:21 -04:00
Jacob Fletcher
a071b97607 fix(live-preview): posts message to proper window (#3585) 2023-10-12 11:06:32 -04:00
Elliot DeNolf
cfd9231403 docs: update config overview 2023-10-12 10:28:04 -04:00
Elliot DeNolf
71dce62646 chore(script): list packages (#3590)
* chore(script): list-packages script

* chore(script): show commits since last tag
2023-10-12 09:40:57 -04:00
Elliot DeNolf
db376f24ba ci: add plugins build/test job 2023-10-11 22:20:17 -04:00
Elliot DeNolf
2752483ac7 Merge pull request #3588 from payloadcms/import/plugin-cloud
chore: import plugin-cloud
2023-10-11 22:13:22 -04:00
Elliot DeNolf
860f867c62 chore(plugin-cloud): use proper tsconfig.json 2023-10-11 22:04:12 -04:00
Elliot DeNolf
5b0adbe9c3 chore: git ignore lint/format of plugin-cloud 2023-10-11 21:59:31 -04:00
Elliot DeNolf
fb7d1be2f3 chore(plugin-cloud): lint and format 2023-10-11 21:57:47 -04:00
Elliot DeNolf
687a2e85d0 chore(plugin-cloud): cleanup after import 2023-10-11 21:50:22 -04:00
Elliot DeNolf
df80483afe chore: import plugin-cloud 2023-10-11 21:27:18 -04:00
Elliot DeNolf
8781770d83 chore: move all files into packages/plugin-cloud 2023-10-11 20:58:24 -04:00
Alessio Gravili
ed1d5a60f7 chore(richtext-lexical): link drawer: skip unnecessary reduceFieldsToValues call 2023-10-12 00:54:19 +02:00
Elliot DeNolf
50a0965561 0.5.2 2023-10-11 18:32:02 -04:00
Elliot DeNolf
c31fa5dd83 fix(create-payload-app): ensure only one db adapter 2023-10-11 18:31:43 -04:00
Elliot DeNolf
440eb8d9c6 2.2.5 2023-10-11 17:02:10 -04:00
Elliot DeNolf
1523b2be41 feat: add payload 2.0 to peer deps 2023-10-11 17:00:57 -04:00
Jarrod Flesch
68f55c4064 chore: temp readme updates 2023-10-11 15:53:28 -04:00
Jarrod Flesch
0d3544ea04 Merge branch 'main' of https://github.com/payloadcms/payload 2023-10-11 15:39:53 -04:00
Jarrod Flesch
f152f451dc chore: fix readme 2023-10-11 15:39:47 -04:00
Elliot DeNolf
5d92436e39 chore: sync pnpm-lock.yaml 2023-10-11 15:23:01 -04:00
Elliot DeNolf
63edecddd8 chore(release): bundler-webpack/1.0.3 2023-10-11 14:41:17 -04:00
Elliot DeNolf
65cdf6bfd0 fix(bundler-webpack): pnpm webpack aliases 2023-10-11 14:39:44 -04:00
Elliot DeNolf
9e831a6a00 chore(deps): revert drizzle-kit bump 2023-10-11 11:53:27 -04:00
Elliot DeNolf
72249d1ecd test(create-payload-app): stub out template verification 2023-10-11 10:31:59 -04:00
Elliot DeNolf
dc22496103 0.5.1 2023-10-11 10:27:49 -04:00
Elliot DeNolf
af1c2e924e chore: rename create-project test suite 2023-10-11 10:24:27 -04:00
Elliot DeNolf
1487250752 feat(create-payload-app): more explicit versioning of deps 2023-10-11 10:23:23 -04:00
Elliot DeNolf
1ebb9f3915 0.5.0 2023-10-09 00:01:27 -04:00
Elliot DeNolf
eab04d9b4d chore: point payload at latest 2023-10-09 00:01:01 -04:00
Elliot DeNolf
1758b6c449 chore: indentation 2023-10-08 23:37:03 -04:00
Elliot DeNolf
bca1be8cb6 0.5.0-beta.10 2023-10-08 23:26:58 -04:00
Elliot DeNolf
1e197933dd feat: pull templates from main 2023-10-08 23:26:54 -04:00
Elliot DeNolf
4eb929d57c 0.5.0-beta.9 2023-10-08 21:20:16 -04:00
Elliot DeNolf
198209c2a4 feat: default to slate, not lexical 2023-10-08 21:20:07 -04:00
Elliot DeNolf
54f19ce1e3 0.5.0-beta.8 2023-10-08 21:08:04 -04:00
Elliot DeNolf
d32f3ade1b chore: point to branch for testing 2023-10-08 21:07:56 -04:00
Elliot DeNolf
bf189abc91 0.5.0-beta.7 2023-10-08 19:55:09 -04:00
Elliot DeNolf
69a379e49f chore: point all adapters at latest 2023-10-08 18:39:10 -04:00
Elliot DeNolf
493fc3ed68 chore: switch from 2.0 to default branch for templates 2023-10-08 18:32:20 -04:00
Elliot DeNolf
138e495e1a feat: default to slate, not lexical 2023-10-08 18:31:04 -04:00
Elliot DeNolf
8fe619a221 0.5.0-beta.6 2023-10-07 12:17:44 -04:00
Elliot DeNolf
5195a80dba chore: format db choice title on connection string prompt 2023-10-07 12:17:27 -04:00
Elliot DeNolf
909cf90fa2 chore: add -d shorthand for --db 2023-10-07 12:16:56 -04:00
Elliot DeNolf
c1d1a00d4a 0.5.0-beta.5 2023-10-07 11:54:30 -04:00
Elliot DeNolf
ae68093f35 chore: update postgres replacement 2023-10-07 11:53:52 -04:00
Elliot DeNolf
0f2f355a01 chore: add beta tag to postgres selection 2023-10-07 11:49:43 -04:00
Elliot DeNolf
0101aa60d9 chore: update yarn.lock 2023-10-07 11:49:30 -04:00
Elliot DeNolf
c823ee07cd 0.5.0-beta.4 2023-10-06 10:02:04 -04:00
Elliot DeNolf
1f12f9b480 chore: force beta packages for now 2023-10-06 10:02:04 -04:00
Elliot DeNolf
ec31ab3a2c 0.5.0-beta.3 2023-10-06 10:02:04 -04:00
Elliot DeNolf
a7bea35d69 chore: rename editors 2023-10-06 10:02:04 -04:00
Elliot DeNolf
64a4f19539 0.5.0-beta.2 2023-10-06 10:02:04 -04:00
Elliot DeNolf
c35661e16e feat: add editor import and replacement 2023-10-06 10:02:04 -04:00
Elliot DeNolf
69b6179521 0.5.0-beta.1 2023-10-06 10:02:04 -04:00
Elliot DeNolf
3d2e167e78 chore: proper postgres adapter import replacement 2023-10-06 10:02:04 -04:00
Elliot DeNolf
aa1955221c 0.5.0-beta.0 2023-10-06 10:02:04 -04:00
Elliot DeNolf
7a9b11e2c4 feat: bump template branch to 2.0 2023-10-06 10:02:04 -04:00
Elliot DeNolf
a82c0d0e50 chore: check DATABASE_URI key 2023-10-06 10:02:04 -04:00
Elliot DeNolf
35a6daa10d test: reorganize tests 2023-10-06 10:02:04 -04:00
Elliot DeNolf
bf5db4e44a chore(templates): update branch on starter urls temporarily 2023-10-06 10:02:04 -04:00
Elliot DeNolf
a87e8aa82b chore: replace DATABASE_URI env value 2023-10-06 10:02:04 -04:00
Elliot DeNolf
e00d87a791 test: add debug for cli 2023-10-06 10:02:04 -04:00
Elliot DeNolf
b61babca73 test: dependency and config replacement tests 2023-10-06 10:02:04 -04:00
Elliot DeNolf
e403a0492e feat: update templates with bundler and db adapter 2023-10-06 10:02:04 -04:00
Elliot DeNolf
54a76e1401 feat: implement db selection 2023-10-06 10:02:04 -04:00
Elliot DeNolf
91f6e36420 2.2.2 2023-10-06 09:44:48 -04:00
Elliot DeNolf
9e74fe558f 2.2.2-beta.0 2023-10-05 14:23:22 -04:00
James
ac8bcfac23 chore: proper dev webpack config 2023-10-05 14:17:57 -04:00
James
91b0a691ed chore: moves admin plugin into src 2023-10-05 12:19:09 -04:00
James
3ced6ec2a0 chore: merge 2023-10-05 12:17:23 -04:00
James
650fe159ee chore: renames mock to admin.js 2023-10-05 12:16:54 -04:00
James
b92657fb39 chore: simplifies alias 2023-10-05 12:12:31 -04:00
Elliot DeNolf
17dbe066c1 0.4.2 2023-09-06 12:54:59 -04:00
Elliot DeNolf
5acc88ee9a fix: disregard yarn.lock file when using templates 2023-09-06 12:53:35 -04:00
Elliot DeNolf
d4983af3fc 0.4.1 2023-08-28 11:19:47 -04:00
Elliot DeNolf
6f2dcfd44e fix: all env vars copied from .env.example for starters 2023-08-28 11:18:52 -04:00
Elliot DeNolf
3df6435353 0.4.0 2023-08-17 16:04:52 -04:00
Elliot DeNolf
3ef03364be feat: remove static templates 2023-08-17 16:03:26 -04:00
Elliot DeNolf
5396af9bfc feat: use templates from payload/payloadcms/templates 2023-08-17 15:19:42 -04:00
Elliot DeNolf
56eddf3a93 0.3.34 2023-08-16 09:49:56 -04:00
Elliot DeNolf
6dd900e6b9 feat: add plugin template 2023-08-16 09:23:21 -04:00
Elliot DeNolf
441a26b79c 2.2.1 2023-07-31 10:59:44 -04:00
Elliot DeNolf
471d5ca17f 2.2.1-beta.0 2023-07-31 10:42:13 -04:00
Elliot DeNolf
536f995ab4 feat: bump resend 2023-07-31 10:41:50 -04:00
Elliot DeNolf
bb3c97828e 2.2.0 2023-07-27 11:07:32 -04:00
Elliot DeNolf
c4d32d5418 feat: adjust multipart upload settings for memory optimization (#7) 2023-07-26 20:43:21 -04:00
Elliot DeNolf
8522fd9f27 feat: cloudflare upload caching (#6)
* feat: implement caching hooks

* 2.1.2-canary.0

* feat: allow caching configuration per collection

* 2.1.2-canary.1

* feat: implement purge call to cloud-cms

* 2.1.2-canary.2

* chore: adjust purge logging

* 2.1.2-canary.3

* chore: req.body nesting

* 2.1.2-canary.4

* feat: handle /purge-cache response properly

* feat: clean up caching call logic

* 2.1.2-canary.5

* chore: handle enabled: false for caching collection

* 2.1.2-canary.6

* chore: tweaks

* 2.1.2-canary.7

* chore: remove immutable

* chore: update README

* 2.1.2-canary.8

* feat: rework caching config type, do not await purge call

* 2.1.2-canary.9

* chore: cleanup logging

* chore: bump payload

* chore: update README
2023-07-26 15:01:43 -04:00
Elliot DeNolf
ee93118688 0.3.33 2023-07-20 17:34:04 -04:00
Elliot DeNolf
47f9b89175 fix: help message rendering 2023-07-20 17:33:54 -04:00
Elliot DeNolf
891fc55e25 2.1.1 2023-07-17 20:53:17 -04:00
Elliot DeNolf
988755f202 fix: fallback domain email format 2023-07-17 20:52:45 -04:00
Elliot DeNolf
b5483a46f6 chore: add accessing file storage section to README 2023-07-15 08:23:01 -04:00
Elliot DeNolf
04fd2d6e82 0.3.32 2023-07-06 10:40:28 -04:00
Elliot DeNolf
4cdc7cf3c4 revert: use standalone template repos until frontend handling done 2023-07-06 10:40:23 -04:00
Elliot DeNolf
fbaa1028e6 0.3.31 2023-07-06 10:31:04 -04:00
Elliot DeNolf
df26e19c16 feat: use templates inside payload repo instead of standalone repos 2023-07-06 10:30:58 -04:00
Elliot DeNolf
98501cf4c0 0.3.30 2023-07-05 11:25:00 -04:00
Elliot DeNolf
d0ac142871 fix: move cross-env from dev dep to dep 2023-07-05 11:23:38 -04:00
Elliot DeNolf
d60c66ebd6 feat: change user collection access to default on all templates 2023-07-05 11:23:22 -04:00
Elliot DeNolf
a515bdae56 2.1.0 2023-06-21 12:29:02 -04:00
Elliot DeNolf
cc40853903 feat: support sending from custom email domains (#3)
* feat: support sending from custom email domains

* feat: proper callback for nodemailer transport

* 2.1.0-beta.0
2023-06-21 12:28:19 -04:00
Elliot DeNolf
121d69faf9 0.3.29 2023-06-20 23:07:53 -04:00
Elliot DeNolf
0a2d7c858a chore: fix static dirs 2023-06-20 23:06:50 -04:00
Elliot DeNolf
0962e1e563 feat: support for git templates (#8)
* feat: add payload demo as template

* feat: rework templates, add descriptions, better help message

* wip: parse .env

* chore: remove js templates

* feat: populate mongo and secret from .env.example

* feat: add cloud templates

* test: fix test suite template dir
2023-06-20 22:47:04 -04:00
Elliot DeNolf
a324768b3f 2.0.0 2023-05-24 09:53:33 -04:00
Elliot DeNolf
0973ee512e 2.0.0-0 2023-05-23 16:29:57 -04:00
Elliot DeNolf
f74e492448 feat: email (#1)
* chore: bump payload

* test: add jest

* chore: update eslint rules

* feat: add email transport to plugin

* ci: actions build and test

* chore: bump typescript

* chore: adjust top-level plugin options

* test: clean up

* feat: better env var handling, assertions

* chore: update README

* 1.1.0-beta.0

* chore: update package.json files

* 1.1.0-beta.1

* chore: fix webpack recursion

* 1.1.0-beta.2

* chore: bump payload

* chore: email logging on success on error

* 1.1.0-beta.3

* chore: use proper env var for default domain

* 1.1.0-beta.4

* chore: log sendEmail error with better message

* 1.1.0-beta.5

* chore: add comments to plugin options properties

* 1.1.0-beta.6

* chore: bump payload peer dep

* 1.1.0-beta.7

* chore: update README

* chore: bump payload in dev dir

* chore: package.json license

* test: move test
2023-05-23 15:52:00 -04:00
Elliot DeNolf
e567627809 1.0.1 2023-05-11 22:55:53 -04:00
Elliot DeNolf
56965bc0ed feat: export some utils (#2) 2023-05-11 22:55:13 -04:00
Elliot DeNolf
e43d6520c5 0.3.28 2023-05-09 13:39:19 -04:00
Elliot DeNolf
1123909960 fix: revert template serverURL back to localhost 2023-05-09 13:39:12 -04:00
Elliot DeNolf
bc1853c2e7 0.3.27 2023-05-09 10:59:13 -04:00
Elliot DeNolf
318b734f96 feat: use 127.0.0.1 for node compatibility 2023-05-09 10:58:20 -04:00
Elliot DeNolf
2734b1d54a 0.3.26 2023-04-19 09:48:09 -04:00
Elliot DeNolf
82510c1574 feat: remove .npmrc from templates 2023-04-19 09:48:04 -04:00
Elliot DeNolf
0a2b02f206 1.0.0 2023-04-02 23:17:54 -04:00
Elliot DeNolf
41ee127de8 chore: update readme 2023-04-02 23:17:43 -04:00
James
9ddec59ddd 0.0.10 2023-04-02 18:12:24 -04:00
James
b72b22c628 fix: specifies region 2023-04-02 18:12:17 -04:00
Elliot DeNolf
a685f30245 0.0.6 2023-04-02 13:26:19 -04:00
Elliot DeNolf
173ec6f0f8 feat: set bucket region in s3 storage client 2023-04-02 13:26:05 -04:00
James
349ab5343e chore: update readme 2023-04-02 13:25:52 -04:00
James
cf97adab7c 0.0.5 2023-03-29 12:07:16 -04:00
James
b6fc940f18 chore: mocks email provider 2023-03-29 12:07:06 -04:00
James
2fb685c0fe 0.0.4 2023-03-28 13:58:15 -04:00
James
54be5847f7 chore: only initializes email transport if running within payload cloud 2023-03-28 13:58:03 -04:00
James
bf4f37b514 0.0.3 2023-03-28 13:55:05 -04:00
James
9e577e7214 chore: refactors email transport 2023-03-28 13:54:57 -04:00
James
69185c06c2 0.0.2 2023-03-28 13:49:47 -04:00
James
e561016d07 Merge branch 'main' of github.com:payloadcms/plugin-cloud 2023-03-28 13:49:25 -04:00
James
9db4dadce3 feat: builds email transport w/ resend 2023-03-28 13:49:08 -04:00
Elliot DeNolf
fa40d511c2 chore: .env.example update, dev debugger 2023-03-23 11:53:23 -04:00
James
ebfb86866f chore: bumps payload 2023-03-20 13:32:06 -04:00
James
be853a0657 chore: little cleanup 2023-03-11 00:01:59 -08:00
James
c880342099 chore: streamlines env var naming 2023-03-10 23:39:04 -08:00
James
d09bbd2171 chore: initial build 2023-03-10 23:30:00 -08:00
James
c1823f719a chore: init 2023-03-10 22:20:27 -08:00
Elliot DeNolf
39686e3f05 0.3.25 2023-02-09 14:55:40 -05:00
Elliot DeNolf
482973559d feat: add media collection to blog template 2023-02-09 14:55:16 -05:00
Elliot DeNolf
600dbd72f4 0.3.24 2023-02-03 14:47:58 -05:00
Elliot DeNolf
785337dc5d chore: add rimraf to prepublish 2023-02-03 14:47:17 -05:00
Elliot DeNolf
14a35f35c3 0.3.23 2023-02-01 15:53:54 -05:00
Elliot DeNolf
ed2e176285 feat: add cross-env to graphql schema gen command 2023-02-01 14:51:11 -05:00
Elliot DeNolf
2d79280999 0.3.22 2023-01-31 13:15:13 -05:00
Elliot DeNolf
8b5084ab43 0.3.21 2023-01-31 13:14:45 -05:00
Elliot DeNolf
b19356597b chore: revert onInit passing payload
Signed-off-by: Elliot DeNolf <denolfe@gmail.com>
2023-01-31 13:14:09 -05:00
Elliot DeNolf
0bd412edbd feat: server init async and onInit use scoped payload 2023-01-19 15:23:19 -05:00
Elliot DeNolf
1a6ba25e5d 0.3.21-beta.0 2023-01-18 16:01:41 -05:00
Elliot DeNolf
26d0cd18a1 feat: add generate-types path and ts-node swc to tsconfig 2023-01-18 15:58:41 -05:00
Elliot DeNolf
31653fe76e 0.3.21 2023-01-16 11:34:53 -05:00
Elliot DeNolf
a6d52223d5 chore: update readme with template names 2023-01-16 11:23:33 -05:00
Elliot DeNolf
1bf7c4084c feat: remove javascript templates 2023-01-16 11:21:51 -05:00
Elliot DeNolf
419a3eef53 0.3.20 2023-01-02 15:50:08 -05:00
Elliot DeNolf
f62531bafd feat: pass through npm run command to package.json template 2023-01-02 15:50:02 -05:00
Elliot DeNolf
dc815dad14 0.3.19 2022-12-26 21:08:06 -05:00
Elliot DeNolf
bde2ce9b53 chore: stricter typing on prompts validation 2022-12-26 21:07:25 -05:00
Elliot DeNolf
041af0100c feat: use project name as package.json name 2022-12-26 21:00:05 -05:00
Elliot DeNolf
2e18c5b8cf feat: add Dockerfile 2022-12-21 00:41:22 -05:00
Elliot DeNolf
53f0c526f7 0.3.18 2022-11-20 12:38:48 -05:00
Elliot DeNolf
dc5c4eced0 0.3.18-beta.1 2022-11-18 15:45:59 -05:00
Elliot DeNolf
0d8b6d03ed feat: add fallback for terminal link for unsupported terminals 2022-11-18 15:44:26 -05:00
Elliot DeNolf
cfa364280f v0.3.18-beta.0 2022-11-08 11:17:27 -08:00
Elliot DeNolf
7a293563fb feat: implement using current directory 2022-11-08 11:16:02 -08:00
Elliot DeNolf
9359954233 0.3.17 2022-10-25 13:26:46 -04:00
Elliot DeNolf
9fcc05676e 0.3.16
0.3.16
2022-10-25 13:20:24 -04:00
Elliot DeNolf
1e95f5de49 chore: update scripts 2022-10-25 13:17:44 -04:00
Elliot DeNolf
f8983e9e5c refactor: rework templating 2022-10-25 12:54:39 -04:00
Elliot DeNolf
aab71f03b3 chore: linting, update deps 2022-10-24 23:25:04 -04:00
Elliot DeNolf
447d88bf82 test: update createProject test 2022-10-24 23:06:45 -04:00
Elliot DeNolf
897e94f2f4 feat: basic readme template 2022-10-24 22:58:55 -04:00
Elliot DeNolf
87936e5b52 refactor: linting and file naming 2022-10-24 22:57:09 -04:00
Elliot DeNolf
5e02762715 0.3.14 2022-09-13 19:14:19 -07:00
Elliot DeNolf
0785820539 feat: automatically generate secret 2022-09-13 19:14:04 -07:00
Elliot DeNolf
bb6d545aae 0.3.13 2022-08-22 10:15:08 -04:00
Elliot DeNolf
4cdc94d92f feat: always write .npmrc 2022-08-22 10:14:22 -04:00
Elliot DeNolf
c28dca6fc0 0.3.12 2022-07-28 00:59:46 -04:00
Elliot DeNolf
95e630201a feat: include .gitignore 2022-07-28 00:59:35 -04:00
Elliot DeNolf
84100be7eb 0.3.11 2022-07-23 18:23:23 -07:00
Elliot DeNolf
0c7007ae9a feat: legacy-peer-deps=true template 2022-07-23 18:23:04 -07:00
Elliot DeNolf
3e7e3669fe 0.3.10 2022-07-23 17:56:38 -07:00
Elliot DeNolf
1d3bb9c287 feat: add .npmrc with legacy-peer-deps=true 2022-07-23 17:54:15 -07:00
Elliot DeNolf
cacc624f5a 0.3.9 2022-07-21 13:51:01 -07:00
Elliot DeNolf
04dd824f0a feat: add graphql schema gen script 2022-07-21 13:43:01 -07:00
Elliot DeNolf
dc929732b1 feat: copyfiles script, allowJs, bump payload 2022-07-21 13:13:48 -07:00
Elliot DeNolf
a47fd23199 0.3.8 2022-05-16 18:45:11 -04:00
Elliot DeNolf
d205da0aa4 feat: remove licensing env var comments 2022-05-16 18:43:13 -04:00
Elliot DeNolf
c414f12527 feat: use prepublishOnly instead of prepublish 2022-03-23 00:48:31 -04:00
Elliot DeNolf
d9418c9fe3 0.3.7 2022-03-18 10:30:36 -04:00
Elliot DeNolf
65e2ba9bd0 feat: update sentry and error handling 2022-03-18 10:04:46 -04:00
Elliot DeNolf
b3f808644f chore: add comment about populating license key 2022-03-04 16:23:29 -05:00
Elliot DeNolf
1e30435525 test: fix tests 2022-03-02 10:16:58 -05:00
Elliot DeNolf
156d25741b 0.3.6 2022-03-02 09:35:09 -05:00
Elliot DeNolf
de3ee812cd fix: available templates in help message 2022-03-02 09:23:27 -05:00
Elliot DeNolf
234fb33864 0.3.5 2022-02-13 08:59:52 -05:00
Elliot DeNolf
c168bb5201 fix: removing onlyNameIfPublic hook, causes confusion in other hooks 2022-02-13 08:57:31 -05:00
Elliot DeNolf
0ce5d774cb fix: use proper type for collection hook 2022-02-12 09:18:05 -05:00
Elliot DeNolf
d2c2bbd711 0.3.4 2022-02-05 14:21:18 -05:00
Elliot DeNolf
88193adebb feat: add beta flag to use payload@beta 2022-02-05 14:21:03 -05:00
Elliot DeNolf
eac44f9496 0.3.3 2022-02-03 11:01:30 -05:00
Elliot DeNolf
6400095f1f feat: redirect root to admin panel 2022-02-03 11:00:22 -05:00
Elliot DeNolf
b57267e60a feat: add prepublish script, upgrade jest 2021-12-13 21:43:21 -05:00
Elliot DeNolf
79541b6ba7 0.3.2 2021-12-13 21:33:29 -05:00
Elliot DeNolf
0420098e94 fix: better check for existing dir 2021-12-13 21:32:52 -05:00
Elliot DeNolf
9f80634be4 0.3.1 2021-12-10 10:27:04 -05:00
Elliot DeNolf
25ecb27aed 0.3.0 2021-12-09 17:19:51 -05:00
Elliot DeNolf
2ff2efd4b2 feat: add type generation scripts 2021-12-09 17:14:40 -05:00
Elliot DeNolf
ff7a29179d feat: bump payload version 2021-12-09 17:14:14 -05:00
Elliot DeNolf
8403f8ac2a 0.2.0 2021-09-10 17:36:01 -04:00
Elliot DeNolf
df0d4fa726 fix: proper format for server.js files 2021-09-10 17:31:14 -04:00
Elliot DeNolf
2a4bb5a11d fix: add script with shebang 2021-09-10 17:09:45 -04:00
Elliot DeNolf
2b6c5e42b5 feat: only include dist/ and package.json 2021-09-10 17:01:50 -04:00
Elliot DeNolf
a1a4765a94 refactor: rewrite in typescript (#7) 2021-09-10 16:56:37 -04:00
Elliot DeNolf
64d0bc7a16 0.1.22 2021-06-01 14:02:24 -04:00
Elliot DeNolf
b1fb43baf5 fix: remove optional chaining 2021-06-01 14:01:26 -04:00
Elliot DeNolf
bb309ca843 0.1.21 2021-05-11 23:21:21 -04:00
Elliot DeNolf
760662263f feat: tag package manager 2021-05-11 23:18:45 -04:00
Elliot DeNolf
bce5205cf1 Merge pull request #6 from payloadcms/fix/node-16-npm-peer-dependencies
fix: workaround for npm install peer dependency on node 16
2021-05-06 21:24:48 -04:00
Dan Ribbens
0b21726af6 fix: workaround for npm install peer dependency on node 16 2021-05-06 13:19:53 -04:00
Elliot DeNolf
04a7d256c5 0.1.20 2021-04-29 13:44:34 -04:00
Elliot DeNolf
8a9915b58a Merge pull request #5 from payloadcms/sentry 2021-04-29 13:44:09 -04:00
Elliot DeNolf
820e867804 feat: add payload_version tag 2021-04-29 13:33:32 -04:00
Elliot DeNolf
699314a781 feat: add sentry 2021-04-28 15:43:51 -04:00
Elliot DeNolf
86552e62ff 0.1.19 2021-04-19 11:17:53 -04:00
Elliot DeNolf
13769d3cdc feat: remove all explicit labels 2021-04-19 11:17:26 -04:00
Elliot DeNolf
158ae0de30 chore: add package.json descriptions 2021-04-19 10:43:42 -04:00
Elliot DeNolf
04056513d7 0.1.18 2021-04-06 23:06:50 -04:00
Elliot DeNolf
2b7e6dda2f feat: add blank templates 2021-04-06 23:06:25 -04:00
Elliot DeNolf
b11464542a 0.1.17 2021-04-01 09:54:07 -04:00
Elliot DeNolf
b97568f394 chore: add jsx property to ts templates 2021-04-01 09:41:38 -04:00
Elliot DeNolf
18e8839b8c 0.1.16 2021-02-04 22:44:45 -05:00
Elliot DeNolf
f5e5bfae81 chore: update README 2021-02-04 22:44:23 -05:00
Elliot DeNolf
b27ab75e07 Merge pull request #4 from payloadcms/feature/project-name-first-argument 2021-02-04 22:42:50 -05:00
Elliot DeNolf
5799e4015f Merge pull request #3 from payloadcms/feature/compatible-package-dependency-version
feat: create project use compatible version of payload dependency
2021-02-04 22:42:38 -05:00
Dan Ribbens
2f72ed78e1 feat: set the project name from first argument 2021-02-04 15:24:21 -05:00
Dan Ribbens
5f3f038a6b feat: create project use compatible version of payload dependency 2021-02-04 15:01:30 -05:00
Elliot DeNolf
5b29852c0a 0.1.15 2021-01-26 14:43:57 -05:00
Elliot DeNolf
9616e43035 fix: adjust verbiage for spinner and others 2021-01-26 12:32:49 -05:00
Elliot DeNolf
b918425e72 fix: mask encryption key input 2021-01-26 12:32:16 -05:00
Elliot DeNolf
9ed5f5b6fc feat: validate template before asking for project name 2021-01-19 14:23:09 -05:00
Elliot DeNolf
1721d118a8 feat: prompt language and show/validate template names 2021-01-19 10:51:25 -05:00
Elliot DeNolf
8dc400c65a refactor: rename template directories 2021-01-19 10:20:10 -05:00
Elliot DeNolf
a5ac793443 0.1.14 2021-01-18 18:47:54 -05:00
Elliot DeNolf
8d6d995d78 chore: missing comment 2021-01-18 18:46:14 -05:00
Elliot DeNolf
4652255d4f feat: add admin settings to todos 2021-01-18 18:44:54 -05:00
Elliot DeNolf
a274f2e5ca fix: simplify user for todo template 2021-01-18 18:38:29 -05:00
Elliot DeNolf
ed4528096a Merge pull request #2 from payloadcms/blog-template 2021-01-18 18:29:09 -05:00
Elliot DeNolf
f7946af404 feat: add blog template 2021-01-18 18:23:36 -05:00
Elliot DeNolf
26ead270a2 feat: add beforeRead hook for user 2021-01-18 16:19:29 -05:00
Elliot DeNolf
c45c784c58 feat: update blog access 2021-01-18 16:02:14 -05:00
Elliot DeNolf
6b6977cc00 feat: add blog-typescript template 2021-01-18 15:34:46 -05:00
Elliot DeNolf
41a6abd2e4 feat: retrieve latest payload and put into package.json 2021-01-18 14:45:47 -05:00
Elliot DeNolf
d59ccc0f34 0.1.13 2021-01-16 23:55:53 -05:00
Elliot DeNolf
870946d01b fix: handle if no yarn 2021-01-16 23:52:41 -05:00
Elliot DeNolf
3bf68ef9d4 0.1.12 2021-01-16 21:40:57 -05:00
Elliot DeNolf
60d7d51a0a chore: payload version 0.1.145 2021-01-16 21:40:52 -05:00
Elliot DeNolf
61deb2c873 0.1.11 2021-01-16 10:35:37 -05:00
Elliot DeNolf
0ae27d4212 0.1.10 2021-01-16 10:26:56 -05:00
Elliot DeNolf
3c96622313 chore: payload version 0.1.144 2021-01-16 10:19:16 -05:00
Elliot DeNolf
8066ce6f49 feat: add auth and adjust access on Users 2021-01-16 10:16:56 -05:00
Elliot DeNolf
b7f9ffc51a feat: use explicit Users collection instead of default, rename Todo 2021-01-14 23:42:05 -05:00
Elliot DeNolf
4a873a5ae3 fix: nodemon configs 2021-01-14 22:36:29 -05:00
Elliot DeNolf
c080deb0b8 0.1.9 2021-01-14 09:35:22 -05:00
Elliot DeNolf
8cefa8181c chore: payload version 0.1.143 2021-01-14 09:34:48 -05:00
Elliot DeNolf
a34dd651b1 0.1.8 2021-01-13 10:09:23 -05:00
Elliot DeNolf
a86041836f feat: better messaging for encryption key 2021-01-13 10:08:01 -05:00
Elliot DeNolf
6dbd760a2e 0.1.7 2021-01-09 10:51:00 -05:00
Elliot DeNolf
4181a84e9b feat: log admin url to console 2021-01-09 10:50:49 -05:00
Elliot DeNolf
b4d5168409 0.1.6 2021-01-09 01:36:45 -05:00
Elliot DeNolf
74756c0703 feat: add list name, task name, and output URLs to console 2021-01-09 01:35:34 -05:00
Elliot DeNolf
dce57d6fdd 0.1.5 2021-01-08 09:29:01 -05:00
Dan Ribbens
d55df67642 fix: create typescript project use cross-env for windows support for payload config path 2021-01-06 14:12:55 -05:00
Elliot DeNolf
769d9063d5 0.1.3 2021-01-05 19:13:44 -05:00
Elliot DeNolf
8f95a23df9 0.1.2 2021-01-05 19:13:36 -05:00
Elliot DeNolf
9816c33015 fix: template path 2021-01-05 19:11:37 -05:00
Elliot DeNolf
1d14c976f2 0.1.1 2021-01-05 19:07:57 -05:00
Elliot DeNolf
63c436e0ac fix: add shebang 2021-01-05 19:07:50 -05:00
Elliot DeNolf
a4700d7a9d 0.1.0 2021-01-05 19:04:48 -05:00
Elliot DeNolf
e5a1fe0771 chore: MIT license 2021-01-05 19:04:27 -05:00
Elliot DeNolf
b101ff86a9 feat: move cli inside bin 2021-01-05 19:03:20 -05:00
Elliot DeNolf
0c5a6044a0 feat: welcome message and better help 2021-01-05 19:00:50 -05:00
Elliot DeNolf
6fc7c0b9ad fix: js and ts template fixes 2021-01-05 18:45:22 -05:00
Elliot DeNolf
9459e82161 chore: bump payload and adjust tsconfig.json 2021-01-05 16:53:23 -05:00
Elliot DeNolf
62501eb3b8 feat: bump payload version 2021-01-05 14:44:44 -05:00
Elliot DeNolf
df000b7508 feat: add build script and update payload package 2021-01-05 13:59:53 -05:00
Elliot DeNolf
c04bde6725 feat: better header and messages 2020-12-28 21:55:04 -05:00
Elliot DeNolf
1fe8ae39cb feat: add nodemon to all templates 2020-12-28 20:43:13 -05:00
Elliot DeNolf
e2049b9564 feat: add typescript template 2020-12-28 20:29:38 -05:00
Elliot DeNolf
49d9836ab4 chore: remove unused dependencies 2020-12-28 16:16:55 -05:00
Elliot DeNolf
4ed38575bf feat: add --use-npm flag 2020-12-28 16:15:31 -05:00
Elliot DeNolf
ef166cd70d feat: write db uri and secret to .env file 2020-12-28 02:54:16 -05:00
Elliot DeNolf
d45665f092 feat: cross-platform symbols and success message formatting 2020-12-27 23:29:08 -05:00
Elliot DeNolf
10bae6dab7 feat: better messaging and documentation links 2020-12-27 23:00:38 -05:00
Elliot DeNolf
ad25c86fdd chore: add package.json name 2020-12-27 21:32:19 -05:00
Elliot DeNolf
f064ff35f3 fix: remove --name requirement 2020-12-27 21:32:05 -05:00
Elliot DeNolf
55b44b41bf feat: add help message and README 2020-12-27 21:25:14 -05:00
Elliot DeNolf
1f3e9b22f4 feat: initial commit 2020-12-27 21:03:19 -05:00
270 changed files with 13616 additions and 4578 deletions

View File

@@ -10,3 +10,9 @@ cdaa0acd61d3001407609915bd573b78565d5571
# prettier write again
dfac7395fed95fc5d8ebca21b786ce70821942bb
# lint and format plugin-cloud
fb7d1be2f3325d076b7c967b1730afcef37922c2
# lint and format create-payload-app
5fd3d430001efe86515262ded5e26f00c1451181

View File

@@ -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

View File

@@ -26,9 +26,8 @@
</h4>
<hr/>
<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>
> [!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>

View File

@@ -129,7 +129,7 @@ To add a _new_ view to the Admin Panel, simply add another key to the `views` ob
}
```
_For more examples regarding how to customize components, look at the following [examples](https://github.com/payloadcms/payload/tree/master/test/admin/components)._
_For more examples regarding how to customize components, look at the following [examples](https://github.com/payloadcms/payload/tree/main/test/admin/components)._
For help on how to build your own custom view components, see [building a custom view component](#building-a-custom-view-component).
@@ -399,12 +399,12 @@ Your custom view components will be given all the props that a React Router `<Ro
#### Example
You can find examples of custom views in the [Payload source code `/test/admin/components/views` folder](https://github.com/payloadcms/payload/tree/master/test/admin/components/views). There, you'll find two custom views:
You can find examples of custom views in the [Payload source code `/test/admin/components/views` folder](https://github.com/payloadcms/payload/tree/main/test/admin/components/views). There, you'll find two custom views:
1. A custom view that uses the `DefaultTemplate`, which is the built-in Payload template that displays the sidebar and "eyebrow nav"
1. A custom view that uses the `MinimalTemplate` - which is just a centered template used for things like logging in or out
To see how to pass in your custom views to create custom views of your own, take a look at the `admin.components.views` property of the [Payload test admin config](https://github.com/payloadcms/payload/blob/master/test/admin/config.ts).
To see how to pass in your custom views to create custom views of your own, take a look at the `admin.components.views` property of the [Payload test admin config](https://github.com/payloadcms/payload/blob/main/test/admin/config.ts).
### Fields

View File

@@ -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

View File

@@ -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: [

View File

@@ -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)
}

View File

@@ -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'],
},
}

View File

@@ -131,6 +131,7 @@ const result = await payload.find({
depth: 2,
page: 1,
limit: 10,
pagination: false, // If you want to disable pagination count, etc.
where: {}, // pass a `where` query here
sort: '-title',
locale: 'en',

View File

@@ -59,3 +59,7 @@ All Payload APIs support the pagination controls below. With them, you can creat
| ------- | --------------------------------------- |
| `limit` | Limits the number of documents returned |
| `page` | Get a specific page number |
### Disabling pagination within Local API
For `find` operations within the Local API, you can disable pagination to retrieve all documents from a collection by passing `pagination: false` to the `find` local operation. This is not supported in REST or GraphQL, however, because it could potentially lead to malicious activity.

View File

@@ -17,9 +17,9 @@
"lint:fix": "eslint --fix --ext .ts,.tsx src"
},
"dependencies": {
"@payloadcms/bundler-webpack": "^1.0.0-beta.5",
"@payloadcms/db-mongodb": "^1.0.0-beta.8",
"@payloadcms/richtext-slate": "^1.0.0-beta.4",
"@payloadcms/bundler-webpack": "latest",
"@payloadcms/db-mongodb": "latest",
"@payloadcms/richtext-slate": "latest",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"payload": "latest"

File diff suppressed because it is too large Load Diff

View File

@@ -20,9 +20,9 @@
"lint:fix": "eslint --fix --ext .ts,.tsx src"
},
"dependencies": {
"@payloadcms/bundler-webpack": "^1.0.0-beta.5",
"@payloadcms/db-mongodb": "^1.0.0-beta.8",
"@payloadcms/richtext-slate": "^1.0.0-beta.4",
"@payloadcms/bundler-webpack": "latest",
"@payloadcms/db-mongodb": "latest",
"@payloadcms/richtext-slate": "latest",
"dotenv": "^8.2.0",
"escape-html": "^1.0.3",
"express": "^4.17.1",

File diff suppressed because it is too large Load Diff

View File

@@ -18,9 +18,9 @@
"lint:fix": "eslint --fix --ext .ts,.tsx src"
},
"dependencies": {
"@payloadcms/bundler-webpack": "^1.0.0-beta.5",
"@payloadcms/db-mongodb": "^1.0.0-beta.8",
"@payloadcms/richtext-slate": "^1.0.0-beta.4",
"@payloadcms/bundler-webpack": "latest",
"@payloadcms/db-mongodb": "latest",
"@payloadcms/richtext-slate": "latest",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"payload": "latest"

File diff suppressed because it is too large Load Diff

View File

@@ -15,9 +15,9 @@
"generate:graphQLSchema": "PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:graphQLSchema"
},
"dependencies": {
"@payloadcms/bundler-webpack": "^1.0.0-beta.5",
"@payloadcms/db-mongodb": "^1.0.0-beta.8",
"@payloadcms/richtext-slate": "^1.0.0-beta.4",
"@payloadcms/bundler-webpack": "latest",
"@payloadcms/db-mongodb": "latest",
"@payloadcms/richtext-slate": "latest",
"@faceless-ui/modal": "^2.0.1",
"@payloadcms/plugin-form-builder": "^1.0.12",
"@payloadcms/plugin-seo": "^1.0.8",

File diff suppressed because it is too large Load Diff

View File

@@ -18,9 +18,9 @@
"lint:fix": "eslint --fix --ext .ts,.tsx src"
},
"dependencies": {
"@payloadcms/bundler-webpack": "^1.0.0-beta.5",
"@payloadcms/db-mongodb": "^1.0.0-beta.8",
"@payloadcms/richtext-slate": "^1.0.0-beta.4",
"@payloadcms/bundler-webpack": "latest",
"@payloadcms/db-mongodb": "latest",
"@payloadcms/richtext-slate": "latest",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"payload": "latest"

View File

@@ -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: {

File diff suppressed because it is too large Load Diff

View File

@@ -18,9 +18,9 @@
"lint:fix": "eslint --fix --ext .ts,.tsx src"
},
"dependencies": {
"@payloadcms/bundler-webpack": "^1.0.0-beta.5",
"@payloadcms/db-mongodb": "^1.0.0-beta.8",
"@payloadcms/richtext-slate": "^1.0.0-beta.4",
"@payloadcms/bundler-webpack": "latest",
"@payloadcms/db-mongodb": "latest",
"@payloadcms/richtext-slate": "latest",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"payload": "latest"

File diff suppressed because it is too large Load Diff

View File

@@ -17,9 +17,9 @@
"lint:fix": "eslint --fix --ext .ts,.tsx src"
},
"dependencies": {
"@payloadcms/bundler-webpack": "^1.0.0-beta.5",
"@payloadcms/db-mongodb": "^1.0.0-beta.8",
"@payloadcms/richtext-slate": "^1.0.0-beta.4",
"@payloadcms/bundler-webpack": "latest",
"@payloadcms/db-mongodb": "latest",
"@payloadcms/richtext-slate": "latest",
"@payloadcms/plugin-redirects": "^1.0.0",
"dotenv": "^8.2.0",
"express": "^4.17.1",

File diff suppressed because it is too large Load Diff

View File

@@ -20,8 +20,8 @@
"lint-staged": "lint-staged",
"pretest": "pnpm build",
"reinstall": "pnpm clean:unix && pnpm install",
"list:packages": "./scripts/list_published_packages.sh beta",
"script:release:beta": "./scripts/release_beta.sh",
"script:list-packages": "tsx ./scripts/list-packages.ts",
"script:release": "tsx ./scripts/release.ts",
"test": "pnpm test:int && pnpm test:components && pnpm test:e2e",
"test:components": "cross-env jest --config=jest.components.config.js",
"test:e2e": "npx playwright install --with-deps && ts-node -T ./test/runE2E.ts",
@@ -74,6 +74,7 @@
"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",

View File

@@ -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",

View File

@@ -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 => ({

View File

@@ -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[]),
],
},

View 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,
}

View 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
```

View File

@@ -0,0 +1,2 @@
#!/usr/bin/env node
require('../dist/index.js')

View 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,
}

View 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"
}
}

View 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}`))

View 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')
}
}

View 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
})
})

View 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')
}
}

View File

@@ -0,0 +1,5 @@
import { randomBytes } from 'crypto'
export function generateSecret(): string {
return randomBytes(32).toString('hex').slice(0, 24)
}

View 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',
},
}

View 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
}

View 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
}

View 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)
}

View 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',
},
]
}

View 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)
}
}

View 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
}

View 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'

View 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)}`)
}

View 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}`,
})
}

View 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" }]
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "0.1.3",
"version": "0.1.7",
"description": "The officially supported Postgres database adapter for Payload",
"repository": "https://github.com/payloadcms/payload",
"license": "MIT",
@@ -22,7 +22,7 @@
"dependencies": {
"@libsql/client": "^0.3.1",
"console-table-printer": "2.11.2",
"drizzle-kit": "0.19.13",
"drizzle-kit": "0.19.13-e99bac1",
"drizzle-orm": "0.28.5",
"pg": "8.11.3",
"prompts": "2.4.2",
@@ -34,9 +34,6 @@
"@types/to-snake-case": "1.0.0",
"payload": "workspace:*"
},
"peerDependencies": {
"better-sqlite3": "^8.5.0"
},
"publishConfig": {
"main": "./dist/index.js",
"registry": "https://registry.npmjs.org/",

View File

@@ -1,6 +1,5 @@
import type { Connect } from 'payload/database'
import { pushSchema } from 'drizzle-kit/utils'
import { eq, sql } from 'drizzle-orm'
import { drizzle } from 'drizzle-orm/node-postgres'
import { numeric, pgTable, timestamp, varchar } from 'drizzle-orm/pg-core'
@@ -40,6 +39,8 @@ export const connect: Connect = async function connect(this: PostgresAdapter, pa
)
return
const { pushSchema } = require('drizzle-kit/utils')
// This will prompt if clarifications are needed for Drizzle to push new schema
const { apply, hasDataLoss, statementsToExecute, warnings } = await pushSchema(
this.schema,

View File

@@ -2,7 +2,6 @@
import type { DrizzleSnapshotJSON } from 'drizzle-kit/utils'
import type { CreateMigration } from 'payload/database'
import { generateDrizzleJson, generateMigration } from 'drizzle-kit/utils'
import fs from 'fs'
import prompts from 'prompts'
@@ -61,6 +60,8 @@ export const createMigration: CreateMigration = async function createMigration(
fs.mkdirSync(dir)
}
const { generateDrizzleJson, generateMigration } = require('drizzle-kit/utils')
const [yyymmdd, hhmmss] = new Date().toISOString().split('T')
const formattedDate = yyymmdd.replace(/\D/g, '')
const formattedTime = hhmmss.split('.')[0].replace(/\D/g, '')

View File

@@ -15,7 +15,6 @@ type Args = Omit<FindArgs, 'collection'> & {
adapter: PostgresAdapter
fields: Field[]
tableName: string
version?: boolean
}
export const findMany = async function find({
@@ -27,9 +26,8 @@ export const findMany = async function find({
pagination,
req = {} as PayloadRequest,
skip,
sort: sortArg,
sort,
tableName,
version,
where: whereArg,
}: Args) {
const db = adapter.sessions[req.transactionID]?.db || adapter.drizzle
@@ -42,11 +40,6 @@ export const findMany = async function find({
let hasNextPage: boolean
let pagingCounter: number
let selectDistinctResult
let sort = sortArg
if (version && sort) {
const direction = sort[0] === '-' ? '-' : ''
sort = `${direction}version.${direction.length === 1 ? sort.substring(1) : sort}`
}
const { joinAliases, joins, orderBy, selectFields, where } = await buildQuery({
adapter,

View File

@@ -39,7 +39,6 @@ export const findVersions: FindVersions = async function findVersions(
skip,
sort,
tableName,
version: true,
where,
})
}

View File

@@ -2,9 +2,7 @@
import type { Payload } from 'payload'
import type { Migration } from 'payload/database'
import { generateDrizzleJson } from 'drizzle-kit/utils'
import { readMigrationFiles } from 'payload/database'
import { DatabaseError } from 'pg'
import prompts from 'prompts'
import type { PostgresAdapter } from './types'
@@ -78,6 +76,8 @@ export async function migrate(this: PostgresAdapter): Promise<void> {
}
async function runMigrationFile(payload: Payload, migration: Migration, batch: number) {
const { generateDrizzleJson } = require('drizzle-kit/utils')
const start = Date.now()
payload.logger.info({ msg: `Migrating: ${migration.name}` })

View File

@@ -34,12 +34,14 @@ type Args = {
aliasTable?: GenericTable
collectionPath: string
columnPrefix?: string
constraintPath?: string
constraints?: Constraint[]
fields: (Field | TabAsField)[]
joinAliases: BuildQueryJoinAliases
joins: BuildQueryJoins
locale?: string
pathSegments: string[]
rootTableName?: string
selectFields: Record<string, GenericColumn>
tableName: string
}
@@ -53,17 +55,22 @@ export const getTableColumnFromPath = ({
aliasTable,
collectionPath,
columnPrefix = '',
constraintPath: incomingConstraintPath,
constraints = [],
fields,
joinAliases,
joins,
locale: incomingLocale,
pathSegments: incomingSegments,
rootTableName: incomingRootTableName,
selectFields,
tableName,
}: Args): TableColumn => {
const fieldPath = incomingSegments[0]
let locale = incomingLocale
const rootTableName = incomingRootTableName || tableName
let constraintPath = incomingConstraintPath || ''
const field = flattenTopLevelFields(fields as Field[]).find(
(fieldToFind) => fieldAffectsData(fieldToFind) && fieldToFind.name === fieldPath,
) as Field | TabAsField
@@ -105,6 +112,7 @@ export const getTableColumnFromPath = ({
aliasTable,
collectionPath,
columnPrefix,
constraintPath,
constraints,
fields: field.tabs.map((tab) => ({
...tab,
@@ -114,6 +122,7 @@ export const getTableColumnFromPath = ({
joins,
locale,
pathSegments: pathSegments.slice(1),
rootTableName,
selectFields,
tableName: newTableName,
})
@@ -125,12 +134,14 @@ export const getTableColumnFromPath = ({
aliasTable,
collectionPath,
columnPrefix: `${columnPrefix}${field.name}_`,
constraintPath,
constraints,
fields: field.fields,
joinAliases,
joins,
locale,
pathSegments: pathSegments.slice(1),
rootTableName,
selectFields,
tableName: newTableName,
})
@@ -140,12 +151,14 @@ export const getTableColumnFromPath = ({
aliasTable,
collectionPath,
columnPrefix,
constraintPath,
constraints,
fields: field.fields,
joinAliases,
joins,
locale,
pathSegments: pathSegments.slice(1),
rootTableName,
selectFields,
tableName: newTableName,
})
@@ -172,12 +185,14 @@ export const getTableColumnFromPath = ({
aliasTable,
collectionPath,
columnPrefix: `${columnPrefix}${field.name}_`,
constraintPath,
constraints,
fields: field.fields,
joinAliases,
joins,
locale,
pathSegments: pathSegments.slice(1),
rootTableName,
selectFields,
tableName: newTableName,
})
@@ -185,6 +200,7 @@ export const getTableColumnFromPath = ({
case 'array': {
newTableName = `${tableName}_${toSnakeCase(field.name)}`
constraintPath = `${constraintPath}${field.name}.%.`
if (locale && field.localized && adapter.payload.config.localization) {
joins[newTableName] = and(
eq(adapter.tables[tableName].id, adapter.tables[newTableName]._parentID),
@@ -206,12 +222,14 @@ export const getTableColumnFromPath = ({
return getTableColumnFromPath({
adapter,
collectionPath,
constraintPath,
constraints,
fields: field.fields,
joinAliases,
joins,
locale,
pathSegments: pathSegments.slice(1),
rootTableName,
selectFields,
tableName: newTableName,
})
@@ -229,12 +247,14 @@ export const getTableColumnFromPath = ({
result = getTableColumnFromPath({
adapter,
collectionPath,
constraintPath: '',
constraints: blockConstraints,
fields: block.fields,
joinAliases,
joins,
locale,
pathSegments: pathSegments.slice(1),
rootTableName,
selectFields: blockSelectFields,
tableName: newTableName,
})
@@ -283,9 +303,8 @@ export const getTableColumnFromPath = ({
case 'relationship':
case 'upload': {
let relationshipFields
const relationTableName = `${tableName}_rels`
const relationTableName = `${rootTableName}_rels`
const newCollectionPath = pathSegments.slice(1).join('.')
const aliasRelationshipTableName = uuid()
const aliasRelationshipTable = alias(
adapter.tables[relationTableName],
@@ -295,7 +314,7 @@ export const getTableColumnFromPath = ({
// Join in the relationships table
joinAliases.push({
condition: eq(
(aliasTable || adapter.tables[tableName]).id,
(aliasTable || adapter.tables[rootTableName]).id,
aliasRelationshipTable.parent,
),
table: aliasRelationshipTable,
@@ -306,7 +325,7 @@ export const getTableColumnFromPath = ({
constraints.push({
columnName: 'path',
table: aliasRelationshipTable,
value: field.name,
value: `${constraintPath}${field.name}`,
})
let newAliasTable
@@ -368,6 +387,7 @@ export const getTableColumnFromPath = ({
joins,
locale,
pathSegments: pathSegments.slice(1),
rootTableName: newTableName,
selectFields,
tableName: newTableName,
})

View File

@@ -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'
@@ -100,7 +100,11 @@ export async function parseParams({
const val = where[relationOrPath][operator]
queryConstraints.forEach(({ columnName: col, table: constraintTable, value }) => {
constraints.push(operatorMap.equals(constraintTable[col], value))
if (typeof value === 'string' && value.indexOf('%') > -1) {
constraints.push(operatorMap.like(constraintTable[col], value))
} else {
constraints.push(operatorMap.equals(constraintTable[col], value))
}
})
if (['json', 'richText'].includes(field.type) && Array.isArray(pathSegments)) {
@@ -147,6 +151,7 @@ export async function parseParams({
const { operator: queryOperator, value: queryValue } = sanitizeQueryValue({
field,
operator,
relationOrPath,
val,
})
@@ -158,6 +163,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),

View File

@@ -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

View File

@@ -32,7 +32,6 @@ export const queryDrafts: QueryDrafts = async function queryDrafts({
req,
sort,
tableName,
version: true,
where: combinedWhere,
})

View File

@@ -6,11 +6,12 @@
// drizzle-kit@utils
import { generateDrizzleJson, generateMigration, pushSchema } from 'drizzle-kit/utils'
import { drizzle } from 'drizzle-orm/node-postgres'
import { Pool } from 'pg'
async function generateUsage() {
const { generateDrizzleJson, generateMigration } = require('drizzle-kit/utils')
// @ts-expect-error Just TypeScript being broken // TODO: Open TypeScript issue
const schema = await import('./data/users')
// @ts-expect-error Just TypeScript being broken // TODO: Open TypeScript issue
@@ -25,6 +26,8 @@ async function generateUsage() {
}
async function pushUsage() {
const { pushSchema } = require('drizzle-kit/utils')
// @ts-expect-error Just TypeScript being broken // TODO: Open TypeScript issue
const schemaAfter = await import('./data/users-after')

View File

@@ -252,6 +252,8 @@ export const traverseFields = ({
}
case 'array': {
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
const arrayTableName = `${newTableName}_${toSnakeCase(field.name)}`
const baseColumns: Record<string, PgColumnBuilder> = {
_order: integer('_order').notNull(),
@@ -277,7 +279,7 @@ export const traverseFields = ({
adapter,
baseColumns,
baseExtraConfig,
disableNotNull,
disableNotNull: disableNotNullFromHere,
disableUnique,
fields: field.fields,
rootRelationsToBuild,
@@ -314,6 +316,8 @@ export const traverseFields = ({
}
case 'blocks': {
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
field.blocks.forEach((block) => {
const blockTableName = `${rootTableName}_blocks_${toSnakeCase(block.slug)}`
if (!adapter.tables[blockTableName]) {
@@ -343,7 +347,7 @@ export const traverseFields = ({
adapter,
baseColumns,
baseExtraConfig,
disableNotNull,
disableNotNull: disableNotNullFromHere,
disableUnique,
fields: block.fields,
rootRelationsToBuild,
@@ -428,6 +432,8 @@ export const traverseFields = ({
break
}
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
const {
hasLocalizedField: groupHasLocalizedField,
hasLocalizedManyNumberField: groupHasLocalizedManyNumberField,
@@ -438,7 +444,7 @@ export const traverseFields = ({
buildRelationships,
columnPrefix: `${columnName}_`,
columns,
disableNotNull,
disableNotNull: disableNotNullFromHere,
disableUnique,
fieldPrefix: `${fieldName}_`,
fields: field.fields,
@@ -463,6 +469,8 @@ export const traverseFields = ({
}
case 'tabs': {
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
const {
hasLocalizedField: tabHasLocalizedField,
hasLocalizedManyNumberField: tabHasLocalizedManyNumberField,
@@ -473,7 +481,7 @@ export const traverseFields = ({
buildRelationships,
columnPrefix,
columns,
disableNotNull,
disableNotNull: disableNotNullFromHere,
disableUnique,
fieldPrefix,
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
@@ -500,6 +508,7 @@ export const traverseFields = ({
case 'row':
case 'collapsible': {
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
const {
hasLocalizedField: rowHasLocalizedField,
hasLocalizedManyNumberField: rowHasLocalizedManyNumberField,
@@ -510,7 +519,7 @@ export const traverseFields = ({
buildRelationships,
columnPrefix,
columns,
disableNotNull,
disableNotNull: disableNotNullFromHere,
disableUnique,
fieldPrefix,
fields: field.fields,

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview-react",
"version": "0.1.2",
"version": "0.1.3",
"description": "The official live preview React SDK for Payload",
"repository": "https://github.com/payloadcms/payload",
"license": "MIT",

View File

@@ -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)
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview",
"version": "0.1.2",
"version": "0.1.3",
"description": "The official live preview JavaScript SDK for Payload",
"repository": "https://github.com/payloadcms/payload",
"license": "MIT",

View File

@@ -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,

View File

@@ -1,4 +1,5 @@
export { handleMessage } from './handleMessage'
export { mergeData } from './mergeData'
export { ready } from './ready'
export { subscribe } from './subscribe'
export { unsubscribe } from './unsubscribe'

View 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,
)
}
}

View File

@@ -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
}

View File

@@ -1,6 +1,6 @@
{
"name": "payload",
"version": "2.0.4",
"version": "2.0.6",
"description": "Node, React and MongoDB Headless CMS and Application Framework",
"license": "MIT",
"main": "./dist/index.js",
@@ -208,8 +208,7 @@
"webpack": "^5.78.0"
},
"engines": {
"node": ">=14",
"pnpm": ">=8"
"node": ">=14"
},
"files": [
"bin.js",

View File

@@ -55,24 +55,6 @@ export const DocumentControls: React.FC<{
const { i18n, t } = useTranslation('general')
let showPreviewButton = false
if (collection) {
showPreviewButton =
isEditing &&
collection?.admin?.preview &&
collection?.versions?.drafts &&
!collection?.versions?.drafts?.autosave
}
if (global) {
showPreviewButton =
isEditing &&
global?.admin?.preview &&
global?.versions?.drafts &&
!global?.versions?.drafts?.autosave
}
const showDotMenu = Boolean(collection && id && !disableActions)
return (
@@ -165,9 +147,12 @@ export const DocumentControls: React.FC<{
</div>
<div className={`${baseClass}__controls-wrapper`}>
<div className={`${baseClass}__controls`}>
{showPreviewButton && (
{(collection?.admin?.preview || global?.admin?.preview) && (
<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 +163,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>
)}

View File

@@ -51,6 +51,7 @@ const Duplicate: React.FC<Props> = ({ id, collection, slug }) => {
},
params: {
depth: 0,
draft: true,
locale,
},
})

View File

@@ -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>
)

View File

@@ -61,6 +61,7 @@ export const addFieldStatePromise = async ({
user,
value: data?.[field.name],
})
if (data?.[field.name]) {
data[field.name] = valueWithDefault
}
@@ -145,8 +146,8 @@ export const addFieldStatePromise = async ({
fieldState.value = null
fieldState.initialValue = null
} else {
fieldState.value = arrayValue
fieldState.initialValue = arrayValue
fieldState.value = arrayValue.length
fieldState.initialValue = arrayValue.length
if (arrayValue.length > 0) {
fieldState.disableFormData = true
@@ -236,8 +237,8 @@ export const addFieldStatePromise = async ({
fieldState.value = null
fieldState.initialValue = null
} else {
fieldState.value = blocksValue
fieldState.initialValue = blocksValue
fieldState.value = blocksValue.length
fieldState.initialValue = blocksValue.length
if (blocksValue.length > 0) {
fieldState.disableFormData = true

View File

@@ -8,6 +8,9 @@ import getSiblingData from './getSiblingData'
import reduceFieldsToValues from './reduceFieldsToValues'
import { flattenRows, separateRows } from './rows'
/**
* Reducer which modifies the form field state (all the current data of the fields in the form). When called using dispatch, it will return a new state object.
*/
export function fieldReducer(state: Fields, action: FieldAction): Fields {
switch (action.type) {
case 'REPLACE_STATE': {
@@ -123,7 +126,7 @@ export function fieldReducer(state: Fields, action: FieldAction): Fields {
...state[path],
disableFormData: rows.length > 0,
rows: rowsMetadata,
value: rows,
value: rows.length,
},
...flattenRows(path, rows),
}
@@ -161,10 +164,6 @@ export function fieldReducer(state: Fields, action: FieldAction): Fields {
const { remainingFields, rows: siblingRows } = separateRows(path, state)
siblingRows.splice(rowIndex, 0, subFieldState)
// add new row to array _value_
const currentValue = (Array.isArray(state[path]?.value) ? state[path]?.value : []) as Fields[]
const newValue = currentValue.splice(rowIndex, 0, reduceFieldsToValues(subFieldState, true))
const newState: Fields = {
...remainingFields,
...flattenRows(path, siblingRows),
@@ -172,7 +171,7 @@ export function fieldReducer(state: Fields, action: FieldAction): Fields {
...state[path],
disableFormData: true,
rows: rowsMetadata,
value: newValue,
value: siblingRows.length,
},
}
@@ -203,10 +202,6 @@ export function fieldReducer(state: Fields, action: FieldAction): Fields {
// replace form _field state_
siblingRows[rowIndex] = subFieldState
// replace array _value_
const newValue = Array.isArray(state[path]?.value) ? state[path]?.value : []
newValue[rowIndex] = reduceFieldsToValues(subFieldState, true)
const newState: Fields = {
...remainingFields,
...flattenRows(path, siblingRows),
@@ -214,7 +209,7 @@ export function fieldReducer(state: Fields, action: FieldAction): Fields {
...state[path],
disableFormData: true,
rows: rowsMetadata,
value: newValue,
value: siblingRows.length,
},
}
@@ -245,7 +240,7 @@ export function fieldReducer(state: Fields, action: FieldAction): Fields {
...state[path],
disableFormData: true,
rows: rowsMetadata,
value: rows,
value: rows.length,
},
...flattenRows(path, rows),
}

View File

@@ -50,12 +50,15 @@ import reduceFieldsToValues from './reduceFieldsToValues'
const baseClass = 'form'
const Form: React.FC<Props> = (props) => {
const { id, collection, getDocPreferences, global } = useDocumentInfo()
const {
action,
children,
className,
disableSuccessStatus,
disabled,
fields: fieldsFromProps = collection?.fields || global?.fields,
handleResponse,
initialData, // values only, paths are required as key - form should build initial state as convenience
initialState, // fully formed initial field state
@@ -71,7 +74,6 @@ const Form: React.FC<Props> = (props) => {
const { code: locale } = useLocale()
const { i18n, t } = useTranslation('general')
const { refreshCookie, user } = useAuth()
const { id, collection, getDocPreferences, global } = useDocumentInfo()
const operation = useOperation()
const config = useConfig()
@@ -90,6 +92,10 @@ const Form: React.FC<Props> = (props) => {
if (initialState) initialFieldState = initialState
const fieldsReducer = useReducer(fieldReducer, {}, () => initialFieldState)
/**
* `fields` is the current, up-to-date state/data of all fields in the form. It can be modified by using dispatchFields,
* which calls the fieldReducer, which then updates the state.
*/
const [fields, dispatchFields] = fieldsReducer
contextRef.current.fields = fields
@@ -167,7 +173,13 @@ const Form: React.FC<Props> = (props) => {
let validationResult: boolean | string = true
if (typeof field.validate === 'function') {
validationResult = await field.validate(field.value, {
let valueToValidate = field.value
if (field?.rows && Array.isArray(field.rows)) {
valueToValidate = contextRef.current.getDataByPath(path)
}
validationResult = await field.validate(valueToValidate, {
id,
config,
data,
@@ -434,7 +446,7 @@ const Form: React.FC<Props> = (props) => {
const getRowSchemaByPath = React.useCallback(
({ blockType, path }: { blockType?: string; path: string }) => {
const rowConfig = traverseRowConfigs({
fieldConfig: collection?.fields || global?.fields,
fieldConfig: fieldsFromProps,
path,
})
const rowFieldConfigs = buildFieldSchemaMap(rowConfig)
@@ -442,10 +454,11 @@ const Form: React.FC<Props> = (props) => {
const fieldKey = pathSegments.at(-1)
return rowFieldConfigs.get(blockType ? `${fieldKey}.${blockType}` : fieldKey)
},
[traverseRowConfigs, collection?.fields, global?.fields],
[traverseRowConfigs, fieldsFromProps],
)
// Array/Block row manipulation
// Array/Block row manipulation. This is called when, for example, you add a new block to a blocks field.
// The block data is saved in the rows property of the state, which is modified updated here.
const addFieldRow: Context['addFieldRow'] = useCallback(
async ({ data, path, rowIndex }) => {
const preferences = await getDocPreferences()

View File

@@ -2,7 +2,12 @@ import type React from 'react'
import type { Dispatch } from 'react'
import type { User } from '../../../../auth/types'
import type { Condition, Field as FieldConfig, Validate } from '../../../../fields/config/types'
import type {
Condition,
Field,
Field as FieldConfig,
Validate,
} from '../../../../fields/config/types'
export type Row = {
blockType?: string
@@ -41,6 +46,12 @@ export type Props = {
className?: string
disableSuccessStatus?: boolean
disabled?: boolean
/**
* By default, the form will get the field schema (not data) from the current document. If you pass this in, you can override that behavior.
* This is very useful for sub-forms, where the form's field schema is not necessarily the field schema of the current document (e.g. for the Blocks
* feature of the Lexical Rich Text field)
*/
fields?: Field[]
handleResponse?: (res: Response) => void
initialData?: Data
initialState?: Fields

View File

@@ -17,10 +17,21 @@ const intersectionObserverOptions = {
rootMargin: '1000px',
}
// If you send `fields` through, it will render those fields explicitly
// Otherwise, it will reduce your fields using the other provided props
// This is so that we can conditionally render fields before reducing them, if desired
// See the sidebar in '../collections/Edit/Default/index.tsx' for an example
/**
* If you send `fields` through, it will render those fields explicitly
* Otherwise, it will reduce your fields using the other provided props
* This is so that we can conditionally render fields before reducing them, if desired
* See the sidebar in '../collections/Edit/Default/index.tsx' for an example
*
* The state/data for the fields it renders is not managed by this component. Instead, every component it renders has
* their own handling of their own value, usually through the useField hook. This hook will get the field's value
* from the Form the field is in, using the field's path.
*
* Thus, if you would like to set the value of a field you render here, you must do so in the Form that contains the field, or in the
* Field component itself.
*
* All this component does is render the field's Field Components, and pass them the props they need to function.
**/
const RenderFields: React.FC<Props> = (props) => {
const { className, fieldTypes, forceRender, margins } = props

View File

@@ -6,21 +6,23 @@ import type { ReducedField } from './filterFields'
export type Props = {
className?: string
fieldTypes: FieldTypes
margins?: 'small' | false
forceRender?: boolean
margins?: 'small' | false
permissions?:
| {
[field: string]: FieldPermissions
}
| FieldPermissions
readOnly?: boolean
} & (
| {
// Fields to be filtered by the component
fieldSchema: FieldWithPath[]
filter?: (field: Field) => boolean
indexPath?: string
permissions?:
| {
[field: string]: FieldPermissions
}
| FieldPermissions
readOnly?: boolean
}
| {
// Pre-filtered fields to be simply rendered
fields: ReducedField[]
}
)

View File

@@ -91,7 +91,7 @@ const ArrayFieldType: React.FC<Props> = (props) => {
showError,
valid,
value,
} = useField<[]>({
} = useField<number>({
condition,
hasRows: true,
path,
@@ -123,8 +123,8 @@ const ArrayFieldType: React.FC<Props> = (props) => {
)
const removeRow = useCallback(
async (rowIndex: number) => {
await removeFieldRow({ path, rowIndex })
(rowIndex: number) => {
removeFieldRow({ path, rowIndex })
setModified(true)
},
[removeFieldRow, path, setModified],
@@ -278,7 +278,7 @@ const ArrayFieldType: React.FC<Props> = (props) => {
icon="plus"
iconPosition="left"
iconStyle="with-border"
onClick={() => addRow(value?.length || 0)}
onClick={() => addRow(value || 0)}
>
{t('addLabel', { label: getTranslation(labels.singular, i18n) })}
</Button>

View File

@@ -90,7 +90,7 @@ const BlocksField: React.FC<Props> = (props) => {
showError,
valid,
value,
} = useField<[]>({
} = useField<number>({
condition,
hasRows: true,
path,
@@ -128,8 +128,8 @@ const BlocksField: React.FC<Props> = (props) => {
)
const removeRow = useCallback(
async (rowIndex: number) => {
await removeFieldRow({ path, rowIndex })
(rowIndex: number) => {
removeFieldRow({ path, rowIndex })
setModified(true)
},
[path, removeFieldRow, setModified],
@@ -297,7 +297,7 @@ const BlocksField: React.FC<Props> = (props) => {
</DrawerToggler>
<BlocksDrawer
addRow={addRow}
addRowIndex={value?.length || 0}
addRowIndex={value || 0}
blocks={blocks}
drawerSlug={drawerSlug}
labels={labels}

View File

@@ -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>>
}

View File

@@ -29,7 +29,7 @@ const useField = <T,>(options: Options): FieldType<T> => {
const dispatchField = useFormFields(([_, dispatch]) => dispatch)
const config = useConfig()
const { getData, getSiblingData, setModified } = useForm()
const { getData, getDataByPath, getSiblingData, setModified } = useForm()
const value = field?.value as T
const initialValue = field?.initialValue as T
@@ -116,8 +116,14 @@ const useField = <T,>(options: Options): FieldType<T> => {
user,
}
let valueToValidate = value
if (field?.rows && Array.isArray(field.rows)) {
valueToValidate = getDataByPath(path)
}
const validationResult =
typeof validate === 'function' ? await validate(value, validateOptions) : true
typeof validate === 'function' ? await validate(valueToValidate, validateOptions) : true
if (typeof validationResult === 'string') {
action.errorMessage = validationResult
@@ -132,7 +138,7 @@ const useField = <T,>(options: Options): FieldType<T> => {
}
}
validateField()
void validateField()
},
150,
[
@@ -142,6 +148,7 @@ const useField = <T,>(options: Options): FieldType<T> => {
dispatchField,
getData,
getSiblingData,
getDataByPath,
id,
operation,
path,

View File

@@ -220,7 +220,13 @@ export const API: React.FC<EditViewProps> = (props) => {
return (
<Gutter className={classes} right={false}>
<SetStepNav collection={collection} global={global} id={id} isEditing={isEditing} />
<SetStepNav
collection={collection}
global={global}
id={id}
isEditing={isEditing}
view="API"
/>
<div className={`${baseClass}__configuration`}>
<div className={`${baseClass}__api-url`}>
<span className={`${baseClass}__label`}>

View File

@@ -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`}

View File

@@ -27,7 +27,8 @@
}
&__fields {
& > .tabs-field {
& > .tabs-field,
& > .group-field {
margin-right: calc(var(--base) * -2);
}
}
@@ -51,7 +52,7 @@
position: sticky;
top: var(--doc-controls-height);
width: 33.33%;
height: 100%;
height: calc(100vh - var(--doc-controls-height));
}
&__sidebar {
@@ -110,7 +111,8 @@
}
&__fields {
& > .tabs-field {
& > .tabs-field,
& > .group-field {
margin-right: calc(var(--gutter-h) * -1);
}
}

View File

@@ -90,9 +90,8 @@ export const DefaultGlobalEdit: React.FC<GlobalEditViewProps> = (props) => {
<div className={`${baseClass}__sidebar-sticky-wrap`}>
<div className={`${baseClass}__sidebar-fields`}>
<RenderFields
fieldSchema={fields}
fieldTypes={fieldTypes}
filter={(field) => field.admin.position === 'sidebar'}
fields={sidebarFields}
permissions={permissions.fields}
readOnly={!hasSavePermission}
/>

View File

@@ -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: () => {},

View File

@@ -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,

View File

@@ -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'

View File

@@ -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'

View File

@@ -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)
}
}

View File

@@ -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}

View File

@@ -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

View File

@@ -21,4 +21,11 @@
margin: 0;
}
}
&__inputWrap {
display: flex;
flex-direction: column;
gap: base(1);
margin-bottom: base(0.25);
}
}

View File

@@ -44,6 +44,8 @@ const Login: React.FC = () => {
}
}
const prefillForm = autoLogin && autoLogin.prefillOnly
return (
<React.Fragment>
{user ? (
@@ -75,22 +77,33 @@ const Login: React.FC = () => {
action={`${serverURL}${api}/${userSlug}/login`}
className={`${baseClass}__form`}
disableSuccessStatus
initialData={{
email: autoLogin && autoLogin.prefillOnly ? autoLogin.email : undefined,
password: autoLogin && autoLogin.prefillOnly ? autoLogin.password : undefined,
}}
initialData={
prefillForm
? {
email: autoLogin.email,
password: autoLogin.password,
}
: undefined
}
method="post"
onSuccess={onSuccess}
waitForAutocomplete
>
<FormLoadingOverlayToggle action="loading" name="login-form" />
<Email
admin={{ autoComplete: 'email' }}
label={t('general:email')}
name="email"
required
/>
<Password autoComplete="off" label={t('general:password')} name="password" required />
<div className={`${baseClass}__inputWrap`}>
<Email
admin={{ autoComplete: 'email' }}
label={t('general:email')}
name="email"
required
/>
<Password
autoComplete="off"
label={t('general:password')}
name="password"
required
/>
</div>
<Link to={`${admin}/forgot`}>{t('forgotPasswordQuestion')}</Link>
<FormSubmit>{t('login')}</FormSubmit>
</Form>

View File

@@ -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} />

View File

@@ -27,7 +27,8 @@
}
&__fields {
& > .tabs-field {
& > .tabs-field,
& > .group-field {
margin-right: calc(var(--base) * -2);
}
}
@@ -55,7 +56,7 @@
position: sticky;
top: var(--doc-controls-height);
width: 33.33%;
height: 100%;
height: calc(100vh - var(--doc-controls-height));
}
&__sidebar {
@@ -106,7 +107,8 @@
}
&__fields {
& > .tabs-field {
& > .tabs-field,
& > .group-field {
margin-right: calc(var(--gutter-h) * -1);
}
}

View File

@@ -115,7 +115,12 @@ export const DefaultCollectionEdit: React.FC<CollectionEditViewProps> = (props)
<div className={`${baseClass}__sidebar`}>
<div className={`${baseClass}__sidebar-sticky-wrap`}>
<div className={`${baseClass}__sidebar-fields`}>
<RenderFields fieldTypes={fieldTypes} fields={sidebarFields} />
<RenderFields
fieldTypes={fieldTypes}
fields={sidebarFields}
permissions={permissions.fields}
readOnly={!hasSavePermission}
/>
</div>
</div>
</div>

View File

@@ -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: number | string
isEditing: boolean
view?: string
}
| {
global: SanitizedGlobalConfig
view?: string
}
> = (props) => {
let collection: SanitizedCollectionConfig | undefined
@@ -28,6 +31,7 @@ export const SetStepNav: React.FC<
let slug: string
let isEditing = false
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

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