Compare commits

...

397 Commits

Author SHA1 Message Date
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
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
0b64c5fb66 chore(deps): bump graphql, graphql-request, mongoose, drizzle-kit 2023-10-11 11:20:58 -04:00
James Mikrut
40426a25df Update CHANGELOG.md 2023-10-11 11:07:16 -04:00
James Mikrut
c91b1e8310 Merge pull request #3578 from payloadcms/fix/#3570
fix: postgres select fields within groups (#3570)
2023-10-11 11:00:49 -04:00
James
06e2fa9d11 fix: postgres select fields within groups (#3570) 2023-10-11 10:47:10 -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
Nathan Clevenger
c6925ec29f Fix typo on graphql-schema.mdx (#3548) 2023-10-11 10:25:20 -04:00
Leonard Struck
8f46b31249 fix "crop" translation de (#3567) 2023-10-11 10:24:43 -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
James Mikrut
e3c776523a fix: #3511, documents don't delete their versions (#3520)
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2023-10-11 10:20:57 -04:00
Dan Ribbens
c09e9d96cf fix(db-postgres): update password error (#3575) 2023-10-11 10:19:00 -04:00
Dan Ribbens
aabc0650f8 fix(db-postgres): drafts cannot be saved because not null constraint (#3547) 2023-10-11 10:18:44 -04:00
Elliot DeNolf
4dd4e9aaae chore(deps): script deps 2023-10-11 10:16:06 -04:00
Elliot DeNolf
3b63b7fc3c chore: update contributing.md links 2023-10-10 22:31:25 -04:00
Alexander
f0e2e78b82 fix(i18n): "crop" translation for ru and ua (#3566) 2023-10-10 19:31:36 -04:00
Elliot DeNolf
a154adf066 chore(examples): update nodemon to respond to prompts 2023-10-10 18:37:33 -04:00
Elliot DeNolf
d86bcc1495 chore: update publish script 2023-10-10 17:48:42 -04:00
Elliot DeNolf
2b7043c6e6 chore(release): payload/2.0.4 2023-10-10 17:45:36 -04:00
Jarrod Flesch
e0afeeca97 fix: API tab breadcrumbs and results indentation (#3564) 2023-10-10 17:33:15 -04:00
Jacob Fletcher
76e306ddd8 fix: sticky sidebar (#3563) 2023-10-10 17:20:11 -04:00
Jacob Fletcher
cfc78ed4f5 fix: sidebar width when fields have long descriptions (#3562) 2023-10-10 17:04:54 -04:00
Elliot DeNolf
64b0db5a7a chore(readme): announcement link adjustment 2023-10-10 16:14:40 -04:00
Elliot DeNolf
48600f0c66 chore(release): db-mongodb/1.0.3 2023-10-10 16:09:53 -04:00
Elliot DeNolf
6d2dd5849d chore(release): db-postgres/0.1.3 2023-10-10 16:09:24 -04:00
Jacob Fletcher
6d9353b53f fix: row field margins (#3558) 2023-10-10 16:00:41 -04:00
James Mikrut
1914be75aa Merge pull request #3555 from payloadcms/fix/#3521
fix: #3521
2023-10-10 15:59:52 -04:00
Elliot DeNolf
b17f627e02 chore(release): richtext-slate/1.0.2 2023-10-10 15:59:33 -04:00
Elliot DeNolf
bef79621ee chore(release): db-postgres/0.1.2 2023-10-10 15:59:33 -04:00
Jarrod Flesch
af892ecb0e fix: removes nested array field configs from array value (#3549)
* fix: array controls 'addBelow' was adding above
2023-10-10 15:55:00 -04:00
James
eb97acd408 fix: #3521 2023-10-10 14:46:52 -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
272 changed files with 8814 additions and 1786 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

@@ -2,7 +2,7 @@
<!-- Please include a summary of the pull request and any related issues it fixes. Please also include relevant motivation and context. -->
- [ ] I have read and understand the [CONTRIBUTING.md](../CONTRIBUTING.md) document in this repository.
- [ ] I have read and understand the [CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md) document in this repository.
## Type of change

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

@@ -121,6 +121,12 @@ This means that in some fringe cases, if you are creating a doc and then instant
To avoid any issues, you can pass the `req.transactionID` through to your Local API calls, so that your Local API calls are included as part of the parent transaction.
### ⚠️ Locales now have more functionality, and in some places, you might need to update custom code
Payload's locales have become more powerful and now allow you to customize more aspects per locale such as a human-friendly label and if the locale is RTL or not.
This means that certain functions now return a different shape, such as `useLocale`. This hook used to return a string of the locale code you are currently editing, but it now returns an object with type of `Locale`.
### ⚠️ Admin panel CSS classes may have changed
The revisions we've made in 2.0 required changes to both HTML and CSS within the admin panel. For this reason, if you were loading custom CSS into the admin panel to customize the look and feel, your stylesheets may need to be updated. If your CSS is targeting elements on the page using HTML selectors or class names, you may need to update these selectors based on the current markup. It may also be necessary to update your style definitions if the core Payload component you are targeting has undergone significant change.

View File

@@ -26,7 +26,8 @@
</h4>
<hr/>
### 🎉 Payload 2.0 is now available! Read more in the [announcement post](https://payloadcms.com/blog/payload-2-0).
> [!IMPORTANT]
> 🎉 <strong>Payload 2.0 is now available!<strong> Read more in the <a target="_blank" href="https://payloadcms.com/blog/payload-2-0" rel="dofollow"><strong>announcement post</strong></a>.
<h3>Benefits over a regular CMS</h3>
<ul>

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

@@ -347,7 +347,7 @@ The `useForm` hook returns an object with the following properties: |
value: <strong><code>rowIndex</code></strong>,
},
{
value: "The index of the row to add",
value: "The index of the row to add. If omitted, the row will be added to the end of the array.",
},
],
[

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

@@ -65,7 +65,7 @@ After a user logs in, they can change their language selection in the `/account`
<strong>Note:</strong>
<br />
If there is a language that Payload does not yet support, we accept code
[contributions](https://github.com/payloadcms/payload/blob/main/contributing.md).
[contributions](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md).
</Banner>
### Node Express

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

@@ -8,7 +8,7 @@ keywords: headless cms, typescript, documentation, Content Management System, cm
When working with GraphQL it is useful to have the schema for development of other projects that need to call on your GraphQL endpoint. In Payload the schema is controlled by your collections and globals and is made available to the developer or third parties, it is not necessary for developers using Payload to write schema types manually.
### Schema generatation script
### Schema generation script
Run the following command in a Payload project to generate your project's GraphQL schema from Payload:

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

@@ -1,4 +1,5 @@
{
"ext": "ts",
"exec": "ts-node src/server.ts"
"exec": "ts-node src/server.ts -- -I",
"stdin": false
}

View File

@@ -1,5 +1,6 @@
{
"watch": ["server.ts"],
"exec": "ts-node --project tsconfig.server.json src/server.ts",
"ext": "js ts"
"exec": "ts-node --project tsconfig.server.json src/server.ts -- -I",
"ext": "js ts",
"stdin": false
}

View File

@@ -1,4 +1,5 @@
{
"ext": "ts",
"exec": "ts-node src/server.ts"
"exec": "ts-node src/server.ts -- -I",
"stdin": false
}

View File

@@ -1,4 +1,5 @@
{
"ext": "ts",
"exec": "ts-node src/server.ts"
"exec": "ts-node src/server.ts -- -I",
"stdin": false
}

View File

@@ -1,4 +1,5 @@
{
"ext": "ts",
"exec": "ts-node src/server.ts"
"exec": "ts-node src/server.ts -- -I",
"stdin": false
}

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

View File

@@ -1,4 +1,5 @@
{
"ext": "ts",
"exec": "ts-node src/server.ts"
"exec": "ts-node src/server.ts -- -I",
"stdin": false
}

View File

@@ -1,4 +1,5 @@
{
"ext": "ts",
"exec": "ts-node src/server.ts"
"exec": "ts-node src/server.ts -- -I",
"stdin": false
}

View File

@@ -1,4 +1,5 @@
{
"ext": "ts",
"exec": "ts-node src/server.ts"
"exec": "ts-node src/server.ts -- -I",
"stdin": false
}

View File

@@ -1,4 +1,5 @@
{
"ext": "ts",
"exec": "ts-node src/server.ts"
"exec": "ts-node src/server.ts -- -I",
"stdin": false
}

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",
@@ -50,6 +50,7 @@
"@types/shelljs": "0.8.12",
"@types/testing-library__jest-dom": "5.14.8",
"chalk": "^5.3.0",
"chalk-template": "1.1.0",
"copyfiles": "2.4.1",
"cross-env": "7.0.3",
"dotenv": "8.6.0",
@@ -58,7 +59,6 @@
"fs-extra": "10.1.0",
"get-port": "5.1.1",
"glob": "8.1.0",
"graphql-request": "6.1.0",
"husky": "^8.0.3",
"isomorphic-fetch": "3.0.0",
"jest": "29.6.4",
@@ -74,9 +74,11 @@
"qs": "6.11.2",
"rimraf": "3.0.2",
"shelljs": "0.8.5",
"simple-git": "^3.20.0",
"slash": "3.0.0",
"slate": "0.91.4",
"ts-node": "10.9.1",
"tsx": "^3.13.0",
"turbo": "^1.10.15",
"typescript": "5.2.2",
"uuid": "^9.0.0"

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-mongodb",
"version": "1.0.2",
"version": "1.0.3",
"description": "The officially supported MongoDB database adapter for Payload",
"repository": "https://github.com/payloadcms/payload",
"license": "MIT",
@@ -23,7 +23,7 @@
"bson-objectid": "2.0.4",
"deepmerge": "4.3.1",
"get-port": "5.1.1",
"mongoose": "6.11.4",
"mongoose": "6.12.0",
"mongoose-aggregate-paginate-v2": "1.0.6",
"mongoose-paginate-v2": "1.7.22",
"prompts": "2.4.2",

View File

@@ -68,7 +68,9 @@ export type MongooseAdapter = BaseDatabaseAdapter &
type MongooseAdapterResult = (args: { payload: Payload }) => MongooseAdapter
declare module 'payload' {
export interface DatabaseAdapter extends Args {
export interface DatabaseAdapter
extends Omit<BaseDatabaseAdapter, 'sessions'>,
Omit<Args, 'migrationDir'> {
collections: {
[slug: string]: CollectionModel
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "0.1.1",
"version": "0.1.5",
"description": "The officially supported Postgres database adapter for Payload",
"repository": "https://github.com/payloadcms/payload",
"license": "MIT",

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

@@ -150,7 +150,10 @@ export const findMany = async function find({
const countResult = await chainMethods({
methods: selectCountMethods,
query: db
.select({ count: sql<number>`count(*)` })
.select({
count: sql<number>`count
(*)`,
})
.from(table)
.where(where),
})

View File

@@ -24,6 +24,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
buildTable({
adapter: this,
buildRelationships: true,
disableNotNull: !!collection?.versions?.drafts,
disableUnique: false,
fields: collection.fields,
tableName,
@@ -37,6 +38,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
buildTable({
adapter: this,
buildRelationships: true,
disableNotNull: !!collection.versions?.drafts,
disableUnique: true,
fields: versionFields,
tableName: versionsTableName,
@@ -51,6 +53,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
buildTable({
adapter: this,
buildRelationships: true,
disableNotNull: !!global?.versions?.drafts,
disableUnique: false,
fields: global.fields,
tableName,
@@ -64,6 +67,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
buildTable({
adapter: this,
buildRelationships: true,
disableNotNull: !!global.versions?.drafts,
disableUnique: true,
fields: versionFields,
tableName: versionsTableName,

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

@@ -2,7 +2,7 @@
import type { SQL } from 'drizzle-orm'
import type { Field, Operator, Where } from 'payload/types'
import { and, ilike, isNotNull, isNull, ne, or, sql } from 'drizzle-orm'
import { and, ilike, isNotNull, isNull, ne, notInArray, or, sql } from 'drizzle-orm'
import { QueryError } from 'payload/errors'
import { validOperators } from 'payload/types'
@@ -147,6 +147,7 @@ export async function parseParams({
const { operator: queryOperator, value: queryValue } = sanitizeQueryValue({
field,
operator,
relationOrPath,
val,
})
@@ -158,6 +159,17 @@ export async function parseParams({
ne<any>(rawColumn || table[columnName], queryValue),
),
)
} else if (
(field.type === 'relationship' || field.type === 'upload') &&
Array.isArray(queryValue) &&
operator === 'not_in'
) {
constraints.push(
sql`${notInArray(table[columnName], queryValue)} OR
${table[columnName]}
IS
NULL`,
)
} else {
constraints.push(
operatorMap[queryOperator](rawColumn || table[columnName], queryValue),

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

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

@@ -27,6 +27,7 @@ type Args = {
baseColumns?: Record<string, PgColumnBuilder>
baseExtraConfig?: Record<string, (cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder>
buildRelationships?: boolean
disableNotNull: boolean
disableUnique: boolean
fields: Field[]
rootRelationsToBuild?: Map<string, string>
@@ -46,6 +47,7 @@ export const buildTable = ({
baseColumns = {},
baseExtraConfig = {},
buildRelationships,
disableNotNull,
disableUnique = false,
fields,
rootRelationsToBuild,
@@ -102,6 +104,7 @@ export const buildTable = ({
adapter,
buildRelationships,
columns,
disableNotNull,
disableUnique,
fields,
indexes,

View File

@@ -34,6 +34,7 @@ type Args = {
buildRelationships: boolean
columnPrefix?: string
columns: Record<string, PgColumnBuilder>
disableNotNull: boolean
disableUnique?: boolean
fieldPrefix?: string
fields: (Field | TabAsField)[]
@@ -62,6 +63,7 @@ export const traverseFields = ({
buildRelationships,
columnPrefix,
columns,
disableNotNull,
disableUnique = false,
fieldPrefix,
fields,
@@ -174,7 +176,7 @@ export const traverseFields = ({
case 'radio':
case 'select': {
const enumName = `enum_${newTableName}_${columnPrefix || ''}${toSnakeCase(field.name)}`
const enumName = `enum_${newTableName}_${toSnakeCase(field.name)}`
adapter.enums[enumName] = pgEnum(
enumName,
@@ -188,7 +190,7 @@ export const traverseFields = ({
)
if (field.type === 'select' && field.hasMany) {
const selectTableName = `${newTableName}_${toSnakeCase(fieldName)}`
const selectTableName = `${newTableName}_${toSnakeCase(field.name)}`
const baseColumns: Record<string, PgColumnBuilder> = {
order: integer('order').notNull(),
parent: parentIDColumnMap[parentIDColType]('parent_id')
@@ -218,6 +220,7 @@ export const traverseFields = ({
adapter,
baseColumns,
baseExtraConfig,
disableNotNull,
disableUnique,
fields: [],
tableName: selectTableName,
@@ -274,6 +277,7 @@ export const traverseFields = ({
adapter,
baseColumns,
baseExtraConfig,
disableNotNull,
disableUnique,
fields: field.fields,
rootRelationsToBuild,
@@ -339,6 +343,7 @@ export const traverseFields = ({
adapter,
baseColumns,
baseExtraConfig,
disableNotNull,
disableUnique,
fields: block.fields,
rootRelationsToBuild,
@@ -399,6 +404,7 @@ export const traverseFields = ({
buildRelationships,
columnPrefix,
columns,
disableNotNull,
disableUnique,
fieldPrefix,
fields: field.fields,
@@ -432,6 +438,7 @@ export const traverseFields = ({
buildRelationships,
columnPrefix: `${columnName}_`,
columns,
disableNotNull,
disableUnique,
fieldPrefix: `${fieldName}_`,
fields: field.fields,
@@ -466,6 +473,7 @@ export const traverseFields = ({
buildRelationships,
columnPrefix,
columns,
disableNotNull,
disableUnique,
fieldPrefix,
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
@@ -502,6 +510,7 @@ export const traverseFields = ({
buildRelationships,
columnPrefix,
columns,
disableNotNull,
disableUnique,
fieldPrefix,
fields: field.fields,
@@ -544,7 +553,13 @@ export const traverseFields = ({
const condition = field.admin && field.admin.condition
if (targetTable[fieldName] && 'required' in field && field.required && !condition) {
if (
!disableNotNull &&
targetTable[fieldName] &&
'required' in field &&
field.required &&
!condition
) {
targetTable[fieldName].notNull()
}
})

View File

@@ -69,7 +69,9 @@ export type MigrateUpArgs = { payload: Payload }
export type MigrateDownArgs = { payload: Payload }
declare module 'payload' {
export interface DatabaseAdapter extends Omit<Args, 'pool'> {
export interface DatabaseAdapter
extends Omit<Args, 'migrationDir' | 'pool'>,
BaseDatabaseAdapter {
drizzle: DrizzleDB
enums: Record<string, GenericEnum>
pool: Pool

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

@@ -26,7 +26,9 @@
</h4>
<hr/>
### 🎉 Payload 2.0 is now available! Read more in the [announcement post](https://payloadcms.com/blog/payload-2-0).
<h3>
🎉 Payload 2.0 is now available! Read more in the <a target="_blank" href="https://payloadcms.com/blog/payload-2-0" rel="dofollow"><strong>announcement post</strong></a>
</h3>
<h3>Benefits over a regular CMS</h3>
<ul>

View File

@@ -1,6 +1,6 @@
{
"name": "payload",
"version": "2.0.3",
"version": "2.0.6",
"description": "Node, React and MongoDB Headless CMS and Application Framework",
"license": "MIT",
"main": "./dist/index.js",
@@ -77,7 +77,7 @@
"flatley": "5.2.0",
"fs-extra": "10.1.0",
"get-tsconfig": "4.6.2",
"graphql": "16.7.1",
"graphql": "16.8.1",
"graphql-http": "1.21.0",
"graphql-playground-middleware-express": "1.7.23",
"graphql-query-complexity": "0.12.0",
@@ -187,7 +187,7 @@
"file-loader": "6.2.0",
"form-data": "3.0.1",
"get-port": "5.1.1",
"graphql-request": "3.7.0",
"graphql-request": "6.1.0",
"mini-css-extract-plugin": "1.6.2",
"node-fetch": "2.6.12",
"nodemon": "3.0.1",
@@ -208,8 +208,7 @@
"webpack": "^5.78.0"
},
"engines": {
"node": ">=14",
"pnpm": ">=8"
"node": ">=14"
},
"files": [
"bin.js",

View File

@@ -66,7 +66,7 @@ export const ArrayAction: React.FC<Props> = ({
<PopupList.Button
className={`${baseClass}__action ${baseClass}__add`}
onClick={() => {
addRow(index)
addRow(index + 1)
close()
}}
>

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),
}
@@ -132,7 +135,9 @@ export function fieldReducer(state: Fields, action: FieldAction): Fields {
}
case 'ADD_ROW': {
const { blockType, path, rowIndex, subFieldState } = action
const { blockType, path, rowIndex: rowIndexFromArgs, subFieldState } = action
const rowIndex =
typeof rowIndexFromArgs === 'number' ? rowIndexFromArgs : state[path]?.rows?.length || 0
const rowsMetadata = [...(state[path]?.rows || [])]
rowsMetadata.splice(
@@ -155,19 +160,18 @@ export function fieldReducer(state: Fields, action: FieldAction): Fields {
}
}
const { remainingFields, rows } = separateRows(path, state)
// actual form state (value saved in db)
rows.splice(rowIndex, 0, subFieldState)
// add new row to array _field state_
const { remainingFields, rows: siblingRows } = separateRows(path, state)
siblingRows.splice(rowIndex, 0, subFieldState)
const newState: Fields = {
...remainingFields,
...flattenRows(path, rows),
...flattenRows(path, siblingRows),
[path]: {
...state[path],
disableFormData: true,
rows: rowsMetadata,
value: rows,
value: siblingRows.length,
},
}
@@ -176,8 +180,8 @@ export function fieldReducer(state: Fields, action: FieldAction): Fields {
case 'REPLACE_ROW': {
const { blockType, path, rowIndex: rowIndexArg, subFieldState } = action
const { remainingFields, rows } = separateRows(path, state)
const rowIndex = Math.max(0, Math.min(rowIndexArg, rows?.length - 1 || 0))
const { remainingFields, rows: siblingRows } = separateRows(path, state)
const rowIndex = Math.max(0, Math.min(rowIndexArg, siblingRows?.length - 1 || 0))
const rowsMetadata = [...(state[path]?.rows || [])]
rowsMetadata[rowIndex] = {
@@ -195,17 +199,17 @@ export function fieldReducer(state: Fields, action: FieldAction): Fields {
}
}
// replace form field state
rows[rowIndex] = subFieldState
// replace form _field state_
siblingRows[rowIndex] = subFieldState
const newState: Fields = {
...remainingFields,
...flattenRows(path, rows),
...flattenRows(path, siblingRows),
[path]: {
...state[path],
disableFormData: true,
rows: rowsMetadata,
value: rows,
value: siblingRows.length,
},
}
@@ -236,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,
@@ -431,10 +443,10 @@ const Form: React.FC<Props> = (props) => {
[],
)
const getRowConfigByPath = React.useCallback(
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,30 +454,32 @@ 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()
const fieldConfig = getRowConfigByPath({
const rowSchema = getRowSchemaByPath({
blockType: data?.blockType,
path,
})
if (fieldConfig) {
if (rowSchema) {
const subFieldState = await buildStateFromSchema({
id,
config,
data,
fieldSchema: fieldConfig,
fieldSchema: rowSchema,
locale,
operation,
preferences,
t,
user,
})
dispatchFields({
blockType: data?.blockType,
path,
@@ -475,11 +489,11 @@ const Form: React.FC<Props> = (props) => {
})
}
},
[dispatchFields, getDocPreferences, id, user, operation, locale, t, getRowConfigByPath, config],
[dispatchFields, getDocPreferences, id, user, operation, locale, t, getRowSchemaByPath, config],
)
const removeFieldRow: Context['removeFieldRow'] = useCallback(
async ({ path, rowIndex }) => {
({ path, rowIndex }) => {
dispatchFields({ path, rowIndex, type: 'REMOVE_ROW' })
},
[dispatchFields],
@@ -488,17 +502,17 @@ const Form: React.FC<Props> = (props) => {
const replaceFieldRow: Context['replaceFieldRow'] = useCallback(
async ({ data, path, rowIndex }) => {
const preferences = await getDocPreferences()
const fieldConfig = getRowConfigByPath({
const rowSchema = getRowSchemaByPath({
blockType: data?.blockType,
path,
})
if (fieldConfig) {
if (rowSchema) {
const subFieldState = await buildStateFromSchema({
id,
config,
data,
fieldSchema: fieldConfig,
fieldSchema: rowSchema,
locale,
operation,
preferences,
@@ -514,7 +528,7 @@ const Form: React.FC<Props> = (props) => {
})
}
},
[dispatchFields, getDocPreferences, id, user, operation, locale, t, getRowConfigByPath, config],
[dispatchFields, getDocPreferences, id, user, operation, locale, t, getRowSchemaByPath, config],
)
const getFields = useCallback(() => contextRef.current.fields, [contextRef])

View File

@@ -12,9 +12,9 @@ export const separateRows = (path: string, fields: Fields): Result => {
const newRows = incomingRows
if (fieldPath.indexOf(`${path}.`) === 0) {
const index = Number(fieldPath.replace(`${path}.`, '').split('.')[0])
if (!newRows[index]) newRows[index] = {}
newRows[index][fieldPath.replace(`${path}.${String(index)}.`, '')] = { ...field }
const [rowIndex] = fieldPath.replace(`${path}.`, '').split('.')
if (!newRows[rowIndex]) newRows[rowIndex] = {}
newRows[rowIndex][fieldPath.replace(`${path}.${String(rowIndex)}.`, '')] = { ...field }
} else {
remainingFields[fieldPath] = field
}

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
@@ -174,7 +185,10 @@ export type Context = {
}: {
data?: Data
path: string
rowIndex: number
/*
* by default the new row will be added to the end of the list
*/
rowIndex?: number
}) => Promise<void>
buildRowErrors: () => void
createFormData: CreateFormData
@@ -190,7 +204,7 @@ export type Context = {
getField: GetField
getFields: GetFields
getSiblingData: GetSiblingData
removeFieldRow: ({ path, rowIndex }: { path: string; rowIndex: number }) => Promise<void>
removeFieldRow: ({ path, rowIndex }: { path: string; rowIndex: number }) => void
replaceFieldRow: ({
data,
path,

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

@@ -5,9 +5,9 @@ import type { Props } from './types'
import { createNestedFieldPath } from '../../Form/createNestedFieldPath'
import RenderFields from '../../RenderFields'
import withCondition from '../../withCondition'
import { fieldBaseClass } from '../shared'
import './index.scss'
import { RowProvider } from './provider'
import { fieldBaseClass } from '../shared'
const Row: React.FC<Props> = (props) => {
const {
@@ -29,7 +29,6 @@ const Row: React.FC<Props> = (props) => {
}))}
fieldTypes={fieldTypes}
indexPath={indexPath}
margins={false}
permissions={permissions}
readOnly={readOnly}
/>

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

@@ -7,6 +7,10 @@
gap: calc(var(--base) * 2);
align-items: flex-start;
ul {
padding-left: calc(var(--base) * 1);
}
&--fullscreen {
padding-left: 0;
.query-inspector__configuration {
@@ -173,6 +177,10 @@
}
}
&__row-line--nested {
margin-left: 25px;
}
&__bracket {
position: relative;

View File

@@ -1,6 +1,8 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import type { EditViewProps } from '../types'
import { Chevron } from '../..'
import { requests } from '../../../api'
import CopyToClipboard from '../../elements/CopyToClipboard'
@@ -11,6 +13,7 @@ import { MinimizeMaximize } from '../../icons/MinimizeMaximize'
import { useConfig } from '../../utilities/Config'
import { useDocumentInfo } from '../../utilities/DocumentInfo'
import { useLocale } from '../../utilities/Locale'
import { SetStepNav } from '../collections/Edit/SetStepNav'
import './index.scss'
const chars = {
@@ -119,12 +122,19 @@ const RecursivelyRenderObjectData = ({
}
if (type === 'date' || type === 'string' || type === 'null' || type === 'number') {
const parentHasKey = Boolean(parentType === 'object' && key)
const rowClasses = [
`${baseClass}__row-line`,
`${baseClass}__value-type--${type}`,
`${baseClass}__row-line--${objectKey ? 'nested' : 'top'}`,
]
.filter(Boolean)
.join(' ')
return (
<li
className={`${baseClass}__row-line ${baseClass}__value-type--${type}`}
key={`${key}-${keyIndex}`}
>
{parentType === 'object' ? <span>{`"${key}": `}</span> : null}
<li className={rowClasses} key={`${key}-${keyIndex}`}>
{parentHasKey ? <span>{`"${key}": `}</span> : null}
{type === 'string' ? (
<span className={`${baseClass}__value`}>{`"${value}"`}</span>
@@ -156,7 +166,8 @@ function createURL(url: string) {
}
}
export const API = ({ apiURL }) => {
export const API: React.FC<EditViewProps> = (props) => {
const { apiURL } = props
const { i18n } = useTranslation()
const {
localization,
@@ -201,8 +212,21 @@ export const API = ({ apiURL }) => {
const classes = [baseClass, fullscreen && `${baseClass}--fullscreen`].filter(Boolean).join(' ')
let isEditing: boolean
if ('collection' in props) {
isEditing = props?.isEditing
}
return (
<Gutter className={classes} right={false}>
<SetStepNav
collection={collection}
global={global}
id={id}
isEditing={isEditing}
view="API"
/>
<div className={`${baseClass}__configuration`}>
<div className={`${baseClass}__api-url`}>
<span className={`${baseClass}__label`}>

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);
}
}
@@ -49,8 +50,9 @@
&__sidebar-wrap {
position: sticky;
top: 0;
flex-grow: 1;
top: var(--doc-controls-height);
width: 33.33%;
height: calc(100vh - var(--doc-controls-height));
}
&__sidebar {
@@ -109,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);
}
}

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