Compare commits

..

148 Commits

Author SHA1 Message Date
Elliot DeNolf
b9854ed60a chore(release): db-postgres/0.8.4 [skip ci] 2024-05-17 14:36:02 -04:00
Elliot DeNolf
576ee14976 chore(release): payload/2.18.3 [skip ci] 2024-05-17 14:33:14 -04:00
Dan Ribbens
bf77cec7e9 fix(db-postgres): query with like on id columns (#6416)
copy of https://github.com/payloadcms/payload/pull/6414 to main
2024-05-17 14:07:57 -04:00
Patrik
ab8b2f3fb8 fix: nested disableListColumn in rows (#6412)
## Description

Fixes #6407 

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] Existing test suite passes locally with my changes
2024-05-17 14:01:08 -04:00
Patrik
db5f3f3ccd fix(db-postgres): uuid custom db name (#6409)
## Description

Fixes an issue with creating versions when using custom DB names,
`uuid`, and drafts.

v3 PR [here](https://github.com/payloadcms/payload/pull/6408)

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [x] Existing test suite passes locally with my changes
2024-05-17 13:58:08 -04:00
Francis Turmel
cece39957f chore(i18n): French translation improvements (#6035)
## Description

see #6406 for the changes against `beta`

* The apostrophe character `’` should be used instead of the single
quote `'`
* Gender corrections: "L’adresse e-mail fourni**e**", "Vérification
échoué**e**"
* Lowercase: "Supprimer le **té**léversement"
* Dark and light theme: I think it makes more sense to use "Sombre" and
"Clair" here to identify the theme. Day/Night modes imply a hue/warmth
correction and are different features altogether. Reference:
https://fr.wikipedia.org/wiki/Mode_sombre#Mode_sombre_et_mode_nuit_ou_chaud
* Fix accent: "Mis à jour avec succ**è**s"
* "Bienvenue" I think would be the correct standalone greeting form.
Reference:
https://www.projet-voltaire.fr/question-orthographe/orthographe-bienvenu-bienvenue-chez-moi/
* "Recadrer" is the correct word for "crop". "Récolte" means "crop" in
the sense of "harvest", so this was probably a bad literal Google
Translate that slipped through.
* Correct all "Es-tu sûr ?" to the proper formal "Êtes-vous sûr ?" for
consistency
* Use _article défini_ since we will enumerate the values: "Ce champ
contient **les** sélections invalides suivantes :"
* Space before question marks

---

<!-- Please include a summary of the pull request and any related issues
it fixes. Please also include relevant motivation and context. -->

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

<!-- Please delete options that are not relevant. -->

- [x] Chore (non-breaking change which does not add functionality)
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- [ ] Change to the
[templates](https://github.com/payloadcms/payload/tree/main/templates)
directory (does not affect core functionality)
- [ ] Change to the
[examples](https://github.com/payloadcms/payload/tree/main/examples)
directory (does not affect core functionality)
- [ ] This change requires a documentation update

## Checklist:

- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] Existing test suite passes locally with my changes
- [ ] I have made corresponding changes to the documentation
2024-05-17 13:51:47 -03:00
Elliot DeNolf
157fff0417 chore(release): payload/2.18.2 [skip ci] 2024-05-17 10:09:35 -04:00
Elliot DeNolf
88e113a545 fix: allow focal point when no sizes defined (#6397)
Allow focal points to be saved even when no imageSizes are defined.
Expands upon #6364 .
2024-05-17 09:06:42 -04:00
Elliot DeNolf
d33afe48fe chore(release): payload/2.18.1 [skip ci] 2024-05-16 15:46:23 -04:00
Elliot DeNolf
e76df32f09 fix: add back explicit crop x and y values (#6391)
Cropping x and y values pulling from the UI were unintentionally removed
in #6364
2024-05-16 15:32:36 -04:00
Elliot DeNolf
b068f30f51 chore(release): richtext-lexical/0.11.1 [skip ci] 2024-05-16 14:05:28 -04:00
Elliot DeNolf
82bd5c656f chore(release): db-postgres/0.8.3 [skip ci] 2024-05-16 14:05:16 -04:00
Elliot DeNolf
afe8992ca6 chore(release): payload/2.18.0 [skip ci] 2024-05-16 14:04:05 -04:00
Alessio Gravili
48a410e294 fix(richtext-lexical): upload, relationship and block node insertion fails sometimes (#6390)
Backport of https://github.com/payloadcms/payload/pull/6389
2024-05-16 13:34:43 -04:00
Elliot DeNolf
82b88a315f feat: store focal point on uploads (#6364)
Store focal point data on uploads as `focalX` and `focalY`

Addresses https://github.com/payloadcms/payload/discussions/4082
2024-05-16 11:58:49 -04:00
Patrik
cc94078607 fix: filter with ID not_in AND queries - postgres (#6358)
## Description

Fixes #5151 

`Issue`: With `Postgres`, when filtering by two queries with `AND`, if
the first query involved `ID` and the `not_in` operator, the second
query in the filter would never be evaluated.

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] Existing test suite passes locally with my changes
2024-05-15 15:09:53 -04:00
Elliot DeNolf
1c30ad73b6 chore(release): richtext-lexical/0.11.0 [skip ci] 2024-05-15 11:35:05 -04:00
Elliot DeNolf
d9442dcce3 chore(release): payload/2.17.0 [skip ci] 2024-05-15 11:33:53 -04:00
Patrik
de92c50847 fix: safely access cookie header for uploads (#6367)
## Description

Issue with editing and changing the crop or focal point of an image

`Error`:

```
ERROR (payload): FileRetrievalError: There was a problem while uploading the file. Cannot read properties of undefined (reading 'cookie')
    at generateFileData (/node_modules/payload/src/uploads/generateFileData.ts:86:15)
```
(`payload v2.16.1` and `plugin-cloud v3.0.1`)

Fix: add optional chaing to safely access `cookie` header when fetching
image

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [x] Existing test suite passes locally with my changes
2024-05-15 10:50:14 -04:00
Jacob Fletcher
b7f5f932f6 fix(examples/live-preview): regenerates yarn lockfiles (#6356) 2024-05-14 15:27:51 -04:00
Jessica Chowdhury
bc9e591e37 chore: removes letter spacing in loading overlay for script languages (#4769) 2024-05-14 16:51:34 +01:00
Jessica Chowdhury
ad38011348 chore: fix api preview indentation (#6152) 2024-05-14 16:09:17 +01:00
Patrik
d02b1fb084 fix: step-nav breadcrumbs ellipsis (#6345) 2024-05-14 11:08:59 -04:00
Jessica Chowdhury
51efe4f39b fix: collection labels with locales not working when creating new doc (#5995) 2024-05-14 10:45:39 -04:00
Jessica Chowdhury
30e535b5b9 feat: adds misc translations to API view and react select (#6138) 2024-05-13 08:52:12 -04:00
Jessica Chowdhury
4bd3bb9400 chore: adds translations for checkbox field in collection view (#6167) 2024-05-13 12:31:36 +01:00
Patrik
69c93d3c62 fix: appends editDepth value to radio & checkbox IDs when inside drawer (#6181) 2024-05-10 11:24:21 -04:00
Elliot DeNolf
78aa957043 fix(plugin-object-ids): add webpack to dev deps 2024-05-10 11:02:29 -04:00
Patrik
cd06c022c0 feat(plugin-relationship-object-ids): adds plugin-relationship-object-ids package (#6044) 2024-05-10 10:39:47 -04:00
Jacob Fletcher
8299e9fc33 docs(examples): adds examples docs (#6312) 2024-05-10 10:22:22 -04:00
Alessio Gravili
9df5ab8a10 feat(richtext-lexical)!: remove LexicalBlock, RichTextFieldRequiredEditor and FieldWithRichTextRequiredEditor types (#6279)
BREAKING:

`LexicalBlock`, `RichTextFieldRequiredEditor` and `FieldWithRichTextRequiredEditor` types have been removed. Instead of `LexicalBlock`, use `Block`. Instead of `RichTextFieldRequiredEditor`, use `RichTextField`. And instead of `FieldWithRichTextRequiredEditor`, use `Field`.
2024-05-09 09:46:03 -04:00
Elliot DeNolf
6979f5a1b1 ci: register release-canary workflow 2024-05-09 09:22:18 -04:00
Elliot DeNolf
723791b94f chore(release): richtext-lexical/0.10.0 [skip ci] 2024-05-07 15:53:11 -04:00
Elliot DeNolf
aa3833ec83 chore(release): payload/2.16.1 [skip ci] 2024-05-07 15:52:06 -04:00
Dan Ribbens
2972af2af1 chore: add index to version status field (#6256) 2024-05-07 15:47:55 -04:00
Alessio Gravili
857b9a4ac3 feat(richtext-lexical): add maxDepth property to various lexical features (#6250)
Backports #6242
2024-05-07 09:39:48 -04:00
Alessio Gravili
f829b084ba fix(richtext-lexical): export missing HorizontalRuleFeature (#6236) 2024-05-06 13:54:49 -04:00
Elliot DeNolf
6ae682698d chore(release): payload/2.16.0 [skip ci] 2024-05-06 12:47:28 -04:00
Francisco Lourenço
622cdb0440 fix: hide drag handles when admin.isSortable: false (#6225)
Co-authored-by: Kendell Joseph <kendelljoseph@gmail.com>
2024-05-06 12:21:35 -04:00
Jarrod Flesch
cac52da638 fix: graphql upload relations returning null (#6233) 2024-05-06 11:58:48 -04:00
DracoBlue
3cb3c1aceb docs(plugin-cloud): Fix link to local-dev.md (#6222) 2024-05-05 22:27:26 -04:00
Patrik
db4aacebb8 feat: adds disableListColumn, disableListFilter to fields admin props (#6188) 2024-05-03 16:49:06 -04:00
Paul
b735d6aa16 fix(plugin-form-builder): hook overrides not working as intended (#6203) 2024-05-03 17:36:03 -03:00
Elliot DeNolf
fbec3a33e0 chore(release): richtext-slate/1.5.2 [skip ci] 2024-05-03 11:23:16 -04:00
Elliot DeNolf
034be89bdb chore(release): richtext-lexical/0.9.3 [skip ci] 2024-05-03 11:23:00 -04:00
Elliot DeNolf
1f2af0963b chore(release): plugin-cloud/3.0.1 [skip ci] 2024-05-03 11:22:42 -04:00
Elliot DeNolf
20f1ece2d7 chore(release): payload/2.15.0 [skip ci] 2024-05-03 11:21:26 -04:00
Jessica Chowdhury
2be5ad0eba fix: hide unusable fields from collection filter select (#6135) 2024-05-03 11:01:47 -04:00
Kendell Joseph
5c58bd322d feat: add isSortable to arrays and blocks (#5962) 2024-05-03 10:38:02 -04:00
Dan Ribbens
23f3eb1cf0 feat: use filterOptions in list relationship filter (#6156) 2024-05-03 10:26:49 -04:00
Patrik
af67749e49 fix: incorrect localesNotSaved translation (#5996) 2024-05-03 10:22:06 -04:00
Patrik
43dab5c705 fix: sanitizes fields in default edit view for drawer content (#6175) 2024-05-03 10:16:24 -04:00
Patrik
9b7e62dc20 fix: properly adds readonly styles to disabled radio fields (#6176) 2024-05-03 10:07:52 -04:00
Patrik
6e38cc2bcf fix: resets filter state when param state change within route (#6169) 2024-05-02 13:21:51 -04:00
Tylan Davis
83551bfcaa docs: adjust line breaks in code blocks - v2 (#6002) 2024-05-01 15:58:15 -04:00
Francisco Lourenço
7b44d9d28a docs: fix outdated #aliasing-server-only-modules urls (#5014) 2024-05-01 15:52:18 -04:00
ovalice
182d5db6de docs: Fix RowLabel code snippet that causes compilation error (#4947)
Co-authored-by: smarten <user@example.com>
2024-05-01 15:42:43 -04:00
Mina Sameh
93109ec84a docs: edit code of generate email subject in verify auth section (#4607) 2024-05-01 15:31:24 -04:00
Take Weiland
4d9e0f35f0 docs: clarify docs around direction transaction access (#3648) 2024-05-01 15:22:48 -04:00
Carlo Taleon
19327c8d6d docs: slate linebreak serialization in 'Generating HTML' example (#3804) 2024-05-01 15:21:03 -04:00
Elliot DeNolf
831f1ff5be fix(plugin-cloud): purge cache for all sizes (#5301) 2024-05-01 15:15:16 -04:00
Jarrod Flesch
a8ac8b4633 fix: cascade draft arg in nested GraphQL relationship queries (#6141) 2024-04-30 14:19:20 -04:00
Alessio Gravili
36b1f5a763 fix(richtext-lexical): floating toolbar caret positioned incorrectly for some line heights (#6151) 2024-04-30 12:06:02 -04:00
Alessio Gravili
24f697219b fix(richtext-lexical): drag and add block handles disappear too quickly for smaller screen sizes. (#6145) 2024-04-30 10:52:37 -04:00
Jarrod Flesch
3fccd34abe fix: GraphQL nested relationships not respecting req locale (#6117) 2024-04-29 16:32:33 -04:00
Friggo
a38f8e93a6 chore: Czech translation improvement (#6079) 2024-04-28 07:57:47 -04:00
Dan Ribbens
84570e6e3b fix: bulk publish from collection list (#6063) 2024-04-28 07:30:37 -04:00
Friggo
5ad8e0edcb chore: Czech translation improvements (#6077) 2024-04-28 07:27:26 -04:00
Jarrod Flesch
91bac9c0aa fix: version restoration (#6039) 2024-04-26 16:15:14 -04:00
Elliot DeNolf
33f6edc9d5 chore(release): richtext-slate/1.5.1 [skip ci] 2024-04-26 16:05:01 -04:00
Elliot DeNolf
e1f91f5170 chore(release): richtext-lexical/0.9.2 [skip ci] 2024-04-26 16:04:37 -04:00
Elliot DeNolf
0e75dfb5c1 chore(release): plugin-stripe/0.0.17 [skip ci] 2024-04-26 16:04:29 -04:00
Elliot DeNolf
1300e264be chore(release): plugin-seo/2.3.2 [skip ci] 2024-04-26 16:04:21 -04:00
Elliot DeNolf
5600125de7 chore(release): plugin-search/1.1.1 [skip ci] 2024-04-26 16:04:14 -04:00
Elliot DeNolf
22e270f89c chore(release): plugin-redirects/1.0.2 [skip ci] 2024-04-26 16:04:06 -04:00
Elliot DeNolf
593f82bcba chore(release): plugin-form-builder/1.2.2 [skip ci] 2024-04-26 16:03:58 -04:00
Elliot DeNolf
cbf3da1144 chore(release): bundler-vite/0.1.7 [skip ci] 2024-04-26 16:03:49 -04:00
Elliot DeNolf
1a2aab4126 chore(release): payload/2.14.2 [skip ci] 2024-04-26 16:01:09 -04:00
Jacob Fletcher
d0ba694c80 fix(deps): dedupes react (#6058) 2024-04-26 11:43:14 -04:00
Elliot DeNolf
d78df36d9b ci(scripts): safer package details retrieval 2024-04-25 14:50:01 -04:00
Elliot DeNolf
02572d945a chore(release): db-postgres/0.8.2 [skip ci] 2024-04-25 14:39:37 -04:00
Elliot DeNolf
f05a433320 chore(release): richtext-lexical/0.9.1 [skip ci] 2024-04-25 14:38:56 -04:00
Elliot DeNolf
9ce3b3ab29 chore(release): db-mongodb/1.5.1 [skip ci] 2024-04-25 14:38:44 -04:00
Elliot DeNolf
ade637befb chore(release): payload/2.14.1 [skip ci] 2024-04-25 14:37:27 -04:00
Dan Ribbens
c31b8dcaa0 fix(db-postgres): cummulative updates (#6033) 2024-04-25 13:05:49 -04:00
Dan Ribbens
0ffdcc685f fix: disable api key checkbox does not remove api key (#6017) 2024-04-25 09:39:09 -04:00
James Mikrut
881119ba3a Update README.md (#6028) 2024-04-25 09:35:41 -04:00
Alessio Gravili
60372faf36 fix(richtext-lexical): minimize the amount of times sanitizeFields is called (#6018) 2024-04-24 17:51:14 -04:00
Elliot DeNolf
8bca0b0b86 chore(release): db-postgres/0.8.1 [skip ci] 2024-04-24 15:41:48 -04:00
Elliot DeNolf
87a1d698b2 chore(release): payload/2.14.0 [skip ci] 2024-04-24 15:40:04 -04:00
Dan Ribbens
c11600aac3 fix: bulk publish (#6006) 2024-04-24 15:04:50 -04:00
Elliot DeNolf
ad01c6784d fix: header filters (#5997) 2024-04-24 11:01:24 -04:00
Elliot DeNolf
62601c54a7 chore: update .vscode/settings.json 2024-04-24 08:59:45 -04:00
Elliot DeNolf
4a144ddc44 ci: register pr-title workflow 2024-04-23 23:31:13 -04:00
Patrik
9152a238d2 fix(db-postgres): row table names were not being built properly - v2 (#5961) 2024-04-22 16:56:03 -04:00
Mike Keefe
fc8b835264 docs: fix typo in admin custom components docs (#5944) 2024-04-21 20:39:32 -04:00
Elliot DeNolf
28ee5e34c3 chore(readme): add 3.0 beta announcement [skip ci] 2024-04-21 16:54:26 -04:00
Ricardo Domingues
e25886649f fix(db-postgres): Fixes nested groups inside nested blocks (#5882)
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2024-04-21 00:18:25 -04:00
Rafał Nawojczyk
985796be54 fix: min/max attributes missing from number input (#5779) 2024-04-20 23:36:32 -04:00
Dan Ribbens
bd8b5123b0 fix(db-postgres): extra version suffix added to table names (#5939) 2024-04-20 23:23:31 -04:00
Ritsu
c380deee4a feat: add count operation to collections (#5936) 2024-04-20 23:05:37 -04:00
Elliot DeNolf
0b12aac895 ci: bump actions node version (#5103) 2024-04-20 08:54:12 -04:00
Christian Gil
90d3f178ab feat(live-preview-vue): Vue Hook for Live Preview (#5925) 2024-04-20 06:45:18 -04:00
Patrik
a8c9625cde fix: removes equals & not_equals operators from fields with hasMany (#5885) 2024-04-19 11:41:39 -04:00
Elliot DeNolf
938d069523 chore(release): plugin-seo/2.3.1 [skip ci] 2024-04-19 11:38:41 -04:00
Elliot DeNolf
1a337ec223 chore(release): richtext-lexical/0.9.0 [skip ci] 2024-04-19 11:38:09 -04:00
Elliot DeNolf
08f372e6c2 chore(release): db-postgres/0.8.0 [skip ci] 2024-04-19 11:37:56 -04:00
Elliot DeNolf
2994269f22 chore(release): db-mongodb/1.5.0 [skip ci] 2024-04-19 11:37:43 -04:00
Elliot DeNolf
8d1a706928 chore(release): payload/2.13.0 [skip ci] 2024-04-19 11:36:17 -04:00
Pan
fcb29bb1c6 feat(plugin-seo): add Chinese translation (#5429) 2024-04-19 11:13:26 -04:00
Kendell Joseph
2c402cc65c feat: json field schemas (#5726) 2024-04-19 10:45:40 -04:00
Elliot DeNolf
ad38f76011 fix: properly handle drafts in bulk update (#5872)
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2024-04-19 10:08:26 -04:00
Jessica Chowdhury
b974a2c042 chore(i18n): adds document translation key (#5889) 2024-04-18 10:17:35 +01:00
Dan Ribbens
cb8d562132 fix(db-mongodb): ignore end session errors (#5904) 2024-04-17 16:44:15 -04:00
Patrik
1f0036054a fix: adds type error validations for email and password in login operation (#4852) 2024-04-17 16:31:35 -04:00
Elliot DeNolf
aaa4397351 ci: cut down on codeowners noise [skip ci] 2024-04-16 22:10:44 -04:00
Dan Ribbens
6185f8a5d8 fix(db-postgres): query hasMany fields with in (#5881) 2024-04-16 17:09:53 -04:00
Jacob Fletcher
e47e544364 fix(plugin-seo): uses correct key for ukrainian translation (#5873) 2024-04-16 11:16:33 -04:00
Bohdan Kucheriavyi
2e80350a5a chore(plugin-seo): adds Ukrainian translations (#5843)
Signed-off-by: Bohdan Kucheriavyi <bohdan.kucheriavyi@zapal.tech>
2024-04-16 09:31:18 -04:00
Ritsu
4c4f924e90 fix(db-postgres): validateExistingBlockIsIdentical localized (#5839) 2024-04-15 16:49:43 -04:00
Elliot DeNolf
170957c380 feat: allow configuration for setting headers on external file fetch (#5865) 2024-04-15 15:09:00 -04:00
Elliot DeNolf
9061ae05e7 docs: add externalFileHeaderFilter 2024-04-15 15:08:03 -04:00
Dan Ribbens
fe0028c899 fix(db-mongodb): version fields indexSortableFields (#5863) 2024-04-15 14:46:06 -04:00
Elliot DeNolf
ec1ad0b662 feat: allow configuration for setting headers on external file fetch 2024-04-15 14:40:55 -04:00
Tylan Davis
e5f32562a3 docs: adds missing tick marks (#5858) 2024-04-15 13:06:17 -04:00
Tylan Davis
ba6100cbe8 docs: adds missing tick marks 2024-04-15 13:03:44 -04:00
Alessio Gravili
c08c8b5628 ci: add weird tune linux network step which seems to reduce flakes (#5857) 2024-04-15 12:38:38 -04:00
James Mikrut
9e918831d1 fix: number ids were not sanitized to number in rest api (#5794) 2024-04-15 11:02:36 -04:00
Patrik
25c9a145be fix: passes parent id instead of incoming id to saveVersion (#5831) 2024-04-15 10:42:36 -04:00
Alessio Gravili
3b1d331316 fix(richtext-lexical): use correct nodeType on HorizontalRule feature HTML converter (#5805) 2024-04-11 16:10:55 -04:00
Alessio Gravili
5aa68b5e5d feat(richtext-lexical): add HorizontalRuleFeature, improve block handle positioning (#5804) 2024-04-11 15:54:56 -04:00
Alessio Gravili
d8e9084db2 feat(richtext-lexical): add HorizontalRuleFeature 2024-04-11 15:37:45 -04:00
Dan Ribbens
65690a675c fix(db-postgres): relationship query pagination (#5802) 2024-04-11 15:37:19 -04:00
PatrikKozak
9530d28a67 fix: updates var 2024-04-11 15:08:24 -04:00
PatrikKozak
509ec677c4 fix: uses find instead of fieldIndex for custom ID check 2024-04-11 14:19:45 -04:00
Alessio Gravili
a00439ea89 fix(richtext-lexical): limit unnecessary floating handle positioning updates 2024-04-11 14:12:59 -04:00
Alessio Gravili
0055a8eb36 feat(richtext-lexical): improve floating handle y-positioning by positioning it in the center for smaller elements. 2024-04-11 13:50:24 -04:00
Alessio Gravili
de5d6cc4bd fix(richtext-lexical): incorrect floating handle y-position calculation next to certain kinds of HTML elements like HR 2024-04-11 12:29:49 -04:00
PatrikKozak
51f84a4fcf fix: number ids were not sanitized to number in rest api 2024-04-11 12:23:57 -04:00
Paul
c0ba6cc19a fix: use isolateObjectProperty function in createLocalReq (#5748) 2024-04-11 09:27:42 -04:00
Patrik
5fa99fb060 fix(db-mongodb): failing contains query with special chars (#5774) 2024-04-11 09:23:05 -04:00
Patrik
e3c3ddac34 fix: avoids getting and setting doc preferences when creating new (#5757) 2024-04-10 11:20:02 -04:00
Alessio Gravili
6186493246 fix(richtext-lexical)!: do not allow omitting editor prop for sub-richtext fields within lexical defined in the payload config (#5766)
**BREAKING:** Modifies fields types which are allowed to be passed in to upload, link, and blocks lexical features. Can break your types even if no sub-richText editor is passed in
2024-04-10 11:14:32 -04:00
Alessio Gravili
9b44296092 fix(richtext-lexical): catch errors that may occur during HTML generation (#5754) 2024-04-09 15:58:34 -04:00
Paul
cbd03ed2f8 fix: req.collection being lost when querying a global inside a collection (#5727) 2024-04-08 15:16:37 -03:00
Alessio Gravili
cf135fd1e4 fix(richtext-lexical): pass through config for schema generation. Makes it more robust (#5700) 2024-04-05 15:55:51 -04:00
Dan Ribbens
e7608f5507 fix: block field type missing dbName (#5695) 2024-04-05 15:52:06 -04:00
Alessio Gravili
608d6d0a87 fix(db-postgres): hasMany relationship query contains operator (#4212)
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2024-04-05 13:59:00 -04:00
350 changed files with 14902 additions and 8132 deletions

46
.github/CODEOWNERS vendored
View File

@@ -1,50 +1,32 @@
# Order matters. The last matching pattern takes precedence.
### Catch-all ###
* @denolfe @jmikrut @DanRibbens
.* @denolfe @jmikrut @DanRibbens
### Core ###
/packages/payload/ @denolfe @jmikrut @DanRibbens
/packages/payload/src/uploads/ @denolfe
/packages/payload/src/admin/ @jmikrut @jacobsfletch @JarrodMFlesch
### Adapters ###
/packages/bundler-*/ @denolfe @jmikrut @DanRibbens @JarrodMFlesch
/packages/db-*/ @denolfe @jmikrut @DanRibbens
/packages/richtext-*/ @denolfe @jmikrut @DanRibbens @AlessioGr
/packages/richtext-*/ @AlessioGr
### Plugins ###
/packages/plugin-*/ @denolfe @jmikrut @DanRibbens @jacobsfletch @JarrodMFlesch @AlessioGr
/packages/plugin-cloud*/ @denolfe
/packages/plugin-form-builder/ @jacobsfletch
/packages/plugin-live-preview*/ @jacobsfletch
/packages/plugin-nested-docs/ @jacobsfletch
/packages/plugin-password-protection/ @jmikrut
/packages/plugin-redirects/ @jacobsfletch
/packages/plugin-search/ @jacobsfletch
/packages/plugin-sentry/ @JessChowdhury
/packages/plugin-seo/ @jacobsfletch
/packages/plugin-stripe/ @jacobsfletch
/packages/plugin-zapier/ @JarrodMFlesch
### Examples ###
/examples/ @jacobsfletch
/examples/testing/ @JarrodMFlesch
/examples/email/ @JessChowdhury
/examples/whitelabel/ @JessChowdhury
### Templates ###
/templates/ @jacobsfletch
/templates/blank/ @denolfe
/templates/ @jacobsfletch @denolfe
### Misc ###
/packages/create-payload-app/ @denolfe
/packages/eslint-config-payload/ @denolfe
/packages/payload-admin-bar/ @jacobsfletch
/packages/eslint-*/ @denolfe
### Build Files ###
/**/package.json @denolfe
/tsconfig.json @denolfe
/**/tsconfig*.json @denolfe
/jest.config.js @denolfe
/**/jest.config.js @denolfe
### Root ###
/package.json @denolfe
/scripts/ @denolfe
/.husky/ @denolfe
/.vscode/ @denolfe
/.github/ @denolfe
/.github/CODEOWNERS @denolfe

View File

@@ -15,6 +15,10 @@ jobs:
needs_build: ${{ steps.filter.outputs.needs_build }}
templates: ${{ steps.filter.outputs.templates }}
steps:
# https://github.com/actions/virtual-environments/issues/1187
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- uses: actions/checkout@v4
with:
fetch-depth: 25
@@ -45,13 +49,17 @@ jobs:
with:
fetch-depth: 25
- name: Use Node.js 18
uses: actions/setup-node@v3
# https://github.com/actions/virtual-environments/issues/1187
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
- name: Install pnpm
uses: pnpm/action-setup@v2
uses: pnpm/action-setup@v3
with:
version: 8
run_install: false
@@ -61,7 +69,7 @@ jobs:
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v3
- uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
@@ -74,7 +82,7 @@ jobs:
- run: pnpm run build
- name: Cache build
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
@@ -96,19 +104,23 @@ jobs:
AWS_REGION: us-east-1
steps:
- name: Use Node.js 18
uses: actions/setup-node@v3
# https://github.com/actions/virtual-environments/issues/1187
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
- name: Install pnpm
uses: pnpm/action-setup@v2
uses: pnpm/action-setup@v3
with:
version: 8
run_install: false
- name: Restore build
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
@@ -177,19 +189,23 @@ jobs:
part: [ 1/8, 2/8, 3/8, 4/8, 5/8, 6/8, 7/8, 8/8 ]
steps:
- name: Use Node.js 18
uses: actions/setup-node@v3
# https://github.com/actions/virtual-environments/issues/1187
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
- name: Install pnpm
uses: pnpm/action-setup@v2
uses: pnpm/action-setup@v3
with:
version: 8
run_install: false
- name: Restore build
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
@@ -214,19 +230,23 @@ jobs:
needs: core-build
steps:
- name: Use Node.js 18
uses: actions/setup-node@v3
# https://github.com/actions/virtual-environments/issues/1187
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
- name: Install pnpm
uses: pnpm/action-setup@v2
uses: pnpm/action-setup@v3
with:
version: 8
run_install: false
- name: Restore build
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
@@ -254,19 +274,23 @@ jobs:
- live-preview-react
steps:
- name: Use Node.js 18
uses: actions/setup-node@v3
# https://github.com/actions/virtual-environments/issues/1187
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
- name: Install pnpm
uses: pnpm/action-setup@v2
uses: pnpm/action-setup@v3
with:
version: 8
run_install: false
- name: Restore build
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
@@ -291,19 +315,23 @@ jobs:
- plugin-seo
steps:
- name: Use Node.js 18
uses: actions/setup-node@v3
# https://github.com/actions/virtual-environments/issues/1187
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
- name: Install pnpm
uses: pnpm/action-setup@v2
uses: pnpm/action-setup@v3
with:
version: 8
run_install: false
- name: Restore build
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
@@ -329,10 +357,14 @@ jobs:
with:
fetch-depth: 25
- name: Use Node.js 18
uses: actions/setup-node@v3
# https://github.com/actions/virtual-environments/issues/1187
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
- name: Start MongoDB
uses: supercharge/mongodb-github-action@1.10.0

11
.github/workflows/release-canary.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
name: release-canary
on:
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Echo
run: echo "Register release-canary workflow"

7
.vscode/launch.json vendored
View File

@@ -19,6 +19,13 @@
"PAYLOAD_PUBLIC_CLOUD_STORAGE_ADAPTER": "s3"
}
},
{
"command": "pnpm run dev collections-graphql",
"cwd": "${workspaceFolder}",
"name": "Run Dev GraphQL",
"request": "launch",
"type": "node-terminal"
},
{
"command": "pnpm run dev fields",
"cwd": "${workspaceFolder}",

View File

@@ -5,21 +5,21 @@
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
"source.fixAll.eslint": "explicit"
}
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
"source.fixAll.eslint": "explicit"
}
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
"source.fixAll.eslint": "explicit"
}
},
"[json]": {

View File

@@ -1,3 +1,190 @@
## [2.18.3](https://github.com/payloadcms/payload/compare/v2.18.2...v2.18.3) (2024-05-17)
### Bug Fixes
* **db-postgres:** query with like on id columns ([#6416](https://github.com/payloadcms/payload/issues/6416)) ([bf77cec](https://github.com/payloadcms/payload/commit/bf77cec7e9e7db4988e481d464178636203fca32))
* **db-postgres:** uuid custom db name ([#6409](https://github.com/payloadcms/payload/issues/6409)) ([db5f3f3](https://github.com/payloadcms/payload/commit/db5f3f3ccdaedd9e8036c3e39fc20650a309a151))
* nested `disableListColumn` in rows ([#6412](https://github.com/payloadcms/payload/issues/6412)) ([ab8b2f3](https://github.com/payloadcms/payload/commit/ab8b2f3fb87864484582b7d819ca307888a9449b)), closes [#6407](https://github.com/payloadcms/payload/issues/6407)
## [2.18.2](https://github.com/payloadcms/payload/compare/v2.18.1...v2.18.2) (2024-05-17)
### Bug Fixes
* allow focal point when no sizes defined ([#6397](https://github.com/payloadcms/payload/issues/6397)) ([88e113a](https://github.com/payloadcms/payload/commit/88e113a5452300434f690186d10ea02ab159ffc3))
## [2.18.1](https://github.com/payloadcms/payload/compare/v2.18.0...v2.18.1) (2024-05-16)
### Bug Fixes
* add back explicit crop x and y values ([#6391](https://github.com/payloadcms/payload/issues/6391)) ([e76df32](https://github.com/payloadcms/payload/commit/e76df32f0987cc92dc8d9c693950e650c52576bf))
## [2.18.0](https://github.com/payloadcms/payload/compare/v2.17.0...v2.18.0) (2024-05-16)
### Features
* store focal point on uploads ([#6364](https://github.com/payloadcms/payload/issues/6364)) ([82b88a3](https://github.com/payloadcms/payload/commit/82b88a315ff1d52f0b19a70224d5c600a3a97eb5))
### Bug Fixes
* **db-postgres:** filter with ID not_in AND queries - postgres ([#6358](https://github.com/payloadcms/payload/issues/6358)) ([cc94078](https://github.com/payloadcms/payload/commit/cc940786072c0065f10fdd2893050bddc4595a21)), closes [#5151](https://github.com/payloadcms/payload/issues/5151)
* **richtext-lexical:** upload, relationship and block node insertion fails sometimes ([#6390](https://github.com/payloadcms/payload/issues/6390)) ([48a410e](https://github.com/payloadcms/payload/commit/48a410e294598af9c73577a04f86466248f93da0))
## [2.17.0](https://github.com/payloadcms/payload/compare/v2.16.1...v2.17.0) (2024-05-15)
### Features
* adds misc translations to API view and react select ([#6138](https://github.com/payloadcms/payload/issues/6138)) ([30e535b](https://github.com/payloadcms/payload/commit/30e535b5b929dddead007d8a9adca62808595e2c))
* **richtext-lexical:** remove LexicalBlock, RichTextFieldRequiredEditor and FieldWithRichTextRequiredEditor types ([#6279](https://github.com/payloadcms/payload/issues/6279)) ([9df5ab8](https://github.com/payloadcms/payload/commit/9df5ab8a10a35ad34615d7e4da024f59ff037e0e))
### Bug Fixes
* appends `editDepth` value to `radio` & `checkbox` IDs when inside drawer ([#6181](https://github.com/payloadcms/payload/issues/6181)) ([69c93d3](https://github.com/payloadcms/payload/commit/69c93d3c62394a5cf995a2eaec9a3ab30e0f77af))
* collection labels with locales not working when creating new doc ([#5995](https://github.com/payloadcms/payload/issues/5995)) ([51efe4f](https://github.com/payloadcms/payload/commit/51efe4f39bcaadccb109a2a02a690ca65041ee57))
* safely access cookie header for uploads ([#6367](https://github.com/payloadcms/payload/issues/6367)) ([de92c50](https://github.com/payloadcms/payload/commit/de92c50847640661f915455f8db0029873ddc7ab))
* step-nav breadcrumbs ellipsis ([#6345](https://github.com/payloadcms/payload/issues/6345)) ([d02b1fb](https://github.com/payloadcms/payload/commit/d02b1fb084e636e49122ad55b25b9c49eb761f1c))
*
### ⚠ BREAKING CHANGES
* **richtext-lexical:** remove LexicalBlock, RichTextFieldRequiredEditor and FieldWithRichTextRequiredEditor types (#6279)
## [2.16.1](https://github.com/payloadcms/payload/compare/v2.16.0...v2.16.1) (2024-05-07)
### Features
* **richtext-lexical:** add maxDepth property to various lexical features ([#6250](https://github.com/payloadcms/payload/issues/6250)) ([857b9a4](https://github.com/payloadcms/payload/commit/857b9a4ac3236c740458750f156a3a4274eda210)), closes [#6242](https://github.com/payloadcms/payload/issues/6242)
### Bug Fixes
* **richtext-lexical:** export missing HorizontalRuleFeature ([#6236](https://github.com/payloadcms/payload/issues/6236)) ([f829b08](https://github.com/payloadcms/payload/commit/f829b084ba9649ef596cce4a7bf6ae8c7ccf57e3))
## [2.16.0](https://github.com/payloadcms/payload/compare/v2.15.0...v2.16.0) (2024-05-06)
### Features
* adds disableListColumn, disableListFilter to fields admin props ([#6188](https://github.com/payloadcms/payload/issues/6188)) ([db4aace](https://github.com/payloadcms/payload/commit/db4aacebb801f1cc11ef8732f9f3b78475256641))
### Bug Fixes
* graphql upload relations returning null ([#6233](https://github.com/payloadcms/payload/issues/6233)) ([cac52da](https://github.com/payloadcms/payload/commit/cac52da638a0df4356120a2f61c6aaf25641a5ad))
* hide drag handles when `admin.isSortable: false` ([#6225](https://github.com/payloadcms/payload/issues/6225)) ([622cdb0](https://github.com/payloadcms/payload/commit/622cdb044002b2c3182c3b0432b51befbfb9b979))
* **plugin-form-builder:** hook overrides not working as intended ([#6203](https://github.com/payloadcms/payload/issues/6203)) ([b735d6a](https://github.com/payloadcms/payload/commit/b735d6aa169acca8cb638859d2c8ba43e315f02c))
## [2.15.0](https://github.com/payloadcms/payload/compare/v2.14.2...v2.15.0) (2024-05-03)
### Features
* add isSortable to arrays and blocks ([#5962](https://github.com/payloadcms/payload/issues/5962)) ([5c58bd3](https://github.com/payloadcms/payload/commit/5c58bd322da966fe610959df13dfd49add35a2ef))
* use filterOptions in list relationship filter ([#6156](https://github.com/payloadcms/payload/issues/6156)) ([23f3eb1](https://github.com/payloadcms/payload/commit/23f3eb1cf0b75a4044319d7cd3e5000d5b4e42c4))
### Bug Fixes
* bulk publish from collection list ([#6063](https://github.com/payloadcms/payload/issues/6063)) ([84570e6](https://github.com/payloadcms/payload/commit/84570e6e3bbb81fcae80da92b01bc56d09906072))
* cascade draft arg in nested GraphQL relationship queries ([#6141](https://github.com/payloadcms/payload/issues/6141)) ([a8ac8b4](https://github.com/payloadcms/payload/commit/a8ac8b463349664f3188ae77217f037da72f796b))
* GraphQL nested relationships not respecting req locale ([#6117](https://github.com/payloadcms/payload/issues/6117)) ([3fccd34](https://github.com/payloadcms/payload/commit/3fccd34abe5a332f88f5e950b755cd1d21441fb6))
* hide unusable fields from collection filter select ([#6135](https://github.com/payloadcms/payload/issues/6135)) ([2be5ad0](https://github.com/payloadcms/payload/commit/2be5ad0ebafd1d3c1c0567e2085ccfd593f18271))
* incorrect `localesNotSaved` translation ([#5996](https://github.com/payloadcms/payload/issues/5996)) ([af67749](https://github.com/payloadcms/payload/commit/af67749e49db92e675b63b52190e562468894706))
* **plugin-cloud:** purge cache for all sizes ([#5301](https://github.com/payloadcms/payload/issues/5301)) ([831f1ff](https://github.com/payloadcms/payload/commit/831f1ff5bed7e083cc076e9eb5ff9a2b2f1ed710))
* properly adds `readonly` styles to disabled `radio` fields ([#6176](https://github.com/payloadcms/payload/issues/6176)) ([9b7e62d](https://github.com/payloadcms/payload/commit/9b7e62dc20dca7402c6c68dfb8a5995c211993af))
* resets filter state when param state change within route ([#6169](https://github.com/payloadcms/payload/issues/6169)) ([6e38cc2](https://github.com/payloadcms/payload/commit/6e38cc2bcfb08b608abcb6aac4b4c1f6eea63428))
* **richtext-lexical:** drag and add block handles disappear too quickly for smaller screen sizes. ([#6145](https://github.com/payloadcms/payload/issues/6145)) ([24f6972](https://github.com/payloadcms/payload/commit/24f697219b5071d91a5c37aafb50e2d823b68d4c))
* **richtext-lexical:** floating toolbar caret positioned incorrectly for some line heights ([#6151](https://github.com/payloadcms/payload/issues/6151)) ([36b1f5a](https://github.com/payloadcms/payload/commit/36b1f5a763f782c140e62aa062b4077d6efd0738))
* sanitizes fields in default edit view for drawer content ([#6175](https://github.com/payloadcms/payload/issues/6175)) ([43dab5c](https://github.com/payloadcms/payload/commit/43dab5c7053831a0c71f3a6860113f653cab674f))
* version restoration ([#6039](https://github.com/payloadcms/payload/issues/6039)) ([91bac9c](https://github.com/payloadcms/payload/commit/91bac9c0aa1ff3da052b9c2ad83fa5ac23a16d1d))
## [2.14.2](https://github.com/payloadcms/payload/compare/v2.14.1...v2.14.2) (2024-04-26)
### Bug Fixes
* **deps:** dedupes react ([#6058](https://github.com/payloadcms/payload/issues/6058)) ([d0ba694](https://github.com/payloadcms/payload/commit/d0ba694c80a1b699c4f2cad98b1f0bde1f0d43ca))
## [2.14.1](https://github.com/payloadcms/payload/compare/v2.14.0...v2.14.1) (2024-04-25)
### Bug Fixes
* **db-postgres:** cumulative updates ([#6033](https://github.com/payloadcms/payload/issues/6033)) ([c31b8dc](https://github.com/payloadcms/payload/commit/c31b8dcaa0c43132d8a01e0cc43094f466cc9168))
* disable api key checkbox does not remove api key ([#6017](https://github.com/payloadcms/payload/issues/6017)) ([0ffdcc6](https://github.com/payloadcms/payload/commit/0ffdcc685f4e917a02e62dbaccec7cc8ebbf695d))
* **richtext-lexical:** minimize the amount of times sanitizeFields is called ([#6018](https://github.com/payloadcms/payload/issues/6018)) ([60372fa](https://github.com/payloadcms/payload/commit/60372faf36b7f6d92a61ccbaee0f528e50f5a51a))
## [2.14.0](https://github.com/payloadcms/payload/compare/v2.13.0...v2.14.0) (2024-04-24)
### Features
* add count operation to collections ([#5936](https://github.com/payloadcms/payload/issues/5936)) ([c380dee](https://github.com/payloadcms/payload/commit/c380deee4a1db82bce9fea264060000957a53eee))
### Bug Fixes
* bulk publish ([#6006](https://github.com/payloadcms/payload/issues/6006)) ([c11600a](https://github.com/payloadcms/payload/commit/c11600aac38cd67019765faf2a41e62df13e50cc))
* **db-postgres:** extra version suffix added to table names ([#5939](https://github.com/payloadcms/payload/issues/5939)) ([bd8b512](https://github.com/payloadcms/payload/commit/bd8b5123b0991e53eb209315897dbca10d14d45e))
* **db-postgres:** Fixes nested groups inside nested blocks ([#5882](https://github.com/payloadcms/payload/issues/5882)) ([e258866](https://github.com/payloadcms/payload/commit/e25886649fce414d5d47918f35ba2d4d2ba59174))
* **db-postgres:** row table names were not being built properly - v2 ([#5961](https://github.com/payloadcms/payload/issues/5961)) ([9152a23](https://github.com/payloadcms/payload/commit/9152a238d2982503e7f509350651b0ba3f83b1ec))
* header filters ([#5997](https://github.com/payloadcms/payload/issues/5997)) ([ad01c67](https://github.com/payloadcms/payload/commit/ad01c6784d283386dc819dfcd47455cad5accfaa))
* min/max attributes missing from number input ([#5779](https://github.com/payloadcms/payload/issues/5779)) ([985796b](https://github.com/payloadcms/payload/commit/985796be54b593af0a4934685ab8621b9badda10))
* removes `equals` & `not_equals` operators from fields with `hasMany` ([#5885](https://github.com/payloadcms/payload/issues/5885)) ([a8c9625](https://github.com/payloadcms/payload/commit/a8c9625cdec33476a5da87bcd9f010f9d7fb9a94))
## [2.13.0](https://github.com/payloadcms/payload/compare/v2.12.1...v2.13.0) (2024-04-19)
### Features
* allow configuration for setting headers on external file fetch ([ec1ad0b](https://github.com/payloadcms/payload/commit/ec1ad0b6628d400d7435821c8a72b6746bf87577))
* **db-\*:** custom db table and enum names ([#5045](https://github.com/payloadcms/payload/issues/5045)) ([9bbacc4](https://github.com/payloadcms/payload/commit/9bbacc4fb1ad247634f394e95c42ee3adade8048))
* json field schemas ([#5726](https://github.com/payloadcms/payload/issues/5726)) ([2c402cc](https://github.com/payloadcms/payload/commit/2c402cc65c9e8f7f33e2fb0ce5e1a8ceff52af1b))
* **plugin-seo:** add Chinese translation ([#5429](https://github.com/payloadcms/payload/issues/5429)) ([fcb29bb](https://github.com/payloadcms/payload/commit/fcb29bb1c637867301bbc1070b4a84383bf0e90a))
* **richtext-lexical:** add HorizontalRuleFeature ([d8e9084](https://github.com/payloadcms/payload/commit/d8e9084db21828968046ab59775633e409ce5c2a))
* **richtext-lexical:** improve floating handle y-positioning by positioning it in the center for smaller elements. ([0055a8e](https://github.com/payloadcms/payload/commit/0055a8eb36b95722cccdc5eb3101a79d3e764f8b))
### Bug Fixes
* adds type error validations for `email` and `password` in login operation ([#4852](https://github.com/payloadcms/payload/issues/4852)) ([1f00360](https://github.com/payloadcms/payload/commit/1f0036054a9461535b0992f2449e91e4eaf97d4e))
* avoids getting and setting doc preferences when creating new ([#5757](https://github.com/payloadcms/payload/issues/5757)) ([e3c3dda](https://github.com/payloadcms/payload/commit/e3c3ddac34dff8fa085f5b702be2838d513be300))
* block field type missing dbName ([#5695](https://github.com/payloadcms/payload/issues/5695)) ([e7608f5](https://github.com/payloadcms/payload/commit/e7608f5507d3b85ea3f44b5cb1f43edf67608b1b))
* **db-mongodb:** failing `contains` query with special chars ([#5774](https://github.com/payloadcms/payload/issues/5774)) ([5fa99fb](https://github.com/payloadcms/payload/commit/5fa99fb060cabbb69b5d6688748260e562e6bea3))
* **db-mongodb:** ignore end session errors ([#5904](https://github.com/payloadcms/payload/issues/5904)) ([cb8d562](https://github.com/payloadcms/payload/commit/cb8d562132bee437798880e1d7f64dbfdee36949))
* **db-mongodb:** version fields indexSortableFields ([#5863](https://github.com/payloadcms/payload/issues/5863)) ([fe0028c](https://github.com/payloadcms/payload/commit/fe0028c89945303a431b48efdae7b6e22304c8a3))
* **db-postgres:** hasMany relationship query contains operator ([#4212](https://github.com/payloadcms/payload/issues/4212)) ([608d6d0](https://github.com/payloadcms/payload/commit/608d6d0a872af224ea42c3e6c8a3b4f21678f550))
* **db-postgres:** issue querying by localised relationship not respecting locale as constraint ([#5666](https://github.com/payloadcms/payload/issues/5666)) ([44599cb](https://github.com/payloadcms/payload/commit/44599cbc7b8f23d6d8c7a3e05466237406812a6d))
* **db-postgres:** query hasMany fields with in ([#5881](https://github.com/payloadcms/payload/issues/5881)) ([6185f8a](https://github.com/payloadcms/payload/commit/6185f8a5d845d12651f5a3ee128eb43d3b9d2449))
* **db-postgres:** relationship query pagination ([#5802](https://github.com/payloadcms/payload/issues/5802)) ([65690a6](https://github.com/payloadcms/payload/commit/65690a675c17cfacebe775a327a57741ac09416a))
* **db-postgres:** validateExistingBlockIsIdentical localized ([#5839](https://github.com/payloadcms/payload/issues/5839)) ([4c4f924](https://github.com/payloadcms/payload/commit/4c4f924e90ee23a73c9a7cc7e69bbc2caf902b92))
* duplicate document multiple times in quick succession ([#5642](https://github.com/payloadcms/payload/issues/5642)) ([373787d](https://github.com/payloadcms/payload/commit/373787de31cbbd33b587aa4be6344948f082f5bb))
* missing date locales ([#5656](https://github.com/payloadcms/payload/issues/5656)) ([c1c8600](https://github.com/payloadcms/payload/commit/c1c86009a5e9aad401a05f7c63ad37bd3f88dc84))
* number ids were not sanitized to number in rest api ([51f84a4](https://github.com/payloadcms/payload/commit/51f84a4fcfd437eb73c7d83205b66e3620085909))
* passes parent id instead of incoming id to saveVersion ([#5831](https://github.com/payloadcms/payload/issues/5831)) ([25c9a14](https://github.com/payloadcms/payload/commit/25c9a145bec9e9566d2bbcba59d5b34394e10bbd))
* **plugin-seo:** uses correct key for ukrainian translation ([#5873](https://github.com/payloadcms/payload/issues/5873)) ([e47e544](https://github.com/payloadcms/payload/commit/e47e544364031ac834565a4d86ef6ec9c04e63c0))
* properly handle drafts in bulk update ([#5872](https://github.com/payloadcms/payload/issues/5872)) ([ad38f76](https://github.com/payloadcms/payload/commit/ad38f760111abf947c6b0ee4b983ee1224a9bf1b))
* req.collection being lost when querying a global inside a collection ([#5727](https://github.com/payloadcms/payload/issues/5727)) ([cbd03ed](https://github.com/payloadcms/payload/commit/cbd03ed2f8819ee8ac20e8739cc03e88ff4caa25))
* **richtext-lexical:** catch errors that may occur during HTML generation ([#5754](https://github.com/payloadcms/payload/issues/5754)) ([9b44296](https://github.com/payloadcms/payload/commit/9b442960929d00faa07f1383b1267f71e6f44efe))
* **richtext-lexical:** do not allow omitting editor prop for sub-richtext fields within lexical defined in the payload config ([#5766](https://github.com/payloadcms/payload/issues/5766)) ([6186493](https://github.com/payloadcms/payload/commit/6186493246157b4d4b33c8c47378f08581315942))
* **richtext-lexical:** incorrect floating handle y-position calculation next to certain kinds of HTML elements like HR ([de5d6cc](https://github.com/payloadcms/payload/commit/de5d6cc4bd591745156f0b8c56795b7bd2eaad7e))
* **richtext-lexical:** limit unnecessary floating handle positioning updates ([a00439e](https://github.com/payloadcms/payload/commit/a00439ea893e074d64be83ee6af1e780178a7ee3))
* **richtext-lexical:** pass through config for schema generation. Makes it more robust ([#5700](https://github.com/payloadcms/payload/issues/5700)) ([cf135fd](https://github.com/payloadcms/payload/commit/cf135fd1e4aeb30121281399e26be901393ada6d))
* **richtext-lexical:** use correct nodeType on HorizontalRule feature HTML converter ([#5805](https://github.com/payloadcms/payload/issues/5805)) ([3b1d331](https://github.com/payloadcms/payload/commit/3b1d3313165499616673f6d363c90ef884994525))
* updates type name of `CustomPublishButtonProps` to `CustomPublishButtonType` ([#5644](https://github.com/payloadcms/payload/issues/5644)) ([7df7bf4](https://github.com/payloadcms/payload/commit/7df7bf448bd26e870a1fde8aaa47430904d68366))
* updates var ([9530d28](https://github.com/payloadcms/payload/commit/9530d28a6760a667b718027a49ea43ba1accd546))
* use isolateObjectProperty function in createLocalReq ([#5748](https://github.com/payloadcms/payload/issues/5748)) ([c0ba6cc](https://github.com/payloadcms/payload/commit/c0ba6cc19a20c043a08ca77caacd47ef7cfb48f4))
* uses find instead of fieldIndex for custom ID check ([509ec67](https://github.com/payloadcms/payload/commit/509ec677c42993d9c08facf6928a5ef1e9767508))
### ⚠ BREAKING CHANGES
* **richtext-lexical:** do not allow omitting editor prop for sub-richtext fields within lexical defined in the payload config ([#5766](https://github.com/payloadcms/payload/issues/5766))
## [2.12.1](https://github.com/payloadcms/payload/compare/v2.12.0...v2.12.1) (2024-04-03)

View File

@@ -17,7 +17,7 @@
<hr/>
> [!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>.
> 🎉 <strong>Payload 3.0 beta released!</strong> You can now deploy Payload fully in any Next.js app folder. Read more in the <a target="_blank" href="https://payloadcms.com/blog/30-beta-install-payload-into-any-nextjs-app-with-one-line" rel="dofollow"><strong>announcement post</strong></a>.
<h3>Benefits over a regular CMS</h3>
<ul>

View File

@@ -657,7 +657,7 @@ As your admin customizations gets more complex you may want to share state betwe
### Styling Custom Components
Payload exports its SCSS variables and mixins for reuse in your own custom components. This is helpful in cases where you might want to style a custom input similarly to Payload's built-ini styling, so it blends more thoroughly into the existing admin UI.
Payload exports its SCSS variables and mixins for reuse in your own custom components. This is helpful in cases where you might want to style a custom input similarly to Payload's built-in styling, so it blends more thoroughly into the existing admin UI.
To make use of Payload SCSS variables / mixins to use directly in your own components, you can import them as follows:

View File

@@ -216,7 +216,7 @@ Example:
{
slug: 'customers',
auth: {
forgotPassword: {
verify: {
// highlight-start
generateEmailSubject: ({ req, user }) => {
return `Hey ${user.email}, reset your password!`;

View File

@@ -49,7 +49,8 @@ export default buildConfig({
{
label: 'Arabic',
code: 'ar',
// opt-in to setting default text-alignment on Input fields to rtl (right-to-left) when current locale is rtl
// opt-in to setting default text-alignment on Input fields to rtl (right-to-left)
// when current locale is rtl
rtl: true,
},
],
@@ -134,13 +135,9 @@ to support localization, you need to specify each field that you would like to l
```js
{
name: 'title',
type
:
'text',
// highlight-start
localized
:
true,
type: 'text',
// highlight-start
localized: true,
// highlight-end
}
```

View File

@@ -20,7 +20,8 @@ The initial request made to Payload will begin a new transaction and attach it t
```ts
const afterChange: CollectionAfterChangeHook = async ({ req }) => {
// because req.transactionID is assigned from Payload and passed through, my-slug will only persist if the entire request is successful
// because req.transactionID is assigned from Payload and passed through,
// my-slug will only persist if the entire request is successful
await req.payload.create({
req,
collection: 'my-slug',
@@ -60,10 +61,44 @@ const afterChange: CollectionAfterChangeHook = async ({ req }) => {
### Direct Transaction Access
When writing your own scripts or custom endpoints, you may wish to have direct control over transactions. This is useful for interacting with your database outside of Payload's local API.
When writing your own scripts or custom endpoints, you may wish to have direct control over transactions. This is useful for interacting with your database in something like a background job, outside the normal request-response flow.
The following functions can be used for managing transactions:
`payload.db.beginTransaction` - Starts a new session and returns a transaction ID for use in other Payload Local API calls.
`payload.db.commitTransaction` - Takes the identifier for the transaction, finalizes any changes.
`payload.db.rollbackTransaction` - Takes the identifier for the transaction, discards any changes.
`payload.db.beginTransaction` - Starts a new session and returns a transaction ID for use in other Payload Local API calls. Note that if your database does not support transactions, this will return `null`.\
`payload.db.commitTransaction` - Takes the identifier for the transaction, finalizes any changes.\
`payload.db.rollbackTransaction` - Takes the identifier for the transaction, discards any changes.
You can then use the transaction ID with Payload's local API by passing it inside the `PayloadRequest` object.
Here is an example for a "background job" function, which utilizes the direct transaction API to make sure it either succeeds completely or gets rolled back in case of an error.
```ts
async function allOrNothingJob() {
const req = {} as PayloadRequest;
req.transactionID = await payload.db.beginTransaction();
try {
await payload.create({
req, // use our manual transaction
collection: 'my-slug',
data: {
some: 'data'
}
});
await payload.create({
req, // use our manual transaction
collection: 'something-else',
data: {
some: 'data'
}
});
console.log('Everything done.');
if (req.transactionID) await payload.db.commitTransaction(req.transactionID);
} catch (e) {
console.error('Oh no, something went wrong!');
if (req.transactionID) await payload.db.rollbackTransaction(req.transactionID);
}
}
```

View File

@@ -0,0 +1,39 @@
---
title: Examples
label: Overview
order: 10
desc:
keywords: example, examples, starter, boilerplate, template, templates
---
Payload provides a vast array of examples to help you get started with your project no matter what you are working on. These examples are designed to be easy to get up and running, and to be easy to understand. They showcase nothing more than the specific features being demonstrated, so you can easily decipher what is going on.
Examples are changing every day, so be sure to check back often to see what new examples have been added. If you have a specific example you would like to see, please feel free to start a new [Discussion](https://github.com/payloadcms/payload/discussions) or open a new [PR](https://github.com/payloadcms/payload/pulls) to add it yourself.
- [Auth](https://github.com/payloadcms/payload/tree/main/examples/auth)
- [Custom Server](https://github.com/payloadcms/payload/tree/main/examples/custom-server)
- [Draft Preview](https://github.com/payloadcms/payload/tree/main/examples/draft-preview)
- [Email](https://github.com/payloadcms/payload/tree/main/examples/email)
- [Form Builder](https://github.com/payloadcms/payload/tree/main/examples/form-builder)
- [Hierarchy](https://github.com/payloadcms/payload/tree/main/examples/hierarchy)
- [Live Preview](https://github.com/payloadcms/payload/tree/main/examples/live-preview)
- [Multi-tenant](https://github.com/payloadcms/payload/tree/main/examples/multi-tenant)
- [Nested Docs](https://github.com/payloadcms/payload/tree/main/examples/nested-docs)
- [Redirects](https://github.com/payloadcms/payload/tree/main/examples/redirects)
- [Tests](https://github.com/payloadcms/payload/tree/main/examples/testing)
- [Virtual Fields](https://github.com/payloadcms/payload/tree/main/examples/virtual-fields)
- [White-label Admin UI](https://github.com/payloadcms/payload/tree/main/examples/whitelabel)
Where necessary, some examples include a front-end. Examples that require a front-end share this folder structure:
```plaintext
example/
├── payload/
├── next-app/
├── next-pages/
├── react-router/
├── vue/
├── svelte/
```
Where `payload` is your Payload project, and the other directories are dedicated to their respective front-end framework. We are adding new examples every day, so if your framework of choice is not yet supported in any particular example, please feel free to start a new [Discussion](https://github.com/payloadcms/payload/discussions) or open a new [PR](https://github.com/payloadcms/payload/pulls) to add it yourself.

View File

@@ -59,6 +59,7 @@ properties:
| Option | Description |
|---------------------------|----------------------------------------------------------------------------------------------------------------------|
| **`initCollapsed`** | Set the initial collapsed state |
| **`isSortable`** | Disable array order sorting by setting this value to `false` |
| **`components.RowLabel`** | Function or React component to be rendered as the label on the array row. Receives `({ data, index, path })` as args |
### Example
@@ -67,6 +68,7 @@ properties:
```ts
import { CollectionConfig } from 'payload/types'
import { RowLabelArgs } from 'payload/dist/admin/components/forms/RowLabel/types'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
@@ -101,7 +103,7 @@ export const ExampleCollection: CollectionConfig = {
],
admin: {
components: {
RowLabel: ({ data, index }) => {
RowLabel: ({ data, index }: RowLabelArgs) => {
return data?.title || `Slide ${String(index).padStart(2, '0')}`
},
},

View File

@@ -58,6 +58,7 @@ properties:
| Option | Description |
|---------------------|---------------------------------|
| **`initCollapsed`** | Set the initial collapsed state |
| **`isSortable`** | Disable block order sorting by setting this value to `false` |
### Block configs

View File

@@ -4,7 +4,7 @@ label: JSON
order: 50
desc: The JSON field type will store any string in the Database. Learn how to use JSON fields, see examples and options.
keywords: json, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
keywords: json, jsonSchema, schema, validation, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner>
@@ -30,6 +30,7 @@ This field uses the `monaco-react` editor syntax highlighting.
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`jsonSchema`** | Provide a JSON schema that will be used for validation. [JSON schemas](https://json-schema.org/learn/getting-started-step-by-step) |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
@@ -52,7 +53,7 @@ In addition to the default [field admin config](/docs/fields/overview#admin-conf
### Example
`collections/ExampleCollection.ts
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types'
@@ -68,3 +69,68 @@ export const ExampleCollection: CollectionConfig = {
],
}
```
### JSON Schema Validation
Payload JSON fields fully support the [JSON schema](https://json-schema.org/) standard. By providing a schema in your field config, the editor will be guided in the admin UI, getting typeahead for properties and their formats automatically. When the document is saved, the default validation will prevent saving any invalid data in the field according to the schema in your config.
If you only provide a URL to a schema, Payload will fetch the desired schema if it is publicly available. If not, it is recommended to add the schema directly to your config or import it from another file so that it can be implemented consistently in your project.
#### Local JSON Schema
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'customerJSON', // required
type: 'json', // required
jsonSchema: {
uri: 'a://b/foo.json', // required
fileMatch: ['a://b/foo.json'], // required
schema: {
type: 'object',
properties: {
foo: {
enum: ['bar', 'foobar'],
}
},
},
},
},
],
}
// {"foo": "bar"} or {"foo": "foobar"} - ok
// Attempting to create {"foo": "not-bar"} will throw an error
```
#### Remote JSON Schema
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'customerJSON', // required
type: 'json', // required
jsonSchema: {
uri: 'https://example.com/customer.schema.json', // required
fileMatch: ['https://example.com/customer.schema.json'], // required
},
},
],
}
// If 'https://example.com/customer.schema.json' has a JSON schema
// {"foo": "bar"} or {"foo": "foobar"} - ok
// Attempting to create {"foo": "not-bar"} will throw an error
```

View File

@@ -163,19 +163,21 @@ Example:
In addition to each field's base configuration, you can define specific traits and properties for fields that only have effect on how they are rendered in the Admin panel. The following properties are available for all fields within the `admin` property:
| Option | Description |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `condition` | You can programmatically show / hide fields based on what other fields are doing. [Click here](#conditional-logic) for more info. |
| `components` | All field components can be completely and easily swapped out for custom components that you define. [Click here](#custom-components) for more info. |
| `description` | Helper text to display with the field to provide more information for the editor user. [Click here](#description) for more info. |
| `position` | Specify if the field should be rendered in the sidebar by defining `position: 'sidebar'`. |
| `width` | Restrict the width of a field. you can pass any string-based value here, be it pixels, percentages, etc. This property is especially useful when fields are nested within a `Row` type where they can be organized horizontally. |
| `style` | Attach raw CSS style properties to the root DOM element of a field. |
| `className` | Attach a CSS class name to the root DOM element of a field. |
| `readOnly` | Setting a field to `readOnly` has no effect on the API whatsoever but disables the admin component's editability to prevent editors from modifying the field's value. |
| `disabled` | If a field is `disabled`, it is completely omitted from the Admin panel. |
| `disableBulkEdit` | Set `disableBulkEdit` to `true` to prevent fields from appearing in the select options when making edits for multiple documents. |
| `hidden` | Setting a field's `hidden` property on its `admin` config will transform it into a `hidden` input type. Its value will still submit with the Admin panel's requests, but the field itself will not be visible to editors. |
| Option | Description |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `condition` | You can programmatically show / hide fields based on what other fields are doing. [Click here](#conditional-logic) for more info. |
| `components` | All field components can be completely and easily swapped out for custom components that you define. [Click here](#custom-components) for more info. |
| `description` | Helper text to display with the field to provide more information for the editor user. [Click here](#description) for more info. |
| `position` | Specify if the field should be rendered in the sidebar by defining `position: 'sidebar'`. |
| `width` | Restrict the width of a field. you can pass any string-based value here, be it pixels, percentages, etc. This property is especially useful when fields are nested within a `Row` type where they can be organized horizontally. |
| `style` | Attach raw CSS style properties to the root DOM element of a field. |
| `className` | Attach a CSS class name to the root DOM element of a field. |
| `readOnly` | Setting a field to `readOnly` has no effect on the API whatsoever but disables the admin component's editability to prevent editors from modifying the field's value. |
| `disabled` | If a field is `disabled`, it is completely omitted from the Admin panel. |
| `disableBulkEdit` | Set `disableBulkEdit` to `true` to prevent fields from appearing in the select options when making edits for multiple documents. |
| `disableListColumn` | Set `disableListColumn` to `true` to prevent fields from appearing in the list view column selector. |
| `disableListFilter` | Set `disableListFilter` to `true` to prevent fields from appearing in the list view filter options. |
| `hidden` | Setting a field's `hidden` property on its `admin` config will transform it into a `hidden` input type. Its value will still submit with the Admin panel's requests, but the field itself will not be visible to editors. |
### Custom components

View File

@@ -43,11 +43,12 @@ export const PublicUser: CollectionConfig = {
**Payload will automatically open up the following queries:**
| Query Name | Operation |
| ------------------ | ------------------- |
| **`PublicUser`** | `findByID` |
| **`PublicUsers`** | `find` |
| **`mePublicUser`** | `me` auth operation |
| Query Name | Operation |
| ------------------ | ------------------- |
| **`PublicUser`** | `findByID` |
| **`PublicUsers`** | `find` |
| **`countPublicUsers`** | `count` |
| **`mePublicUser`** | `me` auth operation |
**And the following mutations:**

View File

@@ -36,7 +36,7 @@ If your Hook simply performs a side-effect, such as updating a CRM, it might be
#### Server-only execution
Payload Hooks are only triggered on the server. You can safely [remove your hooks](/docs/admin/webpack#aliasing-server-only-modules) from your Admin panel's client-side code by customizing the Webpack config, which not only keeps your Admin bundles' filesize small but also ensures that any server-side only code does not cause problems within browser environments.
Payload Hooks are only triggered on the server. You can safely [remove your hooks](/docs/admin/excluding-server-code#aliasing-server-only-modules) from your Admin panel's client-side code by customizing the Webpack config, which not only keeps your Admin bundles' filesize small but also ensures that any server-side only code does not cause problems within browser environments.
## Hook Types

View File

@@ -8,7 +8,7 @@ keywords: live preview, frontend, react, next.js, vue, nuxt.js, svelte, hook, us
While using Live Preview, the Admin panel emits a new `window.postMessage` event every time a change is made to the document. Your front-end application can listen for these events and re-render accordingly.
Wiring your front-end into Live Preview is easy. If your front-end application is built with React or Next.js, use the [`useLivePreview`](#react) React hook that Payload provides. In the future, all other major frameworks like Vue, Svelte, etc will be officially supported. If you are using any of these frameworks today, you can still integrate with Live Preview yourself using the underlying tooling that Payload provides. See [building your own hook](#building-your-own-hook) for more information.
Wiring your front-end into Live Preview is easy. If your front-end application is built with React, Next.js, Vue or Nuxt.js, use the `useLivePreview` hook that Payload provides. In the future, all other major frameworks like Svelte will be officially supported. If you are using any of these frameworks today, you can still integrate with Live Preview yourself using the underlying tooling that Payload provides. See [building your own hook](#building-your-own-hook) for more information.
By default, all hooks accept the following args:
@@ -32,6 +32,10 @@ And return the following values:
If your front-end is tightly coupled to required fields, you should ensure that your UI does not break when these fields are removed. For example, if you are rendering something like `data.relatedPosts[0].title`, your page will break once you remove the first related post. To get around this, use conditional logic, optional chaining, or default values in your UI where needed. For example, `data?.relatedPosts?.[0]?.title`.
</Banner>
<Banner type="info">
If is important that the `depth` argument matches exactly with the depth of your initial page request. The depth property is used to populated relationships and uploads beyond their IDs. See [Depth](../getting-started/concepts#depth) for more information.
</Banner>
### React
If your front-end application is built with React or Next.js, you can use the `useLivePreview` hook that Payload provides.
@@ -69,9 +73,40 @@ export const PageClient: React.FC<{
}
```
<Banner type="info">
If is important that the `depth` argument matches exactly with the depth of your initial page request. The depth property is used to populated relationships and uploads beyond their IDs. See [Depth](../getting-started/concepts#depth) for more information.
</Banner>
### Vue
If your front-end application is built with Vue 3 or Nuxt 3, you can use the `useLivePreview` composable that Payload provides.
First, install the `@payloadcms/live-preview-vue` package:
```bash
npm install @payloadcms/live-preview-vue
```
Then, use the `useLivePreview` hook in your Vue component:
```vue
<script setup lang="ts">
import type { PageData } from '~/types';
import { defineProps } from 'vue';
import { useLivePreview } from '@payloadcms/live-preview-vue';
// Fetch the initial data on the parent component or using async state
const props = defineProps<{ initialData: PageData }>();
// The hook will take over from here and keep the preview in sync with the changes you make.
// The `data` property will contain the live data of the document only when viewed from the Preview view of the Admin UI.
const { data } = useLivePreview<PageData>({
initialData: props.initialData,
serverURL: "<PAYLOAD_SERVER_URL>",
depth: 2,
});
</script>
<template>
<h1>{{ data.title }}</h1>
</template>
```
## Building your own hook

View File

@@ -164,6 +164,22 @@ const result = await payload.findByID({
})
```
#### Count
```js
// Result will be an object with:
// {
// totalDocs: 10, // count of the documents satisfies query
// }
const result = await payload.count({
collection: 'posts', // required
locale: 'en',
where: {}, // pass a `where` query here
user: dummyUser,
overrideAccess: false,
})
```
#### Update by ID
```js

View File

@@ -247,7 +247,7 @@ In the template, we have stubbed out a basic `onInitExtension` file that you can
### Webpack
If any of your files use server only packages such as fs, stripe, nodemailer, etc, they will need to be removed from the browser bundle. To do that, you can [alias the file imports with webpack](https://payloadcms.com/docs/admin/webpack#aliasing-server-only-modules).
If any of your files use server only packages such as fs, stripe, nodemailer, etc, they will need to be removed from the browser bundle. To do that, you can [alias the file imports with webpack](https://payloadcms.com/docs/admin/excluding-server-code#aliasing-server-only-modules).
When files are bundled for the browser, the import paths are essentially crawled to determine what files to include in the bundle. To prevent the server only files from making it into the bundle, we can alias their import paths to a file that can be included in the browser. This will short-circuit the import path crawling and ensure browser only code is bundled.

View File

@@ -90,6 +90,19 @@ Note: Collection slugs must be formatted in kebab-case
},
},
},
{
operation: "Count",
method: "GET",
path: "/api/{collection-slug}/count",
description: "Count the documents",
example: {
slug: "count",
req: true,
res: {
totalDocs: 10
},
},
},
{
operation: "Create",
method: "POST",

View File

@@ -153,13 +153,14 @@ Here's an overview of all the included features:
| **`HeadingFeature`** | Yes | Adds Heading Nodes (by default, H1 - H6, but that can be customized) |
| **`AlignFeature`** | Yes | Allows you to align text left, centered and right |
| **`IndentFeature`** | Yes | Allows you to indent text with the tab key |
| **`UnorderedListFeature`** | Yes | Adds unordered lists (ul) |
| **`UnorderedListFeature`** | Yes | Adds unordered lists (ul) |
| **`OrderedListFeature`** | Yes | Adds ordered lists (ol) |
| **`CheckListFeature`** | Yes | Adds checklists |
| **`LinkFeature`** | Yes | Allows you to create internal and external links |
| **`RelationshipFeature`** | Yes | Allows you to create block-level (not inline) relationships to other documents |
| **`BlockQuoteFeature`** | Yes | Allows you to create block-level quotes |
| **`UploadFeature`** | Yes | Allows you to create block-level upload nodes - this supports all kinds of uploads, not just images |
| **`HorizontalRuleFeature`** | Yes | Horizontal rules / separators. Basically displays an `<hr>` element |
| **`BlocksFeature`** | No | Allows you to use Payload's [Blocks Field](/docs/fields/blocks) directly inside your editor. In the feature props, you can specify the allowed blocks - just like in the Blocks field. |
| **`TreeViewFeature`** | No | Adds a debug box under the editor, which allows you to see the current editor state live, the dom, as well as time travel. Very useful for debugging |
@@ -202,7 +203,8 @@ const Pages: CollectionConfig = {
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
// The HTMLConverter Feature is the feature which manages the HTML serializers. If you do not pass any arguments to it, it will use the default serializers.
// The HTMLConverter Feature is the feature which manages the HTML serializers.
// If you do not pass any arguments to it, it will use the default serializers.
HTMLConverterFeature({}),
],
}),

View File

@@ -270,6 +270,10 @@ const serialize = (children) =>
text = <em key={i}>{text}</em>;
}
if (node.text === '') {
text = <br />;
}
// Handle other leaf types here...
return <Fragment key={i}>{text}</Fragment>;

View File

@@ -40,21 +40,22 @@ Every Payload Collection can opt-in to supporting Uploads by specifying the `upl
### Collection Upload Options
| Option | Description |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`staticURL`** \* | The URL path to use to access your uploads. Relative path like `/media` will be served by payload. Full path like `https://example.com/media` needs to be served by another web server. |
| **`staticDir`** \* | The folder directory to use to store media in. Can be either an absolute path or relative to the directory that contains your config. |
| **`adminThumbnail`** | Set the way that the Admin panel will display thumbnails for this Collection. [More](#admin-thumbnails) |
| **`crop`** | Set to `false` to disable the cropping tool in the Admin panel. Crop is enabled by default. [More](#crop-and-focal-point-selector) |
| **`disableLocalStorage`** | Completely disable uploading files to disk locally. [More](#disabling-local-upload-storage) |
| **`focalPoint`** | Set to `false` to disable the focal point selection tool in the Admin panel. The focal point selector is only available when `imageSizes` or `resizeOptions` are defined. [More](#crop-and-focal-point-selector) |
| **`formatOptions`** | An object with `format` and `options` that are used with the Sharp image library to format the upload file. [More](https://sharp.pixelplumbing.com/api-output#toformat) |
| **`handlers`** | Array of Express request handlers to execute before the built-in Payload static middleware executes. |
| **`imageSizes`** | If specified, image uploads will be automatically resized in accordance to these image sizes. [More](#image-sizes) |
| **`mimeTypes`** | Restrict mimeTypes in the file picker. Array of valid mimetypes or mimetype wildcards [More](#mimetypes) |
| **`staticOptions`** | Set options for `express.static` to use while serving your static files. [More](http://expressjs.com/en/resources/middleware/serve-static.html) |
| **`resizeOptions`** | An object passed to the the Sharp image library to resize the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize) |
| **`filesRequiredOnCreate`** | Mandate file data on creation, default is true. |
| Option | Description |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`staticURL`** \* | The URL path to use to access your uploads. Relative path like `/media` will be served by payload. Full path like `https://example.com/media` needs to be served by another web server. |
| **`staticDir`** \* | The folder directory to use to store media in. Can be either an absolute path or relative to the directory that contains your config. |
| **`adminThumbnail`** | Set the way that the Admin panel will display thumbnails for this Collection. [More](#admin-thumbnails) |
| **`crop`** | Set to `false` to disable the cropping tool in the Admin panel. Crop is enabled by default. [More](#crop-and-focal-point-selector) |
| **`disableLocalStorage`** | Completely disable uploading files to disk locally. [More](#disabling-local-upload-storage) |
| **`externalFileHeaderFilter`** | Accepts existing headers and can filter/modify them. |
| **`focalPoint`** | Set to `false` to disable the focal point selection tool in the Admin panel. The focal point selector is only available when `imageSizes` or `resizeOptions` are defined. [More](#crop-and-focal-point-selector) |
| **`formatOptions`** | An object with `format` and `options` that are used with the Sharp image library to format the upload file. [More](https://sharp.pixelplumbing.com/api-output#toformat) |
| **`handlers`** | Array of Express request handlers to execute before the built-in Payload static middleware executes. |
| **`imageSizes`** | If specified, image uploads will be automatically resized in accordance to these image sizes. [More](#image-sizes) |
| **`mimeTypes`** | Restrict mimeTypes in the file picker. Array of valid mimetypes or mimetype wildcards [More](#mimetypes) |
| **`staticOptions`** | Set options for `express.static` to use while serving your static files. [More](http://expressjs.com/en/resources/middleware/serve-static.html) |
| **`resizeOptions`** | An object passed to the the Sharp image library to resize the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize) |
| **`filesRequiredOnCreate`** | Mandate file data on creation, default is true. |
_An asterisk denotes that a property above is required._

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -128,6 +128,7 @@
]
},
"dependencies": {
"@sentry/react": "^7.77.0"
"@sentry/react": "^7.77.0",
"ajv": "^8.12.0"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/bundler-vite",
"version": "0.1.6",
"version": "0.1.7",
"description": "The officially supported Vite bundler adapter for Payload",
"repository": {
"type": "git",
@@ -41,7 +41,7 @@
},
"peerDependencies": {
"payload": "^2.0.0",
"react-dom": "18.2.0"
"react-dom": "^18.0.0"
},
"publishConfig": {
"main": "./dist/index.js",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-mongodb",
"version": "1.4.4",
"version": "1.5.1",
"description": "The officially supported MongoDB database adapter for Payload",
"repository": {
"type": "git",

View File

@@ -0,0 +1,49 @@
import type { QueryOptions } from 'mongoose'
import type { Count } from 'payload/database'
import type { PayloadRequest } from 'payload/types'
import { flattenWhereToOperators } from 'payload/database'
import type { MongooseAdapter } from '.'
import { withSession } from './withSession'
export const count: Count = async function count(
this: MongooseAdapter,
{ collection, locale, req = {} as PayloadRequest, where },
) {
const Model = this.collections[collection]
const options: QueryOptions = withSession(this, req.transactionID)
let hasNearConstraint = false
if (where) {
const constraints = flattenWhereToOperators(where)
hasNearConstraint = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near'))
}
const query = await Model.buildQuery({
locale,
payload: this.payload,
where,
})
// useEstimatedCount is faster, but not accurate, as it ignores any filters. It is thus set to true if there are no filters.
const useEstimatedCount = hasNearConstraint || !query || Object.keys(query).length === 0
if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) {
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
// the correct indexed field
options.hint = {
_id: 1,
}
}
const result = await Model.countDocuments(query, options)
return {
totalDocs: result,
}
}

View File

@@ -11,6 +11,7 @@ import { createDatabaseAdapter } from 'payload/database'
import type { CollectionModel, GlobalModel } from './types'
import { connect } from './connect'
import { count } from './count'
import { create } from './create'
import { createGlobal } from './createGlobal'
import { createGlobalVersion } from './createGlobalVersion'
@@ -108,6 +109,7 @@ export function mongooseAdapter({
collections: {},
connectOptions: connectOptions || {},
connection: undefined,
count,
disableIndexHints,
globals: undefined,
mongoMemoryServer: undefined,
@@ -115,7 +117,6 @@ export function mongooseAdapter({
transactionOptions: transactionOptions === false ? undefined : transactionOptions,
url,
versions: {},
// DatabaseAdapter
beginTransaction: transactionOptions ? beginTransaction : undefined,
commitTransaction,

View File

@@ -6,11 +6,7 @@ import type { SanitizedCollectionConfig } from 'payload/types'
import mongoose from 'mongoose'
import mongooseAggregatePaginate from 'mongoose-aggregate-paginate-v2'
import paginate from 'mongoose-paginate-v2'
import {
buildVersionCollectionFields,
buildVersionGlobalFields,
getVersionsModelName,
} from 'payload/versions'
import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload/versions'
import type { MongooseAdapter } from '.'
import type { CollectionModel } from './types'
@@ -33,6 +29,7 @@ export const init: Init = async function init(this: MongooseAdapter) {
const versionSchema = buildSchema(this.payload.config, versionCollectionFields, {
disableUnique: true,
draftsEnabled: true,
indexSortableFields: this.payload.config.indexSortableFields,
options: {
minimize: false,
timestamps: false,

View File

@@ -142,7 +142,10 @@ export const sanitizeQueryValue = ({
if (path !== '_id' || (path === '_id' && hasCustomID && field.type === 'text')) {
if (operator === 'contains') {
formattedValue = { $options: 'i', $regex: formattedValue }
formattedValue = {
$options: 'i',
$regex: formattedValue.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&'),
}
}
}

View File

@@ -6,6 +6,10 @@ export const commitTransaction: CommitTransaction = async function commitTransac
}
await this.sessions[id].commitTransaction()
await this.sessions[id].endSession()
try {
await this.sessions[id].endSession()
} catch (error) {
// ending sessions is only best effort and won't impact anything if it fails since the transaction was committed
}
delete this.sessions[id]
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "0.7.1",
"version": "0.8.4",
"description": "The officially supported Postgres database adapter for Payload",
"repository": {
"type": "git",

View File

@@ -0,0 +1,62 @@
import type { Count } from 'payload/database'
import type { SanitizedCollectionConfig } from 'payload/types'
import { sql } from 'drizzle-orm'
import toSnakeCase from 'to-snake-case'
import type { ChainedMethods } from './find/chainMethods'
import type { PostgresAdapter } from './types'
import { chainMethods } from './find/chainMethods'
import buildQuery from './queries/buildQuery'
export const count: Count = async function count(
this: PostgresAdapter,
{ collection, locale, req, where: whereArg },
) {
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
const tableName = this.tableNameMap.get(toSnakeCase(collectionConfig.slug))
const db = this.sessions[req.transactionID]?.db || this.drizzle
const table = this.tables[tableName]
const { joinAliases, joins, where } = await buildQuery({
adapter: this,
fields: collectionConfig.fields,
locale,
tableName,
where: whereArg,
})
const selectCountMethods: ChainedMethods = []
joinAliases.forEach(({ condition, table }) => {
selectCountMethods.push({
args: [table, condition],
method: 'leftJoin',
})
})
Object.entries(joins).forEach(([joinTable, condition]) => {
if (joinTable) {
selectCountMethods.push({
args: [this.tables[joinTable], condition],
method: 'leftJoin',
})
}
})
const countResult = await chainMethods({
methods: selectCountMethods,
query: db
.select({
count: sql<number>`count
(DISTINCT ${this.tables[tableName].id})`,
})
.from(table)
.where(where),
})
return { totalDocs: Number(countResult[0].count) }
}

View File

@@ -2,8 +2,8 @@ import type { Create } from 'payload/database'
import type { PostgresAdapter } from './types'
import { getTableName } from './schema/getTableName'
import { upsertRow } from './upsertRow'
import toSnakeCase from 'to-snake-case'
export const create: Create = async function create(
this: PostgresAdapter,
@@ -12,6 +12,8 @@ export const create: Create = async function create(
const db = this.sessions[req.transactionID]?.db || this.drizzle
const collection = this.payload.collections[collectionSlug].config
const tableName = this.tableNameMap.get(toSnakeCase(collection.slug))
const result = await upsertRow({
adapter: this,
data,
@@ -19,10 +21,7 @@ export const create: Create = async function create(
fields: collection.fields,
operation: 'create',
req,
tableName: getTableName({
adapter: this,
config: collection,
}),
tableName,
})
return result

View File

@@ -1,9 +1,10 @@
import type { CreateGlobalArgs } from 'payload/database'
import type { PayloadRequest, TypeWithID } from 'payload/types'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types'
import { getTableName } from './schema/getTableName'
import { upsertRow } from './upsertRow'
export async function createGlobal<T extends TypeWithID>(
@@ -13,6 +14,8 @@ export async function createGlobal<T extends TypeWithID>(
const db = this.sessions[req.transactionID]?.db || this.drizzle
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
const tableName = this.tableNameMap.get(toSnakeCase(globalConfig.slug))
const result = await upsertRow<T>({
adapter: this,
data,
@@ -20,10 +23,7 @@ export async function createGlobal<T extends TypeWithID>(
fields: globalConfig.fields,
operation: 'create',
req,
tableName: getTableName({
adapter: this,
config: globalConfig,
}),
tableName,
})
return result

View File

@@ -4,10 +4,10 @@ import type { PayloadRequest, TypeWithID } from 'payload/types'
import { sql } from 'drizzle-orm'
import { type CreateGlobalVersionArgs } from 'payload/database'
import { buildVersionGlobalFields } from 'payload/versions'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types'
import { getTableName } from './schema/getTableName'
import { upsertRow } from './upsertRow'
export async function createGlobalVersion<T extends TypeWithID>(
@@ -16,11 +16,8 @@ export async function createGlobalVersion<T extends TypeWithID>(
) {
const db = this.sessions[req.transactionID]?.db || this.drizzle
const global = this.payload.globals.config.find(({ slug }) => slug === globalSlug)
const tableName = getTableName({
adapter: this,
config: global,
versions: true,
})
const tableName = this.tableNameMap.get(`_${toSnakeCase(global.slug)}${this.versionsSuffix}`)
const result = await upsertRow<TypeWithVersion<T>>({
adapter: this,
@@ -40,9 +37,9 @@ export async function createGlobalVersion<T extends TypeWithID>(
if (global.versions.drafts) {
await db.execute(sql`
UPDATE ${table}
SET latest = false
WHERE ${table.id} != ${result.id};
UPDATE ${table}
SET latest = false
WHERE ${table.id} != ${result.id};
`)
}

View File

@@ -3,10 +3,10 @@ import type { PayloadRequest, TypeWithID } from 'payload/types'
import { sql } from 'drizzle-orm'
import { buildVersionCollectionFields } from 'payload/versions'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types'
import { getTableName } from './schema/getTableName'
import { upsertRow } from './upsertRow'
export async function createVersion<T extends TypeWithID>(
@@ -21,11 +21,12 @@ export async function createVersion<T extends TypeWithID>(
) {
const db = this.sessions[req.transactionID]?.db || this.drizzle
const collection = this.payload.collections[collectionSlug].config
const tableName = getTableName({
adapter: this,
config: collection,
versions: true,
})
const defaultTableName = toSnakeCase(collection.slug)
const tableName = this.tableNameMap.get(`_${defaultTableName}${this.versionsSuffix}`)
const version = { ...versionData }
if (version.id) delete version.id
const result = await upsertRow<TypeWithVersion<T>>({
adapter: this,
@@ -33,7 +34,7 @@ export async function createVersion<T extends TypeWithID>(
autosave,
latest: true,
parent,
version: versionData,
version,
},
db,
fields: buildVersionCollectionFields(collection),
@@ -43,25 +44,18 @@ export async function createVersion<T extends TypeWithID>(
})
const table = this.tables[tableName]
const relationshipsTable =
this.tables[
getTableName({
adapter: this,
config: collection,
relationships: true,
versions: true,
})
]
const relationshipsTable = this.tables[`${tableName}${this.relationshipsSuffix}`]
if (collection.versions.drafts) {
await db.execute(sql`
UPDATE ${table}
SET latest = false
FROM ${relationshipsTable}
WHERE ${table.id} = ${relationshipsTable.parent}
AND ${relationshipsTable.path} = ${'parent'}
AND ${relationshipsTable[`${collectionSlug}ID`]} = ${parent}
AND ${table.id} != ${result.id};
UPDATE ${table}
SET latest = false
FROM ${relationshipsTable}
WHERE ${table.id} = ${relationshipsTable.parent}
AND ${relationshipsTable.path} = ${'parent'}
AND ${relationshipsTable[`${collectionSlug}ID`]} = ${parent}
AND ${table.id} != ${result.id};
`)
}

View File

@@ -2,11 +2,11 @@ import type { DeleteMany } from 'payload/database'
import type { PayloadRequest } from 'payload/types'
import { inArray } from 'drizzle-orm'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types'
import { findMany } from './find/findMany'
import { getTableName } from './schema/getTableName'
export const deleteMany: DeleteMany = async function deleteMany(
this: PostgresAdapter,
@@ -14,7 +14,8 @@ export const deleteMany: DeleteMany = async function deleteMany(
) {
const db = this.sessions[req.transactionID]?.db || this.drizzle
const collectionConfig = this.payload.collections[collection].config
const tableName = getTableName({ adapter: this, config: collectionConfig })
const tableName = this.tableNameMap.get(toSnakeCase(collectionConfig.slug))
const result = await findMany({
adapter: this,

View File

@@ -2,13 +2,13 @@ import type { DeleteOne } from 'payload/database'
import type { PayloadRequest } from 'payload/types'
import { eq } from 'drizzle-orm'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types'
import { buildFindManyArgs } from './find/buildFindManyArgs'
import buildQuery from './queries/buildQuery'
import { selectDistinct } from './queries/selectDistinct'
import { getTableName } from './schema/getTableName'
import { transform } from './transform/read'
export const deleteOne: DeleteOne = async function deleteOne(
@@ -17,10 +17,9 @@ export const deleteOne: DeleteOne = async function deleteOne(
) {
const db = this.sessions[req.transactionID]?.db || this.drizzle
const collection = this.payload.collections[collectionSlug].config
const tableName = getTableName({
adapter: this,
config: collection,
})
const tableName = this.tableNameMap.get(toSnakeCase(collection.slug))
let docToDelete: Record<string, unknown>
const { joinAliases, joins, selectFields, where } = await buildQuery({

View File

@@ -3,11 +3,11 @@ import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
import { inArray } from 'drizzle-orm'
import { buildVersionCollectionFields } from 'payload/versions'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types'
import { findMany } from './find/findMany'
import { getTableName } from './schema/getTableName'
export const deleteVersions: DeleteVersions = async function deleteVersion(
this: PostgresAdapter,
@@ -16,11 +16,10 @@ export const deleteVersions: DeleteVersions = async function deleteVersion(
const db = this.sessions[req.transactionID]?.db || this.drizzle
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
const tableName = getTableName({
adapter: this,
config: collectionConfig,
versions: true,
})
const tableName = this.tableNameMap.get(
`_${toSnakeCase(collectionConfig.slug)}${this.versionsSuffix}`,
)
const fields = buildVersionCollectionFields(collectionConfig)
const { docs } = await findMany({

View File

@@ -1,10 +1,11 @@
import type { Find } from 'payload/database'
import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types'
import { findMany } from './find/findMany'
import { getTableName } from './schema/getTableName'
export const find: Find = async function find(
this: PostgresAdapter,
@@ -21,10 +22,8 @@ export const find: Find = async function find(
) {
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
const sort = typeof sortArg === 'string' ? sortArg : collectionConfig.defaultSort
const tableName = getTableName({
adapter: this,
config: collectionConfig,
})
const tableName = this.tableNameMap.get(toSnakeCase(collectionConfig.slug))
return findMany({
adapter: this,

View File

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

View File

@@ -2,12 +2,11 @@
import type { Field } from 'payload/types'
import { fieldAffectsData, tabHasName } from 'payload/types'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from '../types'
import type { Result } from './buildFindManyArgs'
import { getTableName } from '../schema/getTableName'
type TraverseFieldArgs = {
_locales: Record<string, unknown>
adapter: PostgresAdapter
@@ -79,20 +78,11 @@ export const traverseFields = ({
with: {},
}
const arrayTableName = getTableName({
adapter,
config: field,
parentTableName: currentTableName,
prefix: `${currentTableName}_${path}`,
})
const arrayTableName = adapter.tableNameMap.get(
`${currentTableName}_${path}${toSnakeCase(field.name)}`,
)
const arrayTableNameWithLocales = getTableName({
adapter,
config: field,
locales: true,
parentTableName: currentTableName,
prefix: `${currentTableName}_${path}`,
})
const arrayTableNameWithLocales = `${arrayTableName}${adapter.localesSuffix}`
if (adapter.tables[arrayTableNameWithLocales]) withArray.with._locales = _locales
currentArgs.with[`${path}${field.name}`] = withArray
@@ -142,15 +132,13 @@ export const traverseFields = ({
with: {},
}
const tableName = getTableName({
adapter,
config: block,
parentTableName: topLevelTableName,
prefix: `${topLevelTableName}_blocks_`,
})
const tableName = adapter.tableNameMap.get(
`${topLevelTableName}_blocks_${toSnakeCase(block.slug)}`,
)
if (adapter.tables[`${tableName}${adapter.localesSuffix}`])
if (adapter.tables[`${tableName}${adapter.localesSuffix}`]) {
withBlock.with._locales = _locales
}
topLevelArgs.with[blockKey] = withBlock
traverseFields({

View File

@@ -1,19 +1,18 @@
import type { FindGlobal } from 'payload/database'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types'
import { findMany } from './find/findMany'
import { getTableName } from './schema/getTableName'
export const findGlobal: FindGlobal = async function findGlobal(
this: PostgresAdapter,
{ slug, locale, req, where },
) {
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
const tableName = getTableName({
adapter: this,
config: globalConfig,
})
const tableName = this.tableNameMap.get(toSnakeCase(globalConfig.slug))
const {
docs: [doc],

View File

@@ -2,11 +2,11 @@ import type { FindGlobalVersions } from 'payload/database'
import type { PayloadRequest, SanitizedGlobalConfig } from 'payload/types'
import { buildVersionGlobalFields } from 'payload/versions'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types'
import { findMany } from './find/findMany'
import { getTableName } from './schema/getTableName'
export const findGlobalVersions: FindGlobalVersions = async function findGlobalVersions(
this: PostgresAdapter,
@@ -27,11 +27,10 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
)
const sort = typeof sortArg === 'string' ? sortArg : '-createdAt'
const tableName = getTableName({
adapter: this,
config: globalConfig,
versions: true,
})
const tableName = this.tableNameMap.get(
`_${toSnakeCase(globalConfig.slug)}${this.versionsSuffix}`,
)
const fields = buildVersionGlobalFields(globalConfig)
return findMany({

View File

@@ -1,20 +1,19 @@
import type { FindOneArgs } from 'payload/database'
import type { PayloadRequest, SanitizedCollectionConfig, TypeWithID } from 'payload/types'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types'
import { findMany } from './find/findMany'
import { getTableName } from './schema/getTableName'
export async function findOne<T extends TypeWithID>(
this: PostgresAdapter,
{ collection, locale, req = {} as PayloadRequest, where }: FindOneArgs,
): Promise<T> {
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
const tableName = getTableName({
adapter: this,
config: collectionConfig,
})
const tableName = this.tableNameMap.get(toSnakeCase(collectionConfig.slug))
const { docs } = await findMany({
adapter: this,

View File

@@ -2,11 +2,11 @@ import type { FindVersions } from 'payload/database'
import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
import { buildVersionCollectionFields } from 'payload/versions'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types'
import { findMany } from './find/findMany'
import { getTableName } from './schema/getTableName'
export const findVersions: FindVersions = async function findVersions(
this: PostgresAdapter,
@@ -25,11 +25,10 @@ export const findVersions: FindVersions = async function findVersions(
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
const sort = typeof sortArg === 'string' ? sortArg : collectionConfig.defaultSort
const tableName = getTableName({
adapter: this,
config: collectionConfig,
versions: true,
})
const tableName = this.tableNameMap.get(
`_${toSnakeCase(collectionConfig.slug)}${this.versionsSuffix}`,
)
const fields = buildVersionCollectionFields(collectionConfig)
return findMany({

View File

@@ -7,6 +7,7 @@ import { createDatabaseAdapter } from 'payload/database'
import type { Args, PostgresAdapter, PostgresAdapterResult } from './types'
import { connect } from './connect'
import { count } from './count'
import { create } from './create'
import { createGlobal } from './createGlobal'
import { createGlobalVersion } from './createGlobalVersion'
@@ -40,18 +41,18 @@ import { updateVersion } from './updateVersion'
export type { MigrateDownArgs, MigrateUpArgs } from './types'
export function postgresAdapter(args: Args): PostgresAdapterResult {
const postgresIDType = args.idType || 'serial'
const payloadIDType = postgresIDType === 'serial' ? 'number' : 'text'
function adapter({ payload }: { payload: Payload }) {
const migrationDir = findMigrationDir(args.migrationDir)
const idType = args.idType || 'serial'
return createDatabaseAdapter<PostgresAdapter>({
name: 'postgres',
// Postgres-specific
blockTableNames: {},
drizzle: undefined,
enums: {},
fieldConstraints: {},
idType,
idType: postgresIDType,
localesSuffix: args.localesSuffix || '_locales',
logger: args.logger,
pgSchema: undefined,
@@ -63,6 +64,7 @@ export function postgresAdapter(args: Args): PostgresAdapterResult {
schema: {},
schemaName: args.schemaName,
sessions: {},
tableNameMap: new Map<string, string>(),
tables: {},
versionsSuffix: args.versionsSuffix || '_v',
@@ -70,15 +72,13 @@ export function postgresAdapter(args: Args): PostgresAdapterResult {
beginTransaction,
commitTransaction,
connect,
count,
create,
createGlobal,
createGlobalVersion,
createMigration,
createVersion,
/**
* This represents how a default ID is treated in Payload as were a field type
*/
defaultIDType: idType === 'serial' ? 'number' : 'text',
defaultIDType: payloadIDType,
deleteMany,
deleteOne,
deleteVersions,

View File

@@ -8,7 +8,7 @@ import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload/
import type { PostgresAdapter } from './types'
import { buildTable } from './schema/build'
import { getTableName } from './schema/getTableName'
import { createTableName } from './schema/createTableName'
export const init: Init = async function init(this: PostgresAdapter) {
if (this.schemaName) {
@@ -25,7 +25,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
}
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
const tableName = getTableName({
const tableName = createTableName({
adapter: this,
config: collection,
})
@@ -44,10 +44,11 @@ export const init: Init = async function init(this: PostgresAdapter) {
})
if (collection.versions) {
const versionsTableName = getTableName({
const versionsTableName = createTableName({
adapter: this,
config: collection,
versions: true,
versionsCustomName: true,
})
const versionFields = buildVersionCollectionFields(collection)
@@ -67,7 +68,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
})
this.payload.config.globals.forEach((global) => {
const tableName = getTableName({ adapter: this, config: global })
const tableName = createTableName({ adapter: this, config: global })
buildTable({
adapter: this,
@@ -83,7 +84,12 @@ export const init: Init = async function init(this: PostgresAdapter) {
})
if (global.versions) {
const versionsTableName = getTableName({ adapter: this, config: global, versions: true })
const versionsTableName = createTableName({
adapter: this,
config: global,
versions: true,
versionsCustomName: true,
})
const versionFields = buildVersionGlobalFields(global)
buildTable({

View File

@@ -14,8 +14,6 @@ import { v4 as uuid } from 'uuid'
import type { GenericColumn, GenericTable, PostgresAdapter } from '../types'
import type { BuildQueryJoinAliases, BuildQueryJoins } from './buildQuery'
import { getTableName } from '../schema/getTableName'
type Constraint = {
columnName: string
table: GenericTable | PgTableWithColumns<any>
@@ -185,13 +183,7 @@ export const getTableColumnFromPath = ({
case 'group': {
if (locale && field.localized && adapter.payload.config.localization) {
newTableName = getTableName({
adapter,
config: field,
locales: true,
parentTableName: tableName,
prefix: `${tableName}_`,
})
newTableName = `${tableName}${adapter.localesSuffix}`
joins[tableName] = eq(
adapter.tables[tableName].id,
@@ -225,13 +217,87 @@ export const getTableColumnFromPath = ({
})
}
case 'select': {
if (field.hasMany) {
const newTableName = adapter.tableNameMap.get(
`${tableName}_${tableNameSuffix}${toSnakeCase(field.name)}`,
)
if (locale && field.localized && adapter.payload.config.localization) {
joins[newTableName] = and(
eq(adapter.tables[tableName].id, adapter.tables[newTableName].parent),
eq(adapter.tables[newTableName]._locale, locale),
)
if (locale !== 'all') {
constraints.push({
columnName: '_locale',
table: adapter.tables[newTableName],
value: locale,
})
}
} else {
joins[newTableName] = eq(
adapter.tables[tableName].id,
adapter.tables[newTableName].parent,
)
}
return {
columnName: 'value',
constraints,
field,
table: adapter.tables[newTableName],
}
}
break
}
case 'text':
case 'number': {
if (field.hasMany) {
let tableType = 'texts'
let columnName = 'text'
if (field.type === 'number') {
tableType = 'numbers'
columnName = 'number'
}
newTableName = `${tableName}_${tableType}`
const joinConstraints = [
eq(adapter.tables[tableName].id, adapter.tables[newTableName].parent),
eq(adapter.tables[newTableName].path, `${constraintPath}${field.name}`),
]
if (locale && field.localized && adapter.payload.config.localization) {
joins[newTableName] = and(
...joinConstraints,
eq(adapter.tables[newTableName]._locale, locale),
)
if (locale !== 'all') {
constraints.push({
columnName: 'locale',
table: adapter.tables[newTableName],
value: locale,
})
}
} else {
joins[newTableName] = and(...joinConstraints)
}
return {
columnName,
constraints,
field,
table: adapter.tables[newTableName],
}
}
break
}
case 'array': {
newTableName = getTableName({
adapter,
config: field,
parentTableName: `${tableName}_${tableNameSuffix}`,
prefix: `${tableName}_${tableNameSuffix}`,
})
newTableName = adapter.tableNameMap.get(
`${tableName}_${tableNameSuffix}${toSnakeCase(field.name)}`,
)
constraintPath = `${constraintPath}${field.name}.%.`
if (locale && field.localized && adapter.payload.config.localization) {
joins[newTableName] = and(
@@ -278,12 +344,11 @@ export const getTableColumnFromPath = ({
const blockTypes = Array.isArray(value) ? value : [value]
blockTypes.forEach((blockType) => {
const block = field.blocks.find((block) => block.slug === blockType)
newTableName = getTableName({
adapter,
config: block,
parentTableName: tableName,
prefix: `${tableName}_blocks_`,
})
newTableName = adapter.tableNameMap.get(
`${tableName}_blocks_${toSnakeCase(block.slug)}`,
)
joins[newTableName] = eq(
adapter.tables[tableName].id,
adapter.tables[newTableName]._parentID,
@@ -303,13 +368,9 @@ export const getTableColumnFromPath = ({
}
const hasBlockField = field.blocks.some((block) => {
newTableName = getTableName({
adapter,
config: block,
parentTableName: tableName,
prefix: `${tableName}_blocks_`,
})
newTableName = adapter.tableNameMap.get(`${tableName}_blocks_${toSnakeCase(block.slug)}`)
constraintPath = `${constraintPath}${field.name}.%.`
let result
const blockConstraints = []
const blockSelectFields = {}
@@ -382,6 +443,7 @@ export const getTableColumnFromPath = ({
aliasRelationshipTableName,
)
// Join in the relationships table
if (locale && field.localized && adapter.payload.config.localization) {
joinAliases.push({
condition: and(
@@ -415,10 +477,9 @@ export const getTableColumnFromPath = ({
if (typeof field.relationTo === 'string') {
const relationshipConfig = adapter.payload.collections[field.relationTo].config
newTableName = getTableName({
adapter,
config: relationshipConfig,
})
newTableName = adapter.tableNameMap.get(toSnakeCase(relationshipConfig.slug))
// parent to relationship join table
relationshipFields = relationshipConfig.fields
@@ -438,13 +499,13 @@ export const getTableColumnFromPath = ({
}
}
} else if (newCollectionPath === 'value') {
const tableColumnsNames = field.relationTo.map(
(relationTo) =>
`"${aliasRelationshipTableName}"."${getTableName({
adapter,
config: adapter.payload.collections[relationTo].config,
})}_id"`,
)
const tableColumnsNames = field.relationTo.map((relationTo) => {
const relationTableName = adapter.tableNameMap.get(
toSnakeCase(adapter.payload.collections[relationTo].config.slug),
)
return `"${aliasRelationshipTableName}"."${relationTableName}_id"`
})
return {
constraints,
field,
@@ -484,43 +545,41 @@ export const getTableColumnFromPath = ({
value,
})
}
}
default: {
if (fieldAffectsData(field)) {
if (field.localized && adapter.payload.config.localization) {
// If localized, we go to localized table and set aliasTable to undefined
// so it is not picked up below to be used as targetTable
newTableName = `${tableName}${adapter.localesSuffix}`
if (fieldAffectsData(field)) {
if (field.localized && adapter.payload.config.localization) {
// If localized, we go to localized table and set aliasTable to undefined
// so it is not picked up below to be used as targetTable
newTableName = `${tableName}${adapter.localesSuffix}`
const parentTable = aliasTable || adapter.tables[tableName]
const parentTable = aliasTable || adapter.tables[tableName]
joins[newTableName] = eq(parentTable.id, adapter.tables[newTableName]._parentID)
joins[newTableName] = eq(parentTable.id, adapter.tables[newTableName]._parentID)
aliasTable = undefined
aliasTable = undefined
if (locale !== 'all') {
constraints.push({
columnName: '_locale',
table: adapter.tables[newTableName],
value: locale,
})
}
}
const targetTable = aliasTable || adapter.tables[newTableName]
selectFields[`${newTableName}.${columnPrefix}${field.name}`] =
targetTable[`${columnPrefix}${field.name}`]
return {
columnName: `${columnPrefix}${field.name}`,
constraints,
field,
pathSegments,
table: targetTable,
}
if (locale !== 'all') {
constraints.push({
columnName: '_locale',
table: adapter.tables[newTableName],
value: locale,
})
}
}
const targetTable = aliasTable || adapter.tables[newTableName]
selectFields[`${newTableName}.${columnPrefix}${field.name}`] =
targetTable[`${columnPrefix}${field.name}`]
return {
columnName: `${columnPrefix}${field.name}`,
constraints,
field,
pathSegments,
table: targetTable,
}
}
}

View File

@@ -71,7 +71,7 @@ export async function parseParams({
// So we need to loop on keys again here to handle each operator independently
const pathOperators = where[relationOrPath]
if (typeof pathOperators === 'object') {
for (const operator of Object.keys(pathOperators)) {
for (let operator of Object.keys(pathOperators)) {
if (validOperators.includes(operator as Operator)) {
const val = where[relationOrPath][operator]
const {
@@ -157,6 +157,13 @@ export async function parseParams({
break
}
if (
operator === 'like' &&
(field.type === 'number' || table[columnName].columnType === 'PgUUID')
) {
operator = 'equals'
}
if (operator === 'like') {
constraints.push(
and(...val.split(' ').map((word) => ilike(table[columnName], `%${word}%`))),
@@ -195,10 +202,10 @@ export async function parseParams({
operator === 'not_in'
) {
constraints.push(
sql`${notInArray(table[columnName], queryValue)} OR
sql`(${notInArray(table[columnName], queryValue)} OR
${table[columnName]}
IS
NULL`,
NULL)`,
)
break

View File

@@ -85,6 +85,10 @@ export const sanitizeQueryValue = ({
}
}
if ('hasMany' in field && field.hasMany && operator === 'contains') {
operator = 'equals'
}
if (operator === 'near' || operator === 'within' || operator === 'intersects') {
throw new APIError(
`Querying with '${operator}' is not supported with the postgres database adapter.`,

View File

@@ -2,26 +2,20 @@ import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
import { type QueryDrafts, combineQueries } from 'payload/database'
import { buildVersionCollectionFields } from 'payload/versions'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types'
import { findMany } from './find/findMany'
import { getTableName } from './schema/getTableName'
export const queryDrafts: QueryDrafts = async function queryDrafts({
collection,
limit,
locale,
page = 1,
pagination,
req = {} as PayloadRequest,
sort,
where,
}) {
export const queryDrafts: QueryDrafts = async function queryDrafts(
this: PostgresAdapter,
{ collection, limit, locale, page = 1, pagination, req = {} as PayloadRequest, sort, where },
) {
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
const tableName = getTableName({
adapter: this,
config: collectionConfig,
versions: true,
})
const tableName = this.tableNameMap.get(
`_${toSnakeCase(collectionConfig.slug)}${this.versionsSuffix}`,
)
const fields = buildVersionCollectionFields(collectionConfig)
const combinedWhere = combineQueries({ latest: { equals: true } }, where)

View File

@@ -1,28 +1,43 @@
/* eslint-disable no-param-reassign */
import type { Relation } from 'drizzle-orm'
import type {
ForeignKeyBuilder,
IndexBuilder,
PgColumnBuilder,
PgTableWithColumns,
UniqueConstraintBuilder,
} from 'drizzle-orm/pg-core'
import type { Field } from 'payload/types'
import { Field, fieldAffectsData } from 'payload/types'
import { relations } from 'drizzle-orm'
import { index, integer, numeric, serial, timestamp, unique, varchar } from 'drizzle-orm/pg-core'
import { fieldAffectsData } from 'payload/types'
import {
foreignKey,
index,
integer,
numeric,
serial,
timestamp,
unique,
varchar,
} from 'drizzle-orm/pg-core'
import toSnakeCase from 'to-snake-case'
import type { GenericColumns, GenericTable, IDType, PostgresAdapter } from '../types'
import { getTableName } from './getTableName'
import { createTableName } from './createTableName'
import { parentIDColumnMap } from './parentIDColumnMap'
import { setColumnID } from './setColumnID'
import { traverseFields } from './traverseFields'
export type BaseExtraConfig = Record<
string,
(cols: GenericColumns) => ForeignKeyBuilder | IndexBuilder | UniqueConstraintBuilder
>
type Args = {
adapter: PostgresAdapter
baseColumns?: Record<string, PgColumnBuilder>
baseExtraConfig?: Record<string, (cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder>
baseExtraConfig?: BaseExtraConfig
buildNumbers?: boolean
buildRelationships?: boolean
buildTexts?: boolean
@@ -66,13 +81,6 @@ export const buildTable = ({
const columns: Record<string, PgColumnBuilder> = baseColumns
const indexes: Record<string, (cols: GenericColumns) => IndexBuilder> = {}
let hasLocalizedField = false
let hasLocalizedRelationshipField = false
let hasManyTextField: 'index' | boolean = false
let hasManyNumberField: 'index' | boolean = false
let hasLocalizedManyTextField = false
let hasLocalizedManyNumberField = false
const localesColumns: Record<string, PgColumnBuilder> = {}
const localesIndexes: Record<string, (cols: GenericColumns) => IndexBuilder> = {}
let localesTable: GenericTable | PgTableWithColumns<any>
@@ -89,7 +97,7 @@ export const buildTable = ({
const idColType: IDType = setColumnID({ adapter, columns, fields })
;({
const {
hasLocalizedField,
hasLocalizedManyNumberField,
hasLocalizedManyTextField,
@@ -116,7 +124,7 @@ export const buildTable = ({
rootTableIDColType: rootTableIDColType || idColType,
rootTableName,
versions,
}))
})
if (timestamps) {
columns.createdAt = timestamp('created_at', {
@@ -141,10 +149,12 @@ export const buildTable = ({
return config
}, {})
return Object.entries(indexes).reduce((acc, [colName, func]) => {
const result = Object.entries(indexes).reduce((acc, [colName, func]) => {
acc[colName] = func(cols)
return acc
}, extraConfig)
return result
})
adapter.tables[tableName] = table
@@ -153,9 +163,7 @@ export const buildTable = ({
const localeTableName = `${tableName}${adapter.localesSuffix}`
localesColumns.id = serial('id').primaryKey()
localesColumns._locale = adapter.enums.enum__locales('_locale').notNull()
localesColumns._parentID = parentIDColumnMap[idColType]('_parent_id')
.references(() => table.id, { onDelete: 'cascade' })
.notNull()
localesColumns._parentID = parentIDColumnMap[idColType]('_parent_id').notNull()
localesTable = adapter.pgSchema.table(localeTableName, localesColumns, (cols) => {
return Object.entries(localesIndexes).reduce(
@@ -168,6 +176,11 @@ export const buildTable = ({
cols._locale,
cols._parentID,
),
_parentIdFk: foreignKey({
name: `${localeTableName}_parent_id_fk`,
columns: [cols._parentID],
foreignColumns: [table.id],
}).onDelete('cascade'),
},
)
})
@@ -189,9 +202,7 @@ export const buildTable = ({
const columns: Record<string, PgColumnBuilder> = {
id: serial('id').primaryKey(),
order: integer('order').notNull(),
parent: parentIDColumnMap[idColType]('parent_id')
.references(() => table.id, { onDelete: 'cascade' })
.notNull(),
parent: parentIDColumnMap[idColType]('parent_id').notNull(),
path: varchar('path').notNull(),
text: varchar('text'),
}
@@ -201,19 +212,24 @@ export const buildTable = ({
}
textsTable = adapter.pgSchema.table(textsTableName, columns, (cols) => {
const indexes: Record<string, IndexBuilder> = {
const config: Record<string, ForeignKeyBuilder | IndexBuilder> = {
orderParentIdx: index(`${textsTableName}_order_parent_idx`).on(cols.order, cols.parent),
parentFk: foreignKey({
name: `${textsTableName}_parent_fk`,
columns: [cols.parent],
foreignColumns: [table.id],
}).onDelete('cascade'),
}
if (hasManyTextField === 'index') {
indexes.text_idx = index(`${textsTableName}_text_idx`).on(cols.text)
config.text_idx = index(`${textsTableName}_text_idx`).on(cols.text)
}
if (hasLocalizedManyTextField) {
indexes.localeParent = index(`${textsTableName}_locale_parent`).on(cols.locale, cols.parent)
config.localeParent = index(`${textsTableName}_locale_parent`).on(cols.locale, cols.parent)
}
return indexes
return config
})
adapter.tables[textsTableName] = textsTable
@@ -234,9 +250,7 @@ export const buildTable = ({
id: serial('id').primaryKey(),
number: numeric('number'),
order: integer('order').notNull(),
parent: parentIDColumnMap[idColType]('parent_id')
.references(() => table.id, { onDelete: 'cascade' })
.notNull(),
parent: parentIDColumnMap[idColType]('parent_id').notNull(),
path: varchar('path').notNull(),
}
@@ -245,22 +259,27 @@ export const buildTable = ({
}
numbersTable = adapter.pgSchema.table(numbersTableName, columns, (cols) => {
const indexes: Record<string, IndexBuilder> = {
const config: Record<string, ForeignKeyBuilder | IndexBuilder> = {
orderParentIdx: index(`${numbersTableName}_order_parent_idx`).on(cols.order, cols.parent),
parentFk: foreignKey({
name: `${numbersTableName}_parent_fk`,
columns: [cols.parent],
foreignColumns: [table.id],
}).onDelete('cascade'),
}
if (hasManyNumberField === 'index') {
indexes.numberIdx = index(`${numbersTableName}_number_idx`).on(cols.number)
config.numberIdx = index(`${numbersTableName}_number_idx`).on(cols.number)
}
if (hasLocalizedManyNumberField) {
indexes.localeParent = index(`${numbersTableName}_locale_parent`).on(
config.localeParent = index(`${numbersTableName}_locale_parent`).on(
cols.locale,
cols.parent,
)
}
return indexes
return config
})
adapter.tables[numbersTableName] = numbersTable
@@ -280,9 +299,7 @@ export const buildTable = ({
const relationshipColumns: Record<string, PgColumnBuilder> = {
id: serial('id').primaryKey(),
order: integer('order'),
parent: parentIDColumnMap[idColType]('parent_id')
.references(() => table.id, { onDelete: 'cascade' })
.notNull(),
parent: parentIDColumnMap[idColType]('parent_id').notNull(),
path: varchar('path').notNull(),
}
@@ -290,36 +307,61 @@ export const buildTable = ({
relationshipColumns.locale = adapter.enums.enum__locales('locale')
}
const relationExtraConfig: BaseExtraConfig = {}
const relationshipsTableName = `${tableName}${adapter.relationshipsSuffix}`
relationships.forEach((relationTo) => {
const relationshipConfig = adapter.payload.collections[relationTo].config
const formattedRelationTo = getTableName({
const formattedRelationTo = createTableName({
adapter,
config: relationshipConfig,
throwValidationError: true,
})
let colType = adapter.idType === 'uuid' ? 'uuid' : 'integer'
const relatedCollectionCustomID = relationshipConfig.fields.find(
(field) => fieldAffectsData(field) && field.name === 'id',
)
if (relatedCollectionCustomID?.type === 'number') colType = 'numeric'
if (relatedCollectionCustomID?.type === 'text') colType = 'varchar'
const relatedCollectionCustomIDType = relatedCollectionCustomID?.type
if (relatedCollectionCustomIDType === 'number') colType = 'numeric'
if (relatedCollectionCustomIDType === 'text') colType = 'varchar'
relationshipColumns[`${relationTo}ID`] = parentIDColumnMap[colType](
`${formattedRelationTo}_id`,
).references(() => adapter.tables[formattedRelationTo].id, { onDelete: 'cascade' })
})
)
const relationshipsTableName = `${tableName}${adapter.relationshipsSuffix}`
relationExtraConfig[`${relationTo}IdFk`] = (cols) =>
foreignKey({
name: `${relationshipsTableName}_${toSnakeCase(relationTo)}_fk`,
columns: [cols[`${relationTo}ID`]],
foreignColumns: [adapter.tables[formattedRelationTo].id],
}).onDelete('cascade')
})
relationshipsTable = adapter.pgSchema.table(
relationshipsTableName,
relationshipColumns,
(cols) => {
const result: Record<string, unknown> = {
order: index(`${relationshipsTableName}_order_idx`).on(cols.order),
parentIdx: index(`${relationshipsTableName}_parent_idx`).on(cols.parent),
pathIdx: index(`${relationshipsTableName}_path_idx`).on(cols.path),
}
const result: Record<string, ForeignKeyBuilder | IndexBuilder> = Object.entries(
relationExtraConfig,
).reduce(
(config, [key, func]) => {
config[key] = func(cols)
return config
},
{
order: index(`${relationshipsTableName}_order_idx`).on(cols.order),
parentFk: foreignKey({
name: `${relationshipsTableName}_parent_fk`,
columns: [cols.parent],
foreignColumns: [table.id],
}).onDelete('cascade'),
parentIdx: index(`${relationshipsTableName}_parent_idx`).on(cols.parent),
pathIdx: index(`${relationshipsTableName}_path_idx`).on(cols.path),
},
)
if (hasLocalizedRelationshipField) {
result.localeIdx = index(`${relationshipsTableName}_locale_idx`).on(cols.locale)
@@ -341,7 +383,7 @@ export const buildTable = ({
}
relationships.forEach((relationTo) => {
const relatedTableName = getTableName({
const relatedTableName = createTableName({
adapter,
config: adapter.payload.collections[relationTo].config,
throwValidationError: true,

View File

@@ -14,53 +14,59 @@ type Args = {
name?: string
slug?: string
}
/** Localized tables need to be given the locales suffix */
locales?: boolean
/** For nested tables passed for the user custom dbName functions to handle their own iterations */
parentTableName?: string
/** For sub tables (array for example) this needs to include the parentTableName */
prefix?: string
/** Adds the relationships suffix */
relationships?: boolean
/** For tables based on fields that could have both enumName and dbName (ie: select with hasMany), default: 'dbName' */
target?: 'dbName' | 'enumName'
throwValidationError?: boolean
/** Adds the versions suffix, should only be used on the base collection to duplicate suffixing */
/** Adds the versions suffix to the default table name - should only be used on the base collection to avoid duplicate suffixing */
versions?: boolean
/** Adds the versions suffix to custom dbName only - this is used while creating blocks / selects / arrays / etc */
versionsCustomName?: boolean
}
/**
* Used to name database enums and tables
* Returns the table or enum name for a given entity
*/
export const getTableName = ({
export const createTableName = ({
adapter,
config: { name, slug },
config,
locales = false,
parentTableName,
prefix = '',
relationships = false,
target = 'dbName',
throwValidationError = false,
versions = false,
versionsCustomName = false,
}: Args): string => {
let result: string
let custom = config[target]
let customNameDefinition = config[target]
if (!custom && target === 'enumName') {
custom = config['dbName']
let defaultTableName = `${prefix}${toSnakeCase(name ?? slug)}`
if (versions) defaultTableName = `_${defaultTableName}${adapter.versionsSuffix}`
let customTableNameResult: string
if (!customNameDefinition && target === 'enumName') {
customNameDefinition = config['dbName']
}
if (custom) {
result = typeof custom === 'function' ? custom({ tableName: parentTableName }) : custom
} else {
result = `${prefix}${toSnakeCase(name ?? slug)}`
if (customNameDefinition) {
customTableNameResult =
typeof customNameDefinition === 'function'
? customNameDefinition({ tableName: parentTableName })
: customNameDefinition
if (versionsCustomName)
customTableNameResult = `_${customTableNameResult}${adapter.versionsSuffix}`
}
if (locales) result = `${result}${adapter.localesSuffix}`
if (versions) result = `_${result}${adapter.versionsSuffix}`
if (relationships) result = `${result}${adapter.relationshipsSuffix}`
const result = customTableNameResult || defaultTableName
adapter.tableNameMap.set(defaultTableName, result)
if (!throwValidationError) {
return result
@@ -71,5 +77,6 @@ export const getTableName = ({
`Exceeded max identifier length for table or enum name of 63 characters. Invalid name: ${result}`,
)
}
return result
}

View File

@@ -1,6 +1,6 @@
/* eslint-disable no-param-reassign */
import type { Relation } from 'drizzle-orm'
import type { IndexBuilder, PgColumnBuilder, UniqueConstraintBuilder } from 'drizzle-orm/pg-core'
import type { IndexBuilder, PgColumnBuilder } from 'drizzle-orm/pg-core'
import type { Field, TabAsField } from 'payload/types'
import { relations } from 'drizzle-orm'
@@ -9,6 +9,7 @@ import {
PgUUIDBuilder,
PgVarcharBuilder,
boolean,
foreignKey,
index,
integer,
jsonb,
@@ -23,11 +24,12 @@ import { fieldAffectsData, optionIsObject } from 'payload/types'
import toSnakeCase from 'to-snake-case'
import type { GenericColumns, IDType, PostgresAdapter } from '../types'
import type { BaseExtraConfig } from './build'
import { hasLocalesTable } from '../utilities/hasLocalesTable'
import { buildTable } from './build'
import { createIndex } from './createIndex'
import { getTableName } from './getTableName'
import { createTableName } from './createTableName'
import { idToUUID } from './idToUUID'
import { parentIDColumnMap } from './parentIDColumnMap'
import { validateExistingBlockIsIdentical } from './validateExistingBlockIsIdentical'
@@ -221,14 +223,13 @@ export const traverseFields = ({
case 'radio':
case 'select': {
const enumName = getTableName({
const enumName = createTableName({
adapter,
config: field,
parentTableName: newTableName,
prefix: `enum_${newTableName}_`,
target: 'enumName',
throwValidationError,
versions,
})
adapter.enums[enumName] = pgEnum(
@@ -243,27 +244,27 @@ export const traverseFields = ({
)
if (field.type === 'select' && field.hasMany) {
const selectTableName = getTableName({
const selectTableName = createTableName({
adapter,
config: field,
parentTableName: newTableName,
prefix: `${newTableName}_`,
throwValidationError,
versions,
})
const baseColumns: Record<string, PgColumnBuilder> = {
order: integer('order').notNull(),
parent: parentIDColumnMap[parentIDColType]('parent_id')
.references(() => adapter.tables[parentTableName].id, { onDelete: 'cascade' })
.notNull(),
parent: parentIDColumnMap[parentIDColType]('parent_id').notNull(),
value: adapter.enums[enumName]('value'),
}
const baseExtraConfig: Record<
string,
(cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder
> = {
const baseExtraConfig: BaseExtraConfig = {
orderIdx: (cols) => index(`${selectTableName}_order_idx`).on(cols.order),
parentFk: (cols) =>
foreignKey({
name: `${selectTableName}_parent_fk`,
columns: [cols.parent],
foreignColumns: [adapter.tables[parentTableName].id],
}),
parentIdx: (cols) => index(`${selectTableName}_parent_idx`).on(cols.parent),
}
@@ -316,25 +317,28 @@ export const traverseFields = ({
case 'array': {
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
const arrayTableName = getTableName({
const arrayTableName = createTableName({
adapter,
config: field,
parentTableName: newTableName,
prefix: `${newTableName}_`,
throwValidationError,
versionsCustomName: versions,
})
const baseColumns: Record<string, PgColumnBuilder> = {
_order: integer('_order').notNull(),
_parentID: parentIDColumnMap[parentIDColType]('_parent_id')
.references(() => adapter.tables[parentTableName].id, { onDelete: 'cascade' })
.notNull(),
_parentID: parentIDColumnMap[parentIDColType]('_parent_id').notNull(),
}
const baseExtraConfig: Record<
string,
(cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder
> = {
const baseExtraConfig: BaseExtraConfig = {
_orderIdx: (cols) => index(`${arrayTableName}_order_idx`).on(cols._order),
_parentIDFk: (cols) =>
foreignKey({
name: `${arrayTableName}_parent_id_fk`,
columns: [cols['_parentID']],
foreignColumns: [adapter.tables[parentTableName].id],
}).onDelete('cascade'),
_parentIDIdx: (cols) => index(`${arrayTableName}_parent_id_idx`).on(cols._parentID),
}
@@ -402,28 +406,30 @@ export const traverseFields = ({
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
field.blocks.forEach((block) => {
const blockTableName = getTableName({
const blockTableName = createTableName({
adapter,
config: block,
parentTableName: rootTableName,
prefix: `${rootTableName}_blocks_`,
throwValidationError,
versionsCustomName: versions,
})
if (!adapter.tables[blockTableName]) {
const baseColumns: Record<string, PgColumnBuilder> = {
_order: integer('_order').notNull(),
_parentID: parentIDColumnMap[rootTableIDColType]('_parent_id')
.references(() => adapter.tables[rootTableName].id, { onDelete: 'cascade' })
.notNull(),
_parentID: parentIDColumnMap[rootTableIDColType]('_parent_id').notNull(),
_path: text('_path').notNull(),
}
const baseExtraConfig: Record<
string,
(cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder
> = {
const baseExtraConfig: BaseExtraConfig = {
_orderIdx: (cols) => index(`${blockTableName}_order_idx`).on(cols._order),
_parentIDIdx: (cols) => index(`${blockTableName}_parent_id_idx`).on(cols._parentID),
_parentIdFk: (cols) =>
foreignKey({
name: `${blockTableName}_parent_id_fk`,
columns: [cols._parentID],
foreignColumns: [adapter.tables[rootTableName].id],
}).onDelete('cascade'),
_pathIdx: (cols) => index(`${blockTableName}_path_idx`).on(cols._path),
}
@@ -493,9 +499,9 @@ export const traverseFields = ({
localized: field.localized,
rootTableName,
table: adapter.tables[blockTableName],
tableLocales: adapter.tables[`${blockTableName}${adapter.localesSuffix}`],
})
}
adapter.blockTableNames[`${rootTableName}.${toSnakeCase(block.slug)}`] = blockTableName
rootRelationsToBuild.set(`_blocks_${block.slug}`, blockTableName)
})
@@ -658,7 +664,7 @@ export const traverseFields = ({
indexes,
localesColumns,
localesIndexes,
newTableName: parentTableName,
newTableName,
parentTableName,
relationsToBuild,
relationships,

View File

@@ -10,9 +10,13 @@ type Args = {
localized: boolean
rootTableName: string
table: GenericTable
tableLocales?: GenericTable
}
const getFlattenedFieldNames = (fields: Field[], prefix: string = ''): string[] => {
const getFlattenedFieldNames = (
fields: Field[],
prefix: string = '',
): { localized?: boolean; name: string }[] => {
return fields.reduce((fieldsToUse, field) => {
let fieldPrefix = prefix
@@ -24,7 +28,7 @@ const getFlattenedFieldNames = (fields: Field[], prefix: string = ''): string[]
}
if (fieldHasSubFields(field)) {
fieldPrefix = 'name' in field ? `${prefix}${field.name}.` : prefix
fieldPrefix = 'name' in field ? `${prefix}${field.name}_` : prefix
return [...fieldsToUse, ...getFlattenedFieldNames(field.fields, fieldPrefix)]
}
@@ -32,7 +36,7 @@ const getFlattenedFieldNames = (fields: Field[], prefix: string = ''): string[]
return [
...fieldsToUse,
...field.tabs.reduce((tabFields, tab) => {
fieldPrefix = 'name' in tab ? `${prefix}.${tab.name}` : prefix
fieldPrefix = 'name' in tab ? `${prefix}_${tab.name}` : prefix
return [
...tabFields,
...(tabHasName(tab)
@@ -44,7 +48,13 @@ const getFlattenedFieldNames = (fields: Field[], prefix: string = ''): string[]
}
if (fieldAffectsData(field)) {
return [...fieldsToUse, `${fieldPrefix?.replace('.', '_') || ''}${field.name}`]
return [
...fieldsToUse,
{
name: `${fieldPrefix}${field.name}`,
localized: field.localized,
},
]
}
return fieldsToUse
@@ -56,22 +66,30 @@ export const validateExistingBlockIsIdentical = ({
localized,
rootTableName,
table,
tableLocales,
}: Args): void => {
const fieldNames = getFlattenedFieldNames(block.fields)
const missingField =
// ensure every field from the config is in the matching table
fieldNames.find((name) => Object.keys(table).indexOf(name) === -1) ||
fieldNames.find(({ name, localized }) => {
const fieldTable = localized && tableLocales ? tableLocales : table
return Object.keys(fieldTable).indexOf(name) === -1
}) ||
// ensure every table column is matched for every field from the config
Object.keys(table).find((fieldName) => {
if (!['_locale', '_order', '_parentID', '_path', '_uuid'].includes(fieldName)) {
return fieldNames.indexOf(fieldName) === -1
return fieldNames.findIndex((field) => field.name) === -1
}
})
if (missingField) {
throw new InvalidConfiguration(
`The table ${rootTableName} has multiple blocks with slug ${block.slug}, but the schemas do not match. One block includes the field ${missingField}, while the other block does not.`,
`The table ${rootTableName} has multiple blocks with slug ${
block.slug
}, but the schemas do not match. One block includes the field ${
typeof missingField === 'string' ? missingField : missingField.name
}, while the other block does not.`,
)
}

View File

@@ -4,11 +4,11 @@ import type { TextField } from 'payload/types'
type Args = {
field: TextField
locale?: string
textRows: Record<string, unknown>[]
ref: Record<string, unknown>
textRows: Record<string, unknown>[]
}
export const transformHasManyText = ({ field, locale, textRows, ref }: Args) => {
export const transformHasManyText = ({ field, locale, ref, textRows }: Args) => {
const result = textRows.map(({ text }) => text)
if (locale) {

View File

@@ -48,11 +48,11 @@ export const transform = <T extends TypeWithID>({ config, data, fields }: Transf
deletions,
fieldPrefix: '',
fields,
texts,
numbers,
path: '',
relationships,
table: data,
texts,
})
deletions.forEach((deletion) => deletion())

View File

@@ -18,7 +18,6 @@ type Args = {
data: unknown
field: ArrayField
locale?: string
texts: Record<string, unknown>[]
numbers: Record<string, unknown>[]
path: string
relationships: Record<string, unknown>[]
@@ -26,6 +25,7 @@ type Args = {
selects: {
[tableName: string]: Record<string, unknown>[]
}
texts: Record<string, unknown>[]
}
export const transformArray = ({
@@ -37,14 +37,15 @@ export const transformArray = ({
data,
field,
locale,
texts,
numbers,
path,
relationships,
relationshipsToDelete,
selects,
texts,
}: Args) => {
const newRows: ArrayRowToInsert[] = []
const hasUUID = adapter.tables[arrayTableName]._uuid
if (isArrayOfRows(data)) {
@@ -88,7 +89,6 @@ export const transformArray = ({
fieldPrefix: '',
fields: field.fields,
locales: newRow.locales,
texts,
numbers,
parentTableName: arrayTableName,
path: `${path || ''}${field.name}.${i}.`,
@@ -96,6 +96,7 @@ export const transformArray = ({
relationshipsToDelete,
row: newRow.row,
selects,
texts,
})
newRows.push(newRow)

View File

@@ -61,7 +61,7 @@ export const transformBlocks = ({
if (field.localized && locale) newRow.row._locale = locale
const blockTableName = `${baseTableName}_blocks_${blockType}`
const blockTableName = adapter.tableNameMap.get(`${baseTableName}_blocks_${blockType}`)
const hasUUID = adapter.tables[blockTableName]._uuid

View File

@@ -27,12 +27,12 @@ export const transformForWrite = ({
blocks: {},
blocksToDelete: new Set(),
locales: {},
texts: [],
numbers: [],
relationships: [],
relationshipsToDelete: [],
row: {},
selects: {},
texts: [],
}
// This function is responsible for building up the
@@ -48,7 +48,6 @@ export const transformForWrite = ({
fieldPrefix: '',
fields,
locales: rowToInsert.locales,
texts: rowToInsert.texts,
numbers: rowToInsert.numbers,
parentTableName: tableName,
path,
@@ -56,6 +55,7 @@ export const transformForWrite = ({
relationshipsToDelete: rowToInsert.relationshipsToDelete,
row: rowToInsert.row,
selects: rowToInsert.selects,
texts: rowToInsert.texts,
})
return rowToInsert

View File

@@ -8,8 +8,8 @@ export const transformTexts = ({ baseRow, data, texts }: Args) => {
data.forEach((val, i) => {
texts.push({
...baseRow,
text: val,
order: i + 1,
text: val,
})
})
}

View File

@@ -7,7 +7,6 @@ import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from '../../types'
import type { ArrayRowToInsert, BlockRowToInsert, RelationshipToDelete } from './types'
import { getTableName } from '../../schema/getTableName'
import { isArrayOfRows } from '../../utilities/isArrayOfRows'
import { transformArray } from './array'
import { transformBlocks } from './blocks'
@@ -89,18 +88,13 @@ export const traverseFields = ({
let fieldData: unknown
if (fieldAffectsData(field)) {
columnName = `${columnPrefix || ''}${getTableName({
adapter,
config: field,
// do not pass columnPrefix here because it is required and custom dbNames also need it
prefix: '',
})}`
columnName = `${columnPrefix || ''}${toSnakeCase(field.name)}`
fieldName = `${fieldPrefix || ''}${field.name}`
fieldData = data[field.name]
}
if (field.type === 'array') {
const arrayTableName = `${parentTableName}_${columnName}`
const arrayTableName = adapter.tableNameMap.get(`${parentTableName}_${columnName}`)
if (!arrays[arrayTableName]) arrays[arrayTableName] = []
@@ -153,8 +147,8 @@ export const traverseFields = ({
}
if (field.type === 'blocks') {
field.blocks.forEach((block) => {
blocksToDelete.add(getTableName({ adapter, config: block }))
field.blocks.forEach(({ slug }) => {
blocksToDelete.add(toSnakeCase(slug))
})
if (field.localized) {
@@ -464,7 +458,7 @@ export const traverseFields = ({
}
if (field.type === 'select' && field.hasMany) {
const selectTableName = `${parentTableName}_${columnName}`
const selectTableName = adapter.tableNameMap.get(`${parentTableName}_${columnName}`)
if (!selects[selectTableName]) selects[selectTableName] = []
if (field.localized) {
@@ -494,11 +488,7 @@ export const traverseFields = ({
}
if (fieldAffectsData(field)) {
const valuesToTransform: {
localeKey?: string
ref: unknown
value: unknown
}[] = []
const valuesToTransform: { localeKey?: string; ref: unknown; value: unknown }[] = []
if (field.localized) {
if (typeof fieldData === 'object' && fieldData !== null) {

View File

@@ -34,7 +34,6 @@ export type RowToInsert = {
locales: {
[locale: string]: Record<string, unknown>
}
texts: Record<string, unknown>[]
numbers: Record<string, unknown>[]
relationships: Record<string, unknown>[]
relationshipsToDelete: RelationshipToDelete[]
@@ -42,4 +41,5 @@ export type RowToInsert = {
selects: {
[tableName: string]: Record<string, unknown>[]
}
texts: Record<string, unknown>[]
}

View File

@@ -23,14 +23,14 @@ import type { Pool, PoolConfig } from 'pg'
export type DrizzleDB = NodePgDatabase<Record<string, unknown>>
export type Args = {
localesSuffix?: string
idType?: 'serial' | 'uuid'
localesSuffix?: string
logger?: DrizzleConfig['logger']
migrationDir?: string
pool: PoolConfig
push?: boolean
schemaName?: string
relationshipsSuffix?: string
schemaName?: string
versionsSuffix?: string
}
@@ -61,10 +61,6 @@ export type DrizzleTransaction = PgTransaction<
>
export type PostgresAdapter = BaseDatabaseAdapter & {
/**
* Used internally to map the block name to the table name
*/
blockTableNames: Record<string, string>
drizzle: DrizzleDB
enums: Record<string, GenericEnum>
/**
@@ -90,6 +86,7 @@ export type PostgresAdapter = BaseDatabaseAdapter & {
resolve: () => Promise<void>
}
}
tableNameMap: Map<string, string>
tables: Record<string, GenericTable | PgTableWithColumns<any>>
versionsSuffix?: string
}

View File

@@ -2,12 +2,10 @@ import type { UpdateOne } from 'payload/database'
import toSnakeCase from 'to-snake-case'
import type { ChainedMethods } from './find/chainMethods'
import type { PostgresAdapter } from './types'
import buildQuery from './queries/buildQuery'
import { selectDistinct } from './queries/selectDistinct'
import { getTableName } from './schema/getTableName'
import { upsertRow } from './upsertRow'
export const updateOne: UpdateOne = async function updateOne(
@@ -16,10 +14,7 @@ export const updateOne: UpdateOne = async function updateOne(
) {
const db = this.sessions[req.transactionID]?.db || this.drizzle
const collection = this.payload.collections[collectionSlug].config
const tableName = getTableName({
adapter: this,
config: collection,
})
const tableName = this.tableNameMap.get(toSnakeCase(collection.slug))
const whereToUse = whereArg || { id: { equals: id } }
let idToUpdate = id

View File

@@ -1,9 +1,10 @@
import type { UpdateGlobalArgs } from 'payload/database'
import type { PayloadRequest, TypeWithID } from 'payload/types'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types'
import { getTableName } from './schema/getTableName'
import { upsertRow } from './upsertRow'
export async function updateGlobal<T extends TypeWithID>(
@@ -12,10 +13,7 @@ export async function updateGlobal<T extends TypeWithID>(
): Promise<T> {
const db = this.sessions[req.transactionID]?.db || this.drizzle
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
const tableName = getTableName({
adapter: this,
config: globalConfig,
})
const tableName = this.tableNameMap.get(toSnakeCase(globalConfig.slug))
const existingGlobal = await db.query[tableName].findFirst({})

View File

@@ -2,11 +2,11 @@ import type { TypeWithVersion, UpdateGlobalVersionArgs } from 'payload/database'
import type { PayloadRequest, SanitizedGlobalConfig, TypeWithID } from 'payload/types'
import { buildVersionGlobalFields } from 'payload/versions'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types'
import buildQuery from './queries/buildQuery'
import { getTableName } from './schema/getTableName'
import { upsertRow } from './upsertRow'
export async function updateGlobalVersion<T extends TypeWithID>(
@@ -25,11 +25,11 @@ export async function updateGlobalVersion<T extends TypeWithID>(
({ slug }) => slug === global,
)
const whereToUse = whereArg || { id: { equals: id } }
const tableName = getTableName({
adapter: this,
config: globalConfig,
versions: true,
})
const tableName = this.tableNameMap.get(
`_${toSnakeCase(globalConfig.slug)}${this.versionsSuffix}`,
)
const fields = buildVersionGlobalFields(globalConfig)
const { where } = await buildQuery({

View File

@@ -2,11 +2,11 @@ import type { TypeWithVersion, UpdateVersionArgs } from 'payload/database'
import type { PayloadRequest, SanitizedCollectionConfig, TypeWithID } from 'payload/types'
import { buildVersionCollectionFields } from 'payload/versions'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from './types'
import buildQuery from './queries/buildQuery'
import { getTableName } from './schema/getTableName'
import { upsertRow } from './upsertRow'
export async function updateVersion<T extends TypeWithID>(
@@ -23,11 +23,10 @@ export async function updateVersion<T extends TypeWithID>(
const db = this.sessions[req.transactionID]?.db || this.drizzle
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
const whereToUse = whereArg || { id: { equals: id } }
const tableName = getTableName({
adapter: this,
config: collectionConfig,
versions: true,
})
const tableName = this.tableNameMap.get(
`_${toSnakeCase(collectionConfig.slug)}${this.versionsSuffix}`,
)
const fields = buildVersionCollectionFields(collectionConfig)
const { where } = await buildQuery({

View File

@@ -224,14 +224,14 @@ export const upsertRow = async <T extends TypeWithID>({
if (operation === 'update') {
for (const blockName of rowToInsert.blocksToDelete) {
const blockTableName = adapter.blockTableNames[`${tableName}.${blockName}`]
const blockTableName = adapter.tableNameMap.get(`${tableName}_blocks_${blockName}`)
const blockTable = adapter.tables[blockTableName]
await db.delete(blockTable).where(eq(blockTable._parentID, insertedRow.id))
}
}
for (const [blockName, blockRows] of Object.entries(blocksToInsert)) {
const blockTableName = adapter.blockTableNames[`${tableName}.${blockName}`]
const blockTableName = adapter.tableNameMap.get(`${tableName}_blocks_${blockName}`)
insertedBlockRows[blockName] = await db
.insert(adapter.tables[blockTableName])
.values(blockRows.map(({ row }) => row))

View File

@@ -73,7 +73,7 @@ export const insertArrays = async ({ adapter, arrays, db, parentRows }: Args): P
// Insert locale rows
if (adapter.tables[`${tableName}${adapter.localesSuffix}`] && row.locales.length > 0) {
if (!row.locales[0]._parentID) {
row.locales = row.locales.map((localeRow, i) => {
row.locales = row.locales.map((localeRow) => {
if (typeof localeRow._getParentID === 'function') {
localeRow._parentID = localeRow._getParentID(insertedRows)
delete localeRow._getParentID

View File

@@ -0,0 +1,10 @@
.tmp
**/.git
**/.hg
**/.pnp.*
**/.svn
**/.yarn/**
**/build
**/dist/**
**/node_modules
**/temp

View File

@@ -0,0 +1,37 @@
/** @type {import('prettier').Config} */
module.exports = {
extends: ['@payloadcms'],
overrides: [
{
extends: ['plugin:@typescript-eslint/disable-type-checked'],
files: ['*.js', '*.cjs', '*.json', '*.md', '*.yml', '*.yaml'],
},
{
files: ['package.json', 'tsconfig.json'],
rules: {
'perfectionist/sort-array-includes': 'off',
'perfectionist/sort-astro-attributes': 'off',
'perfectionist/sort-classes': 'off',
'perfectionist/sort-enums': 'off',
'perfectionist/sort-exports': 'off',
'perfectionist/sort-imports': 'off',
'perfectionist/sort-interfaces': 'off',
'perfectionist/sort-jsx-props': 'off',
'perfectionist/sort-keys': 'off',
'perfectionist/sort-maps': 'off',
'perfectionist/sort-named-exports': 'off',
'perfectionist/sort-named-imports': 'off',
'perfectionist/sort-object-types': 'off',
'perfectionist/sort-objects': 'off',
'perfectionist/sort-svelte-attributes': 'off',
'perfectionist/sort-union-types': 'off',
'perfectionist/sort-vue-attributes': 'off',
},
},
],
parserOptions: {
project: ['./tsconfig.json'],
tsconfigRootDir: __dirname,
},
root: true,
}

View File

@@ -0,0 +1,10 @@
.tmp
**/.git
**/.hg
**/.pnp.*
**/.svn
**/.yarn/**
**/build
**/dist/**
**/node_modules
**/temp

View File

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

View File

@@ -0,0 +1,49 @@
{
"name": "@payloadcms/live-preview-vue",
"version": "0.1.0",
"description": "The official live preview Vue SDK for Payload",
"repository": {
"type": "git",
"url": "https://github.com/payloadcms/payload.git",
"directory": "packages/live-preview-vue"
},
"license": "MIT",
"homepage": "https://payloadcms.com",
"author": "Payload CMS, Inc.",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "pnpm copyfiles && pnpm build:swc && pnpm build:types",
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
"build:types": "tsc --emitDeclarationOnly --outDir dist",
"clean": "rimraf {dist,*.tsbuildinfo}",
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
"prepublishOnly": "pnpm clean && pnpm build"
},
"dependencies": {
"@payloadcms/live-preview": "workspace:^0.x"
},
"devDependencies": {
"@payloadcms/eslint-config": "workspace:*",
"vue": "^3.0.0",
"payload": "workspace:*"
},
"peerDependencies": {
"vue": "^3.0.0"
},
"exports": {
".": {
"default": "./src/index.ts",
"types": "./src/index.ts"
}
},
"publishConfig": {
"exports": null,
"main": "./dist/index.js",
"registry": "https://registry.npmjs.org/",
"types": "./dist/index.d.ts"
},
"files": [
"dist"
]
}

View File

@@ -0,0 +1,58 @@
import type { Ref } from 'vue'
import { ready, subscribe, unsubscribe } from '@payloadcms/live-preview'
import { onMounted, onUnmounted, ref } from 'vue'
/**
* Vue composable to implement Payload CMS Live Preview.
*
* {@link https://payloadcms.com/docs/live-preview/frontend View the documentation}
*/
export const useLivePreview = <T>(props: {
apiRoute?: string
depth?: number
initialData: T
serverURL: string
}): {
data: Ref<T>
isLoading: Ref<boolean>
} => {
const { apiRoute, depth, initialData, serverURL } = props
const data = ref(initialData) as Ref<T>
const isLoading = ref(true)
const hasSentReadyMessage = ref(false)
const onChange = (mergedData: T) => {
data.value = mergedData
isLoading.value = false
}
let subscription: (event: MessageEvent) => void
onMounted(() => {
subscription = subscribe({
apiRoute,
callback: onChange,
depth,
initialData,
serverURL,
})
if (!hasSentReadyMessage.value) {
hasSentReadyMessage.value = true
ready({
serverURL,
})
}
})
onUnmounted(() => {
unsubscribe(subscription)
})
return {
data,
isLoading,
}
}

View File

@@ -0,0 +1,25 @@
{
"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. */,
"jsx": "react"
},
"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" }] // db-mongodb depends on payload
}

View File

@@ -1,6 +1,6 @@
{
"name": "payload",
"version": "2.12.1",
"version": "2.18.3",
"description": "Node, React and MongoDB Headless CMS and Application Framework",
"license": "MIT",
"main": "./dist/index.js",
@@ -118,11 +118,11 @@
"process": "0.11.10",
"qs": "6.11.2",
"qs-middleware": "1.0.3",
"react": "18.2.0",
"react": "^18.0.0",
"react-animate-height": "2.1.2",
"react-datepicker": "4.16.0",
"react-diff-viewer-continued": "3.2.6",
"react-dom": "18.2.0",
"react-dom": "^18.0.0",
"react-helmet": "6.1.0",
"react-i18next": "11.18.6",
"react-image-crop": "10.1.8",

View File

@@ -19,6 +19,7 @@ import { ConfigProvider } from './components/utilities/Config'
import { CustomProvider } from './components/utilities/CustomProvider'
import { DocumentEventsProvider } from './components/utilities/DocumentEvents'
import { I18n } from './components/utilities/I18n'
import { LanguageWrap } from './components/utilities/LanguageWrap'
import { LoadingOverlayProvider } from './components/utilities/LoadingOverlay'
import { LocaleProvider } from './components/utilities/Locale'
import { PreferencesProvider } from './components/utilities/Preferences'
@@ -46,21 +47,23 @@ const Root = ({ config: incomingConfig }: { config?: SanitizedConfig }) => {
<AuthProvider>
<PreferencesProvider>
<ThemeProvider>
<SearchParamsProvider>
<LocaleProvider>
<StepNavProvider>
<LoadingOverlayProvider>
<DocumentEventsProvider>
<NavProvider>
<CustomProvider>
<Routes />
</CustomProvider>
</NavProvider>
</DocumentEventsProvider>
</LoadingOverlayProvider>
</StepNavProvider>
</LocaleProvider>
</SearchParamsProvider>
<LanguageWrap>
<SearchParamsProvider>
<LocaleProvider>
<StepNavProvider>
<LoadingOverlayProvider>
<DocumentEventsProvider>
<NavProvider>
<CustomProvider>
<Routes />
</CustomProvider>
</NavProvider>
</DocumentEventsProvider>
</LoadingOverlayProvider>
</StepNavProvider>
</LocaleProvider>
</SearchParamsProvider>
</LanguageWrap>
</ThemeProvider>
<ModalContainer />
</PreferencesProvider>

View File

@@ -19,6 +19,7 @@ export const ArrayAction: React.FC<Props> = ({
duplicateRow,
hasMaxRows,
index,
isSortable,
moveRow,
removeRow,
rowCount,
@@ -33,7 +34,7 @@ export const ArrayAction: React.FC<Props> = ({
render={({ close }) => {
return (
<PopupList.ButtonGroup buttonSize="small">
{index !== 0 && (
{isSortable && index !== 0 && (
<PopupList.Button
className={`${baseClass}__action ${baseClass}__move-up`}
onClick={() => {
@@ -47,7 +48,7 @@ export const ArrayAction: React.FC<Props> = ({
{t('moveUp')}
</PopupList.Button>
)}
{index < rowCount - 1 && (
{isSortable && index < rowCount - 1 && (
<PopupList.Button
className={`${baseClass}__action`}
onClick={() => {

View File

@@ -3,6 +3,7 @@ export type Props = {
duplicateRow: (current: number) => void
hasMaxRows: boolean
index: number
isSortable: boolean
moveRow: (from: number, to: number) => void
removeRow: (index: number) => void
rowCount: number

View File

@@ -15,7 +15,7 @@ import './index.scss'
const baseClass = 'column-selector'
const ColumnSelector: React.FC<Props> = (props) => {
const { collection } = props
const { slug } = props
const { columns, moveColumn, toggleColumn } = useTableColumns()
@@ -53,7 +53,7 @@ const ColumnSelector: React.FC<Props> = (props) => {
draggable
icon={active ? <X /> : <Plus />}
id={accessor}
key={`${collection.slug}-${col.name || i}${editDepth ? `-${editDepth}-` : ''}${uuid}`}
key={`${slug}-${col.name || i}${editDepth ? `-${editDepth}-` : ''}${uuid}`}
onClick={() => {
toggleColumn(accessor)
}}

View File

@@ -1,5 +1,5 @@
import type { SanitizedCollectionConfig } from '../../../../collections/config/types'
export type Props = {
collection: SanitizedCollectionConfig
slug: SanitizedCollectionConfig['slug']
}

View File

@@ -64,7 +64,9 @@ const DeleteDocument: React.FC<Props> = (props) => {
if (res.status < 400) {
setDeleting(false)
toggleModal(modalSlug)
toast.success(json.message || t('titleDeleted', { label: getTranslation(singular, i18n), title }))
toast.success(
json.message || t('titleDeleted', { label: getTranslation(singular, i18n), title }),
)
return history.push(`${admin}/collections/${slug}`)
}

View File

@@ -5,6 +5,7 @@ import type { CollectionPermission, GlobalPermission } from '../../../../auth'
import type { SanitizedCollectionConfig } from '../../../../collections/config/types'
import type { SanitizedGlobalConfig } from '../../../../globals/config/types'
import { getTranslation } from '../../../../utilities/getTranslation'
import { formatDate } from '../../../utilities/formatDate'
import { useConfig } from '../../utilities/Config'
import { useDocumentInfo } from '../../utilities/DocumentInfo'
@@ -63,6 +64,12 @@ export const DocumentControls: React.FC<{
collection && id && !disableActions && (hasCreatePermission || hasDeletePermission),
)
const collectionLabel = () => {
const label = collection?.labels?.singular
if (!label) return t('document')
return typeof label === 'string' ? label : getTranslation(label, i18n)
}
return (
<Gutter className={baseClass}>
<div className={`${baseClass}__wrapper`}>
@@ -71,12 +78,7 @@ export const DocumentControls: React.FC<{
{collection && !isEditing && !isAccountView && (
<li className={`${baseClass}__list-item`}>
<p className={`${baseClass}__value`}>
{t('creatingNewLabel', {
label:
typeof collection?.labels?.singular === 'string'
? collection.labels.singular
: 'document',
})}
{t('creatingNewLabel', { label: collectionLabel() })}
</p>
</li>
)}

View File

@@ -139,7 +139,7 @@ const Duplicate: React.FC<Props> = ({ id, slug, collection }) => {
if (localeErrors.length > 0) {
toast.error(
`
${t('error:localesNotSaved', { count: localeErrors.length })}
${t('error:localesNotSaved_other', { count: localeErrors.length })}
${localeErrors.join(', ')}
`,
{ autoClose: 5000 },

View File

@@ -83,7 +83,7 @@ const SaveDraft: React.FC<{ action: string; disabled: boolean }> = ({ action, di
)
}
const EditMany: React.FC<Props> = (props) => {
const { collection: { fields, labels: { plural }, slug } = {}, collection, resetParams } = props
const { collection: { slug, fields, labels: { plural } } = {}, collection, resetParams } = props
const { permissions } = useAuth()
const { closeModal } = useModal()
@@ -148,11 +148,11 @@ const EditMany: React.FC<Props> = (props) => {
{collection.versions ? (
<React.Fragment>
<Publish
action={`${serverURL}${api}/${slug}${getQueryParams()}`}
action={`${serverURL}${api}/${slug}${getQueryParams()}&draft=true`}
disabled={selected.length === 0}
/>
<SaveDraft
action={`${serverURL}${api}/${slug}${getQueryParams()}`}
action={`${serverURL}${api}/${slug}${getQueryParams()}&draft=true`}
disabled={selected.length === 0}
/>
</React.Fragment>

View File

@@ -32,7 +32,7 @@ export const EditUpload: React.FC<{
imageCacheTag?: string
showCrop?: boolean
showFocalPoint?: boolean
}> = ({ fileName, fileSrc, imageCacheTag, showCrop, showFocalPoint }) => {
}> = ({ doc, fileName, fileSrc, imageCacheTag, showCrop, showFocalPoint }) => {
const { closeModal } = useModal()
const { t } = useTranslation(['general', 'upload'])
const { formQueryParams, setFormQueryParams } = useFormQueryParams()
@@ -45,10 +45,11 @@ export const EditUpload: React.FC<{
y: uploadEdits?.crop?.y || 0,
})
const [pointPosition, setPointPosition] = useState<{ x: number; y: number }>({
x: uploadEdits?.focalPoint?.x || 50,
y: uploadEdits?.focalPoint?.y || 50,
const [focalPosition, setFocalPosition] = useState<{ x: number; y: number }>({
x: uploadEdits?.focalPoint?.x || doc.focalX || 50,
y: uploadEdits?.focalPoint?.y || doc.focalY || 50,
})
const [checkBounds, setCheckBounds] = useState<boolean>(false)
const [originalHeight, setOriginalHeight] = useState<number>(0)
const [originalWidth, setOriginalWidth] = useState<number>(0)
@@ -72,10 +73,16 @@ export const EditUpload: React.FC<{
})
}
const fineTuneFocalPoint = ({ coordinate, value }: { coordinate: 'x' | 'y'; value: string }) => {
const fineTuneFocalPosition = ({
coordinate,
value,
}: {
coordinate: 'x' | 'y'
value: string
}) => {
const intValue = parseInt(value)
if (intValue >= 0 && intValue <= 100) {
setPointPosition((prevPosition) => ({ ...prevPosition, [coordinate]: intValue }))
setFocalPosition((prevPosition) => ({ ...prevPosition, [coordinate]: intValue }))
}
}
@@ -84,14 +91,14 @@ export const EditUpload: React.FC<{
...formQueryParams,
uploadEdits: {
crop: crop || undefined,
focalPoint: pointPosition ? pointPosition : undefined,
focalPoint: focalPosition ? focalPosition : undefined,
},
})
closeModal(editDrawerSlug)
}
const onDragEnd = React.useCallback(({ x, y }) => {
setPointPosition({ x, y })
setFocalPosition({ x, y })
setCheckBounds(false)
}, [])
@@ -104,7 +111,7 @@ export const EditUpload: React.FC<{
((boundsRect.left - containerRect.left + boundsRect.width / 2) / containerRect.width) * 100
const yCenter =
((boundsRect.top - containerRect.top + boundsRect.height / 2) / containerRect.height) * 100
setPointPosition({ x: xCenter, y: yCenter })
setFocalPosition({ x: xCenter, y: yCenter })
}
const fileSrcToUse = imageCacheTag ? `${fileSrc}?${imageCacheTag}` : fileSrc
@@ -180,7 +187,7 @@ export const EditUpload: React.FC<{
checkBounds={showCrop ? checkBounds : false}
className={`${baseClass}__focalPoint`}
containerRef={focalWrapRef}
initialPosition={pointPosition}
initialPosition={focalPosition}
onDragEnd={onDragEnd}
setCheckBounds={showCrop ? setCheckBounds : false}
>
@@ -251,13 +258,13 @@ export const EditUpload: React.FC<{
<div className={`${baseClass}__inputsWrap`}>
<Input
name="X %"
onChange={(value) => fineTuneFocalPoint({ coordinate: 'x', value })}
value={pointPosition.x.toFixed(0)}
onChange={(value) => fineTuneFocalPosition({ coordinate: 'x', value })}
value={focalPosition.x.toFixed(0)}
/>
<Input
name="Y %"
onChange={(value) => fineTuneFocalPoint({ coordinate: 'y', value })}
value={pointPosition.y.toFixed(0)}
onChange={(value) => fineTuneFocalPosition({ coordinate: 'y', value })}
value={focalPosition.y.toFixed(0)}
/>
</div>
</div>

View File

@@ -9,7 +9,6 @@ import { fieldAffectsData } from '../../../../fields/config/types'
import { getTranslation } from '../../../../utilities/getTranslation'
import Chevron from '../../icons/Chevron'
import { useSearchParams } from '../../utilities/SearchParams'
import Button from '../Button'
import ColumnSelector from '../ColumnSelector'
import DeleteMany from '../DeleteMany'
import EditMany from '../EditMany'
@@ -50,6 +49,8 @@ export const ListControls: React.FC<Props> = (props) => {
const params = useSearchParams()
const shouldInitializeWhereOpened = validateWhereQuery(params?.where)
const hasWhereParam = React.useRef(Boolean(params?.where))
const [textFieldsToBeSearched, setFieldsToBeSearched] = useState(
getTextFieldsToBeSearched(listSearchableFields, fields),
)
@@ -65,6 +66,15 @@ export const ListControls: React.FC<Props> = (props) => {
setFieldsToBeSearched(getTextFieldsToBeSearched(listSearchableFields, fields))
}, [listSearchableFields, fields])
React.useEffect(() => {
if (hasWhereParam.current && !params?.where) {
setVisibleDrawer(undefined)
hasWhereParam.current = false
} else if (params?.where) {
hasWhereParam.current = true
}
}, [setVisibleDrawer, params?.where])
return (
<div className={baseClass}>
<div className={`${baseClass}__wrap`}>
@@ -136,7 +146,7 @@ export const ListControls: React.FC<Props> = (props) => {
height={visibleDrawer === 'columns' ? 'auto' : 0}
id={`${baseClass}-columns`}
>
<ColumnSelector collection={collection} />
<ColumnSelector slug={collection.slug} />
</AnimateHeight>
)}
<AnimateHeight
@@ -147,6 +157,7 @@ export const ListControls: React.FC<Props> = (props) => {
<WhereBuilder
collection={collection}
handleChange={handleWhereChange}
key={String(hasWhereParam.current && !params?.where)}
modifySearchQuery={modifySearchQuery}
/>
</AnimateHeight>

View File

@@ -24,7 +24,6 @@ export const LoadingOverlay: React.FC<Props> = ({
show = true,
}) => {
const { t } = useTranslation('general')
return (
<div
className={[
@@ -59,25 +58,25 @@ type UseLoadingOverlayToggleT = {
}
export const LoadingOverlayToggle: React.FC<UseLoadingOverlayToggleT> = ({
name: key,
type = 'fullscreen',
loadingText,
show,
type = 'fullscreen',
}) => {
const { toggleLoadingOverlay } = useLoadingOverlay()
React.useEffect(() => {
toggleLoadingOverlay({
type,
isLoading: show,
key,
loadingText: loadingText || undefined,
type,
})
return () => {
toggleLoadingOverlay({
type,
isLoading: false,
key,
type,
})
}
}, [show, toggleLoadingOverlay, key, type, loadingText])
@@ -94,10 +93,10 @@ type FormLoadingOverlayToggleT = {
}
export const FormLoadingOverlayToggle: React.FC<FormLoadingOverlayToggleT> = ({
name,
type = 'fullscreen',
action,
formIsLoading = false,
loadingSuffix,
type = 'fullscreen',
}) => {
const isProcessing = useFormProcessing()
const { i18n, t } = useTranslation('general')

View File

@@ -18,7 +18,7 @@ import './index.scss'
const baseClass = 'publish-many'
const PublishMany: React.FC<Props> = (props) => {
const { collection: { labels: { plural }, slug, versions } = {}, resetParams } = props
const { collection: { slug, labels: { plural }, versions } = {}, resetParams } = props
const {
routes: { api },
@@ -27,7 +27,7 @@ const PublishMany: React.FC<Props> = (props) => {
const { permissions } = useAuth()
const { toggleModal } = useModal()
const { i18n, t } = useTranslation('version')
const { count, getQueryParams, selectAll } = useSelection()
const { getQueryParams, selectAll } = useSelection()
const [submitted, setSubmitted] = useState(false)
const collectionPermissions = permissions?.collections?.[slug]
@@ -41,9 +41,11 @@ const PublishMany: React.FC<Props> = (props) => {
const handlePublish = useCallback(() => {
setSubmitted(true)
requests
void requests
.patch(
`${serverURL}${api}/${slug}${getQueryParams({ _status: { not_equals: 'published' } })}`,
`${serverURL}${api}/${slug}${getQueryParams({
_status: { not_equals: 'published' },
})}&draft=true`,
{
body: JSON.stringify({
_status: 'published',

View File

@@ -40,7 +40,7 @@ const SelectAdapter: React.FC<ReactSelectAdapterProps> = (props) => {
isCreatable,
isLoading,
isSearchable = true,
noOptionsMessage,
noOptionsMessage = () => t('general:noOptions'),
numberOnly = false,
onChange,
onMenuOpen,
@@ -50,6 +50,8 @@ const SelectAdapter: React.FC<ReactSelectAdapterProps> = (props) => {
value,
} = props
const loadingMessage = () => t('general:loading') + '...'
const classes = [className, 'react-select', showError && 'react-select--error']
.filter(Boolean)
.join(' ')
@@ -79,6 +81,7 @@ const SelectAdapter: React.FC<ReactSelectAdapterProps> = (props) => {
isClearable={isClearable}
isDisabled={disabled}
isSearchable={isSearchable}
loadingMessage={loadingMessage}
menuPlacement="auto"
noOptionsMessage={noOptionsMessage}
onChange={onChange}
@@ -148,6 +151,7 @@ const SelectAdapter: React.FC<ReactSelectAdapterProps> = (props) => {
isClearable={isClearable}
isDisabled={disabled}
isSearchable={isSearchable}
loadingMessage={loadingMessage}
menuPlacement="auto"
noOptionsMessage={noOptionsMessage}
onChange={onChange}

View File

@@ -2,6 +2,7 @@
.step-nav {
display: flex;
align-items: center;
gap: calc(var(--base) / 2);
&::after {
@@ -56,8 +57,6 @@
}
span {
display: flex;
align-items: center;
max-width: base(8);
text-overflow: ellipsis;
overflow: hidden;

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