Compare commits

..

369 Commits

Author SHA1 Message Date
James
a5139072c8 chore(release): v1.1.19 2022-10-31 18:23:10 -04:00
James
e004682799 Merge branch 'master' of github.com:payloadcms/payload 2022-10-31 17:51:00 -04:00
James
c651835061 fix: #1318, improves popup positioning and logic 2022-10-31 17:50:52 -04:00
Elliot DeNolf
2255ebb64a feat: revert enforce kebab-case slugs (#1322) (#1325) 2022-10-31 16:10:11 -04:00
James
e2ec2f7b97 fix: #1311, select existing upload modal always updates state 2022-10-31 15:40:18 -04:00
James
00196a8631 chore: ensures form is modified when rows are moved, fixes #1314 2022-10-31 13:25:19 -04:00
James
2a09f15a15 fix: #1307, #1321 - bug with disableFormData and blocks field 2022-10-31 13:21:16 -04:00
Elliot DeNolf
0420b6dc27 feat: enforce kebab-case slugs (#1322) 2022-10-31 11:11:00 -04:00
Dan Ribbens
10c30260dd docs: fix listSearchableFields anchor (#1297) 2022-10-31 09:44:49 -04:00
Elliot DeNolf
25000261bd fix: custom pino logger options (#1299) 2022-10-26 15:08:52 -04:00
Elliot DeNolf
bb82cdcef4 docs: better inline docs for InitOptions 2022-10-26 10:25:06 -04:00
Elliot DeNolf
027dff8363 chore(release): v1.1.18 2022-10-25 12:30:47 -04:00
James
31ca1ab379 chore: ensures defaultMaxTextLength is optional 2022-10-25 12:23:45 -04:00
Elliot DeNolf
33c1f287f3 chore(release): v1.1.17 2022-10-25 11:09:30 -04:00
Elliot DeNolf
cd4861afda chore: move release-it back to dev deps 2022-10-25 11:03:13 -04:00
James
9f56ac182f Merge branch 'master' of github.com:payloadcms/payload 2022-10-25 11:00:48 -04:00
James
3301f59822 fix: enforces depth: 0 in graphql resolvers 2022-10-25 11:00:38 -04:00
James
f9ca3a9f96 chore: fixes bad field name 2022-10-25 11:00:22 -04:00
Elliot DeNolf
3ba7594a65 chore: update release-it plugin 2022-10-25 10:31:37 -04:00
TomDo1234
6a1b25ab30 feat: adds default max length for text-based fields
* feat: Added to types.ts the default Max Field Length

* feat: Added the defaultMaxFieldLength to the schema.ts

* feat: applying defaultMaxFieldLength to 3 validators

* feat: renamed defaultMaxFieldLength to defaultMaxTextLength , adding defaultMax and min nums

* feat: validating numbers with new defaultminnum and defaultmaxnum

* feat: FIXED BUG, do not return an error message on the defaultmaxnum and minnum override checks

* Added test fields

* Eslint compliance

* feat : eslint compliacnce

* Added tests, though a reasonable payload config needs to be imported to them

* Removed my failed jest tests, relying on the yarn dev test instead

* Increased default num max and min range to JS safe integer

* Jmi suggestions

* feat: removing the superfluous number max and min default

* Added test for max text field

Co-authored-by: Tom Do <tom@iifuture.com>
Co-authored-by: TomDoFuture <108644869+TomDoFuture@users.noreply.github.com>
2022-10-24 18:57:45 -04:00
James
17dbbc7775 fix: group + group styles within collapsible 2022-10-24 18:56:56 -04:00
James
7e25abf87a chore: type fixes 2022-10-24 12:26:25 -04:00
James
0591dfd05b chore: typo 2022-10-24 12:18:03 -04:00
Will Laeri
17610adf36 chore: upgrade vulnerable dependencies (#1228)
* upgrade vulnerable deps

* removed specified node/yarn versions
2022-10-24 12:14:48 -04:00
Will Laeri
91814777b0 feat: specify node 14+ and yarn classic LTS (#1240) 2022-10-24 12:14:22 -04:00
Daniel Söderling
09d793926d feat: added beforeLogin hook (#1289) 2022-10-24 12:05:12 -04:00
James
a9f2f0ec03 fix: #1290, renders more than one rich text leaf where applicable 2022-10-24 12:03:06 -04:00
James
66bf8c3cbd fix: #1286, uses defaultDepth in graphql rich text depth 2022-10-24 11:31:09 -04:00
James
3967c1233f fix: #1291, add inline relationship drafts 2022-10-24 11:23:01 -04:00
James
c929725dd5 fix: ensures field updates when disableFormData changes 2022-10-24 08:56:08 -04:00
James
9c6098b191 chore(release): v1.1.16 2022-10-21 15:48:40 -04:00
James
6daab398da Merge branch 'master' of github.com:payloadcms/payload 2022-10-21 15:39:36 -04:00
James
36ef3789fb fix: obscure bug where upload collection has upload field relating to itself 2022-10-21 15:39:29 -04:00
Hung Vu
14cbf2f079 docs: correction to demo code of radio field (#1266) 2022-10-18 14:10:27 -04:00
Dan Ribbens
87bbf4416b Merge pull request #1272 from payloadcms/fix/#1271-index-sortable-fields-regression 2022-10-18 14:05:36 -04:00
Dan Ribbens
785b992c3e fix: indexSortableFields not respected 2022-10-17 20:17:55 -04:00
James
b4695e10b6 chore(release): v1.1.15 2022-10-14 11:38:48 -04:00
James
0b0d971491 fix: ensures svg mime type is always image/svg+xml 2022-10-14 11:33:41 -04:00
James
02af6b90b2 chore(release): v1.1.14 2022-10-14 10:22:14 -04:00
James
2181bc84a1 1.1.13 2022-10-14 10:02:40 -04:00
James
036cd5f831 1.1.12 2022-10-14 10:02:33 -04:00
James
da9825cd99 chore: reverts hiding scrollbars in tabs 2022-10-14 09:53:43 -04:00
James
4a43f95952 fix: hides scrollbar in tabs with overflowing tabs, closes #1259 2022-10-14 09:51:44 -04:00
James
9af9b73132 fix: cleans up draft global action buttons 2022-10-14 09:37:27 -04:00
James
7f7d3dbeef Merge branch 'master' of github.com:payloadcms/payload 2022-10-13 08:28:37 -04:00
James
8ef9206001 chore: properly exports BeforeDuplicate type 2022-10-13 08:17:05 -04:00
James
21ba237135 fix: #1183 - better handling of mime types with svgs + similar files 2022-10-13 08:12:15 -04:00
James Mikrut
3bda163e7b Merge pull request #1241 from jacobsfletch/master
feat: bumps @faceless-ui/modal to v2.0.1
2022-10-12 12:00:22 -04:00
James
e4e4ad1b08 chore: ensures beforeDuplicate works on each locale 2022-10-12 11:59:56 -04:00
James
f7352a7d08 chore(release): v1.1.11 2022-10-12 11:52:48 -04:00
James
6f6f2f8e7b feat: builds beforeDuplicate admin hook, closes #1243 2022-10-12 11:44:41 -04:00
James
5ca5abab42 fix: ensures arrays and blocks mount as disableFormData: true, fixes #1242 2022-10-12 10:52:59 -04:00
Jacob Fletcher
9a7553099c feat: migrates @faceless-ui/modal to v2.0.1 2022-10-12 01:28:17 -04:00
James
55d0c917e6 chore(release): v1.1.10 2022-10-11 13:40:19 -04:00
James
f52daeccf0 docs: adds access control vid to docs 2022-10-11 13:34:35 -04:00
James
6c871c57fc chore(release): v1.1.9 2022-10-11 12:12:40 -04:00
James
5322ada9e6 feat: improves access control typing 2022-10-11 11:46:58 -04:00
James
ee83a50ea9 chore: improves read-only styling of all react-selects 2022-10-11 11:46:44 -04:00
James
f6b19e074c chore: more predictably exports Access type 2022-10-11 09:44:52 -04:00
James
6cc1d9e41b chore(release): v1.1.8 2022-10-11 09:24:07 -04:00
James
74863f9462 chore: workaround for faceless-ui modal types 2022-10-11 09:19:20 -04:00
James
fdcf029da2 chore: adjusts LeaveWithoutSaving z-index 2022-10-11 09:00:19 -04:00
James
3e3d151e4c docs: #1235, broken typescript link 2022-10-11 08:32:27 -04:00
James Mikrut
5da204b152 Merge pull request #1224 from payloadcms/feat/use-context-selector
Feat/use context selector
2022-10-10 18:38:36 -04:00
James
3d6c3f7339 chore: cleanup 2022-10-10 18:38:15 -04:00
James
8d49517004 chore: merge master 2022-10-10 15:12:38 -04:00
James Mikrut
d1c0f2b97b Merge pull request #1230 from payloadcms/feat/add-inline-relationship
feat: adds ability to create related docs while editing another
2022-10-10 15:07:41 -04:00
James
1bc42ae098 chore: tests 2022-10-10 14:56:10 -04:00
James
c6edb7f53a chore: improves design 2022-10-10 13:57:08 -04:00
James
1e048fe037 feat: adds ability to create related docs while editing another 2022-10-09 20:24:38 -04:00
James
8fabdce584 chore: restores old useWatchForm to avoid breaking change 2022-10-07 17:54:35 -04:00
James
5c1a3fabee feat: implements use-context-selector for form field access 2022-10-07 17:41:41 -04:00
James
fe6d30210b Merge branch 'master' of github.com:payloadcms/payload into feat/use-context-selector 2022-10-07 10:09:20 -04:00
James
93f71e621c chore(release): v1.1.7 2022-10-06 18:14:37 -04:00
James Mikrut
39d1a09d5a Merge pull request #1215 from damtzi/chore/credentials-include
chore: Add 'credentials: include' to all fetch calls
2022-10-06 17:55:55 -04:00
Damian Tziamtzis
74ae6fd1d5 chore: Add 'credentials: include' to all fetch calls 2022-10-06 23:14:55 +02:00
James
bbbcf8c869 chore(release): v1.1.6 2022-10-06 17:10:10 -04:00
James
b379666dec chore: improves upload field button aesthetics a bit 2022-10-06 17:04:29 -04:00
James Mikrut
6f40b5c9ab Merge pull request #1175 from bigmistqke/fix/responsive-fileupload-width
Fix: responsive fileupload width
2022-10-06 16:42:25 -04:00
James Mikrut
b329be7dc1 Merge pull request #1214 from payloadcms/fix/#1184
fix: #1184
2022-10-06 16:12:05 -04:00
James
c2ec54a7cb fix: #1184 2022-10-06 16:11:24 -04:00
James
3641dfd38a fix: #1189 2022-10-06 15:42:20 -04:00
James
5bf1354741 chore: fixes tests 2022-10-06 15:23:49 -04:00
James Mikrut
b894b809bf Merge pull request #1212 from payloadcms/fix/#940
Fix/#940
2022-10-06 14:24:51 -04:00
James
a4504ca15b Merge branch 'master' of github.com:payloadcms/payload into fix/#940 2022-10-06 14:23:37 -04:00
James
7926083732 fix: #940 2022-10-06 14:23:08 -04:00
James Mikrut
534cd5ae53 Merge pull request #1211 from jacobsfletch/master
feat: async admin access control
2022-10-06 13:31:01 -04:00
James Mikrut
fb329a99ba Merge pull request #1210 from payloadcms/fix/#1204
fix: #1204
2022-10-06 13:24:42 -04:00
James Mikrut
9e726d9b90 Merge pull request #1174 from payloadcms/fix/#1156-file-uploads-changing-extensions
fix: upload xls renaming ext
2022-10-06 13:23:23 -04:00
James Mikrut
8d065d619d Merge pull request #1124 from payloadcms/feat/sortable-by-default
feat: sort select and relationship fields by default
2022-10-06 13:20:32 -04:00
James Mikrut
cbff1776e7 Merge pull request #1168 from payloadcms/fix/read-only
Fix: read only field styles
2022-10-06 13:20:16 -04:00
nwhitmont
e517695000 docs: +note that collection slug must be in kebab-case, refactor example routes to match (#1176)
Co-authored-by: Nils Whitmont <nwhitmont@genvidtech.com>
2022-10-06 13:18:44 -04:00
James Mikrut
4370cfca0c Merge pull request #1195 from bigmistqke/fix/textarea-resize-vertical
fix: resize textarea only vertically
2022-10-06 13:17:51 -04:00
James Mikrut
4135b618ef Merge pull request #1198 from dsod/fix/resize-images-naming-and-mimetype
use the converted image mimeType for filename and admin interface
2022-10-06 13:17:22 -04:00
Jacob Fletcher
1cfce87549 feat: async admin access control 2022-10-06 13:16:15 -04:00
James Mikrut
c48283ac1d Merge pull request #1201 from christian-reichart/fix/sibling-data-in-after-read
fix: sibling data in after read hook
2022-10-06 13:15:52 -04:00
James Mikrut
328be3e4bc Merge pull request #1206 from payloadcms/style/color-scheme
fix: system dark scrollbars
2022-10-06 13:14:54 -04:00
James
b4becd1493 fix: #1204 2022-10-06 13:11:25 -04:00
James
95fac0bd62 chore: wip 2022-10-05 15:51:22 -04:00
Jarrod Flesch
a30d9dc1d7 fix(style): system dark scrollbars 2022-10-05 15:33:41 -04:00
Christian Reichart
7bfcefbfea fix sibling data in after read hook 2022-10-04 14:39:09 +02:00
dsod
131b2796e7 now uses the converted image mimeType for file extension and in the admin interface 2022-10-03 19:24:14 +02:00
Elliot DeNolf
debcb003bb docs: clarify api key auth usage 2022-10-03 08:41:23 -04:00
bigmistqke
6e1dfff1b8 fix: resize textarea only vertically 2022-10-02 17:37:27 +02:00
Jarrod Flesch
a9ebb71a09 Merge branch 'master' into fix/read-only 2022-09-30 10:47:34 -04:00
James
3e34e5216f chore(release): v1.1.5 2022-09-29 17:56:44 -04:00
James
2400c58219 chore: addresses more flaky tests 2022-09-29 17:52:51 -04:00
James
90d504526c chore: adds more delay to flaky test 2022-09-29 17:17:14 -04:00
Jarrod Flesch
c97d4f9545 Merge branch 'master' into fix/read-only 2022-09-29 12:54:02 -04:00
Jarrod Flesch
09a8144f3c fix: richText e2e test, specific selectors 2022-09-29 12:53:16 -04:00
Jarrod Flesch
00ef1700ae fix: ajusts how disabled states are being set on anchors and buttons 2022-09-29 11:49:25 -04:00
James
3e03b2b5df Merge branch 'master' of github.com:payloadcms/payload 2022-09-29 11:11:14 -04:00
James
974f79e57e chore: sends 204 on GraphQL OPTIONS requests 2022-09-29 11:11:02 -04:00
James
34f42083b5 chore: rolls back changes to useThrottledEffect 2022-09-29 10:26:06 -04:00
Elliot DeNolf
c0cae1e834 chore: reorder bug report template 2022-09-29 09:27:33 -04:00
James
3ce8ee4661 fix: bug in useThrottledEffect 2022-09-28 17:40:31 -04:00
bigmistqke
f9feff58d6 add flex-wrap: wrap to upload__wrap 2022-09-26 21:03:28 +02:00
bigmistqke
73848b6037 fix: remove min-width from fileupload 2022-09-26 20:51:45 +02:00
Dan Ribbens
7fd8124df6 fix: upload xls renaming ext 2022-09-26 08:19:58 -04:00
James Mikrut
1c77455403 Merge pull request #1169 from payloadcms/docs/test-cache
Improves contributing doc
2022-09-24 12:06:42 -07:00
James
051a0fad84 chore(release): v1.1.4 2022-09-23 20:15:52 -07:00
Jarrod Flesch
8e53ef47a0 chore: adds note about clearing the node module cache when switching test directories 2022-09-23 15:23:28 -04:00
Jarrod Flesch
918130486e fix: styles readOnly RichTextEditor, removes interactivity within when readOnly 2022-09-23 14:34:02 -04:00
Jarrod Flesch
b454811698 fix: threads readOnly to ReactSelect 2022-09-23 13:17:11 -04:00
James Mikrut
f87c68f310 Merge pull request #1147 from abaco/fix/refine-relationship-typegen
fix: refine type generation for relationships
2022-09-23 09:51:36 -07:00
James
25006d44e8 Merge branch 'master' of github.com:payloadcms/payload 2022-09-23 09:49:06 -07:00
James Mikrut
d8e51dd200 Merge pull request #1157 from payloadcms/docs/cell-component-props
docs: cell component props and example
2022-09-23 09:46:49 -07:00
James Mikrut
f54210a528 Merge pull request #1161 from jacobsfletch/master
feat: supports root endpoints
2022-09-23 09:46:30 -07:00
James Mikrut
96dab15cd1 Merge pull request #1163 from payloadcms/fix/nested-fields-permissions
fix: field level access for nested fields
2022-09-23 09:44:02 -07:00
James Mikrut
4126843619 Merge pull request #1165 from jacobsfletch/docs/webpack-cache
docs: adds tip for clearing webpack cache when aliasing server modules
2022-09-23 09:42:35 -07:00
James
e0238ad393 chore: updates sass 2022-09-23 09:42:10 -07:00
Jacob Fletcher
aa0302c05e docs: adds tip for clearing webpack cache when aliasing server modules 2022-09-23 12:24:51 -04:00
Jacob Fletcher
1040ad2cfe Merge branch 'payloadcms:master' into master 2022-09-23 12:23:57 -04:00
Dan Ribbens
c64f15d4d9 test: field level access for nested fields 2022-09-23 03:26:26 -04:00
Dan Ribbens
22ea98ca33 fix: field level access for nested fields 2022-09-22 21:37:02 -04:00
Jacob Fletcher
75bab716d1 chore: adds root endpoint test 2022-09-22 13:23:42 -04:00
Dan Ribbens
52a8e9624c docs: plugins typo 2022-09-22 12:19:18 -04:00
Jacob Fletcher
52cd3b4a7e feat: supports root endpoints 2022-09-22 10:49:49 -04:00
addison-codes
cc63167307 docs: fix highlighting 2022-09-22 00:51:25 -04:00
Dan Ribbens
314671b3b7 docs: cell component props and example 2022-09-21 14:20:01 -04:00
Dario Aprea
ef83bdb709 fix: refine type generation for relationships 2022-09-20 16:27:09 +02:00
Dan Ribbens
686085496a chore(release): v1.1.3 2022-09-16 17:05:57 -04:00
Dan Ribbens
76ae20aa16 fix: disable-local-strategy-and-graphql 2022-09-16 16:56:29 -04:00
Dan Ribbens
a1a55386f0 fix: duplicate with relationships 2022-09-16 14:57:29 -04:00
Elliot DeNolf
b3bb421c6c fix: adjust prevPage and nextPage graphql typing (#1140) 2022-09-16 09:40:21 -07:00
Dan Ribbens
eabb981243 fix: duplicate with relationships 2022-09-16 12:26:18 -04:00
Dan Ribbens
4be14a12d0 chore(release): v1.1.2 2022-09-14 15:42:13 -04:00
Dan Ribbens
ce174878f3 fix: disableLocalStorage with image resizing 2022-09-14 15:34:59 -04:00
Dan Ribbens
14966796ae fix: resize images without local storage 2022-09-14 13:07:18 -04:00
Dan Ribbens
7b756f3421 fix: resize images without local storage 2022-09-14 11:34:40 -04:00
Marco Sangalli
9fea2b4e08 chore: fixing uploadFile when using s3 bucket 2022-09-14 14:43:47 +02:00
James
f9b1b1fe7f docs: adds Link example in rich text 2022-09-13 21:25:08 -07:00
Elliot DeNolf
205404a88a chore: remove unused cypress package 2022-09-13 20:09:24 -07:00
Elliot DeNolf
813c46c86d feat: sort select and relationship fields by default 2022-09-13 20:06:12 -07:00
Luciano Greiner
8bfe253157 Avoid adding password and email to schemas when disableLocalStrategy is on 2022-09-13 17:55:22 -03:00
Dan Ribbens
ed8e581629 chore: changelog update 2022-09-13 13:44:22 -04:00
Dan Ribbens
7cf66b081a chore(release): v1.1.1 2022-09-13 13:20:24 -04:00
Dan Ribbens
64503f8267 fix: conditions on collapsible fields 2022-09-13 13:16:52 -04:00
Dan Ribbens
d82c12eab6 Merge pull request #1120 from payloadcms/fix/#1117 2022-09-13 11:58:28 -04:00
Dan Ribbens
9c4f2b68b0 fix: conditions on conditionals 2022-09-13 11:53:52 -04:00
James
bcfda707ff Merge branch 'master' of github.com:payloadcms/payload 2022-09-13 07:40:57 -07:00
James
66b77f7dca chore: re-adds key to tabs RenderFields 2022-09-13 07:40:48 -07:00
Dan Ribbens
dcc8dad53b fix: dashboard links to globals 2022-09-13 09:47:28 -04:00
Elliot DeNolf
bb1477e08b chore(release): v1.1.0 2022-09-12 20:54:50 -07:00
James
4e165cf52e chore: ensures new listSearchableFields works with complex queries 2022-09-12 19:40:01 -07:00
James Mikrut
a1083727ef feat: #1001 - builds a way to allow list view to search multiple fields
* make textfields searchable

* shorten namings in placeholder function

* chore: finishes listSearchableFields

Co-authored-by: Christian Reichart <christian.reichart@camperboys.com>
2022-09-12 16:38:02 -07:00
Elliot DeNolf
d5ccd45b53 Ability to get the "previous" state in the AfterChange Hook (#1115)
Co-authored-by: Alessio Gravili <alessio@bonfireleads.com>
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2022-09-12 15:48:50 -07:00
Dan Ribbens
94d355ba5e Merge pull request #1102 from payloadcms/feat/group-collections 2022-09-12 16:52:24 -04:00
Dan Ribbens
892a774998 Merge branch 'master' into feat/group-collections 2022-09-12 16:39:07 -04:00
James
78f39af5cf chore: finishes collapsible nav groups 2022-09-12 12:45:06 -07:00
James
813fa1571c Merge branch 'feat/group-collections' of github.com:payloadcms/payload into feat/group-collections 2022-09-12 10:55:15 -07:00
Dan Ribbens
c40e232ac6 feat: hide nav labels with no un-grouped collections 2022-09-12 13:51:24 -04:00
Dan Ribbens
834f4ebd38 feat/add support for setting collapsable fields (array, block, collapsable… (#1057)
Co-authored-by: liorix <liorix@gmail.com>
Co-authored-by: Elliot DeNolf <denolfe@gmail.com>
2022-09-12 10:39:49 -07:00
Elliot DeNolf
ca434b8a92 feat: implement gravatar (#1107) 2022-09-12 10:29:22 -07:00
Dan Ribbens
7acf944a28 Feat/expose sharp options (#1029)
Co-authored-by: khakimvinh <kha.kim.vinh@gmail.com>
Co-authored-by: Elliot DeNolf <denolfe@gmail.com>
2022-09-12 10:29:07 -07:00
Elliot DeNolf
391c9d8682 fix: word boundaries, no regex lookbehind support for Safari (#1114) 2022-09-12 10:28:29 -07:00
James Mikrut
8e47961da1 Merge pull request #814 from slavanossar/feat/optional-tab-namespacing
feat: optional tab namespacing
2022-09-11 21:42:54 -07:00
James
016f3e47ab chore: ensures generate types works with named tabs 2022-09-11 21:19:20 -07:00
James
21eb19edd1 chore: further refinements 2022-09-11 20:57:30 -07:00
James
ad4f7a5fff chore: fixes and cleanup 2022-09-11 20:07:02 -07:00
James
cd209379b2 chore: merge master 2022-09-11 18:07:05 -07:00
Elliot DeNolf
14a16dc05f chore: remove unneeded package (#1110) 2022-09-11 16:39:16 -07:00
James
9a461b8536 fix: #1106 2022-09-10 17:58:39 -07:00
Dan Ribbens
59af8725b4 feat: globals groups 2022-09-10 19:59:11 -04:00
Dan Ribbens
dffeaf6a69 feat: collection groups 2022-09-10 19:59:11 -04:00
James
c7851f8189 chore(release): v1.0.36 2022-09-10 13:55:23 -07:00
James
ada1871993 fix: bug with account view 2022-09-10 13:50:34 -07:00
James
78ccd2ad9b chore: cleanup exports 2022-09-10 13:33:14 -07:00
James
4cb69039ed chore(release): v1.0.35 2022-09-09 20:12:41 -07:00
James
c8b37f40cb fix: incorrect auth strategy type 2022-09-09 20:03:49 -07:00
Elliot DeNolf
4963f10a18 Merge pull request #1083 from payloadcms/feat/improve-rich-text-link
Co-authored-by: Elliot DeNolf <denolfe@gmail.com>
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2022-09-09 15:34:17 -07:00
Elliot DeNolf
6f77fe7c27 Merge branch 'master' into feat/improve-rich-text-link 2022-09-09 15:23:46 -07:00
Elliot DeNolf
c584c2ed47 test: rich text link e2e 2022-09-09 15:18:02 -07:00
Elliot DeNolf
5a19f6915a fix: rich text link with no selection 2022-09-09 15:17:51 -07:00
Jacob Fletcher
48f0c06edc chore: bumps @faceless-ui/modal to v2.0.0-alpha.4 2022-09-09 00:13:27 -04:00
Jacob Fletcher
50cc3c5a21 chore: bumps @faceless-ui/modal to v2.0.0-alpha.3 2022-09-08 23:08:54 -04:00
James
6674a0df6b Merge branch 'feat/improve-rich-text-link' of github.com:payloadcms/payload into feat/improve-rich-text-link 2022-09-08 18:05:39 -07:00
James
cbbace21ca chore: updates faceless modal 2022-09-08 18:05:21 -07:00
Elliot DeNolf
87346a97ab test: rich text internal link 2022-09-08 15:40:49 -07:00
Elliot DeNolf
4357c5491d docs: add rich text link fields 2022-09-08 15:40:49 -07:00
Elliot DeNolf
03847703b0 chore: update rich text data seeding 2022-09-08 15:40:49 -07:00
James
3c46851426 chore: migrates to new faceless modal version 2022-09-08 15:22:09 -07:00
James Mikrut
8115855e5a Merge pull request #1089 from payloadcms/fix/height-width-by-default
fix: add height/width if imageSizes not specified
2022-09-07 20:03:39 -07:00
James Mikrut
ddb2a0a4f5 Merge pull request #1095 from jacobsfletch/master
chore: bumps @faceless-ui/modal to v1.3.2 #1070
2022-09-07 20:03:27 -07:00
James
a45577f182 chore: misc ux improvements to rich text link 2022-09-07 17:25:15 -07:00
James
eb963066f7 chore: fixes bug with links always populating 2022-09-07 16:52:32 -07:00
James
a99d9c98c3 feat: allows rich text links to link to other docs 2022-09-07 16:42:43 -07:00
James
cdfc0dec70 chore: ensures logic is accurate while updating links 2022-09-07 13:48:36 -07:00
Elliot DeNolf
eb1ff7efce chore: update label for bug reports 2022-09-07 12:40:00 -07:00
Jacob Fletcher
55843f6c55 chore: bumps @faceless-ui/modal to v1.3.2 #1070 2022-09-07 15:23:47 -04:00
James
7856f66b31 merge master 2022-09-07 11:53:02 -07:00
James Mikrut
47ac86b4b7 Merge pull request #1094 from payloadcms/fix/#1059
fix: #1059
2022-09-07 11:51:42 -07:00
James
13dc39dc6d fix: #1059 2022-09-07 11:50:55 -07:00
James
65653bd1d3 chore(release): v1.0.34 2022-09-07 09:39:24 -07:00
James
299ee82ccf chore: pins faceless ui 2022-09-07 09:35:24 -07:00
James
b38b6427b8 fix: pins faceless ui modal 2022-09-07 09:29:04 -07:00
James
784696f9a6 chore: comments todos 2022-09-07 09:27:39 -07:00
Elliot DeNolf
8bd2a0e6c9 fix: add height/width if imageSizes not specified 2022-09-07 08:58:02 -07:00
Dan Ribbens
0f671b1b35 chore(release): v1.0.33 2022-09-07 10:57:08 -04:00
Dan Ribbens
d56882cc20 fix: require min 1 option in field schema validation (#1082) 2022-09-07 10:53:21 -04:00
James
6d13ae6846 chore: removes old comment 2022-09-06 22:58:14 -07:00
James
91000d7fda feat: improves rich text link ux 2022-09-06 22:53:27 -07:00
Elliot DeNolf
888734dcdf fix: accented label char sanitization for GraphQL (#1080) 2022-09-06 16:03:43 -07:00
James Mikrut
e81da57f61 Merge pull request #1079 from payloadcms/fix/#1062
fix: #1062
2022-09-06 13:58:20 -07:00
James
05d1b141b2 fix: #1062 2022-09-06 13:56:53 -07:00
Elliot DeNolf
b7e5828adc feat: cyrillic like query support (#1078) 2022-09-06 16:47:57 -04:00
Dan Ribbens
2ee4c7ad72 fix: update removing an upload with null (#1076) 2022-09-06 16:19:59 -04:00
max frühschütz
77ab11bce8 docs: move description prop doc to right place (#1077) 2022-09-06 16:19:47 -04:00
Arick
cd8edbaa1f fix: reorder plugin wrapping (#1051) 2022-09-06 16:19:21 -04:00
Matt Arnold
5e21048457 docs: update custom endpoints link (#1075) 2022-09-06 15:41:27 -04:00
pixelistik
c0c093d16c docs: Fix missing word (#1063) 2022-09-05 21:03:48 -04:00
James
117153fad7 chore: bump version 2022-09-02 14:56:09 -07:00
James
5ae666b0e0 fix: ensures adding new media to upload works when existing doc does not exist 2022-09-02 14:39:41 -07:00
Dustin Miller
afa03789b8 docs: Correct “autheticate” misspelling in docs (#1048) 2022-09-02 15:27:06 -04:00
Dan Ribbens
44b0073834 fix: update removing a relationship with null (#1056) 2022-09-02 15:21:09 -04:00
Dan Ribbens
254636167d test: custom css (#1053) 2022-09-02 14:03:49 -04:00
Dan Ribbens
29e82ec845 fix: children of conditional fields required in graphql schema (#1055) 2022-09-02 14:02:58 -04:00
Wesley
c3a0bd8625 fix: implement the same word boundary search as the like query (#1038)
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2022-09-01 13:03:21 -04:00
Ikko Ashimine
32a4e8e9b9 docs: update overview.mdx (#1046) 2022-09-01 12:58:09 -04:00
Elliot DeNolf
482cbe71c7 feat: update operator type with contains (#1045) 2022-09-01 12:17:53 -04:00
Elliot DeNolf
7dbcd9ca89 docs: fix relationship where query 2022-09-01 12:14:07 -04:00
Dan Ribbens
a1f2dcab8f docs: fix defaultValue example (#1044) 2022-09-01 10:26:41 -04:00
James
21417c6598 chore: better logic for changing password in ui 2022-08-30 17:58:17 -07:00
James
f1a272f407 1.0.31 2022-08-30 15:21:59 -07:00
James Mikrut
064f797023 Merge pull request #1033 from Elliot67/patch-1
docs: fix graphql `disable` property explanation
2022-08-30 15:21:10 -07:00
James Mikrut
26c2020b12 Merge pull request #1034 from payloadcms/feat/duplicate-all-locales
Feat/duplicate all locales
2022-08-30 15:13:07 -07:00
James
af1a483e64 chore: ensures new Edit view is mounted when going directly between one id and another 2022-08-30 15:12:23 -07:00
James
f517cb5e93 chore: cleanup 2022-08-30 15:07:38 -07:00
James
c8a8914ea7 Merge branch 'feat/duplicate-all-locales' of github.com:payloadcms/payload into feat/duplicate-all-locales 2022-08-30 14:52:51 -07:00
James Mikrut
4f834c6bf8 Merge pull request #1035 from payloadcms/fix/#981
fix: #981
2022-08-30 14:44:40 -07:00
James
d588843121 fix: #981 2022-08-30 14:43:44 -07:00
Dan Ribbens
51c7770b10 feat: duplicate copies all locales 2022-08-30 17:23:30 -04:00
Elliot Lintz
18ff5d29b0 docs: fix graphql disable property explanation 2022-08-30 22:41:00 +02:00
Dan Ribbens
e31098eaa5 test: duplicate copies all locales 2022-08-30 16:25:26 -04:00
James Mikrut
0873050b32 Merge pull request #1032 from payloadcms/fix/#948
fix: #948
2022-08-30 11:55:39 -07:00
James
8df9ee7b2d fix: #948 2022-08-30 11:54:41 -07:00
Dan Ribbens
a9ef557ca4 chore(release): v1.0.30 2022-08-29 22:55:12 -04:00
Dan Ribbens
689fa008fb fix: upload field validation not required (#1025) 2022-08-29 22:49:08 -04:00
James
1a4ce65e6c chore(release): v1.0.29 2022-08-29 15:58:30 -07:00
James Mikrut
f6dff04cfe Merge pull request #1022 from payloadcms/fix/#953
fix: #953
2022-08-29 15:42:44 -07:00
James
a73c391c2c fix: #953 2022-08-29 15:41:59 -07:00
James
e88c7ca4b2 chore: revises the logic while clearing wherebuilder queries 2022-08-29 15:13:14 -07:00
Dan Ribbens
0cd8446735 chore(release): v1.0.28 2022-08-29 17:08:17 -04:00
Dan Ribbens
6a3cfced9a fix: relationship cell loading (#1021)
* fix: relationship cell lazy loading items in viewport

* fix: collection list page param changing
2022-08-29 17:03:45 -04:00
Hung Vu
0586d7aa7d fix: unpublish item will not crash the UI anymore (#1016) 2022-08-29 15:39:46 -04:00
Dan Ribbens
3736755a12 feat: types custom components to allow any props (#1013) 2022-08-29 14:57:30 -04:00
Dan Ribbens
d727fc8e24 feat: validate relationship and upload ids (#1004)
* feat: validate relationship and upload ids

* chore: update fields-relationship test data

* fix: skip FE relationship and upload id validation
2022-08-29 14:57:06 -04:00
Dan Ribbens
50b0303ab3 fix: incorrect field paths when nesting unnamed fields (#1011)
* chore: reproduce issue #976

* fix: incorrect field paths when nesting non-named fields
2022-08-29 14:52:17 -04:00
Dan Ribbens
6bc6e7bb61 fix: require properties in blocks and arrays fields (#1020)
* fix: make blocks required in block fields

* fix: make fields required in array fields
2022-08-29 14:51:12 -04:00
Dan Ribbens
cf2eb3e482 chore: fix dev hooks (#1006) 2022-08-26 11:11:21 -04:00
James Mikrut
4aa5500593 Merge pull request #965 from payloadcms/test/code-field
test: add code fields to test config
2022-08-24 08:26:08 -07:00
James Mikrut
c7432fb6e6 Merge pull request #986 from charkour/patch-1
docs: fix typo about collections
2022-08-24 08:25:46 -07:00
James Mikrut
f895443e33 Merge pull request #990 from payloadcms/docs/examples-with-ts-types
docs: update examples with ts types
2022-08-24 08:25:33 -07:00
James Mikrut
32cebb56ff Merge pull request #999 from jacobsfletch/master
chore: exports PayloadHandler interface
2022-08-24 08:24:14 -07:00
James Mikrut
4a6b01d094 Merge pull request #996 from payloadcms/fix/#948-tab-nav-scroll
fix: remove lazy load of array and blocks components
2022-08-24 08:23:46 -07:00
James Mikrut
f8295f7577 Merge pull request #1000 from payloadcms/ci/actions-on-pr
ci: run actions on PRs
2022-08-24 08:20:33 -07:00
Elliot DeNolf
a2fa99d06f ci: run actions on PRs 2022-08-24 09:00:52 -04:00
Jacob Fletcher
c508ac6dee chore: exports PayloadHandler interface 2022-08-24 07:54:09 -04:00
Dan Ribbens
4900fa799f fix: remove lazy loading of array and blocks 2022-08-23 15:42:33 -04:00
James Mikrut
135b1bcdd7 Merge pull request #975 from dsod/fix/node15-keep-request-properties
Keep original request and add properties to it, instead of copy
2022-08-23 11:19:01 -07:00
Elliot DeNolf
25f5d68b74 feat: export more fields config types and validation type (#989) 2022-08-23 13:26:00 -04:00
Elliot DeNolf
1ed867ce0c docs: update examples with ts types 2022-08-23 11:43:24 -04:00
Charles Kornoelje
5ef5f09a5c fix typo about collections
Really interesting product. Reading through the docs now. Loving it so far. 

Thanks
2022-08-22 16:23:59 -04:00
dsod
bc97d3d6f2 Keep original request and add properties to it, instead of copy 2022-08-20 01:33:29 +02:00
Dan Ribbens
b21a56fdf7 chore(release): v1.0.27 2022-08-18 17:45:16 -04:00
Dan Ribbens
97f2a9b484 test: fix e2e relationship 2022-08-18 17:43:06 -04:00
James
4ef6801230 fix: react-sortable-hoc dependency instead of dev dependency 2022-08-18 14:37:45 -07:00
Dan Ribbens
8e21a91b2c chore(release): v1.0.26 2022-08-18 17:03:45 -04:00
Afzaal Ahmad
38a1a38c0c feat: enable reordering of hasMany relationship and select fields (#952)
* feature: enable reordering of hasMany relationship and select fields using config

* update: correct docs for select, and relationship field for sortable config

* update: move sortable to admin config, and rename it to isSortable, apply grab, and grabbing classes while drag, and drop
2022-08-18 16:59:33 -04:00
Dan Ribbens
2b7785d101 chore: update endpoint type (#966) 2022-08-18 16:22:35 -04:00
Dan Ribbens
2cf9d35fed chore: run ci tests on pull request (#955)
* chore: run ci tests on pull request
2022-08-18 15:17:02 -04:00
Elliot DeNolf
4e1f9c7280 test: add code fields to test config 2022-08-18 14:44:10 -04:00
Will Viles
f45d5a0421 feat: adds more prismjs syntax highlighting options for code blocks (#961) 2022-08-18 13:17:05 -04:00
Dan Ribbens
39586d3cdb fix: missing fields in rows on custom id collections (#954) 2022-08-18 12:26:05 -04:00
James
5e66e3ee78 chore(release): v1.0.25 2022-08-17 15:36:28 -07:00
James Mikrut
a284f6c066 Merge pull request #950 from payloadcms/feat/top-level-endpoints
Feat/top level endpoints
2022-08-17 15:32:35 -07:00
James Mikrut
fdc0ad8a3c Merge pull request #947 from rustyy/chore/code-improvements
Chore/code improvements
2022-08-17 15:32:09 -07:00
James
fa01b353ef chore: removes unused code 2022-08-17 15:31:39 -07:00
Dan Ribbens
dac3308441 docs: custom api endpoints 2022-08-17 14:08:01 -04:00
Dan Ribbens
11d8fc71e8 feat: custom api endpoints 2022-08-17 13:28:50 -04:00
Dan Ribbens
771bbaedbc chore: validation reuses endpoints schema 2022-08-17 13:06:58 -04:00
Dan Ribbens
040833ead8 test: move endpoints tests to new suite 2022-08-17 12:57:07 -04:00
Felix Hofmann
7c2acb4324 chore: use type-var instead of 'as'-keyword for jwtDecode 2022-08-16 23:22:00 +02:00
Felix Hofmann
88fe797007 chore: remove unreachable return statement 2022-08-16 23:21:38 +02:00
Elliot DeNolf
5825d7e64f test: skip flaky sorting test for now (#945) 2022-08-16 12:04:25 -04:00
James Mikrut
97ff49f2bf Merge pull request #926 from rustyy/feat/datepicker-month-only
feat: add new pickerAppearance option 'monthOnly'
2022-08-15 19:21:50 -07:00
James
ccada2e8c9 chore: migrates to PATCH for collection updates 2022-08-15 19:19:44 -07:00
James
1d8bcd6e16 Merge commit 'refs/pull/863/head' of github.com:payloadcms/payload 2022-08-15 18:55:07 -07:00
James
1b58094e6f Merge branch 'master' of github.com:payloadcms/payload 2022-08-15 18:54:43 -07:00
James
1437bfe0c9 merge: #799 2022-08-15 18:54:34 -07:00
James Mikrut
171da32b00 Merge pull request #944 from payloadcms/fix/#568
fix: #568
2022-08-15 18:42:15 -07:00
James
a3edbf4fef fix: #568 2022-08-15 18:41:34 -07:00
James Mikrut
8ab4ec8d54 Update CHANGELOG.md 2022-08-15 18:04:32 -07:00
James
f8365abf1b chore(release): v1.0.24 2022-08-15 18:03:57 -07:00
James
078e8dcc51 feat: ensures you can query on blocks via specifying locale or not specifying locale 2022-08-15 17:59:08 -07:00
James
b1a1575122 fix: #939 2022-08-15 17:58:59 -07:00
James
201a8e1053 Merge branch 'master' of github.com:payloadcms/payload 2022-08-15 17:58:53 -07:00
Dan Ribbens
ff4c1a1c01 chore: simplifies indexing
* fix: create indexes in nested fields

* chore: remove unused devDependency mongodb

* fix: point index

* fix: nested schema indexes

* chore: begins to simplify index creation

* chore: simplifies index creation

Co-authored-by: James <james@trbl.design>
2022-08-15 17:56:39 -07:00
James
7c449e0d30 Merge branch 'chore/simplify-indexes' of github.com:payloadcms/payload 2022-08-15 13:58:10 -07:00
Dan Ribbens
2ca526bd22 chore: simplifies index creation 2022-08-15 16:54:23 -04:00
James
48f929f3ab chore: begins to simplify index creation 2022-08-15 13:03:13 -07:00
James
f2bec90663 Merge branch 'fix/#922-nested-index-creation' of github.com:payloadcms/payload into fix/#922-nested-index-creation 2022-08-15 12:37:10 -07:00
Dan Ribbens
1c7445dc7f fix: format graphql localization input type (#932)
* fix: format locales for graphql

* fix: locale missing from graphql mutation args
2022-08-15 11:42:29 -04:00
James
78630cafa2 chore(release): v1.0.23 2022-08-14 18:54:13 -07:00
James Mikrut
0a40dd43cb test: adds dataloader test (#936)
* chore: adds dataloader test config

* test: passing dataloader test
2022-08-14 11:04:05 -07:00
James Mikrut
32fb72a5bb Merge pull request #935 from payloadcms/fix/#930
fix: #930
2022-08-14 10:42:10 -07:00
James
cbb1c84be7 fix: #930 2022-08-14 10:40:52 -07:00
Dan Ribbens
145e1db05d fix: dev:generate-types on all test configs (#919) 2022-08-13 13:04:24 -04:00
Elliot DeNolf
4115045c15 test: flaky admin test fix (#929) 2022-08-13 08:08:40 -04:00
Dan Ribbens
d0744f3702 fix: nested schema indexes 2022-08-13 07:44:36 -04:00
James
e7caaf57a9 chore(release): v1.0.22 2022-08-12 16:25:55 -07:00
James
fba0847f0f fix: ensures you can query on mixed schema type within blocks 2022-08-12 16:08:36 -07:00
Felix Hofmann
566c6ba3a9 feat: add new pickerAppearance option 'monthOnly' 2022-08-12 23:11:33 +02:00
Dan Ribbens
b860959fea fix: point index 2022-08-12 16:35:08 -04:00
James
9e4e4b231c chore: adds commenting to obscure form function 2022-08-12 13:11:49 -07:00
James
b8421ddc0c fix: #905 2022-08-12 12:18:02 -07:00
Dan Ribbens
9165b25fd6 chore: remove unused devDependency mongodb 2022-08-12 12:01:34 -04:00
Dan Ribbens
f615abc9b1 fix: create indexes in nested fields 2022-08-12 11:59:51 -04:00
James
9237dd67da chore(release): v1.0.21 2022-08-11 15:06:20 -07:00
James
e1347d5a39 Merge branch 'master' of github.com:payloadcms/payload 2022-08-11 15:02:28 -07:00
James
ca852e8cb2 fix: ensures you can query on nested block fields 2022-08-11 15:02:17 -07:00
KGZM
d0da3d7962 fix: saving multiple versions (#918)
* test: saving multiple versions with unique fields

* fix: saving multiple versions with unique fields

Version schemas were getting produced with uniqueness constraints which
caused updates to likely fail because each successive version would
potentially reuse unchanged unique key-values (particularly ident keys
like slug) from previous versions.

This was due to `mongoose.buildSchema` not respecting buildSchemaOptions.disableUnique.

This regression was introduced with the fix at commit c175476e.

* test: eslint fix

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2022-08-11 15:53:51 -04:00
Dan Ribbens
2ae33b603a feat: WIP tab compatible with traverseFields 2022-08-11 12:05:35 -04:00
Dan Ribbens
6a6a69190f feat: tabs support localization at the tab level 2022-08-11 12:05:33 -04:00
Dan Ribbens
a7b882c03d chore: run ci test workflow on pull requests 2022-08-11 12:05:05 -04:00
Dan Ribbens
a83921a2fe feat: support localized tab fields 2022-08-11 12:05:02 -04:00
Slava Nossar
4b7b04d5fa return full Tab object for column display 2022-08-11 12:04:20 -04:00
Slava Nossar
b403f5228b update Tabs field docs 2022-08-11 12:04:20 -04:00
Slava Nossar
dd289ca24a update data and path when tab had name 2022-08-11 12:04:20 -04:00
Slava Nossar
daf5fc83d8 update test data 2022-08-11 12:04:20 -04:00
Slava Nossar
08271086c3 return tab.name for columns 2022-08-11 12:04:20 -04:00
Slava Nossar
30da37becd add namespaced field path 2022-08-11 12:04:20 -04:00
Slava Nossar
df1491de74 add extra test fields and data 2022-08-11 12:04:20 -04:00
Slava Nossar
420fd67905 update mongodb schema builder 2022-08-11 12:04:20 -04:00
Slava Nossar
17a55e4a3a add tab field with name and data to test suite 2022-08-11 12:04:20 -04:00
Slava Nossar
1b3a328388 update graphql schema builder 2022-08-11 12:04:20 -04:00
Slava Nossar
060ee40bab add name to schema and types 2022-08-11 12:04:20 -04:00
Dan Ribbens
103966d36e chore(release): v1.0.20 2022-08-11 11:55:09 -04:00
Dan Ribbens
1ce2dc8e6d chore: update dependencies (#917)
* chore: update dependencies

* chore: mongoose version update

* chore: yarn lock

Co-authored-by: Elliot DeNolf <denolfe@gmail.com>
2022-08-11 11:51:30 -04:00
Tim Hallyburton
2f4f075441 feat: field name validation (#903)
* feat: add simple validation for field names

* test: add invalid field name coverage
2022-08-11 08:02:55 -04:00
Arick
50972b98a1 fix: E11000 duplicate key error has no keyValue (#916) 2022-08-11 07:49:59 -04:00
Dan Ribbens
874c001d3b fix: number validation works with 0 min and max (#906) 2022-08-10 08:18:39 -04:00
Petre Tudor
63519a8f54 docs: missing word (#909)
add `wait` in `...the second hook will wait for the first to complete before it starts.`
2022-08-10 07:59:52 -04:00
finnelliott
63a0336e98 docs: add missing comma to radio example (#901)
Missing comma in example, leading to syntax error when copied by user.
2022-08-08 20:56:55 -04:00
Yves Léon Jansen
ae51836187 docs: tabs example typo (#904) 2022-08-08 20:55:51 -04:00
Falko Woudstra
f0ff1c7c99 Update documentation for PATCH instead of PUT in Rest API 2022-08-02 00:02:50 +02:00
Falko Woudstra
d0d498e9c7 Add warning PATCH update route and add deprecation warning on PUT 2022-08-01 23:59:26 +02:00
lordie
bc83153b60 assert every list element non null 2022-07-27 18:46:39 -03:00
lordie
e13c6fa955 graphql schema: check blockfield 'required' property for null-ability 2022-07-22 18:05:43 -03:00
379 changed files with 13149 additions and 6629 deletions

View File

@@ -40,6 +40,7 @@ module.exports = {
'jest/no-test-callback': 'off',
'jest/prefer-strict-equal': 'off',
'jest/expect-expect': 'off',
'jest-dom/prefer-to-have-attribute': 'off',
}
},
{

View File

@@ -1,27 +1,28 @@
---
name: Bug Report
about: Create a bug report for Payload
labels: 'bug'
labels: 'possible-bug'
---
# Bug Report
<!--- Provide a general summary of the issue in the Title above -->
## Expected Behavior
<!--- Tell us what should happen -->
## Current Behavior
<!--- Tell us what happens instead of the expected behavior -->
## Expected Behavior
<!--- Tell us what you expected happen -->
## Possible Solution
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
<!--- Optional. If familiar with the codebase, suggest a fix/reason for the bug. -->
## Steps to Reproduce
<!--- Steps to reproduce this bug. Include any code, if relevant -->
1.
2.
3.

View File

@@ -1,6 +1,10 @@
name: build
on: [push]
on:
pull_request:
types: [opened, reopened, edited, synchronize]
push:
branches: ['master']
jobs:
build_yarn:

1
.node-version Normal file
View File

@@ -0,0 +1 @@
v16.14.2

View File

@@ -1,3 +1,348 @@
## [1.1.19](https://github.com/payloadcms/payload/compare/v1.1.18...v1.1.19) (2022-10-31)
### Bug Fixes
* [#1307](https://github.com/payloadcms/payload/issues/1307), [#1321](https://github.com/payloadcms/payload/issues/1321) - bug with disableFormData and blocks field ([2a09f15](https://github.com/payloadcms/payload/commit/2a09f15a158ff30e89c5454f81aa140448f15d30))
* [#1311](https://github.com/payloadcms/payload/issues/1311), select existing upload modal always updates state ([e2ec2f7](https://github.com/payloadcms/payload/commit/e2ec2f7b97ed308c4ff7deefbc58cf0df6ff0602))
* [#1318](https://github.com/payloadcms/payload/issues/1318), improves popup positioning and logic ([c651835](https://github.com/payloadcms/payload/commit/c6518350617d14818dfc537b5b0a147274c1119b))
* custom pino logger options ([#1299](https://github.com/payloadcms/payload/issues/1299)) ([2500026](https://github.com/payloadcms/payload/commit/25000261bd6ecb0f05ae79de9a0693078a0e3e0d))
### Features
* enforce kebab-case slugs ([#1322](https://github.com/payloadcms/payload/issues/1322)) ([0420b6d](https://github.com/payloadcms/payload/commit/0420b6dc27319ee56d2eed9ab42352f221af3c72))
* revert enforce kebab-case slugs ([#1322](https://github.com/payloadcms/payload/issues/1322)) ([#1325](https://github.com/payloadcms/payload/issues/1325)) ([2255ebb](https://github.com/payloadcms/payload/commit/2255ebb64a529b76a54e1e0ab40fba8149244b3d))
## [1.1.18](https://github.com/payloadcms/payload/compare/v1.1.17...v1.1.18) (2022-10-25)
## [1.1.17](https://github.com/payloadcms/payload/compare/v1.1.16...v1.1.17) (2022-10-25)
### Bug Fixes
* [#1286](https://github.com/payloadcms/payload/issues/1286), uses defaultDepth in graphql rich text depth ([66bf8c3](https://github.com/payloadcms/payload/commit/66bf8c3cbd080ee5a28b7af521d427d3aae59ba2))
* [#1290](https://github.com/payloadcms/payload/issues/1290), renders more than one rich text leaf where applicable ([a9f2f0e](https://github.com/payloadcms/payload/commit/a9f2f0ec03383ef4c3ef3ba98274b0abaaf962ed))
* [#1291](https://github.com/payloadcms/payload/issues/1291), add inline relationship drafts ([3967c12](https://github.com/payloadcms/payload/commit/3967c1233fda00b48e9df15276502a6b14b737ff))
* enforces depth: 0 in graphql resolvers ([3301f59](https://github.com/payloadcms/payload/commit/3301f598223d517ac310909bb74e455891c27693))
* ensures field updates when disableFormData changes ([c929725](https://github.com/payloadcms/payload/commit/c929725dd565de08871dad655442ee9ac4f29dd5))
* group + group styles within collapsible ([17dbbc7](https://github.com/payloadcms/payload/commit/17dbbc77757a7cd6e517bac443859561fee86e32))
### Features
* added beforeLogin hook ([#1289](https://github.com/payloadcms/payload/issues/1289)) ([09d7939](https://github.com/payloadcms/payload/commit/09d793926dbb642bbcb6ab975735d069df355a8a))
* adds default max length for text-based fields ([6a1b25a](https://github.com/payloadcms/payload/commit/6a1b25ab302cbdf7f312012b29b78288815810af))
* specify node 14+ and yarn classic LTS ([#1240](https://github.com/payloadcms/payload/issues/1240)) ([9181477](https://github.com/payloadcms/payload/commit/91814777b0bf3830c4a468b76783ff6f42ad824a))
## [1.1.16](https://github.com/payloadcms/payload/compare/v1.1.15...v1.1.16) (2022-10-21)
### Bug Fixes
* indexSortableFields not respected ([785b992](https://github.com/payloadcms/payload/commit/785b992c3ea31f7818f1c87c816b8b8de644851d))
* obscure bug where upload collection has upload field relating to itself ([36ef378](https://github.com/payloadcms/payload/commit/36ef3789fbe00cafe8b3587d6c370e28efd5a187))
## [1.1.15](https://github.com/payloadcms/payload/compare/v1.1.14...v1.1.15) (2022-10-14)
### Bug Fixes
* ensures svg mime type is always image/svg+xml ([0b0d971](https://github.com/payloadcms/payload/commit/0b0d9714917b1a56fb899a053e2e35c878a00992))
## [1.1.14](https://github.com/payloadcms/payload/compare/v1.1.13...v1.1.14) (2022-10-14)
## [1.1.11](https://github.com/payloadcms/payload/compare/v1.1.10...v1.1.11) (2022-10-12)
### Bug Fixes
* ensures arrays and blocks mount as disableFormData: true, fixes [#1242](https://github.com/payloadcms/payload/issues/1242) ([5ca5aba](https://github.com/payloadcms/payload/commit/5ca5abab422ad1cdb1b449a8298f439c57dda464))
### Features
* builds beforeDuplicate admin hook, closes [#1243](https://github.com/payloadcms/payload/issues/1243) ([6f6f2f8](https://github.com/payloadcms/payload/commit/6f6f2f8e7b83821ae2f2d30d08460439746cc0c6))
## [1.1.10](https://github.com/payloadcms/payload/compare/v1.1.9...v1.1.10) (2022-10-11)
## [1.1.9](https://github.com/payloadcms/payload/compare/v1.1.8...v1.1.9) (2022-10-11)
### Features
* improves access control typing ([5322ada](https://github.com/payloadcms/payload/commit/5322ada9e690544c4864abba202a14ec1f2f5e9d))
## [1.1.8](https://github.com/payloadcms/payload/compare/v1.1.7...v1.1.8) (2022-10-11)
### Features
* adds ability to create related docs while editing another ([1e048fe](https://github.com/payloadcms/payload/commit/1e048fe03787577fe4d584cec9c2d7c78bc90a17))
* implements use-context-selector for form field access ([5c1a3fa](https://github.com/payloadcms/payload/commit/5c1a3fabeef48b78f173af084f9117515e1297ba))
## [1.1.7](https://github.com/payloadcms/payload/compare/v1.1.6...v1.1.7) (2022-10-06)
## [1.1.6](https://github.com/payloadcms/payload/compare/v1.1.5...v1.1.6) (2022-10-06)
### Bug Fixes
* [#1184](https://github.com/payloadcms/payload/issues/1184) ([c2ec54a](https://github.com/payloadcms/payload/commit/c2ec54a7cbd8cd94bcd4a68d885e35986fec7f18))
* [#1189](https://github.com/payloadcms/payload/issues/1189) ([3641dfd](https://github.com/payloadcms/payload/commit/3641dfd38a147b24e0e3ef93a125b12ad7763f66))
* [#1204](https://github.com/payloadcms/payload/issues/1204) ([b4becd1](https://github.com/payloadcms/payload/commit/b4becd1493d55aae887008ab573ab710c400103a))
* [#940](https://github.com/payloadcms/payload/issues/940) ([7926083](https://github.com/payloadcms/payload/commit/7926083732fbaec78d87f67742cdbd8bd00cd48a))
* ajusts how disabled states are being set on anchors and buttons ([00ef170](https://github.com/payloadcms/payload/commit/00ef1700ae41e68ff0831a587bf3f09fe6c2c966))
* remove min-width from fileupload ([73848b6](https://github.com/payloadcms/payload/commit/73848b603790b3c3d8ad8c9dac81b33c0b65fc7e))
* resize textarea only vertically ([6e1dfff](https://github.com/payloadcms/payload/commit/6e1dfff1b8195a1f81e6ea6ccf3b36dd5359c039))
* richText e2e test, specific selectors ([09a8144](https://github.com/payloadcms/payload/commit/09a8144f3cc63f7ec15fd75f51b8ac8d0cf3f1b5))
* styles readOnly RichTextEditor, removes interactivity within when readOnly ([9181304](https://github.com/payloadcms/payload/commit/918130486e1e38a3d57fb993f466207209c5c0bb))
* **style:** system dark scrollbars ([a30d9dc](https://github.com/payloadcms/payload/commit/a30d9dc1d70340cc6c5ac5b3415a6f57bec117ae))
* threads readOnly to ReactSelect ([b454811](https://github.com/payloadcms/payload/commit/b454811698c7ea0cee944ed50030c13163cf72c9))
* upload xls renaming ext ([7fd8124](https://github.com/payloadcms/payload/commit/7fd8124df68d208813de46172c5cd3f479b9b8be))
### Features
* async admin access control ([1cfce87](https://github.com/payloadcms/payload/commit/1cfce8754947487e6c598ed5bc881526295acabf))
* sort select and relationship fields by default ([813c46c](https://github.com/payloadcms/payload/commit/813c46c86d86f8b0a3ba7280d31f24e844c916b6))
## [1.1.5](https://github.com/payloadcms/payload/compare/v1.1.4...v1.1.5) (2022-09-29)
### Bug Fixes
* bug in useThrottledEffect ([3ce8ee4](https://github.com/payloadcms/payload/commit/3ce8ee4661bfa3825c5b8c41232d5da57f7591ed))
## [1.1.4](https://github.com/payloadcms/payload/compare/v1.1.3...v1.1.4) (2022-09-24)
### Bug Fixes
* field level access for nested fields ([22ea98c](https://github.com/payloadcms/payload/commit/22ea98ca33770a0ec6652f814726454abb6da24e))
* refine type generation for relationships ([ef83bdb](https://github.com/payloadcms/payload/commit/ef83bdb709ebde008b90930a6875b24f042a41b0))
### Features
* supports root endpoints ([52cd3b4](https://github.com/payloadcms/payload/commit/52cd3b4a7ed9bc85e93d753a3aaf190489ca98cd))
## [1.1.3](https://github.com/payloadcms/payload/compare/v1.1.2...v1.1.3) (2022-09-16)
### Bug Fixes
* adjust prevPage and nextPage graphql typing ([#1140](https://github.com/payloadcms/payload/issues/1140)) ([b3bb421](https://github.com/payloadcms/payload/commit/b3bb421c6ca4176974488b3270384386a151560c))
* duplicate with relationships ([eabb981](https://github.com/payloadcms/payload/commit/eabb981243e005facb5fff6d9222903d4704ca55))
## [1.1.2](https://github.com/payloadcms/payload/compare/v1.1.1...v1.1.2) (2022-09-14)
### Bug Fixes
* resize images without local storage ([1496679](https://github.com/payloadcms/payload/commit/14966796ae0d0bcff8cb56b62e3a21c2de2176da))
* resize images without local storage ([7b756f3](https://github.com/payloadcms/payload/commit/7b756f3421f02d1ff55374a72396e15e9f3e23d7))
## [1.1.1](https://github.com/payloadcms/payload/compare/v1.1.0...v1.1.1) (2022-09-13)
### Bug Fixes
* conditions on collapsible fields ([9c4f2b6](https://github.com/payloadcms/payload/commit/9c4f2b68b07bbdd2ac9a6dee280f50379638fc50))
* dashboard links to globals ([dcc8dad](https://github.com/payloadcms/payload/commit/dcc8dad53b006f86e93150f9439eafc8d9e01d79))
# [1.1.0](https://github.com/payloadcms/payload/compare/v1.0.36...v1.1.0) (2022-09-13)
### Bug Fixes
* [#1106](https://github.com/payloadcms/payload/issues/1106) ([9a461b8](https://github.com/payloadcms/payload/commit/9a461b853689fdbc8229c8e103c5e237e451425f))
* word boundaries, no regex lookbehind support for Safari ([#1114](https://github.com/payloadcms/payload/issues/1114)) ([391c9d8](https://github.com/payloadcms/payload/commit/391c9d8682175ea37f1f7b2bb9d1361dc4ac8140))
### Features
* [#1001](https://github.com/payloadcms/payload/issues/1001) - builds a way to allow list view to search multiple fields ([a108372](https://github.com/payloadcms/payload/commit/a1083727ef54ec15ea2c3b4dfd114567639dfef1))
* collection groups ([dffeaf6](https://github.com/payloadcms/payload/commit/dffeaf6a69746b944bf36bd172da3cc19fa025a0))
* globals groups ([59af872](https://github.com/payloadcms/payload/commit/59af8725b4625f8e08aaab730fce177e870279ca))
* hide nav labels with no un-grouped collections ([c40e232](https://github.com/payloadcms/payload/commit/c40e232ac67e7bc1ced3775060beb835efff46b9))
* implement gravatar ([#1107](https://github.com/payloadcms/payload/issues/1107)) ([ca434b8](https://github.com/payloadcms/payload/commit/ca434b8a929af081bb3b92b51f35058a23ce5e37))
* support localized tab fields ([a83921a](https://github.com/payloadcms/payload/commit/a83921a2fe927d59562272cb111c68a840b1914f))
* tabs support localization at the tab level ([6a6a691](https://github.com/payloadcms/payload/commit/6a6a69190fe13bebf3e0b089265d71be2a691488))
* WIP tab compatible with traverseFields ([2ae33b6](https://github.com/payloadcms/payload/commit/2ae33b603abaec4ff80261a465781f508b4a1e06))
## [1.0.36](https://github.com/payloadcms/payload/compare/v1.0.35...v1.0.36) (2022-09-10)
### Bug Fixes
* bug with account view ([ada1871](https://github.com/payloadcms/payload/commit/ada1871993bae92bc7a30f48029b437d63eb3871))
## [1.0.35](https://github.com/payloadcms/payload/compare/v1.0.34...v1.0.35) (2022-09-10)
### Bug Fixes
* [#1059](https://github.com/payloadcms/payload/issues/1059) ([13dc39d](https://github.com/payloadcms/payload/commit/13dc39dc6da4cb7c450477f539b09a3cb54ed5af))
* add height/width if imageSizes not specified ([8bd2a0e](https://github.com/payloadcms/payload/commit/8bd2a0e6c9a9cd05c7b162ade47f3bb111236ba3))
* incorrect auth strategy type ([c8b37f4](https://github.com/payloadcms/payload/commit/c8b37f40cbdc766a45dbe21573b1848bfc091901))
* rich text link with no selection ([5a19f69](https://github.com/payloadcms/payload/commit/5a19f6915a17dbb072b89f63f32705d5f0fc75ce))
### Features
* allows rich text links to link to other docs ([a99d9c9](https://github.com/payloadcms/payload/commit/a99d9c98c3f92d6fbeb65c384ca4d43b82184bfd))
* improves rich text link ux ([91000d7](https://github.com/payloadcms/payload/commit/91000d7fdaa9628650c737fc3f7f6a900b7447d4))
## [1.0.34](https://github.com/payloadcms/payload/compare/v1.0.33...v1.0.34) (2022-09-07)
### Bug Fixes
* pins faceless ui modal ([b38b642](https://github.com/payloadcms/payload/commit/b38b6427b8b813487922db0bb7d3762cc41d3447))
## [1.0.33](https://github.com/payloadcms/payload/compare/v1.0.30...v1.0.33) (2022-09-07)
### Bug Fixes
* [#1062](https://github.com/payloadcms/payload/issues/1062) ([05d1b14](https://github.com/payloadcms/payload/commit/05d1b141b22f66cb9007f20f2ae9d8e31db4f32f))
* [#948](https://github.com/payloadcms/payload/issues/948) ([8df9ee7](https://github.com/payloadcms/payload/commit/8df9ee7b2dfcb2f77f049d02788a5c60c45f8c12))
* [#981](https://github.com/payloadcms/payload/issues/981) ([d588843](https://github.com/payloadcms/payload/commit/d58884312132e109ae3f6619be2e0d7bab3f3111))
* accented label char sanitization for GraphQL ([#1080](https://github.com/payloadcms/payload/issues/1080)) ([888734d](https://github.com/payloadcms/payload/commit/888734dcdf775f416395f8830561c47235bb9019))
* children of conditional fields required in graphql schema ([#1055](https://github.com/payloadcms/payload/issues/1055)) ([29e82ec](https://github.com/payloadcms/payload/commit/29e82ec845f69bf5a09b682739e88529ebc53c16))
* ensures adding new media to upload works when existing doc does not exist ([5ae666b](https://github.com/payloadcms/payload/commit/5ae666b0e08b128bdf2d576428e8638c2b8c2ed8))
* implement the same word boundary search as the like query ([#1038](https://github.com/payloadcms/payload/issues/1038)) ([c3a0bd8](https://github.com/payloadcms/payload/commit/c3a0bd86254dfc3f49e46d4e41bdf717424ea342))
* reorder plugin wrapping ([#1051](https://github.com/payloadcms/payload/issues/1051)) ([cd8edba](https://github.com/payloadcms/payload/commit/cd8edbaa1faa5a94166396918089a01058a4e75e))
* require min 1 option in field schema validation ([#1082](https://github.com/payloadcms/payload/issues/1082)) ([d56882c](https://github.com/payloadcms/payload/commit/d56882cc20764b793049f20a91864c943e711375))
* update removing a relationship with null ([#1056](https://github.com/payloadcms/payload/issues/1056)) ([44b0073](https://github.com/payloadcms/payload/commit/44b0073834830a9d645a11bcafab3869b4eb1899))
* update removing an upload with null ([#1076](https://github.com/payloadcms/payload/issues/1076)) ([2ee4c7a](https://github.com/payloadcms/payload/commit/2ee4c7ad727b9311578d3049660de81c27dace55))
### Features
* cyrillic like query support ([#1078](https://github.com/payloadcms/payload/issues/1078)) ([b7e5828](https://github.com/payloadcms/payload/commit/b7e5828adc7bc6602da7992b073b005b30aa896f))
* duplicate copies all locales ([51c7770](https://github.com/payloadcms/payload/commit/51c7770b10c34a3e40520ca8d64beedc67693c5c))
* update operator type with contains ([#1045](https://github.com/payloadcms/payload/issues/1045)) ([482cbe7](https://github.com/payloadcms/payload/commit/482cbe71c7b1d39b665fb0b29a7a0b69f454180a))
## [1.0.30](https://github.com/payloadcms/payload/compare/v1.0.29...v1.0.30) (2022-08-30)
### Bug Fixes
* upload field validation not required ([#1025](https://github.com/payloadcms/payload/issues/1025)) ([689fa00](https://github.com/payloadcms/payload/commit/689fa008fb0b28fb92be4ca785a77f4c35ae16b2))
## [1.0.29](https://github.com/payloadcms/payload/compare/v1.0.28...v1.0.29) (2022-08-29)
### Bug Fixes
* [#953](https://github.com/payloadcms/payload/issues/953) ([a73c391](https://github.com/payloadcms/payload/commit/a73c391c2cecc3acf8dc3115b56c018f85d9bebf))
## [1.0.28](https://github.com/payloadcms/payload/compare/v1.0.27...v1.0.28) (2022-08-29)
### Bug Fixes
* incorrect field paths when nesting unnamed fields ([#1011](https://github.com/payloadcms/payload/issues/1011)) ([50b0303](https://github.com/payloadcms/payload/commit/50b0303ab39f0d0500c5e4116df95f02d1d7fff3)), closes [#976](https://github.com/payloadcms/payload/issues/976)
* relationship cell loading ([#1021](https://github.com/payloadcms/payload/issues/1021)) ([6a3cfce](https://github.com/payloadcms/payload/commit/6a3cfced9a6e0ef75b398ec663f908c725b10d1a))
* remove lazy loading of array and blocks ([4900fa7](https://github.com/payloadcms/payload/commit/4900fa799ffbeb70e689622b269dc04a67978552))
* require properties in blocks and arrays fields ([#1020](https://github.com/payloadcms/payload/issues/1020)) ([6bc6e7b](https://github.com/payloadcms/payload/commit/6bc6e7bb616bd9f28f2464d3e55e7a1d19a8e7f8))
* unpublish item will not crash the UI anymore ([#1016](https://github.com/payloadcms/payload/issues/1016)) ([0586d7a](https://github.com/payloadcms/payload/commit/0586d7aa7d0938df25492487aa073c2aa366e1e4))
### Features
* export more fields config types and validation type ([#989](https://github.com/payloadcms/payload/issues/989)) ([25f5d68](https://github.com/payloadcms/payload/commit/25f5d68b74b081c060ddf6f0405c9211f5da6b54))
* types custom components to allow any props ([#1013](https://github.com/payloadcms/payload/issues/1013)) ([3736755](https://github.com/payloadcms/payload/commit/3736755a12cf5bbaaa916a5c0363026318a60823))
* validate relationship and upload ids ([#1004](https://github.com/payloadcms/payload/issues/1004)) ([d727fc8](https://github.com/payloadcms/payload/commit/d727fc8e2467e3f438ea6b1d2031e0657bffd183))
## [1.0.27](https://github.com/payloadcms/payload/compare/v1.0.26...v1.0.27) (2022-08-18)
### Bug Fixes
* react-sortable-hoc dependency instead of dev dependency ([4ef6801](https://github.com/payloadcms/payload/commit/4ef6801230cb0309a9d20dd092f8a3372f75f9ca))
## [1.0.26](https://github.com/payloadcms/payload/compare/v1.0.25...v1.0.26) (2022-08-18)
### Bug Fixes
* missing fields in rows on custom id collections ([#954](https://github.com/payloadcms/payload/issues/954)) ([39586d3](https://github.com/payloadcms/payload/commit/39586d3cdb01131b29f1f8f7346086d2bc9903c1))
### Features
* adds more prismjs syntax highlighting options for code blocks ([#961](https://github.com/payloadcms/payload/issues/961)) ([f45d5a0](https://github.com/payloadcms/payload/commit/f45d5a0421117180f85f8e3cd86f835c13ac6d16))
* enable reordering of hasMany relationship and select fields ([#952](https://github.com/payloadcms/payload/issues/952)) ([38a1a38](https://github.com/payloadcms/payload/commit/38a1a38c0c52403083458619b2f9b58044c5c0ea))
## [1.0.25](https://github.com/payloadcms/payload/compare/v1.0.24...v1.0.25) (2022-08-17)
### Bug Fixes
* [#568](https://github.com/payloadcms/payload/issues/568) ([a3edbf4](https://github.com/payloadcms/payload/commit/a3edbf4fef5efd8293cb4d6139b2513441cb741e))
### Features
* add new pickerAppearance option 'monthOnly' ([566c6ba](https://github.com/payloadcms/payload/commit/566c6ba3a9beb13ea9437844313ec6701effce27))
* custom api endpoints ([11d8fc7](https://github.com/payloadcms/payload/commit/11d8fc71e8bdb62c6755789903702b0ee257b448))
## [1.0.24](https://github.com/payloadcms/payload/compare/v1.0.23...v1.0.24) (2022-08-16)
### Bug Fixes
* [#939](https://github.com/payloadcms/payload/issues/939) ([b1a1575](https://github.com/payloadcms/payload/commit/b1a1575122f602ff6ba77973ab2a67893d352487))
* create indexes in nested fields ([f615abc](https://github.com/payloadcms/payload/commit/f615abc9b1d9000aff114010ef7f618ec70b6491))
* format graphql localization input type ([#932](https://github.com/payloadcms/payload/issues/932)) ([1c7445d](https://github.com/payloadcms/payload/commit/1c7445dc7fd883f6d5dcba532e9e048b1cff08f5))
### Features
* ensures you can query on blocks via specifying locale or not specifying locale ([078e8dc](https://github.com/payloadcms/payload/commit/078e8dcc51197133788294bac6fa380b192defbc))
## [1.0.23](https://github.com/payloadcms/payload/compare/v1.0.22...v1.0.23) (2022-08-15)
### Bug Fixes
* [#930](https://github.com/payloadcms/payload/issues/930) ([cbb1c84](https://github.com/payloadcms/payload/commit/cbb1c84be76146301ce41c4bdace647df83a4aac))
* dev:generate-types on all test configs ([#919](https://github.com/payloadcms/payload/issues/919)) ([145e1db](https://github.com/payloadcms/payload/commit/145e1db05db0e71149ba74e95764970dfdfd8b6b))
## [1.0.22](https://github.com/payloadcms/payload/compare/v1.0.21...v1.0.22) (2022-08-12)
### Bug Fixes
* [#905](https://github.com/payloadcms/payload/issues/905) ([b8421dd](https://github.com/payloadcms/payload/commit/b8421ddc0c9357de7a61bdc565fe2f9c4cf62681))
* ensures you can query on mixed schema type within blocks ([fba0847](https://github.com/payloadcms/payload/commit/fba0847f0fbc4c144ec85bb7a1ed3f2a953f5e05))
## [1.0.21](https://github.com/payloadcms/payload/compare/v1.0.20...v1.0.21) (2022-08-11)
### Bug Fixes
* ensures you can query on nested block fields ([ca852e8](https://github.com/payloadcms/payload/commit/ca852e8cb2d78982abeae0b5db4117f0261d8fed))
* saving multiple versions ([#918](https://github.com/payloadcms/payload/issues/918)) ([d0da3d7](https://github.com/payloadcms/payload/commit/d0da3d7962bbddfbdc1c553816409823bf6e1335))
## [1.0.20](https://github.com/payloadcms/payload/compare/v1.0.19...v1.0.20) (2022-08-11)
### Bug Fixes
* E11000 duplicate key error has no keyValue ([#916](https://github.com/payloadcms/payload/issues/916)) ([50972b9](https://github.com/payloadcms/payload/commit/50972b98a1d30c86d8b429ee5ba1c7dacac59f2c))
* number validation works with 0 min and max ([#906](https://github.com/payloadcms/payload/issues/906)) ([874c001](https://github.com/payloadcms/payload/commit/874c001d3b9712bce342c206e66f794a7e4938ba))
### Features
* field name validation ([#903](https://github.com/payloadcms/payload/issues/903)) ([2f4f075](https://github.com/payloadcms/payload/commit/2f4f075441768475f1202587abf578d5e4ae9f2a))
## [1.0.19](https://github.com/payloadcms/payload/compare/v1.0.18...v1.0.19) (2022-08-07)
@@ -1720,4 +2065,4 @@ If none of your collections or globals should be publicly exposed, you don't nee
* add blind index for encrypting API Keys ([9a1c1f6](https://github.com/payloadcms/payload/commit/9a1c1f64c0ea0066b679195f50e6cb1ac4bf3552))
* add license key to access routej ([2565005](https://github.com/payloadcms/payload/commit/2565005cc099797a6e3b8995e0984c28b7837e82))
## [0.0.137](https://github.com/payloadcms/payload/commit/5c1e2846a2694a80cc8707703406c2ac1bb6af8a) (2020-11-12)
## [0.0.137](https://github.com/payloadcms/payload/commit/5c1e2846a2694a80cc8707703406c2ac1bb6af8a) (2020-11-12)

View File

@@ -2,4 +2,4 @@ export { default as Button } from '../dist/admin/components/elements/Button';
export { default as Card } from '../dist/admin/components/elements/Card';
export { default as Eyebrow } from '../dist/admin/components/elements/Eyebrow';
export { default as Nav } from '../dist/admin/components/elements/Nav';
export { default as Gutter } from '../dist/admin/components/elements/Gutter';
export { Gutter } from '../dist/admin/components/elements/Gutter';

View File

@@ -1,12 +1,21 @@
export {
useForm,
/**
* @deprecated useWatchForm is no longer preferred. If you need all form fields, prefer `useAllFormFields`.
*/
useWatchForm,
useFormFields,
useAllFormFields,
useFormSubmitted,
useFormProcessing,
useFormModified,
} from '../dist/admin/components/forms/Form/context';
export { default as useField } from '../dist/admin/components/forms/useField';
/**
* @deprecated This method is now called useField. The useFieldType alias will be removed in an upcoming version.
*/
export { default as useFieldType } from '../dist/admin/components/forms/useField';
export { default as Form } from '../dist/admin/components/forms/Form';
@@ -24,5 +33,6 @@ export { default as Submit } from '../dist/admin/components/forms/Submit';
export { default as Label } from '../dist/admin/components/forms/Label';
export { default as reduceFieldsToValues } from '../dist/admin/components/forms/Form/reduceFieldsToValues';
export { default as getSiblingData } from '../dist/admin/components/forms/Form/getSiblingData';
export { default as withCondition } from '../dist/admin/components/forms/withCondition';

View File

@@ -49,7 +49,9 @@ The directory split up in this way specifically to reduce friction when creating
The following command will start Payload with your config: `yarn dev my-test-dir`. This command will start up Payload using your config and refresh a test database on every restart.
NOTE: It is recommended to add the test credentials to your autofill for `localhost:3000/admin` as this will be required on every nodemon restart.
When switching between test directories, you will want to remove your `node_modules/.cache ` manually or by running `yarn clean:cache`.
NOTE: It is recommended to add the test credentials (located in `test/credentials.ts`) to your autofill for `localhost:3000/admin` as this will be required on every nodemon restart.
## Pull Requests

View File

@@ -27,8 +27,10 @@ If a Collection supports [`Authentication`](/docs/authentication/overview), the
| **[`unlock`](#unlock)** | Used to restrict which users can access the `unlock` operation |
**Example Collection config:**
```js
export default {
```ts
import { CollectionConfig } from 'payload/types';
const Posts: CollectionConfig = {
slug: "posts",
// highlight-start
access: {
@@ -40,6 +42,8 @@ export default {
},
// highlight-end
};
export default Categories;
```
### Create
@@ -55,7 +59,7 @@ Returns a boolean which allows/denies access to the `create` request.
**Example:**
```js
```ts
const PublicUsers = {
slug: 'public-users',
access: {
@@ -82,8 +86,10 @@ Read access functions can return a boolean result or optionally return a [query
**Example:**
```js
const canReadPage = ({ req: { user } }) => {
```ts
import { Access } from 'payload/config';
const canReadPage: Access = ({ req: { user } }) => {
// allow authenticated users
if (user) {
return true;
@@ -92,8 +98,8 @@ const canReadPage = ({ req: { user } }) => {
return {
// assumes we have a checkbox field named 'isPublic'
isPublic: {
equals: true
}
equals: true,
},
}
};
```
@@ -112,11 +118,12 @@ Update access functions can return a boolean result or optionally return a [quer
**Example:**
```js
```ts
import { Access } from 'payload/config';
const canUpdateUser = ({ req: { user }, id }) => {
const canUpdateUser: Access = ({ req: { user }, id }) => {
// allow users with a role of 'admin'
if (user.roles && user.roles.some((role) => role === 'admin')) {
if (user.roles && user.roles.some(role => role === 'admin')) {
return true;
}
// allow any other users to update only oneself
@@ -137,8 +144,10 @@ Similarly to the Update function, returns a boolean or a [query constraint](/doc
**Example:**
```js
const canDeleteCustomer = async ({ req, id }) => {
```ts
import { Access } from 'payload/config'
const canDeleteCustomer: Access = async ({ req, id }) => {
if (!id) {
// allow the admin UI to show controls to delete since it is indeterminate without the id
return true;

View File

@@ -17,8 +17,10 @@ Field Access Control is specified with functions inside a field's config. All fi
| **[`update`](#update)** | Allows or denies the ability to update a field's value |
**Example Collection config:**
```js
export default {
```ts
import { CollectionConfig } from 'payload/types';
const Posts: CollectionConfig = {
slug: 'posts',
fields: [
{
@@ -33,7 +35,7 @@ export default {
// highlight-end
};
],
}
};
```
### Create

View File

@@ -18,16 +18,20 @@ You can define Global-level Access Control within each Global's `access` propert
| **[`update`](#update)** | Used in the `update` Global operation |
**Example Global config:**
```js
export default {
```ts
import { GlobalConfig } from 'payload/types';
const Header: GlobalConfig = {
slug: "header",
// highlight-start
access: {
read: ({ req: { user } }) => { ... },
update: ({ req: { user } }) => { ... },
read: ({ req: { user } }) => { /* */ },
update: ({ req: { user } }) => { /* */ },
},
// highlight-end
};
export default Header;
```
### Read

View File

@@ -8,6 +8,11 @@ keywords: overview, access control, permissions, documentation, Content Manageme
Access control within Payload is extremely powerful while remaining easy and intuitive to manage. Declaring who should have access to what documents is no more complex than writing a simple JavaScript function that either returns a `boolean` or a [`query`](/docs/queries/overview) constraint to restrict which documents users can interact with.
<YouTube
id="DoPLyXG26Dg"
title="Overview of Payload Access Control"
/>
**Example use cases:**
- Allowing anyone `read` access to all `Post`s
@@ -23,7 +28,7 @@ Access control within Payload is extremely powerful while remaining easy and int
**Default Access function:**
```js
```ts
const defaultPayloadAccess = ({ req: { user } }) => {
// Return `true` if a user is found
// and `false` if it is undefined or null

View File

@@ -38,9 +38,16 @@ You can override a set of admin panel-wide components by providing a component t
#### Full example:
`payload.config.js`
```js
import { buildConfig } from 'payload/config';
import { MyCustomNav, MyCustomLogo, MyCustomIcon, MyCustomAccount, MyCustomDashboard, MyProvider } from './customComponents.js';
```ts
import { buildConfig } from 'payload/config'
import {
MyCustomNav,
MyCustomLogo,
MyCustomIcon,
MyCustomAccount,
MyCustomDashboard,
MyProvider,
} from './customComponents';
export default buildConfig({
admin: {
@@ -55,9 +62,9 @@ export default buildConfig({
Dashboard: MyCustomDashboard,
},
providers: [MyProvider],
}
}
})
},
},
});
```
*For more examples regarding how to customize components, look at the following [examples](https://github.com/payloadcms/payload/tree/master/test/admin/components).*
@@ -90,78 +97,73 @@ All Payload fields support the ability to swap in your own React components. So,
**Fields support the following custom components:**
| Component | Description |
| --------------- | -------------|
| **`Filter`** | Override the text input that is presented in the `List` view when a user is filtering documents by the customized field. |
| **`Cell`** | Used in the `List` view's table to represent a table-based preview of the data stored in the field. |
| **`Field`** | Swap out the field itself within all `Edit` views. |
| Component | Description |
| --------------- |------------------------------------------------------------------------------------------------------------------------------|
| **`Filter`** | Override the text input that is presented in the `List` view when a user is filtering documents by the customized field. |
| **`Cell`** | Used in the `List` view's table to represent a table-based preview of the data stored in the field. [More](#cell-component) |
| **`Field`** | Swap out the field itself within all `Edit` views. [More](#field-component) |
## Cell Component
These are the props that will be passed to your custom Cell to use in your own components.
| Property | Description |
|--------------|-------------------------------------------------------------------|
| **`field`** | An object that includes the field configuration. |
| **`colIndex`** | A unique number for the column in the list. |
| **`collection`** | An object with the config of the collection that the field is in. |
| **`cellData`** | The data for the field that the cell represents. |
| **`rowData`** | An object with all the field values for the row. |
#### Example
```tsx
import React from 'react';
import './index.scss';
const baseClass = 'custom-cell';
const CustomCell: React.FC<Props> = (props) => {
const {
field,
colIndex,
collection,
cellData,
rowData,
} = props;
return (
<span className={baseClass}>
{ cellData }
</span>
);
};
```
## Field Component
When writing your own custom components you can make use of a number of hooks to set data, get reactive changes to other fields, get the id of the document or interact with a context from a custom provider.
### Sending and receiving values from the form
When swapping out the `Field` component, you'll be responsible for sending and receiving the field's `value` from the form itself. To do so, import the `useField` hook as follows:
```js
import { useField } from 'payload/components/forms';
```tsx
import { useField } from 'payload/components/forms'
const CustomTextField = ({ path }) => {
type Props = { path: string }
const CustomTextField: React.FC<Props> = ({ path }) => {
// highlight-start
const { value, setValue } = useField({ path });
const { value, setValue } = useField<Props>({ path })
// highlight-end
return (
<input
onChange={(e) => setValue(e.target.value)}
value={value}
/>
)
return <input onChange={e => setValue(e.target.value)} value={value.path} />
}
```
### Getting other field values from the form
There are times when a custom field component needs to have access to data from other fields. This can be done using `getDataByPath` from `useWatchForm` as follows:
```js
import { useWatchForm } from 'payload/components/forms';
const DisplayFee = () => {
const { getDataByPath } = useWatchForm();
const amount = getDataByPath('amount');
const feePercentage = getDataByPath('feePercentage');
if (amount && feePercentage) {
return (
<span>The fee is ${ amount * feePercentage / 100 }</span>
);
}
};
```
### Getting the document ID
The document ID can be very useful for certain custom components. You can get the `id` from the `useDocumentInfo` hook. Here is an example of a `UI` field using `id` to link to related collections:
```js
import { useDocumentInfo } from 'payload/components/utilities';
const LinkFromCategoryToPosts = () => {
// highlight-start
const { id } = useDocumentInfo();
// highlight-end
// id will be undefined on the create form
if (!id) {
return null;
}
return (
<a href={`/admin/collections/posts?where[or][0][and][0][category][in][0]=[${id}]`} >
View posts
</a>
)
};
```
<Banner type="success">
For more information regarding the hooks that are available to you while you build custom components, including the <strong>useField</strong> hook, <a href="/docs/admin/hooks" style={{color: 'black'}}>click here</a>.
</Banner>
## Custom routes
@@ -222,10 +224,10 @@ To make use of Payload SCSS variables / mixins to use directly in your own compo
In any custom component you can get the selected locale with the `useLocale` hook. Here is a simple example:
```js
```tsx
import { useLocale } from 'payload/components/utilities';
const Greeting = () => {
const Greeting: React.FC = () => {
// highlight-start
const locale = useLocale();
// highlight-end
@@ -237,6 +239,6 @@ const Greeting = () => {
return (
<span> { trans[locale] } </span>
)
}
);
};
```

View File

@@ -1,7 +1,7 @@
---
title: Customizing CSS & SCSS
label: Customizing CSS
order: 30
order: 40
desc: Customize your Payload admin panel further by adding your own CSS or SCSS style sheet to the configuration, powerful theme and design options are waiting for you.
keywords: admin, css, scss, documentation, Content Management System, cms, headless, javascript, node, react, express
---
@@ -13,7 +13,7 @@ You can add your own CSS by providing your base Payload config with a path to yo
To do so, provide your base Payload config with a path to your own stylesheet. It can be either a CSS or SCSS file.
**Example in payload.config.js:**
```js
```ts
import { buildConfig } from 'payload/config';
import path from 'path';
@@ -21,7 +21,7 @@ const config = buildConfig({
admin: {
css: path.resolve(__dirname, 'relative/path/to/stylesheet.scss'),
},
})
});
```
### Overriding built-in styles

271
docs/admin/hooks.mdx Normal file
View File

@@ -0,0 +1,271 @@
---
title: React Hooks
label: React Hooks
order: 30
desc: Make use of all of the powerful React hooks that Payload provides.
keywords: admin, components, custom, documentation, Content Management System, cms, headless, javascript, node, react, express
---
Payload provides a variety of powerful hooks that can be used within your own React components. With them, you can interface with Payload itself and build just about any type of complex customization you can think of—directly in familiar React code.
### useField
The `useField` hook is used internally within every applicable Payload field component, and it manages sending and receiving a field's state from its parent form.
Outside of internal use, its most common use-case is in custom `Field` components. When you build a custom React `Field` component, you'll be responsible for sending and receiving the field's `value` from the form itself. To do so, import the `useField` hook as follows:
```tsx
import { useField } from 'payload/components/forms'
type Props = { path: string }
const CustomTextField: React.FC<Props> = ({ path }) => {
// highlight-start
const { value, setValue } = useField<string>({ path })
// highlight-end
return <input onChange={e => setValue(e.target.value)} value={value.path} />
}
```
The `useField` hook accepts an `args` object and sends back information and helpers for you to make use of:
```ts
const field = useField<string>({
path: 'fieldPathHere', // required
validate: myValidateFunc, // optional
disableFormData?: false, // if true, the field's data will be ignored
condition?: myConditionHere, // optional, used to skip validation if condition fails
})
// Here is what `useField` sends back
const {
showError, // whether or not the field should show as errored
errorMessage, // the error message to show, if showError
value, // the current value of the field from the form
formSubmitted, // if the form has been submitted
formProcessing, // if the form is currently processing
setValue, // method to set the field's value in form state
initialValue, // the initial value that the field mounted with
} = field;
// The rest of your component goes here
```
### useFormFields
There are times when a custom field component needs to have access to data from other fields, and you have a few options to do so. The `useFormFields` hook is a powerful and highly performant way to retrieve a form's field state, as well as to retrieve the `dispatchFields` method, which can be helpful for setting other fields' form states from anywhere within a form.
<Banner type="success">
<strong>This hook is great for retrieving only certain fields from form state</strong> because it ensures that it will only cause a rerender when the items that you ask for change.
</Banner>
Thanks to the awesome package [`use-context-selector`](https://github.com/dai-shi/use-context-selector), you can retrieve a specific field's state easily. This is ideal because you can ensure you have an up-to-date field state, and your component will only re-render when _that field's state_ changes.
You can pass a Redux-like selector into the hook, which will ensure that you retrieve only the field that you want. The selector takes an argument with type of `[fields: Fields, dispatch: React.Dispatch<Action>]]`.
```tsx
import { useFormFields } from 'payload/components/forms';
const MyComponent: React.FC = () => {
// Get only the `amount` field state, and only cause a rerender when that field changes
const amount = useFormFields(([fields, dispatch]) => fields.amount);
// Do the same thing as above, but to the `feePercentage` field
const feePercentage = useFormFields(([fields, dispatch]) => fields.feePercentage);
if (typeof amount?.value !== 'undefined' && typeof feePercentage?.value !== 'undefined') {
return (
<span>The fee is ${(amount.value * feePercentage.value) / 100}</span>
);
}
};
```
### useAllFormFields
**To retrieve more than one field**, you can use the `useAllFormFields` hook. Your component will re-render when _any_ field changes, so use this hook only if you absolutely need to. Unlike the `useFormFields` hook, this hook does not accept a "selector", and it always returns an array with type of `[fields: Fields, dispatch: React.Dispatch<Action>]]`.
You can do lots of powerful stuff by retrieving the full form state, like using built-in helper functions to reduce field state to values only, or to retrieve sibling data by path.
```tsx
import { useAllFormFields, reduceFieldsToValues, getSiblingData } from 'payload/components/forms';
const ExampleComponent: React.FC = () => {
// the `fields` const will be equal to all fields' state,
// and the `dispatchFields` method is usable to send field state up to the form
const [fields, dispatchFields] = useAllFormFields();
// Pass in fields, and indicate if you'd like to "unflatten" field data.
// The result below will reflect the data stored in the form at the given time
const formData = reduceFieldsToValues(fields, true);
// Pass in field state and a path,
// and you will be sent all sibling data of the path that you've specified
const siblingData = getSiblingData(fields, 'someFieldName');
return (
// return some JSX here if necessary
)
};
```
##### Updating other fields' values
If you are building a custom component, then you should use `setValue` which is returned from the `useField` hook to programmatically set your field's value. But if you're looking to update _another_ field's value, you can use `dispatchFields` returned from `useFormFields`.
You can send the following actions to the `dispatchFields` function.
| Action | Description |
|------------------------|----------------------------------------------------------------------------|
| **`ADD_ROW`** | Adds a row of data (useful in array / block field data) |
| **`DUPLICATE_ROW`** | Duplicates a row of data (useful in array / block field data) |
| **`MODIFY_CONDITION`** | Updates a field's conditional logic result (true / false) |
| **`MOVE_ROW`** | Moves a row of data (useful in array / block field data) |
| **`REMOVE`** | Removes a field from form state |
| **`REMOVE_ROW`** | Removes a row of data from form state (useful in array / block field data) |
| **`REPLACE_STATE`** | Completely replaces form state |
| **`UPDATE`** | Update any property of a specific field's state |
To see types for each action supported within the `dispatchFields` hook, check out the Form types [here](https://github.com/payloadcms/payload/blob/master/src/admin/components/forms/Form/types.ts).
### useForm
The `useForm` hook can be used to interact with the form itself, and sends back many methods that can be used to reactively fetch form state without causing rerenders within your components each time a field is changed. This is useful if you have action-based callbacks that your components fire, and need to interact with form state _based on a user action_.
<Banner type="warning">
<strong>Warning:</strong><br/>
This hook is optimized to avoid causing rerenders when fields change, and as such, its `fields` property will be out of date. You should only leverage this hook if you need to perform actions against the form in response to your users' actions. Do not rely on its returned "fields" as being up-to-date. They will be removed from this hook's response in an upcoming version.
</Banner>
The `useForm` hook returns an object with the following properties:
| Action | Description |
|----------------------|---------------------------------------------------------------------|
| **`fields`** | Deprecated. This property cannot be relied on as up-to-date. |
| **`submit`** | Method to trigger the form to submit |
| **`dispatchFields`** | Dispatch actions to the form field state |
| **`validateForm`** | Trigger a validation of the form state |
| **`createFormData`** | Create a `multipart/form-data` object from the current form's state |
| **`disabled`** | Boolean denoting whether or not the form is disabled |
| **`getFields`** | Gets all fields from state |
| **`getField`** | Gets a single field from state by path |
| **`getData`** | Returns the data stored in the form |
| **`getSiblingData`** | Returns form sibling data for the given field path |
| **`setModified`** | Set the form's `modified` state |
| **`setProcessing`** | Set the form's `processing` state |
| **`setSubmitted`** | Set the form's `submitted` state |
| **`formRef`** | The ref from the form HTML element |
| **`reset`** | Method to reset the form to its initial state |
### useDocumentInfo
The `useDocumentInfo` hook provides lots of information about the document currently being edited, including the following:
| Property | Description |
|---------------------------|------------------------------------------------------------------------------------|
| **`collection`** | If the doc is a collection, its collection config will be returned |
| **`global`** | If the doc is a global, its global config will be returned |
| **`type`** | The type of document being edited (collection or global) |
| **`id`** | If the doc is a collection, its ID will be returned |
| **`preferencesKey`** | The `preferences` key to use when interacting with document-level user preferences |
| **`versions`** | Versions of the current doc |
| **`unpublishedVersions`** | Unpublished versions of the current doc |
| **`publishedDoc`** | The currently published version of the doc being edited |
| **`getVersions`** | Method to trigger the retrieval of document versions |
**Example:**
```tsx
import { useDocumentInfo } from 'payload/components/utilities';
const LinkFromCategoryToPosts: React.FC = () => {
// highlight-start
const { id } = useDocumentInfo();
// highlight-end
// id will be undefined on the create form
if (!id) {
return null;
}
return (
<a href={`/admin/collections/posts?where[or][0][and][0][category][in][0]=[${id}]`} >
View posts
</a>
)
};
```
### useLocale
In any custom component you can get the selected locale with the `useLocale` hook. Here is a simple example:
```tsx
import { useLocale } from 'payload/components/utilities';
const Greeting: React.FC = () => {
// highlight-start
const locale = useLocale();
// highlight-end
const trans = {
en: 'Hello',
es: 'Hola',
};
return (
<span> { trans[locale] } </span>
);
};
```
### useAuth
Useful to retrieve info about the currently logged in user as well as methods for interacting with it. It sends back an object with the following properties:
| Property | Description |
|---------------------|-----------------------------------------------------------------------------------------|
| **`user`** | The currently logged in user |
| **`logOut`** | A method to log out the currently logged in user |
| **`refreshCookie`** | A method to trigger the silent refreshing of a user's auth token |
| **`setToken`** | Set the token of the user, to be decoded and used to reset the user and token in memory |
| **`token`** | The logged in user's token (useful for creating preview links, etc.) |
| **`permissions`** | The permissions of the current user |
```tsx
import { useAuth } from 'payload/components/utilities';
import { User } from '../payload-types.ts';
const Greeting: React.FC = () => {
// highlight-start
const { user } = useConfig<User>();
// highlight-end
return (
<span>Hi, {user.email}!</span>
);
};
```
### useConfig
Used to easily fetch the full Payload config.
```tsx
import { useConfig } from 'payload/components/utilities';
const MyComponent: React.FC = () => {
// highlight-start
const config = useConfig();
// highlight-end
return (
<span>{config.serverURL}</span>
);
};
```
### usePreferences
Returns methods to set and get user preferences. More info can be found [here](https://payloadcms.com/docs/admin/preferences).

View File

@@ -30,7 +30,8 @@ All options for the Admin panel are defined in your base Payload config file.
| `indexHTML` | Optionally replace the entirety of the `index.html` file used by the Admin panel. Reference the [base index.html file](https://github.com/payloadcms/payload/blob/master/src/admin/index.html) to ensure your replacement has the appropriate HTML elements. |
| `css` | Absolute path to a stylesheet that you can use to override / customize the Admin panel styling. [More](/docs/admin/customizing-css). |
| `scss` | Absolute path to a Sass variables / mixins stylesheet meant to override Payload styles to make for an easy re-skinning of the Admin panel. [More](/docs/admin/customizing-css#overriding-scss-variables). |
| `dateFormat` | Global date format that will be used for all dates in the Admin panel. Any valid [date-fns](https://date-fns.org/) format pattern can be used.
| `dateFormat` | Global date format that will be used for all dates in the Admin panel. Any valid [date-fns](https://date-fns.org/) format pattern can be used. |
| `avatar` | Set account profile picture. Options: `gravatar`, `default` or a custom React component. |
| `components` | Component overrides that affect the entirety of the Admin panel. [More](/docs/admin/components) |
| `webpack` | Customize the Webpack config that's used to generate the Admin panel. [More](/docs/admin/webpack) |
@@ -45,14 +46,14 @@ All options for the Admin panel are defined in your base Payload config file.
To specify which Collection to use to log in to the Admin panel, pass the `admin` options a `user` key equal to the slug of the Collection that you'd like to use.
`payload.config.js`:
```js
```ts
import { buildConfig } from 'payload/config';
const config = buildConfig({
admin: {
user: 'admins', // highlight-line
},
})
});
```
By default, if you have not specified a Collection, Payload will automatically provide you with a `User` Collection which will be used to access the Admin panel. You can customize or override the fields and settings of the default `User` Collection by passing your own collection using `users` as its `slug` to Payload. When this is done, Payload will use your provided `User` Collection instead of its default version.

View File

@@ -1,7 +1,7 @@
---
title: Managing User Preferences
label: Preferences
order: 40
order: 50
desc: Store the preferences of your users as they interact with the Admin panel.
keywords: admin, preferences, custom, customize, documentation, Content Management System, cms, headless, javascript, node, react, express
---

View File

@@ -1,7 +1,7 @@
---
title: Webpack
label: Webpack
order: 50
order: 60
desc: The Payload admin panel uses Webpack 5 and supports many common functionalities such as SCSS and Typescript out of the box to give you more freedom.
keywords: admin, webpack, documentation, Content Management System, cms, headless, javascript, node, react, express
---
@@ -10,8 +10,8 @@ Payload uses Webpack 5 to build the Admin panel. It comes with support for many
To extend the Webpack config, add the `webpack` key to your base Payload config, and provide a function that accepts the default Webpack config as its only argument:
`payload.config.js`
```js
`payload.config.ts`
```ts
import { buildConfig } from 'payload/config';
export default buildConfig({
@@ -24,7 +24,7 @@ export default buildConfig({
}
// highlight-end
}
})
});
```
### Aliasing server-only modules
@@ -52,16 +52,17 @@ You may rely on server-only packages such as the above to perform logic in acces
<br/><br/>
`collections/Subscriptions/index.js`
```js
```ts
import { CollectionConfig } from 'payload/types';
import createStripeSubscription from './hooks/createStripeSubscription';
const Subscription = {
const Subscription: CollectionConfig = {
slug: 'subscriptions',
hooks: {
beforeChange: [
createStripeSubscription,
]
}
},
fields: [
{
name: 'stripeSubscriptionID',
@@ -69,7 +70,7 @@ const Subscription = {
required: true,
}
]
}
};
export default Subscription;
```
@@ -154,6 +155,11 @@ export default {};
Now, when Webpack sees that you're attempting to import your `createStripeSubscriptionPath` file, it'll disregard that actual file and load your mock file instead. Not only will your Admin panel now bundle successfully, you will have optimized its filesize by removing unnecessary code! And you might have learned something about Webpack, too.
<Banner type="success">
<strong>Tip:</strong><br/>
If changes to your Webpack aliases are not surfacing, they might be [cached](https://webpack.js.org/configuration/cache/) in `node_modules/.cache/webpack`. Try deleting that folder and restarting your server.
</Banner>
## Admin environment vars
<Banner type="warning">

View File

@@ -49,7 +49,7 @@ To utilize your API key while interacting with the REST or GraphQL API, add the
**For example, using Fetch:**
```js
```ts
const response = await fetch("http://localhost:3000/api/pages", {
headers: {
Authorization: `${collection.labels.singular} API-Key ${YOUR_API_KEY}`,
@@ -57,6 +57,8 @@ const response = await fetch("http://localhost:3000/api/pages", {
});
```
Note: The label portion of the header is case-sensitive and will likely have a capitalized first character unless the label has been customized.
### Forgot Password
You can customize how the Forgot Password workflow operates with the following options on the `auth.forgotPassword` property:
@@ -77,8 +79,10 @@ Function that accepts one argument, containing `{ req, token, user }`, that allo
Example:
```js
{
```ts
import { CollectionConfig } from 'payload/types';
const Customers: CollectionConfig = {
slug: 'customers',
auth: {
forgotPassword: {
@@ -104,7 +108,7 @@ Example:
// highlight-end
}
}
}
};
```
<Banner type="warning">
@@ -123,7 +127,7 @@ Similarly to the above `generateEmailHTML`, you can also customize the subject o
Example:
```js
```ts
{
slug: 'customers',
auth: {
@@ -148,8 +152,11 @@ Function that accepts one argument, containing `{ req, token, user }`, that allo
Example:
```js
{
```ts
import { CollectionConfig } from 'payload/types';
const Customers: CollectionConfig = {
slug: 'customers',
auth: {
verify: {
@@ -163,7 +170,7 @@ Example:
// highlight-end
}
}
}
};
```
<Banner type="warning">
@@ -182,7 +189,7 @@ Similarly to the above `generateEmailHTML`, you can also customize the subject o
Example:
```js
```ts
{
slug: 'customers',
auth: {

View File

@@ -17,7 +17,7 @@ The Access operation returns what a logged in user can and can't do with the col
`GET http://localhost:3000/api/access`
Example response:
```js
```ts
{
canAccessAdmin: true,
collections: {
@@ -54,7 +54,7 @@ Example response:
**Example GraphQL Query**:
```
```graphql
query {
Access {
pages {
@@ -75,7 +75,7 @@ Returns either a logged in user with token or null when there is no logged in us
`GET http://localhost:3000/api/[collection-slug]/me`
Example response:
```js
```ts
{
user: { // The JWT "payload" ;) from the logged in user
email: 'dev@payloadcms.com',
@@ -90,7 +90,7 @@ Example response:
**Example GraphQL Query**:
```
```graphql
query {
Me[collection-singular-label] {
user {
@@ -106,7 +106,7 @@ query {
Accepts an `email` and `password`. On success, it will return the logged in user as well as a token that can be used to authenticate. In the GraphQL and REST APIs, this operation also automatically sets an HTTP-only cookie including the user's token. If you pass an Express `res` to the Local API operation, Payload will set a cookie there as well.
**Example REST API login**:
```js
```ts
const res = await fetch('http://localhost:3000/api/[collection-slug]/login', {
method: 'POST',
headers: {
@@ -137,7 +137,7 @@ const json = await res.json();
**Example GraphQL Mutation**:
```
```graphql
mutation {
login[collection-singular-label](email: "dev@payloadcms.com", password: "yikes") {
user {
@@ -151,7 +151,7 @@ mutation {
**Example Local API login**:
```js
```ts
const result = await payload.login({
collection: '[collection-slug]',
data: {
@@ -166,7 +166,7 @@ const result = await payload.login({
As Payload sets HTTP-only cookies, logging out cannot be done by just removing a cookie in JavaScript, as HTTP-only cookies are inaccessible by JS within the browser. So, Payload exposes a `logout` operation to delete the token in a safe way.
**Example REST API logout**:
```js
```ts
const res = await fetch('http://localhost:3000/api/[collection-slug]/logout', {
method: 'POST',
headers: {
@@ -192,7 +192,7 @@ This operation requires a non-expired token to send back a new one. If the user'
If successful, this operation will automatically renew the user's HTTP-only cookie and will send back the updated token in JSON.
**Example REST API token refresh**:
```js
```ts
const res = await fetch('http://localhost:3000/api/[collection-slug]/refresh-token', {
method: 'POST',
headers: {
@@ -239,18 +239,18 @@ mutation {
If your collection supports email verification, the Verify operation will be exposed which accepts a verification token and sets the user's `_verified` property to `true`, thereby allowing the user to authenticate with the Payload API.
**Example REST API user verification**:
```js
```ts
const res = await fetch(`http://localhost:3000/api/[collection-slug]/verify/${TOKEN_HERE}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
})
});
```
**Example GraphQL Mutation**:
```
```graphql
mutation {
verifyEmail[collection-singular-label](token: "TOKEN_HERE")
}
@@ -258,7 +258,7 @@ mutation {
**Example Local API verification**:
```js
```ts
const result = await payload.verifyEmail({
collection: '[collection-slug]',
token: 'TOKEN_HERE',
@@ -272,7 +272,7 @@ If a user locks themselves out and you wish to deliberately unlock them, you can
To restrict who is allowed to unlock users, you can utilize the [`unlock`](/docs/access-control/overview#unlock) access control function.
**Example REST API unlock**:
```js
```ts
const res = await fetch(`http://localhost:3000/api/[collection-slug]/unlock`, {
method: 'POST',
headers: {
@@ -291,7 +291,7 @@ mutation {
**Example Local API unlock**:
```js
```ts
const result = await payload.unlock({
collection: '[collection-slug]',
})
@@ -306,7 +306,7 @@ The link to reset the user's password contains a token which is what allows the
By default, the Forgot Password operations send users to the Payload Admin panel to reset their password, but you can customize the generated email to send users to the frontend of your app instead by [overriding the email HTML](/docs/authentication/config#forgot-password).
**Example REST API Forgot Password**:
```js
```ts
const res = await fetch(`http://localhost:3000/api/[collection-slug]/forgot-password`, {
method: 'POST',
headers: {
@@ -315,7 +315,7 @@ const res = await fetch(`http://localhost:3000/api/[collection-slug]/forgot-pass
body: JSON.stringify({
email: 'dev@payloadcms.com',
}),
})
});
```
**Example GraphQL Mutation**:
@@ -328,14 +328,14 @@ mutation {
**Example Local API forgot password**:
```js
```ts
const token = await payload.forgotPassword({
collection: '[collection-slug]',
data: {
email: 'dev@payloadcms.com',
},
disableEmail: false // you can disable the auto-generation of email via local API
})
});
```
<Banner type="success">
@@ -348,7 +348,7 @@ const token = await payload.forgotPassword({
After a user has "forgotten" their password and a token is generated, that token can be used to send to the reset password operation along with a new password which will allow the user to reset their password securely.
**Example REST API Reset Password**:
```js
```ts
const res = await fetch(`http://localhost:3000/api/[collection-slug]/reset-password`, {
method: 'POST',
headers: {
@@ -358,7 +358,7 @@ const res = await fetch(`http://localhost:3000/api/[collection-slug]/reset-passw
token: 'TOKEN_GOES_HERE'
password: 'not-today',
}),
})
});
const json = await res.json();
@@ -379,7 +379,7 @@ const json = await res.json();
**Example GraphQL Mutation**:
```
```graphql
mutation {
resetPassword[collection-singular-label](token: "TOKEN_GOES_HERE", password: "not-today")
}

View File

@@ -32,8 +32,10 @@ Every Payload Collection can opt-in to supporting Authentication by specifying t
Simple example collection:
```js
const Admins = {
```ts
import { CollectionConfig } from 'payload/types';
const Admins: CollectionConfig = {
slug:
// highlight-start
auth: {
@@ -60,7 +62,7 @@ const Admins = {
}
```
**By enabling Authetication on a config, the following modifications will automatically be made to your Collection:**
**By enabling Authentication on a config, the following modifications will automatically be made to your Collection:**
1. `email` as well as password `salt` & `hash` fields will be added to your Collection's schema
1. The Admin panel will feature a new set of corresponding UI to allow for changing password and editing email
@@ -95,7 +97,7 @@ However, if you use `fetch` or similar APIs to retrieve Payload resources from i
Fetch example, including credentials:
```js
```ts
const response = await fetch('http://localhost:3000/api/pages', {
credentials: 'include',
});
@@ -124,8 +126,8 @@ So, if a user of coolsite.com is logged in and just browsing around on the inter
To define domains that should allow users to identify themselves via the Payload HTTP-only cookie, use the `csrf` option on the base Payload config to whitelist domains that you trust.
`payload.config.js`:
```js
`payload.config.ts`:
```ts
import { buildConfig } from 'payload/config';
const config = buildConfig({
@@ -148,7 +150,7 @@ export default config;
In addition to authenticating via an HTTP-only cookie, you can also identify users via the `Authorization` header on an HTTP request.
Example:
```js
```ts
const request = await fetch('http://localhost:3000', {
headers: {
Authorization: `JWT ${token}`

View File

@@ -15,7 +15,7 @@ This approach has a ton of benefits - it's great for isolation of concerns and l
</Banner>
Example in `server.js`:
```js
```ts
import express from 'express';
import payload from 'payload';

View File

@@ -17,7 +17,6 @@ It's often best practice to write your Collections in separate files and then im
| **`slug`** * | Unique, URL-friendly string that will act as an identifier for this Collection. |
| **`fields`** * | Array of field types that will determine the structure and functionality of the data stored within this Collection. [Click here](/docs/fields/overview) for a full list of field types as well as how to configure them. |
| **`labels`** | Singular and plural labels for use in identifying this Collection throughout Payload. Auto-generated from slug if not defined. |
| **`description`**| Text or React component to display below the Collection label in the List view to give editors more information. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-options). |
| **`hooks`** | Entry points to "tie in" to Collection actions at specific points. [More](/docs/hooks/overview#collection-hooks) |
| **`access`** | Provide access control functions to define exactly who should be able to do what with Documents in this Collection. [More](/docs/access-control/overview/#collections) |
@@ -31,8 +30,10 @@ It's often best practice to write your Collections in separate files and then im
#### Simple collection example
```js
const Orders = {
```ts
import { CollectionConfig } from 'payload/types';
const Orders: CollectionConfig = {
slug: 'orders',
fields: [
{
@@ -47,7 +48,7 @@ const Orders = {
required: true,
}
]
}
};
```
#### More collection config examples
@@ -58,14 +59,18 @@ You can find an assortment of [example collection configs](https://github.com/pa
You can customize the way that the Admin panel behaves on a collection-by-collection basis by defining the `admin` property on a collection's config.
| Option | Description |
| ---------------------------- | -------------|
| `useAsTitle` | Specify a top-level field to use for a document title throughout the Admin panel. If no field is defined, the ID of the document is used as the title. |
| `defaultColumns` | Array of field names that correspond to which columns to show by default in this collection's List view. |
| `disableDuplicate ` | Disables the "Duplicate" button while editing documents within this collection. |
| Option | Description |
| --------------------------- | -------------|
| `group` | Text used as a label for grouping collection links together in the navigation. |
| `hooks` | Admin-specific hooks for this collection. [More](#admin-hooks) |
| `useAsTitle` | Specify a top-level field to use for a document title throughout the Admin panel. If no field is defined, the ID of the document is used as the title. |
| `description` | Text or React component to display below the Collection label in the List view to give editors more information. |
| `defaultColumns` | Array of field names that correspond to which columns to show by default in this collection's List view. |
| `disableDuplicate ` | Disables the "Duplicate" button while editing documents within this collection. |
| `enableRichTextRelationship` | The [Rich Text](/docs/fields/rich-text) field features a `Relationship` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
| `preview` | Function to generate preview URLS within the Admin panel that can point to your app. [More](#preview). |
| `components` | Swap in your own React components to be used within this collection. [More](/docs/admin/components#collections) |
| `preview` | Function to generate preview URLS within the Admin panel that can point to your app. [More](#preview). |
| `components` | Swap in your own React components to be used within this collection. [More](/docs/admin/components#collections) |
| `listSearchableFields` | Specify which fields should be searched in the List search view. [More](#list-searchable-fields) |
### Preview
@@ -76,12 +81,14 @@ If the function is specified, a Preview button will automatically appear in the
**The preview function accepts two arguments:**
1. The document being edited
1. An `options` object, containing `locale` and `token` properties. The `token` is the currently logged in user's JWT.
1. An `options` object, containing `locale` and `token` properties. The `token` is the currently logged-in user's JWT.
**Example collection with preview function:**
```js
const Posts = {
```ts
import { CollectionConfig } from 'payload/types';
const Posts: CollectionConfig = {
slug: 'posts',
fields: [
{
@@ -114,18 +121,73 @@ Hooks are a powerful way to extend collection functionality and execute your own
Collections support all field types that Payload has to offer—including simple fields like text and checkboxes all the way to more complicated layout-building field groups like Blocks. [Click here](/docs/fields/overview) to learn more about field types.
### List Searchable Fields
In the List view, there is a "search" box that allows you to quickly find a document with a search. By default, it searches on the ID field. If you have `admin.useAsTitle` defined, the list search will use that field. However, you can define more than one field to search to make it easier on your admin editors to find the data they need.
For example, let's say you have a Posts collection with `title`, `metaDescription`, and `tags` fields - and you want all three of those fields to be searchable in the List view. You can simply add `admin.listSearchableFields: ['title', 'metaDescription', 'tags']` - and the admin UI will automatically search on those three fields plus the ID field.
<Banner type="warning">
<strong>Note:</strong><br/>
If you are adding <strong>listSearchableFields</strong>, make sure you index each of these fields so your admin queries can remain performant.
</Banner>
### Admin Hooks
In addition to collection hooks themselves, Payload provides for admin UI-specific hooks that you can leverage.
**`beforeDuplicate`**
The `beforeDuplicate` hook is an async function that accepts an object containing the data to duplicate, as well as the locale of the doc to duplicate. Within this hook, you can modify the data to be duplicated, which is useful in cases where you have unique fields that need to be incremented or similar, as well as if you want to automatically modify a document's `title`.
Example:
```ts
import { BeforeDuplicate, CollectionConfig } from 'payload/types';
// Your auto-generated Page type
import { Page } from '../payload-types.ts';
const beforeDuplicate: BeforeDuplicate<Page> = ({ data }) => {
return {
...data,
title: `${data.title} Copy`,
uniqueField: data.uniqueField ? `${data.uniqueField}-copy` : '',
};
};
export const Page: CollectionConfig = {
slug: 'pages',
admin: {
hooks: {
beforeDuplicate,
}
},
fields: [
{
name: 'title',
type: 'text',
},
{
name: 'uniqueField',
type: 'text',
unique: true,
}
]
}
```
### TypeScript
You can import collection types as follows:
```js
```ts
import { CollectionConfig } from 'payload/types';
// This is the type used for incoming collection configs.
// Only the bare minimum properties are marked as required.
```
```js
```ts
import { SanitizedCollectionConfig } from 'payload/types';
// This is the type used after an incoming collection config is fully sanitized.

View File

@@ -28,8 +28,10 @@ As with Collection configs, it's often best practice to write your Globals in se
#### Simple Global example
```js
const Nav = {
```ts
import { GlobalConfig } from 'payload/types';
const Nav: GlobalConfig = {
slug: 'nav',
fields: [
{
@@ -47,7 +49,9 @@ const Nav = {
]
},
]
}
};
export default Nav;
```
#### Global config example
@@ -78,14 +82,14 @@ Globals support all field types that Payload has to offer—including simple fie
You can import global types as follows:
```js
```ts
import { GlobalConfig } from 'payload/types';
// This is the type used for incoming global configs.
// Only the bare minimum properties are marked as required.
```
```js
```ts
import { SanitizedGlobalConfig } from 'payload/types';
// This is the type used after an incoming global config is fully sanitized.

View File

@@ -14,21 +14,23 @@ Add the `localization` property to your Payload config to enable localization pr
**Example Payload config set up for localization:**
```js
{
collections: [
... // collections go here
],
localization: {
locales: [
'en',
'es',
'de',
],
defaultLocale: 'en',
fallback: true,
},
}
```ts
import { buildConfig } from 'payload/config'
export default buildConfig({
collections: [
// collections go here
],
localization: {
locales: [
'en',
'es',
'de',
],
defaultLocale: 'en',
fallback: true,
},
});
```
**Here is a brief explanation of each of the options available within the `localization` property:**
@@ -53,11 +55,11 @@ Payload localization works on a **field** level—not a document level. In addit
```js
{
name: 'title',
type: 'text',
// highlight-start
localized: true,
// highlight-end
name: 'title',
type: 'text',
// highlight-start
localized: true,
// highlight-end
}
```
@@ -66,8 +68,8 @@ With the above configuration, the `title` field will now be saved in the databas
All field types with a `name` property support the `localized` property—even the more complex field types like `array`s and `block`s.
<Banner>
<strong>Note:</strong><br/>
Enabling localization for field types that support nested fields will automatically create localized "sets" of all fields contained within the field. For example, if you have a page layout using a blocks field type, you have the choice of either localizing the full layout, by enabling localization on the top-level blocks field, or only certain fields within the layout.
<strong>Note:</strong><br/>
Enabling localization for field types that support nested fields will automatically create localized "sets" of all fields contained within the field. For example, if you have a page layout using a blocks field type, you have the choice of either localizing the full layout, by enabling localization on the top-level blocks field, or only certain fields within the layout.
</Banner>
### Retrieving localized docs
@@ -104,16 +106,16 @@ The `fallbackLocale` arg will accept valid locales as well as `none` to disable
```graphql
query {
Posts(locale: de, fallbackLocale: none) {
docs {
title
}
}
Posts(locale: de, fallbackLocale: none) {
docs {
title
}
}
}
```
<Banner>
In GraphQL, specifying the locale at the top level of a query will automatically apply it throughout all nested relationship fields. You can override this behavior by re-specifying locale arguments in nested related document queries.
In GraphQL, specifying the locale at the top level of a query will automatically apply it throughout all nested relationship fields. You can override this behavior by re-specifying locale arguments in nested related document queries.
</Banner>
##### Local API
@@ -124,9 +126,9 @@ You can specify `locale` as well as `fallbackLocale` within the Local API as wel
```js
const posts = await payload.find({
collection: 'posts',
locale: 'es',
fallbackLocale: false,
collection: 'posts',
locale: 'es',
fallbackLocale: false,
})
```

View File

@@ -39,13 +39,14 @@ Payload is a *config-based*, code-first CMS and application framework. The Paylo
| `rateLimit` | Control IP-based rate limiting for all Payload resources. Used to prevent DDoS attacks and [more](/docs/production/preventing-abuse#rate-limiting-requests). |
| `hooks` | Tap into Payload-wide hooks. [More](/docs/hooks/overview) |
| `plugins` | An array of Payload plugins. [More](/docs/plugins/overview) |
| `endpoints` | An array of custom API endpoints added to the Payload router. [More](/docs/rest-api/overview#custom-endpoints) |
#### Simple example
```js
```ts
import { buildConfig } from 'payload/config';
const config = buildConfig({
export default buildConfig({
collections: [
{
slug: 'pages',
@@ -82,9 +83,6 @@ const config = buildConfig({
}
]
});
export default config;
```
#### Full example config
@@ -173,14 +171,14 @@ Then, you could import this file into both your Payload config and your server,
You can import config types as follows:
```js
```ts
import { Config } from 'payload/config';
// This is the type used for an incoming Payload config.
// Only the bare minimum properties are marked as required.
```
```js
```ts
import { SanitizedConfig } from 'payload/config';
// This is the type used after an incoming Payload config is fully sanitized.

View File

@@ -40,7 +40,7 @@ The following options are configurable in the `email` property object as part of
Simple Mail Transfer Protocol, also known as SMTP can be passed in using the `transportOptions` object on the `email` options.
**Example email part using SMTP:**
```js
```ts
payload.init({
email: {
transportOptions: {
@@ -60,6 +60,7 @@ payload.init({
fromAddress: 'hello@example.com'
}
// ...
})
```
<Banner type="warning">
@@ -70,9 +71,9 @@ payload.init({
Many third party mail providers are available and offer benefits beyond basic SMTP. As an example your payload init could look this if you wanted to use SendGrid.com though the same approach would work for any other [NodeMailer transports](https://nodemailer.com/transports/) shown here or provided by another third party.
```js
const nodemailerSendgrid = require('nodemailer-sendgrid');
const payload = require('payload');
```ts
import payload from 'payload'
import nodemailerSendgrid from 'nodemailer-sendgrid'
const sendGridAPIKey = process.env.SENDGRID_API_KEY;
@@ -92,7 +93,10 @@ payload.init({
### Use a custom NodeMailer transport
To take full control of the mail transport you may wish to use `nodemailer.createTransport()` on your server and provide it to Payload init.
```js
```ts
import payload from 'payload'
import nodemailer from 'nodemailer'
const payload = require('payload');
const nodemailer = require('nodemailer');
@@ -112,7 +116,7 @@ payload.init({
transport
},
// ...
}
});
```
### Sending Mail
@@ -123,7 +127,7 @@ By default, Payload uses a mock implementation that only sends mail to the [ethe
To see ethereal credentials, add `logMockCredentials: true` to the email options. This will cause them to be logged to console on startup.
```js
```ts
payload.init({
email: {
fromName: 'Admin',

View File

@@ -35,15 +35,26 @@ keywords: array, fields, config, configuration, documentation, Content Managemen
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this Array will be kept, so there is no need to specify each nested field as `localized`. |
| **`required`** | Require this field to have a value. |
| **`labels`** | Customize the row labels appearing in the Admin dashboard. |
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
*\* An asterisk denotes that a property is required.*
### Admin Config
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties:
| Option | Description |
| ---------------------- | ------------------------------- |
| **`initCollapsed`** | Set the initial collapsed state |
### Example
`collections/ExampleCollection.js`
```js
{
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types';
const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
@@ -70,6 +81,5 @@ keywords: array, fields, config, configuration, documentation, Content Managemen
]
}
]
}
};
```

View File

@@ -36,10 +36,18 @@ keywords: blocks, fields, config, configuration, documentation, Content Manageme
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this field will be kept, so there is no need to specify each nested field as `localized`. || **`required`** | Require this field to have a value. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`labels`** | Customize the block row labels appearing in the Admin dashboard. |
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
*\* An asterisk denotes that a property is required.*
### Admin Config
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties:
| Option | Description |
| ---------------------- | ------------------------------- |
| **`initCollapsed`** | Set the initial collapsed state |
### Block configs
Blocks are defined as separate configs of their own.
@@ -72,8 +80,10 @@ The Admin panel provides each block with a `blockName` field which optionally al
### Example
`collections/ExampleCollection.js`
```js
const QuoteBlock = {
```ts
import { Block, CollectionConfig } from 'payload/types';
const QuoteBlock: Block = {
slug: 'Quote', // required
imageURL: 'https://google.com/path/to/image.jpg',
imageAltText: 'A nice thumbnail image to show what this block looks like',
@@ -90,7 +100,7 @@ const QuoteBlock = {
]
};
const ExampleCollection = {
const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
@@ -111,7 +121,7 @@ const ExampleCollection = {
As you build your own Block configs, you might want to store them in separate files but retain typing accordingly. To do so, you can import and use Payload's `Block` type:
```js
```ts
import type { Block } from 'payload/types';
```

View File

@@ -31,9 +31,11 @@ keywords: checkbox, fields, config, configuration, documentation, Content Manage
### Example
`collections/ExampleCollection.js`
```js
{
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types';
const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
@@ -43,6 +45,5 @@ keywords: checkbox, fields, config, configuration, documentation, Content Manage
defaultValue: false,
}
]
}
};
```

View File

@@ -39,13 +39,27 @@ This field uses `prismjs` for syntax highlighting and `react-simple-code-editor`
In addition to the default [field admin config](/docs/fields/overview#admin-config), the Code field type also allows for the customization of a `language` property.
Currently, the `language` property only supports JavaScript syntax but more support will be added as requested.
The following `prismjs` plugins are imported, enabling the `language` property to accept the following values:
| Plugin | Language |
| ---------------------------- | ----------- |
| **`prism-css`** | `css` |
| **`prism-clike`** | `clike` |
| **`prism-markup`** | `markup`, `html`, `xml`, `svg`, `mathml`, `ssml`, `atom`, `rss` |
| **`prism-javascript`** | `javascript`, `js` |
| **`prism-json`** | `json` |
| **`prism-jsx`** | `jsx` |
| **`prism-typescript`** | `typescript`, `ts` |
| **`prism-tsx`** | `tsx` |
| **`prism-yaml`** | `yaml`, `yml` |
### Example
`collections/ExampleCollection.js`
```js
{
`collections/ExampleCollection.ts
```ts
import { CollectionConfig } from 'payload/types';
const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
@@ -57,6 +71,5 @@ Currently, the `language` property only supports JavaScript syntax but more supp
}
}
]
}
};
```

View File

@@ -12,19 +12,29 @@ keywords: row, fields, config, configuration, documentation, Content Management
### Config
| Option | Description |
| ---------------- | ----------- |
| **`label`** * | A label to render within the header of the collapsible component. |
| **`fields`** * | Array of field types to nest within this Collapsible. |
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
| Option | Description |
| -------------- | ------------------------------------------------------------------------- |
| **`label`** * | A label to render within the header of the collapsible component. |
| **`fields`** * | Array of field types to nest within this Collapsible. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
*\* An asterisk denotes that a property is required.*
### Admin Config
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties:
| Option | Description |
| ---------------------- | ------------------------------- |
| **`initCollapsed`** | Set the initial collapsed state |
### Example
`collections/ExampleCollection.js`
```js
{
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types';
const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
@@ -39,6 +49,5 @@ keywords: row, fields, config, configuration, documentation, Content Management
],
}
]
}
};
```

View File

@@ -38,8 +38,8 @@ In addition to the default [field admin config](/docs/fields/overview#admin-conf
| Option | Description |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`pickerAppearance`** | Determines the appearance of the datepicker: `dayAndTime` `timeOnly` `dayOnly`. Defaults to `dayAndTime`. |
| **`displayFormat`** | Determines how the date is presented. dayAndTime default to `MMM d, yyy h:mm a` timeOnly defaults to `h:mm a` and dayOnly defaults to `MMM d, yyy`. |
| **`pickerAppearance`** | Determines the appearance of the datepicker: `dayAndTime` `timeOnly` `dayOnly` `monthOnly`. Defaults to `dayAndTime`. |
| **`displayFormat`** | Determines how the date is presented. dayAndTime default to `MMM d, yyy h:mm a` timeOnly defaults to `h:mm a` dayOnly defaults to `MMM d, yyy` and monthOnly defaults to `MM/yyyy`. |
| **`placeholder`** | Placeholder text for the field. |
| **`monthsToShow`** | Number of months to display max is 2. Defaults to 1. |
| **`minDate`** | Passed directly to [react-datepicker](https://github.com/Hacker0x01/react-datepicker/blob/master/docs/datepicker.md). |
@@ -55,10 +55,12 @@ Common use cases for customizing the `date` property are to restrict your field
### Example
`collections/ExampleCollection.js`
`collections/ExampleCollection.ts`
```js
{
```ts
import { CollectionConfig } from 'payload/types';
const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
@@ -74,6 +76,5 @@ Common use cases for customizing the `date` property are to restrict your field
}
}
]
}
};
```

View File

@@ -44,9 +44,11 @@ Set this property to a string that will be used for browser autocomplete.
### Example
`collections/ExampleCollection.js`
```js
{
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types';
const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
@@ -56,6 +58,5 @@ Set this property to a string that will be used for browser autocomplete.
required: true,
}
]
}
};
```

View File

@@ -39,9 +39,11 @@ Set this property to `true` to hide this field's gutter within the admin panel.
### Example
`collections/ExampleCollection.js`
```js
{
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types';
const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
@@ -65,6 +67,5 @@ Set this property to `true` to hide this field's gutter within the admin panel.
],
}
]
}
};
```

View File

@@ -50,9 +50,11 @@ Set this property to a string that will be used for browser autocomplete.
### Example
`collections/ExampleCollection.js`
```js
{
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types';
const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
@@ -64,6 +66,5 @@ Set this property to a string that will be used for browser autocomplete.
}
}
]
}
};
```

View File

@@ -16,8 +16,10 @@ Fields are defined as an array on Collections and Globals via the `fields` key.
The required `type` property on a field determines what values it can accept, how it is presented in the API, and how the field will be rendered in the admin interface.
**Simple collection with two fields:**
```js
const Pages = {
```ts
import { CollectionConfig } from 'payload/types';
const Page: CollectionConfig = {
slug: 'pages',
fields: [
{
@@ -29,7 +31,7 @@ const Pages = {
type: 'checkbox', // highlight-line
},
],
}
};
```
### Field types
@@ -80,8 +82,10 @@ There are two arguments available to custom validation functions.
| `payload` | If the `validate` function is being executed on the server, Payload will be exposed for easily running local operations. |
Example:
```js
{
```ts
import { CollectionConfig } from 'payload/types';
const Orders: CollectionConfig = {
slug: 'orders',
fields: [
{
@@ -101,27 +105,27 @@ Example:
},
},
],
}
};
```
When supplying a field `validate` function, Payload will use yours in place of the default. To make use of the default field validation in your custom logic you can import, call and return the result as needed.
For example:
```js
```ts
import { text } from 'payload/fields/validations';
const field =
{
name: 'notBad',
type: 'text',
validate: (val, args) => {
if (value === 'bad') {
return 'This cannot be "bad"';
}
// highlight-start
return text(val, args);
// highlight-end
},
}
const field: Field = {
name: 'notBad',
type: 'text',
validate: (val, args) => {
if (val === 'bad') {
return 'This cannot be "bad"';
}
// highlight-start
return text(val, args);
// highlight-end
},
};
```
### Customizable ID
@@ -131,7 +135,7 @@ Users are then required to provide a custom ID value when creating a record thro
Valid ID types are `number` and `text`.
Example:
```js
```ts
{
fields: [
{
@@ -174,7 +178,7 @@ The `condition` function should return a boolean that will control if the field
**Example:**
```js
```ts
{
fields: [
{
@@ -212,21 +216,19 @@ Functions are called with an optional argument object containing:
Here is an example of a defaultValue function that uses both:
```js
```ts
const translation: {
en: 'Written by',
es: 'Escrito por',
en: 'Written by',
es: 'Escrito por',
};
const field = {
name: 'attribution',
type: 'text',
admin: {
// highlight-start
defaultValue: ({ user, locale }) => (`${translation[locale]} ${user.name}`)
// highlight-end
}
};
name: 'attribution',
type: 'text',
// highlight-start
defaultValue: ({ user, locale }) => (`${translation[locale]} ${user.name}`)
// highlight-end
};
```
<Banner type="success">
@@ -244,7 +246,7 @@ As shown above, you can simply provide a string that will show by the field, but
**Function Example:**
```js
```ts
{
fields: [
{
@@ -262,7 +264,7 @@ As shown above, you can simply provide a string that will show by the field, but
This example will display the number of characters allowed as the user types.
**Component Example:**
```js
```ts
{
fields: [
{
@@ -289,10 +291,8 @@ This component will count the number of characters entered.
You can import the internal Payload `Field` type as well as other common field types as follows:
```js
```ts
import type {
Field,
Validate,
Condition,
} from 'payload/types';
```

View File

@@ -35,9 +35,11 @@ The data structure in the database matches the GeoJSON structure to represent po
### Example
`collections/ExampleCollection.js`
```js
{
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types';
const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
@@ -46,7 +48,7 @@ The data structure in the database matches the GeoJSON structure to represent po
label: 'Location',
},
]
}
};
```
### Querying

View File

@@ -23,7 +23,7 @@ keywords: radio, fields, config, configuration, documentation, Content Managemen
| **`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) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide data to be used for this field's default value. The default value must exist within provided values in `options`. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
@@ -41,13 +41,15 @@ In addition to the default [field admin config](/docs/fields/overview#admin-conf
**`layout`**
The `layout` property allows for the radio group to be styled as a horizonally or vertically distributed list.
The `layout` property allows for the radio group to be styled as a horizonally or vertically distributed list. The default value is `horizontal`.
### Example
`collections/ExampleCollection.js`
```js
{
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types';
const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
@@ -62,8 +64,8 @@ The `layout` property allows for the radio group to be styled as a horizonally o
label: 'Dark Gray',
value: 'dark_gray',
},
]
defaultValue: 'option_1',
],
defaultValue: 'mint', // The first value in options.
admin: {
layout: 'horizontal',
}

View File

@@ -45,6 +45,14 @@ keywords: relationship, fields, config, configuration, documentation, Content Ma
The <a href="/docs/getting-started/concepts#depth">Depth</a> parameter can be used to automatically populate related documents that are returned by the API.
</Banner>
### Admin config
In addition to the default [field admin config](/docs/fields/overview#admin-config), the Relationship field type also allows for the following admin-specific properties:
**`isSortable`**
Set to `true` if you'd like this field to be sortable within the Admin UI using drag and drop. (Only works when `hasMany` is set to `true`)
### Filtering relationship options
Options can be dynamically limited by supplying a [query constraint](/docs/queries/overview), which will be used both for validating input and filtering available relationships in the UI.
@@ -61,26 +69,26 @@ The `filterOptions` property can either be a `Where` query directly, or a functi
**Example:**
```js
const relationshipField = {
name: 'purchase',
type: 'relationship',
relationTo: ['products', 'services'],
filterOptions: ({ relationTo, siblingData }) => {
// returns a Where query dynamically by the type of relationship
if (relationTo === 'products') {
return {
'stock': { is_greater_than: siblingData.quantity }
}
```ts
const relationshipField = {
name: 'purchase',
type: 'relationship',
relationTo: ['products', 'services'],
filterOptions: ({ relationTo, siblingData }) => {
// returns a Where query dynamically by the type of relationship
if (relationTo === 'products') {
return {
'stock': { greater_than: siblingData.quantity }
}
}
if (relationTo === 'services') {
return {
'isAvailable': { equals: true }
}
if (relationTo === 'services') {
return {
'isAvailable': { equals: true }
}
},
};
}
},
};
```
You can learn more about writing queries [here](/docs/queries/overview).
@@ -98,7 +106,7 @@ Given the variety of options possible within the `relationship` field type, the
The most simple pattern of a relationship is to use `hasMany: false` with a `relationTo` that allows for only one type of collection.
```js
```ts
{
slug: 'example-collection',
fields: [
@@ -129,7 +137,7 @@ When querying documents in this collection via REST API, you could query as foll
Also known as **dynamic references**, in this configuration, the `relationTo` field is an array of Collection slugs that tells Payload which Collections are valid to reference.
```js
```ts
{
slug: 'example-collection',
fields: [
@@ -168,7 +176,7 @@ This query would return only documents that have an owner relationship to organi
The `hasMany` tells Payload that there may be more than one collection saved to the field.
```js
```ts
{
slug: 'example-collection',
fields: [
@@ -196,7 +204,7 @@ When querying documents, the format does not change for arrays:
#### Has Many - Polymorphic
```js
```ts
{
slug: 'example-collection',
fields: [

View File

@@ -77,6 +77,13 @@ The default `leaves` available in Payload are:
Set this property to `true` to hide this field's gutter within the admin panel. The field gutter is rendered as a vertical line and padding, but often if this field is nested within a Group, Block, or Array, you may want to hide the gutter.
**`link.fields`**
This allows [fields](/docs/fields/overview) to be saved as extra fields on a link inside the Rich Text Editor. When this is present, the fields will render inside a modal that can be opened by clicking the "edit" button on the link element.
![RichText link fields](https://payloadcms.com/images/fields/richText/rte-link-fields-modal.jpg)
*RichText link with custom fields*
**`upload.collections[collection-name].fields`**
This allows [fields](/docs/fields/overview) to be saved as meta data on an upload field inside the Rich Text Editor. When this is present, the fields will render inside a modal that can be opened by clicking the "edit" button on the upload element.
@@ -126,9 +133,11 @@ Custom `Leaf` objects follow a similar pattern but require you to define the `Le
### Example
`collections/ExampleCollection.js`
```js
{
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types';
const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
@@ -165,6 +174,20 @@ Custom `Leaf` objects follow a similar pattern but require you to define the `Le
]
}
],
link: {
// Inject your own fields into the Link element
fields: [
{
name: 'rel',
label: 'Rel Attribute',
type: 'select',
hasMany: true,
options: [
'noopener', 'noreferrer', 'nofollow',
],
},
],
},
upload: {
collections: {
media: {
@@ -178,7 +201,7 @@ Custom `Leaf` objects follow a similar pattern but require you to define the `Le
}
}
]
}
};
```
For more examples regarding how to define your own elements and leaves, check out the example [`RichText` field](https://github.com/payloadcms/public-demo/blob/master/src/fields/hero.ts) within the Public Demo source code.
@@ -187,7 +210,7 @@ For more examples regarding how to define your own elements and leaves, check ou
As the Rich Text field saves its content in a JSON format, you'll need to render it as HTML yourself. Here is an example for how to generate JSX / HTML from Rich Text content:
```js
```ts
import React, { Fragment } from 'react';
import escapeHTML from 'escape-html';
import { Text } from 'slate';
@@ -308,7 +331,7 @@ If you want to utilize this functionality within your own custom elements, you c
`customLargeBodyElement.js`:
```js
```ts
import Button from './Button';
import Element from './Element';
import withLargeBody from './plugin';
@@ -338,7 +361,7 @@ The plugin itself extends Payload's built-in `shouldBreakOutOnEnter` Slate funct
If you are building your own custom Rich Text elements or leaves, you may benefit from importing the following types:
```js
```ts
import type {
RichTextCustomElement,
RichTextCustomLeaf,

View File

@@ -21,9 +21,11 @@ keywords: row, fields, config, configuration, documentation, Content Management
### Example
`collections/ExampleCollection.js`
```js
{
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types';
const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{

View File

@@ -51,12 +51,17 @@ In addition to the default [field admin config](/docs/fields/overview#admin-conf
Set to `true` if you'd like this field to be clearable within the Admin UI.
**`isSortable`**
Set to `true` if you'd like this field to be sortable within the Admin UI using drag and drop. (Only works when `hasMany` is set to `true`)
### Example
`collections/ExampleCollection.js`
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types';
```js
{
const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
@@ -65,6 +70,7 @@ Set to `true` if you'd like this field to be clearable within the Admin UI.
hasMany: true,
admin: {
isClearable: true,
isSortable: true, // use mouse to drag and drop different values, and sort them according to your choice
},
options: [
{

View File

@@ -7,7 +7,7 @@ keywords: tabs, fields, config, configuration, documentation, Content Management
---
<Banner >
The Tabs field is presentational-only and only affects the Admin panel. By using it, you can place fields within a nice layout component that separates certain sub-fields by a tabbed interface.
The Tabs field is presentational-only and only affects the Admin panel (unless a tab is named). By using it, you can place fields within a nice layout component that separates certain sub-fields by a tabbed interface.
</Banner>
![Tabs field type used to separate Hero fields from Page Layout](https://payloadcms.com/images/fields/tabs/tabs.jpg)
@@ -26,6 +26,7 @@ Each tab has its own required `label` and `fields` array. You can also optionall
| Option | Description |
| ----------------- | ----------- |
| **`name`** | An optional property name to be used when stored and retrieved from the database, similar to a [Group](/docs/fields/group). |
| **`label`** * | The label to render on the tab itself. |
| **`fields`** * | The fields to render within this tab. |
| **`description`** | Optionally render a description within this tab to describe the contents of the tab itself. |
@@ -34,9 +35,11 @@ Each tab has its own required `label` and `fields` array. You can also optionall
### Example
`collections/ExampleCollection.js`
```js
{
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types';
const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
@@ -44,7 +47,7 @@ Each tab has its own required `label` and `fields` array. You can also optionall
tabs: [ // required
{
label: 'Tab One Label', // required
description: 'This will appear within the tab above the fields.'
description: 'This will appear within the tab above the fields.',
fields: [ // required
{
name: 'someTextField',
@@ -54,10 +57,11 @@ Each tab has its own required `label` and `fields` array. You can also optionall
],
},
{
name: 'tabTwo',
label: 'Tab Two Label', // required
fields: [ // required
{
name: 'numberField',
name: 'numberField', // accessible via tabTwo.numberField
type: 'number',
required: true,
},

View File

@@ -46,9 +46,11 @@ Set this property to a string that will be used for browser autocomplete.
### Example
`collections/ExampleCollection.js`
```js
{
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types';
const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{

View File

@@ -46,9 +46,11 @@ Set this property to a string that will be used for browser autocomplete.
### Example
`collections/ExampleCollection.js`
```js
{
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types';
const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{

View File

@@ -23,20 +23,22 @@ With this field, you can also inject custom `Cell` components that appear as add
### Config
| Option | Description |
| ---------------------------- | ----------- |
| **`name`** * | A unique identifier for this field. |
| **`label`** | Human-readable label for this UI field. |
| **`admin.components.Field`** | React component to be rendered for this field within the Edit view. |
| **`admin.components.Cell`** | React component to be rendered as a Cell within collection List views. |
| Option | Description |
| ---------------------------- |-------------------------------------------------------------------------------------------------------------------|
| **`name`** * | A unique identifier for this field. |
| **`label`** | Human-readable label for this UI field. |
| **`admin.components.Field`** | React component to be rendered for this field within the Edit view. [More](/admin/components/#field-component) |
| **`admin.components.Cell`** | React component to be rendered as a Cell within collection List views. [More](/admin/components/#field-component) |
*\* An asterisk denotes that a property is required.*
### Example
`collections/ExampleCollection.js`
```js
{
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types';
const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{

View File

@@ -46,9 +46,11 @@ keywords: upload, images media, fields, config, configuration, documentation, Co
### Example
`collections/ExampleCollection.js`
```js
{
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types';
const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{

View File

@@ -22,7 +22,7 @@ By default, the Payload config lives in the root folder of your code and is name
A Collection represents a type of content that Payload will store and can contain many documents.
</Banner>
Collections define the shape of your data as well as all functionalities attached to that data. They will contain or many "documents", all corresponding with the same fields and functionalities that you define.
Collections define the shape of your data as well as all functionalities attached to that data. They will contain one or many "documents", all corresponding with the same fields and functionalities that you define.
They can represent anything you can store in a database - for example - pages, posts, users, people, orders, categories, events, customers, transactions, and anything else your app needs.
@@ -70,7 +70,7 @@ For more, visit the [Access Control documentation](/docs/access-control/overview
You can specify population `depth` via query parameter in the REST API and by an option in the local API. *Depth has no effect in the GraphQL API, because there, depth is based on the shape of your queries.*
It is also possible to limit the depth for specific `relation` and `upload` fields using the `maxDepth` property in your configuration.
**For example, let's look the following Collections:** `departments`, `users`, `posts`
**For example, let's look at the following Collections:** `departments`, `users`, `posts`
```
// type: 'relationship' fields are equal to 1 depth level

View File

@@ -33,7 +33,7 @@ Both `graphQL.queries` and `graphQL.mutations` functions should return an object
`payload.config.js`:
```js
```ts
import { buildConfig } from 'payload/config';
import myCustomQueryResolver from './graphQL/resolvers/myCustomQueryResolver';

View File

@@ -22,15 +22,17 @@ At the top of your Payload config you can define all the options to manage Graph
| `queries` | Any custom Queries to be added in addition to what Payload provides. [More](/docs/graphql/extending) |
| `maxComplexity` | A number used to set the maximum allowed complexity allowed by requests [More](/docs/graphql/overview#query-complexity-limits) |
| `disablePlaygroundInProduction` | A boolean that if false will enable the graphQL playground, defaults to true. [More](/docs/graphql/overview#graphql-playground) |
| `disable` | A boolean that if false will disable the graphQL entirely, defaults to false. |
| `disable` | A boolean that if true will disable the graphQL entirely, defaults to false. |
| `schemaOutputFile` | A string for the file path used by the generate schema command. Defaults to `graphql.schema` next to `payload.config.ts` [More](/docs/graphql/graphql-schema) |
## Collections
Everything that can be done to a Collection via the REST or Local API can be done with GraphQL (outside of uploading files, which is REST-only). If you have a collection as follows:
```js
const PublicUser = {
```ts
import { CollectionConfig } from 'payload/types';
const PublicUser: CollectionConfig = {
slug: 'public-users',
auth: true, // Auth is enabled
labels: {
@@ -70,8 +72,10 @@ const PublicUser = {
Globals are also fully supported. For example:
```js
const Header = {
```ts
import { GlobalConfig } from 'payload/types';
const Header: GlobalConfig = {
slug: 'header',
fields: [
...

View File

@@ -30,14 +30,15 @@ Additionally, `auth`-enabled collections feature the following hooks:
All collection Hook properties accept arrays of synchronous or asynchronous functions. Each Hook type receives specific arguments and has the ability to modify specific outputs.
`collections/example-hooks.js`
```js
// Collection config
module.exports = {
`collections/exampleHooks.js`
```ts
import { CollectionConfig } from 'payload/types';
const ExampleHooks: CollectionConfig = {
slug: 'example-hooks',
fields: [
{ name: 'name', type: 'text'},
]
],
hooks: {
beforeOperation: [(args) => {...}],
beforeValidate: [(args) => {...}],
@@ -55,7 +56,7 @@ module.exports = {
afterRefresh: [(args) => {...}],
afterMe: [(args) => {...}],
afterForgotPassword: [(args) => {...}],
}
},
}
```
@@ -65,8 +66,10 @@ The `beforeOperation` Hook type can be used to modify the arguments that operati
Available Collection operations include `create`, `read`, `update`, `delete`, `login`, `refresh` and `forgotPassword`.
```js
const beforeOperationHook = async ({
```ts
import { CollectionBeforeOperationHook } from 'payload/types';
const beforeOperationHook: CollectionBeforeOperationHook = async ({
args, // Original arguments passed into the operation
operation, // name of the operation
}) => {
@@ -78,8 +81,10 @@ const beforeOperationHook = async ({
Runs before the `create` and `update` operations. This hook allows you to add or format data before the incoming data is validated.
```js
const beforeValidateHook = async ({
```ts
import { CollectionBeforeOperationHook } from 'payload/types';
const beforeValidateHook: CollectionBeforeValidateHook = async ({
data, // incoming data to update or create with
req, // full express request
operation, // name of the operation ie. 'create', 'update'
@@ -93,8 +98,10 @@ const beforeValidateHook = async ({
Immediately following validation, `beforeChange` hooks will run within `create` and `update` operations. At this stage, you can be confident that the data that will be saved to the document is valid in accordance to your field validations. You can optionally modify the shape of data to be saved.
```js
const beforeChangeHook = async ({
```ts
import { CollectionBeforeChangeHook } from 'payload/types';
const beforeChangeHook: CollectionBeforeChangeHook = async ({
data, // incoming data to update or create with
req, // full express request
operation, // name of the operation ie. 'create', 'update'
@@ -108,10 +115,13 @@ const beforeChangeHook = async ({
After a document is created or updated, the `afterChange` hook runs. This hook is helpful to recalculate statistics such as total sales within a global, syncing user profile changes to a CRM, and more.
```js
const afterChangeHook = async ({
```ts
import { CollectionAfterChangeHook } from 'payload/types';
const afterChangeHook: CollectionAfterChangeHook = async ({
doc, // full document data
req, // full express request
previousDoc, // document data before updating the collection
operation, // name of the operation ie. 'create', 'update'
}) => {
return doc;
@@ -122,8 +132,10 @@ const afterChangeHook = async ({
Runs before `find` and `findByID` operations are transformed for output by `afterRead`. This hook fires before hidden fields are removed and before localized fields are flattened into the requested locale. Using this Hook will provide you with all locales and all hidden fields via the `doc` argument.
```js
const beforeReadHook = async ({
```ts
import { CollectionBeforeReadHook } from 'payload/types';
const beforeReadHook: CollectionBeforeReadHook = async ({
doc, // full document data
req, // full express request
query, // JSON formatted query
@@ -136,8 +148,10 @@ const beforeReadHook = async ({
Runs as the last step before documents are returned. Flattens locales, hides protected fields, and removes fields that users do not have access to.
```js
const afterReadHook = async ({
```ts
import { CollectionAfterReadHook } from 'payload/types';
const afterReadHook: CollectionAfterReadHook = async ({
doc, // full document data
req, // full express request
query, // JSON formatted query
@@ -151,8 +165,10 @@ const afterReadHook = async ({
Runs before the `delete` operation. Returned values are discarded.
```js
const beforeDeleteHook = async ({
```ts
import { CollectionBeforeDeleteHook } from 'payload/types';
const beforeDeleteHook: CollectionBeforeDeleteHook = async ({
req, // full express request
id, // id of document to delete
}) => {...}
@@ -162,8 +178,10 @@ const beforeDeleteHook = async ({
Runs immediately after the `delete` operation removes records from the database. Returned values are discarded.
```js
const afterDeleteHook = async ({
```ts
import { CollectionAfterDeleteHook } from 'payload/types';
const afterDeleteHook: CollectionAfterDeleteHook = async ({
req, // full express request
id, // id of document to delete
doc, // deleted document
@@ -172,13 +190,14 @@ const afterDeleteHook = async ({
### beforeLogin
For auth-enabled Collections, this hook runs after successful `login` operations. You can optionally modify the user that is returned.
For auth-enabled Collections, this hook runs during `login` operations where a user with the provided credentials exist, but before a token is generated and added to the response. You can optionally modify the user that is returned, or throw an error in order to deny the login operation.
```js
const beforeLoginHook = async ({
```ts
import { CollectionBeforeLoginHook } from 'payload/types';
const beforeLoginHook: CollectionBeforeLoginHook = async ({
req, // full express request
user, // user being logged in
token, // user token
}) => {
return user;
}
@@ -188,9 +207,13 @@ const beforeLoginHook = async ({
For auth-enabled Collections, this hook runs after successful `login` operations. You can optionally modify the user that is returned.
```js
const afterLoginHook = async ({
```ts
import { CollectionAfterLoginHook } from 'payload/types';
const afterLoginHook: CollectionAfterLoginHook = async ({
req, // full express request
user, // user that was logged in
token, // user token
}) => {...}
```
@@ -198,8 +221,10 @@ const afterLoginHook = async ({
For auth-enabled Collections, this hook runs after `logout` operations.
```js
const afterLogoutHook = async ({
```ts
import { CollectionAfterLogoutHook } from 'payload/types';
const afterLogoutHook: CollectionAfterLogoutHook = async ({
req, // full express request
}) => {...}
```
@@ -208,8 +233,10 @@ const afterLogoutHook = async ({
For auth-enabled Collections, this hook runs after `refresh` operations.
```js
const afterRefreshHook = async ({
```ts
import { CollectionAfterRefreshHook } from 'payload/types';
const afterRefreshHook: CollectionAfterRefreshHook = async ({
req, // full express request
res, // full express response
token, // newly refreshed user token
@@ -220,8 +247,10 @@ const afterRefreshHook = async ({
For auth-enabled Collections, this hook runs after `me` operations.
```js
const afterMeHook = async ({
```ts
import { CollectionAfterMeHook } from 'payload/types';
const afterMeHook: CollectionAfterMeHook = async ({
req, // full express request
response, // response to return
}) => {...}
@@ -231,8 +260,10 @@ const afterMeHook = async ({
For auth-enabled Collections, this hook runs after successful `forgotPassword` operations. Returned values are discarded.
```js
const afterLoginHook = async ({
```ts
import { CollectionAfterForgotPasswordHook } from 'payload/types';
const afterLoginHook: CollectionAfterForgotPasswordHook = async ({
req, // full express request
user, // user being logged in
token, // user token
@@ -245,7 +276,7 @@ const afterLoginHook = async ({
Payload exports a type for each Collection hook which can be accessed as follows:
```js
```ts
import type {
CollectionBeforeOperationHook,
CollectionBeforeValidateHook,
@@ -262,7 +293,4 @@ import type {
CollectionAfterMeHook,
CollectionAfterForgotPasswordHook,
} from 'payload/types';
// Use hook types here...
}
```

View File

@@ -26,8 +26,10 @@ Field-level hooks offer incredible potential for encapsulating your logic. They
## Config
Example field configuration:
```js
{
```ts
import { CollectionConfig } from 'payload/types';
const ExampleCollection: CollectionConfig = {
name: 'name',
type: 'text',
// highlight-start
@@ -54,15 +56,17 @@ All field-level hooks are formatted to accept the same arguments, although some
Field Hooks receive one `args` argument that contains the following properties:
| Option | Description |
| ----------------- | -------------|
| **`data`** | The data passed to update the document within `create` and `update` operations, and the full document itself in the `afterRead` hook. |
| **`findMany`** | Boolean to denote if this hook is running against finding one, or finding many within the `afterRead` hook. |
| **`operation`** | A string relating to which operation the field type is currently executing within. Useful within `beforeValidate`, `beforeChange`, and `afterChange` hooks to differentiate between `create` and `update` operations. |
| **`originalDoc`** | The full original document in `update` operations. |
| **`req`** | The Express `request` object. It is mocked for Local API operations. |
| **`siblingData`** | The sibling data passed to a field that the hook is running against. |
| **`value`** | The value of the field. |
| Option | Description |
|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **`data`** | The data passed to update the document within `create` and `update` operations, and the full document itself in the `afterRead` hook. |
| **`siblingData`** | The sibling data passed to a field that the hook is running against. |
| **`findMany`** | Boolean to denote if this hook is running against finding one, or finding many within the `afterRead` hook. |
| **`operation`** | A string relating to which operation the field type is currently executing within. Useful within `beforeValidate`, `beforeChange`, and `afterChange` hooks to differentiate between `create` and `update` operations. |
| **`originalDoc`** | The full original document in `update` operations. In the `afterChange` hook, this is the resulting document of the operation. |
| **`previousDoc`** | The document before changes were applied, only in `afterChange` hooks. |
| **`previousSiblingDoc`** | The sibling data from the previous document in `afterChange` hook. |
| **`req`** | The Express `request` object. It is mocked for Local API operations. |
| **`value`** | The value of the field. |
#### Return value
@@ -77,7 +81,7 @@ All field hooks can optionally modify the return value of the field before the o
Payload exports a type for field hooks which can be accessed and used as follows:
```js
```ts
import type { FieldHook } from 'payload/types';
// Field hook type is a generic that takes three arguments:

View File

@@ -19,9 +19,10 @@ Globals feature the ability to define the following hooks:
All Global Hook properties accept arrays of synchronous or asynchronous functions. Each Hook type receives specific arguments and has the ability to modify specific outputs.
`globals/example-hooks.js`
```js
// Global config
module.exports = {
```ts
import { GlobalConfig } from 'payload/types';
const ExampleHooks: GlobalConfig = {
slug: 'header',
fields: [
{ name: 'title', type: 'text'},
@@ -40,8 +41,10 @@ module.exports = {
Runs before the `update` operation. This hook allows you to add or format data before the incoming data is validated.
```js
const beforeValidateHook = async ({
```ts
import { GlobalBeforeValidateHook } from 'payload/types'
const beforeValidateHook: GlobalBeforeValidateHook = async ({
data, // incoming data to update or create with
req, // full express request
originalDoc, // original document
@@ -54,8 +57,10 @@ const beforeValidateHook = async ({
Immediately following validation, `beforeChange` hooks will run within the `update` operation. At this stage, you can be confident that the data that will be saved to the document is valid in accordance to your field validations. You can optionally modify the shape of data to be saved.
```js
const beforeChangeHook = async ({
```ts
import { GlobalBeforeChangeHook } from 'payload/types'
const beforeChangeHook: GlobalBeforeChangeHook = async ({
data, // incoming data to update or create with
req, // full express request
originalDoc, // original document
@@ -68,9 +73,12 @@ const beforeChangeHook = async ({
After a global is updated, the `afterChange` hook runs. Use this hook to purge caches of your applications, sync site data to CRMs, and more.
```js
const afterChangeHook = async ({
```ts
import { GlobalAfterChangeHook } from 'payload/types'
const afterChangeHook: GlobalAfterChangeHook = async ({
doc, // full document data
previousDoc, // document data before updating the collection
req, // full express request
}) => {
return data;
@@ -81,8 +89,10 @@ const afterChangeHook = async ({
Runs before `findOne` global operation is transformed for output by `afterRead`. This hook fires before hidden fields are removed and before localized fields are flattened into the requested locale. Using this Hook will provide you with all locales and all hidden fields via the `doc` argument.
```js
const beforeReadHook = async ({
```ts
import { GlobalBeforeReadHook } from 'payload/types'
const beforeReadHook: GlobalBeforeReadHook = async ({
doc, // full document data
req, // full express request
}) => {...}
@@ -92,8 +102,10 @@ const beforeReadHook = async ({
Runs as the last step before a global is returned. Flattens locales, hides protected fields, and removes fields that users do not have access to.
```js
const afterReadHook = async ({
```ts
import { GlobalAfterReadHook } from 'payload/types'
const afterReadHook: GlobalAfterReadHook = async ({
doc, // full document data
req, // full express request
findMany, // boolean to denote if this hook is running against finding one, or finding many (useful in versions)
@@ -104,7 +116,7 @@ const afterReadHook = async ({
Payload exports a type for each Global hook which can be accessed as follows:
```js
```ts
import type {
GlobalBeforeValidateHook,
GlobalBeforeChangeHook,
@@ -112,7 +124,4 @@ import type {
GlobalBeforeReadHook,
GlobalAfterReadHook,
} from 'payload/types';
// Use hook types here...
}
```

View File

@@ -28,7 +28,7 @@ There are many more use cases for Hooks and the sky is the limit.
#### Async vs. synchronous
All hooks can be written as either synchronous or asynchronous functions. If the Hook should modify data before a document is updated or created, and it relies on asynchronous actions such as fetching data from a third party, it might make sense to define your Hook as an asynchronous function, so you can be sure that your Hook completes before the operation's lifecycle continues. Async hooks are run in series - so if you have two async hooks defined, the second hook will for the first to complete before it starts.
All hooks can be written as either synchronous or asynchronous functions. If the Hook should modify data before a document is updated or created, and it relies on asynchronous actions such as fetching data from a third party, it might make sense to define your Hook as an asynchronous function, so you can be sure that your Hook completes before the operation's lifecycle continues. Async hooks are run in series - so if you have two async hooks defined, the second hook will wait for the first to complete before it starts.
If your Hook simply performs a side-effect, such as updating a CRM, it might be okay to define it synchronously, so the Payload operation does not have to wait for your hook to complete.

View File

@@ -28,10 +28,11 @@ You can gain access to the currently running `payload` object via two ways:
You can import or require `payload` into your own files after it's been initialized, but you need to make sure that your `import` / `require` statements come **after** you call `payload.init()`—otherwise Payload won't have been initialized yet. That might be obvious. To us, it's usually not.
Example:
```js
```ts
import payload from 'payload';
import { CollectionAfterChangeHook } from 'payload/types';
const afterChangeHook = async () => {
const afterChangeHook: CollectionAfterChangeHook = async () => {
const posts = await payload.find({
collection: 'posts',
});
@@ -43,8 +44,8 @@ const afterChangeHook = async () => {
Payload is available anywhere you have access to the Express `req` - including within your access control and hook functions.
Example:
```js
const afterChangeHook = async ({ req: { payload }}) => {
```ts
const afterChangeHook: CollectionAfterChangeHook = async ({ req: { payload }}) => {
const posts = await payload.find({
collection: 'posts',
});
@@ -319,3 +320,25 @@ const result = await payload.updateGlobal({
showHiddenFields: true,
})
```
## TypeScript
Local API calls also support passing in a generic. This is especially useful if you generate your TS types using a [generate types script](/docs/typescript/generating-types).
Here is an example of usage:
```ts
// Our generated types
import { Post } from './payload-types'
// Add Post types as generic to create function
const post: Post = await payload.create<Post>({
collection: 'posts',
// Data will now be typed as Post and give you type hints
data: {
title: 'my title',
description: 'my description',
},
})
```

View File

@@ -27,7 +27,7 @@ Writing plugins is no more complex than writing regular JavaScript. If you know
### How to install plugins
The base Payload config allows for a `plugins` property which takes an `array` of [`Plugin`s](https://github.com/payloadcms/payload/blob/master/src/config/types.ts#L21).
The base Payload config allows for a `plugins` property which takes an `array` of [`Plugins`](https://github.com/payloadcms/payload/blob/master/src/config/types.ts#L21).
```js
import { buildConfig } from 'payload/config';
@@ -83,11 +83,10 @@ After all plugins are executed, the full config with all plugins will be sanitiz
Here is an example for how to automatically add a `lastModifiedBy` field to all Payload collections using a Plugin written in TypeScript.
```js
import { Config } from 'payload/config';
import { CollectionConfig } from 'payload/dist/collections/config/types';
```ts
import { Config, Plugin } from 'payload/config';
const addLastModified = (incomingConfig: Config): Config => {
const addLastModified: Plugin = (incomingConfig: Config): Config => {
// Find all incoming auth-enabled collections
// so we can create a lastModifiedBy relationship field
// to all auth collections
@@ -135,8 +134,8 @@ const addLastModified = (incomingConfig: Config): Config => {
export default addLastModified;
```
#### Available Plugins
### Available Plugins
You can discover existing plugins by browsing the `payload-plugin` topic on [Github](https://github.com/topics/payload-plugin).
You can discover existing plugins by browsing the `payload-plugin` topic on [GitHub](https://github.com/topics/payload-plugin).
For maintainers building plugins for others to use, please add the topic to help others find it. If you would like one to be built by the core Payload team, [open a Feature Request](https://github.com/payloadcms/payload/discussions) in our GitHub Discussions board. We would be happy to review your code and maybe feature you and your plugin where appropriate.

View File

@@ -37,7 +37,7 @@ Because _**you**_ are in complete control of who can do what with your data, you
Before running in Production, you need to have built a production-ready copy of the Payload Admin panel. To do this, Payload provides the `build` NPM script. You can use it by adding a `script` to your `package.json` file like this:
`package.json`:
```js
```json
{
"name": "project-name-here",
"scripts": {

View File

@@ -16,8 +16,10 @@ Payload provides an extremely granular querying language through all APIs. Each
For example, say you have a collection as follows:
```js
const Post = {
```ts
import { CollectionConfig } from 'payload/types';
const Post: CollectionConfig = {
slug: 'posts',
fields: [
{

View File

@@ -24,7 +24,7 @@ All collection `find` queries are paginated automatically. Responses are returne
| nextPage | `number` of next page, `null` if it doesn't exist |
**Example response:**
```js
```json
{
// Document Array // highlight-line
"docs": [

View File

@@ -22,15 +22,17 @@ All Payload API routes are mounted prefixed to your config's `routes.api` URL se
Each collection is mounted using its `slug` value. For example, if a collection's slug is `users`, all corresponding routes will be mounted on `/api/users`.
Note: Collection slugs must be formatted in kebab-case
**All CRUD operations are exposed as follows:**
| Method | Path | Description |
| -------- | --------------------------- | -------------------------------------- |
| `GET` | `/api/{collectionSlug}` | Find paginated documents |
| `GET` | `/api/{collectionSlug}/:id` | Find a specific document by ID |
| `POST` | `/api/{collectionSlug}` | Create a new document |
| `PUT` | `/api/{collectionSlug}/:id` | Update a document by ID |
| `DELETE` | `/api/{collectionSlug}/:id` | Delete an existing document by ID |
| `GET` | `/api/{collection-slug}` | Find paginated documents |
| `GET` | `/api/{collection-slug}/:id` | Find a specific document by ID |
| `POST` | `/api/{collection-slug}` | Create a new document |
| `PATCH` | `/api/{collection-slug}/:id` | Update a document by ID |
| `DELETE` | `/api/{collection-slug}/:id` | Delete an existing document by ID |
##### Additional `find` query parameters
@@ -47,14 +49,14 @@ Auth enabled collections are also given the following endpoints:
| Method | Path | Description |
| -------- | --------------------------- | ----------- |
| `POST` | `/api/{collectionSlug}/verify/:token` | [Email verification](/docs/authentication/operations#verify-by-email), if enabled. |
| `POST` | `/api/{collectionSlug}/unlock` | [Unlock a user's account](/docs/authentication/operations#unlock), if enabled. |
| `POST` | `/api/{collectionSlug}/login` | [Logs in](/docs/authentication/operations#login) a user with email / password. |
| `POST` | `/api/{collectionSlug}/logout` | [Logs out](/docs/authentication/operations#logout) a user. |
| `POST` | `/api/{collectionSlug}/refresh-token` | [Refreshes a token](/docs/authentication/operations#refresh) that has not yet expired. |
| `GET` | `/api/{collectionSlug}/me` | [Returns the currently logged in user with token](/docs/authentication/operations#me). |
| `POST` | `/api/{collectionSlug}/forgot-password` | [Password reset workflow](/docs/authentication/operations#forgot-password) entry point. |
| `POST` | `/api/{collectionSlug}/reset-password` | [To reset the user's password](/docs/authentication/operations#reset-password). |
| `POST` | `/api/{collection-slug}/verify/:token` | [Email verification](/docs/authentication/operations#verify-by-email), if enabled. |
| `POST` | `/api/{collection-slug}/unlock` | [Unlock a user's account](/docs/authentication/operations#unlock), if enabled. |
| `POST` | `/api/{collection-slug}/login` | [Logs in](/docs/authentication/operations#login) a user with email / password. |
| `POST` | `/api/{collection-slug}/logout` | [Logs out](/docs/authentication/operations#logout) a user. |
| `POST` | `/api/{collection-slug}/refresh-token` | [Refreshes a token](/docs/authentication/operations#refresh) that has not yet expired. |
| `GET` | `/api/{collection-slug}/me` | [Returns the currently logged in user with token](/docs/authentication/operations#me). |
| `POST` | `/api/{collection-slug}/forgot-password` | [Password reset workflow](/docs/authentication/operations#forgot-password) entry point. |
| `POST` | `/api/{collection-slug}/reset-password` | [To reset the user's password](/docs/authentication/operations#reset-password). |
## Globals
@@ -77,7 +79,7 @@ In addition to the dynamically generated endpoints above Payload also has REST e
## Custom Endpoints
Additional REST API endpoints can be added to `collections` and `globals` by providing array of `endpoints` in the configuration. These can be used to write additional middleware on existing routes or build custom functionality into Payload apps and plugins.
Additional REST API endpoints can be added to your application by providing an array of `endpoints` in various places within a Payload config. Custom endpoints are useful for adding additional middleware on existing routes or for building custom functionality into Payload apps and plugins. Endpoints can be added at the top of the Payload config, `collections`, and `globals` and accessed respective of the api and slugs you have configured.
Each endpoint object needs to have:
@@ -86,13 +88,15 @@ Each endpoint object needs to have:
| **`path`** | A string for the endpoint route after the collection or globals slug |
| **`method`** | The lowercase HTTP verb to use: 'get', 'head', 'post', 'put', 'delete', 'connect' or 'options' |
| **`handler`** | A function or array of functions to be called with **req**, **res** and **next** arguments. [Express](https://expressjs.com/en/guide/routing.html#route-handlers) |
| **`root`** | When `true`, defines the endpoint on the root Express app, bypassing Payload handlers and the `routes.api` subpath. Note: this only applies to top-level endpoints of your Payload config, endpoints defined on `collections` or `globals` cannot be root. |
Example:
```js
```ts
import { CollectionConfig } from 'payload/types';
// a collection of 'orders' with an additional route for tracking details, reachable at /api/orders/:id/tracking
const Orders = {
const Orders: CollectionConfig = {
slug: 'orders',
fields: [ /* ... */ ],
// highlight-start

View File

@@ -45,6 +45,8 @@ Every Payload Collection can opt-in to supporting Uploads by specifying the `upl
| **`staticURL`** \* | The base URL path to use to access your uploads. Example: `/media` |
| **`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. |
| **`imageSizes`** | If specified, image uploads will be automatically resized in accordance to these image sizes. [More](#image-sizes) |
| **`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) |
| **`resizeOptions`** | An object passed to the the Sharp image library to resize the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize) |
| **`adminThumbnail`** | Set the way that the Admin panel will display thumbnails for this Collection. [More](#admin-thumbnails) |
| **`mimeTypes`** | Restrict mimeTypes in the file picker. Array of valid mimetypes or mimetype wildcards [More](#mimetypes) |
| **`disableLocalStorage`** | Completely disable uploading files to disk locally. [More](#disabling-local-upload-storage) |
@@ -55,38 +57,40 @@ _An asterisk denotes that a property above is required._
**Example Upload collection:**
```js
const Media = {
slug: "media",
```ts
import { CollectionConfig } from 'payload/types';
const Media: CollectionConfig = {
slug: 'media',
upload: {
staticURL: "/media",
staticDir: "media",
staticURL: '/media',
staticDir: 'media',
imageSizes: [
{
name: "thumbnail",
name: 'thumbnail',
width: 400,
height: 300,
crop: "centre",
position: 'centre',
},
{
name: "card",
name: 'card',
width: 768,
height: 1024,
crop: "centre",
position: 'centre',
},
{
name: "tablet",
name: 'tablet',
width: 1024,
// By specifying `null` or leaving a height undefined,
// the image will be sized to a certain width,
// but it will retain its original aspect ratio
// and calculate a height automatically.
height: null,
crop: "centre",
position: 'centre',
},
],
adminThumbnail: "thumbnail",
mimeTypes: ["image/*"],
adminThumbnail: 'thumbnail',
mimeTypes: ['image/*'],
},
};
```
@@ -97,17 +101,17 @@ Payload relies on the [`express-fileupload`](https://www.npmjs.com/package/expre
A common example of what you might want to customize within Payload-wide Upload options would be to increase the allowed `fileSize` of uploads sent to Payload:
```js
import { buildConfig } from "payload/config";
```ts
import { buildConfig } from 'payload/config';
export default buildConfig({
collections: [
{
slug: "media",
slug: 'media',
fields: [
{
name: "alt",
type: "text",
name: 'alt',
type: 'text',
},
],
upload: true,
@@ -158,12 +162,14 @@ You can specify how Payload retrieves admin thumbnails for your upload-enabled C
**Example custom Admin thumbnail:**
```js
const Media = {
slug: "media",
```ts
import { CollectionConfig } from 'payload/types';
const Media: CollectionConfig = {
slug: 'media',
upload: {
staticURL: "/media",
staticDir: "media",
staticURL: '/media',
staticDir: 'media',
imageSizes: [
// ... image sizes here
],
@@ -191,13 +197,15 @@ Some example values are: `image/*`, `audio/*`, `video/*`, `image/png`, `applicat
**Example mimeTypes usage:**
```js
const Media = {
slug: "media",
```ts
import { CollectionConfig } from 'payload/types';
const Media: CollectionConfig = {
slug: 'media',
upload: {
staticURL: "/media",
staticDir: "media",
mimeTypes: ["image/*", "application/pdf"], // highlight-line
staticURL: '/media',
staticDir: 'media',
mimeTypes: ['image/*', 'application/pdf'], // highlight-line
},
};
```

View File

@@ -25,8 +25,10 @@ Collections and Globals both support the same options for configuring autosave.
**Example config with versions, drafts, and autosave enabled:**
```js
const Pages = {
```ts
import { CollectionConfig } from 'payload/types';
const Pages: CollectionConfig = {
slug: 'pages',
access: {
read: ({ req }) => {

View File

@@ -81,8 +81,10 @@ You can use the `read` [Access Control](/docs/access-control/collections#read) m
Here is an example that utilizes the `_status` field to require a user to be logged in to retrieve drafts:
```js
const Pages = {
```ts
import { CollectionConfig } from 'payload/types';
const Pages: CollectionConfig = {
slug: 'pages',
access: {
read: ({ req }) => {
@@ -114,8 +116,10 @@ const Pages = {
Here is an example for how to write an access control function that grants access to both documents where `_status` is equal to "published" and where `_status` does not exist:
```js
const Pages = {
```ts
import { CollectionConfig } from 'payload/types';
const Pages: CollectionConfig = {
slug: 'pages',
access: {
read: ({ req }) => {

View File

@@ -77,7 +77,7 @@ _slug_versions
Each document in this new `versions` collection will store a set of meta properties about the version as well as a _full_ copy of the document. For example, a version's data might look like this for a Collection document:
```js
```json
{
"_id": "61cf752c19cdf1b1af7b61f1", // a unique ID of this version
"parent": "61ce1354091d5b3ffc20ea6e", // the ID of the parent document

View File

@@ -1,8 +1,12 @@
{
"name": "payload",
"version": "1.0.19",
"version": "1.1.19",
"description": "Node, React and MongoDB Headless CMS and Application Framework",
"license": "MIT",
"engines": {
"node": ">=14",
"yarn": ">=1.22 <2"
},
"author": {
"email": "info@payloadcms.com",
"name": "Payload CMS",
@@ -31,7 +35,7 @@
"build:tsc": "tsc --p tsconfig.admin.json && tsc --p tsconfig.server.json",
"build:components": "webpack --config dist/webpack/components.config.js",
"build": "yarn copyfiles && yarn build:tsc && yarn build:components",
"build:watch": "nodemon --watch 'src/**' --ext 'ts,tsx' --exec 'yarn build:tsc'",
"build:watch": "nodemon --watch 'src/**' --ext 'ts,tsx' --exec \"yarn build:tsc\"",
"dev": "nodemon",
"dev:generate-types": "node ./test/generateTypes.js",
"pretest": "yarn build",
@@ -41,6 +45,7 @@
"test:e2e:headed": "cross-env DISABLE_LOGGING=true playwright test --headed",
"test:e2e:debug": "cross-env PWDEBUG=1 DISABLE_LOGGING=true playwright test",
"test:components": "cross-env jest --config=jest.components.config.js",
"clean:cache": "rimraf node_modules/.cache",
"clean": "rimraf dist",
"release": "release-it",
"release:patch": "release-it patch",
@@ -85,7 +90,7 @@
"@babel/preset-typescript": "^7.12.1",
"@babel/register": "^7.11.5",
"@date-io/date-fns": "^2.10.6",
"@faceless-ui/modal": "^1.1.7",
"@faceless-ui/modal": "^2.0.1",
"@faceless-ui/scroll-info": "^1.2.3",
"@faceless-ui/window-info": "^2.0.2",
"@types/is-plain-object": "^2.0.4",
@@ -104,11 +109,11 @@
"deepmerge": "^4.2.2",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"express-fileupload": "1.3.1",
"express-fileupload": "1.4.0",
"express-graphql": "0.12.0",
"express-rate-limit": "^5.1.3",
"falsey": "^1.0.0",
"file-loader": "^6.2.0",
"file-type": "16.5.4",
"find-up": "4.1.0",
"flatley": "^5.2.0",
"fs-extra": "^10.0.0",
@@ -124,16 +129,16 @@
"isomorphic-fetch": "^3.0.0",
"jest": "^26.6.3",
"joi": "^17.3.0",
"json-schema-to-typescript": "^11.0.0",
"json-schema-to-typescript": "^11.0.2",
"jsonwebtoken": "^8.5.1",
"jwt-decode": "^3.1.2",
"md5": "^2.3.0",
"method-override": "^3.0.0",
"micro-memoize": "^4.0.9",
"mime": "^2.5.0",
"mini-css-extract-plugin": "1.3.3",
"minimist": "^1.2.0",
"mkdirp": "^1.0.4",
"mongoose": "^6.2.0",
"mongoose": "6.5.0",
"mongoose-paginate-v2": "^1.6.1",
"nodemailer": "^6.4.2",
"object-to-formdata": "^4.1.0",
@@ -163,14 +168,15 @@
"react-dom": "^18.0.0",
"react-helmet": "^6.1.0",
"react-router-dom": "^5.1.2",
"react-router-navigation-prompt": "^1.8.11",
"react-router-navigation-prompt": "^1.9.6",
"react-select": "^3.0.8",
"react-simple-code-editor": "^0.11.0",
"react-sortable-hoc": "^2.0.0",
"react-toastify": "^8.2.0",
"sanitize-filename": "^1.6.3",
"sass": "^1.52.1",
"sass": "^1.55.0",
"sass-loader": "^12.6.0",
"sharp": "^0.29.3",
"sharp": "^0.31.1",
"slate": "^0.72.8",
"slate-history": "^0.66.0",
"slate-hyperscript": "^0.66.0",
@@ -179,6 +185,7 @@
"terser-webpack-plugin": "^5.0.3",
"ts-essentials": "^7.0.1",
"url-loader": "^4.1.1",
"use-context-selector": "^1.4.1",
"uuid": "^8.1.0",
"webpack": "^5.6.0",
"webpack-bundle-analyzer": "^4.4.1",
@@ -187,9 +194,8 @@
"webpack-hot-middleware": "^2.25.0"
},
"devDependencies": {
"@bahmutov/cy-api": "^2.1.3",
"@playwright/test": "^1.23.1",
"@release-it/conventional-changelog": "^2.0.0",
"@release-it/conventional-changelog": "^5.1.1",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^13.0.1",
"@trbl/eslint-config": "^1.2.4",
@@ -271,11 +277,10 @@
"get-port": "5.1.1",
"glob": "^8.0.3",
"graphql-request": "^3.4.0",
"mongodb": "^3.6.2",
"mongodb-memory-server": "^7.2.0",
"nodemon": "^2.0.6",
"passport-strategy": "^1.0.0",
"release-it": "^14.2.2",
"release-it": "^15.5.0",
"rimraf": "^3.0.2",
"serve-static": "^1.14.2",
"shelljs": "^0.8.5",

View File

@@ -3,15 +3,16 @@ import qs from 'qs';
export const requests = {
get: (url: string, params: unknown = {}): Promise<Response> => {
const query = qs.stringify(params, { addQueryPrefix: true });
return fetch(`${url}${query}`);
return fetch(`${url}${query}`, { credentials: 'include' });
},
post: (url: string, options: RequestInit = { headers: {} }): Promise<Response> => {
const headers = options && options.headers ? { ...options.headers } : {};
const formattedOptions = {
const formattedOptions: RequestInit = {
...options,
method: 'post',
credentials: 'include',
headers: {
...headers,
},
@@ -23,9 +24,25 @@ export const requests = {
put: (url: string, options: RequestInit = { headers: {} }): Promise<Response> => {
const headers = options && options.headers ? { ...options.headers } : {};
const formattedOptions = {
const formattedOptions: RequestInit = {
...options,
method: 'put',
credentials: 'include',
headers: {
...headers,
},
};
return fetch(url, formattedOptions);
},
patch: (url: string, options: RequestInit = { headers: {} }): Promise<Response> => {
const headers = options && options.headers ? { ...options.headers } : {};
const formattedOptions: RequestInit = {
...options,
method: 'PATCH',
credentials: 'include',
headers: {
...headers,
},
@@ -36,12 +53,16 @@ export const requests = {
delete: (url: string, options: RequestInit = { headers: {} }): Promise<Response> => {
const headers = options && options.headers ? { ...options.headers } : {};
return fetch(url, {
const formattedOptions: RequestInit = {
...options,
method: 'delete',
credentials: 'include',
headers: {
...headers,
},
});
};
return fetch(url, formattedOptions);
},
};

View File

@@ -208,6 +208,7 @@ const Routes = () => {
if (permissions?.collections?.[collection.slug]?.read?.permission) {
return (
<DocumentInfoProvider
key={`${collection.slug}-edit-${id}`}
collection={collection}
id={id}
>

View File

@@ -3,7 +3,7 @@ import { useHistory } from 'react-router-dom';
import { toast } from 'react-toastify';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useConfig } from '../../utilities/Config';
import { useWatchForm, useFormModified } from '../../forms/Form/context';
import { useFormModified, useAllFormFields } from '../../forms/Form/context';
import { useLocale } from '../../utilities/Locale';
import { Props } from './types';
import reduceFieldsToValues from '../../forms/Form/reduceFieldsToValues';
@@ -17,7 +17,7 @@ const baseClass = 'autosave';
const Autosave: React.FC<Props> = ({ collection, global, id, publishedDocUpdatedAt }) => {
const { serverURL, routes: { api, admin } } = useConfig();
const { versions, getVersions } = useDocumentInfo();
const { fields, dispatchFields } = useWatchForm();
const [fields] = useAllFormFields();
const modified = useFormModified();
const locale = useLocale();
const { replace } = useHistory();
@@ -39,6 +39,7 @@ const Autosave: React.FC<Props> = ({ collection, global, id, publishedDocUpdated
const createCollectionDoc = useCallback(async () => {
const res = await fetch(`${serverURL}${api}/${collection.slug}?locale=${locale}&fallback-locale=null&depth=0&draft=true`, {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
@@ -77,7 +78,7 @@ const Autosave: React.FC<Props> = ({ collection, global, id, publishedDocUpdated
if (collection && id) {
url = `${serverURL}${api}/${collection.slug}/${id}?draft=true&autosave=true&locale=${locale}`;
method = 'PUT';
method = 'PATCH';
}
if (global) {
@@ -94,6 +95,7 @@ const Autosave: React.FC<Props> = ({ collection, global, id, publishedDocUpdated
setTimeout(async () => {
const res = await fetch(url, {
method,
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
@@ -112,7 +114,7 @@ const Autosave: React.FC<Props> = ({ collection, global, id, publishedDocUpdated
};
autosave();
}, [debouncedFields, modified, serverURL, api, collection, global, id, dispatchFields, getVersions, locale]);
}, [debouncedFields, modified, serverURL, api, collection, global, id, getVersions, locale]);
useEffect(() => {
if (versions?.docs?.[0]) {

View File

@@ -31,6 +31,7 @@ const DateTime: React.FC<Props> = (props) => {
if (dateTimeFormat === undefined) {
if (pickerAppearance === 'dayAndTime') dateTimeFormat = 'MMM d, yyy h:mm a';
else if (pickerAppearance === 'timeOnly') dateTimeFormat = 'h:mm a';
else if (pickerAppearance === 'monthOnly') dateTimeFormat = 'MM/yyyy';
else dateTimeFormat = 'MMM d, yyy';
}
@@ -50,6 +51,7 @@ const DateTime: React.FC<Props> = (props) => {
showPopperArrow: false,
selected: value && new Date(value),
customInputRef: 'ref',
showMonthYearPicker: pickerAppearance === 'monthOnly',
};
const classes = [

View File

@@ -1,6 +1,6 @@
type SharedProps = {
displayFormat?: string | undefined
pickerAppearance?: 'dayAndTime' | 'timeOnly' | 'dayOnly'
displayFormat?: string
pickerAppearance?: 'dayAndTime' | 'timeOnly' | 'dayOnly' | 'monthOnly'
}
type TimePickerProps = {
@@ -16,6 +16,11 @@ type DayPickerProps = {
maxDate?: Date
}
type MonthPickerProps = {
minDate?: Date
maxDate?: Date
}
export type ConditionalDateProps =
| SharedProps & DayPickerProps & TimePickerProps & {
pickerAppearance?: 'dayAndTime'
@@ -26,6 +31,9 @@ export type ConditionalDateProps =
| SharedProps & DayPickerProps & {
pickerAppearance: 'dayOnly'
}
| SharedProps & MonthPickerProps & {
pickerAppearance: 'monthOnly'
}
export type Props = SharedProps & DayPickerProps & TimePickerProps & {
value?: Date

View File

@@ -33,7 +33,7 @@ const DeleteDocument: React.FC<Props> = (props) => {
const { serverURL, routes: { api, admin } } = useConfig();
const { setModified } = useForm();
const [deleting, setDeleting] = useState(false);
const { closeAll, toggle } = useModal();
const { toggleModal } = useModal();
const history = useHistory();
const title = useTitle(useAsTitle) || id;
const titleToRender = titleFromProps || title;
@@ -55,12 +55,12 @@ const DeleteDocument: React.FC<Props> = (props) => {
try {
const json = await res.json();
if (res.status < 400) {
closeAll();
toggleModal(modalSlug);
toast.success(`${singular} "${title}" successfully deleted.`);
return history.push(`${admin}/collections/${slug}`);
}
closeAll();
toggleModal(modalSlug);
if (json.errors) {
json.errors.forEach((error) => toast.error(error.message));
@@ -72,7 +72,7 @@ const DeleteDocument: React.FC<Props> = (props) => {
return addDefaultError();
}
});
}, [addDefaultError, closeAll, history, id, singular, slug, title, admin, api, serverURL, setModified]);
}, [addDefaultError, toggleModal, modalSlug, history, id, singular, slug, title, admin, api, serverURL, setModified]);
if (id) {
return (
@@ -84,7 +84,7 @@ const DeleteDocument: React.FC<Props> = (props) => {
onClick={(e) => {
e.preventDefault();
setDeleting(false);
toggle(modalSlug);
toggleModal(modalSlug);
}}
>
Delete
@@ -110,7 +110,7 @@ const DeleteDocument: React.FC<Props> = (props) => {
id="confirm-cancel"
buttonStyle="secondary"
type="button"
onClick={deleting ? undefined : () => toggle(modalSlug)}
onClick={deleting ? undefined : () => toggleModal(modalSlug)}
>
Cancel
</Button>

View File

@@ -0,0 +1,22 @@
@import '../../../scss/styles.scss';
.duplicate {
&__modal {
@include blur-bg;
display: flex;
align-items: center;
height: 100%;
.btn {
margin-right: $baseline;
}
}
&__modal-template {
z-index: 1;
position: relative;
}
}

View File

@@ -1,39 +1,163 @@
import React, { useCallback } from 'react';
import React, { useCallback, useState } from 'react';
import { useHistory } from 'react-router-dom';
import { toast } from 'react-toastify';
import { Modal, useModal } from '@faceless-ui/modal';
import { useConfig } from '../../utilities/Config';
import { Props } from './types';
import Button from '../Button';
import { useForm } from '../../forms/Form/context';
import { requests } from '../../../api';
import { useForm, useFormModified } from '../../forms/Form/context';
import MinimalTemplate from '../../templates/Minimal';
import './index.scss';
const baseClass = 'duplicate';
const Duplicate: React.FC<Props> = ({ slug }) => {
const Duplicate: React.FC<Props> = ({ slug, collection, id }) => {
const { push } = useHistory();
const { getData } = useForm();
const modified = useFormModified();
const { toggleModal } = useModal();
const { setModified } = useForm();
const { serverURL, routes: { api }, localization } = useConfig();
const { routes: { admin } } = useConfig();
const [hasClicked, setHasClicked] = useState<boolean>(false);
const handleClick = useCallback(() => {
const data = getData();
const modalSlug = `duplicate-${id}`;
push({
pathname: `${admin}/collections/${slug}/create`,
state: {
data,
},
});
}, [push, getData, slug, admin]);
const handleClick = useCallback(async (override = false) => {
setHasClicked(true);
if (modified && !override) {
toggleModal(modalSlug);
return;
}
const create = async (locale = ''): Promise<string | null> => {
const response = await requests.get(`${serverURL}${api}/${slug}/${id}`, {
locale,
depth: 0,
});
let data = await response.json();
if (typeof collection.admin.hooks?.beforeDuplicate === 'function') {
data = await collection.admin.hooks.beforeDuplicate({
data,
locale,
});
}
const result = await requests.post(`${serverURL}${api}/${slug}`, {
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
const json = await result.json();
if (result.status === 201) {
return json.doc.id;
}
json.errors.forEach((error) => toast.error(error.message));
return null;
};
let duplicateID;
if (localization) {
duplicateID = await create(localization.defaultLocale);
let abort = false;
localization.locales
.filter((locale) => locale !== localization.defaultLocale)
.forEach(async (locale) => {
if (!abort) {
const res = await requests.get(`${serverURL}${api}/${slug}/${id}`, {
locale,
depth: 0,
});
let localizedDoc = await res.json();
if (typeof collection.admin.hooks?.beforeDuplicate === 'function') {
localizedDoc = await collection.admin.hooks.beforeDuplicate({
data: localizedDoc,
locale,
});
}
const patchResult = await requests.patch(`${serverURL}${api}/${slug}/${duplicateID}?locale=${locale}`, {
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(localizedDoc),
});
if (patchResult.status > 400) {
abort = true;
const json = await patchResult.json();
json.errors.forEach((error) => toast.error(error.message));
}
}
});
if (abort) {
// delete the duplicate doc to prevent incomplete
await requests.delete(`${serverURL}${api}/${slug}/${id}`);
}
} else {
duplicateID = await create();
}
toast.success(`${collection.labels.singular} successfully duplicated.`,
{ autoClose: 3000 });
setModified(false);
setTimeout(() => {
push({
pathname: `${admin}/collections/${slug}/${duplicateID}`,
});
}, 10);
}, [modified, localization, collection, setModified, toggleModal, modalSlug, serverURL, api, slug, id, push, admin]);
const confirm = useCallback(async () => {
setHasClicked(false);
await handleClick(true);
}, [handleClick]);
return (
<Button
id="action-duplicate"
buttonStyle="none"
className={baseClass}
onClick={handleClick}
>
Duplicate
</Button>
<React.Fragment>
<Button
id="action-duplicate"
buttonStyle="none"
className={baseClass}
onClick={() => handleClick(false)}
>
Duplicate
</Button>
{modified && hasClicked && (
<Modal
slug={modalSlug}
className={`${baseClass}__modal`}
>
<MinimalTemplate className={`${baseClass}__modal-template`}>
<h1>Confirm duplicate</h1>
<p>
You have unsaved changes. Would you like to continue to duplicate?
</p>
<Button
id="confirm-cancel"
buttonStyle="secondary"
type="button"
onClick={() => toggleModal(modalSlug)}
>
Cancel
</Button>
<Button
onClick={confirm}
id="confirm-duplicate"
>
Duplicate without saving changes
</Button>
</MinimalTemplate>
</Modal>
)}
</React.Fragment>
);
};

View File

@@ -1,3 +1,7 @@
import { SanitizedCollectionConfig } from '../../../../collections/config/types';
export type Props = {
slug: string,
slug: string
collection: SanitizedCollectionConfig
id: string
}

View File

@@ -102,7 +102,7 @@ const FileDetails: React.FC<Props> = (props) => {
</div>
<Meta
{...val}
mimeType={mimeType}
mimeType={val.mimeType}
staticURL={staticURL}
/>
</li>

View File

@@ -4,6 +4,7 @@ import { Modal, useModal } from '@faceless-ui/modal';
import Button from '../Button';
import MinimalTemplate from '../../templates/Minimal';
import { Props } from './types';
import { useDocumentInfo } from '../../utilities/DocumentInfo';
import './index.scss';
@@ -15,13 +16,14 @@ const GenerateConfirmation: React.FC<Props> = (props) => {
highlightField,
} = props;
const { toggle } = useModal();
const { id } = useDocumentInfo();
const { toggleModal } = useModal();
const modalSlug = 'generate-confirmation';
const modalSlug = `generate-confirmation-${id}`;
const handleGenerate = () => {
setKey();
toggle(modalSlug);
toggleModal(modalSlug);
toast.success('New API Key Generated.', { autoClose: 3000 });
highlightField(true);
};
@@ -32,7 +34,7 @@ const GenerateConfirmation: React.FC<Props> = (props) => {
size="small"
buttonStyle="secondary"
onClick={() => {
toggle(modalSlug);
toggleModal(modalSlug);
}}
>
Generate new API key
@@ -57,7 +59,7 @@ const GenerateConfirmation: React.FC<Props> = (props) => {
buttonStyle="secondary"
type="button"
onClick={() => {
toggle(modalSlug);
toggleModal(modalSlug);
}}
>
Cancel

View File

@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import AnimateHeight from 'react-animate-height';
import { fieldAffectsData } from '../../../../fields/config/types';
import { FieldAffectingData, fieldAffectsData } from '../../../../fields/config/types';
import SearchFilter from '../SearchFilter';
import ColumnSelector from '../ColumnSelector';
import WhereBuilder from '../WhereBuilder';
@@ -29,6 +29,7 @@ const ListControls: React.FC<Props> = (props) => {
fields,
admin: {
useAsTitle,
listSearchableFields,
},
},
} = props;
@@ -37,6 +38,7 @@ const ListControls: React.FC<Props> = (props) => {
const shouldInitializeWhereOpened = validateWhereQuery(params?.where);
const [titleField] = useState(() => fields.find((field) => fieldAffectsData(field) && field.name === useAsTitle));
const [textFieldsToBeSearched] = useState(listSearchableFields ? () => fields.filter((field) => fieldAffectsData(field) && listSearchableFields.includes(field.name)) as FieldAffectingData[] : null);
const [visibleDrawer, setVisibleDrawer] = useState<'where' | 'sort' | 'columns'>(shouldInitializeWhereOpened ? 'where' : undefined);
return (
@@ -47,6 +49,7 @@ const ListControls: React.FC<Props> = (props) => {
handleChange={handleWhereChange}
modifySearchQuery={modifySearchQuery}
fieldLabel={titleField && titleField.label ? titleField.label : undefined}
listSearchableFields={textFieldsToBeSearched}
/>
<div className={`${baseClass}__buttons`}>
<div className={`${baseClass}__buttons-wrap`}>

View File

@@ -6,7 +6,7 @@
top: 0;
left: 0;
height: 100vh;
width: base(9);
width: var(--nav-width);
overflow: hidden;
border-right: 1px solid var(--theme-elevation-100);
@@ -81,32 +81,16 @@
}
}
a {
padding: base(.125) 0;
display: flex;
text-decoration: none;
&:focus {
box-shadow: none;
font-weight: 600;
}
&:active {
font-weight: normal;
}
}
nav {
margin: base(.25) 0 $baseline;
a {
position: relative;
padding: base(.125) base(1.5) base(.125) 0;
display: flex;
text-decoration: none;
svg {
opacity: 0;
position: absolute;
left: - base(.5);
transform: rotate(-90deg);
&:focus {
box-shadow: none;
font-weight: 600;
}
&:hover {
@@ -114,18 +98,26 @@
}
&.active {
font-weight: normal;
padding-left: base(.6);
font-weight: 600;
svg {
opacity: 1;
}
}
}
}
@include large-break {
width: base(8);
&__link {
svg {
opacity: 0;
position: absolute;
left: - base(.5);
transform: rotate(-90deg);
}
&.active {
svg {
opacity: 1;
}
}
}
@include mid-break {
@@ -184,4 +176,4 @@
font-weight: 600;
}
}
}
}

View File

@@ -10,6 +10,8 @@ import CloseMenu from '../../icons/CloseMenu';
import Icon from '../../graphics/Icon';
import Account from '../../graphics/Account';
import Localizer from '../Localizer';
import NavGroup from '../NavGroup';
import { groupNavItems, Group, EntityToGroup, EntityType } from '../../../utilities/groupNavItems';
import './index.scss';
@@ -18,6 +20,7 @@ const baseClass = 'nav';
const DefaultNav = () => {
const { permissions } = useAuth();
const [menuActive, setMenuActive] = useState(false);
const [groups, setGroups] = useState<Group[]>([]);
const history = useHistory();
const {
collections,
@@ -36,7 +39,29 @@ const DefaultNav = () => {
const classes = [
baseClass,
menuActive && `${baseClass}--menu-active`,
].filter(Boolean).join(' ');
].filter(Boolean)
.join(' ');
useEffect(() => {
setGroups(groupNavItems([
...collections.map((collection) => {
const entityToGroup: EntityToGroup = {
type: EntityType.collection,
entity: collection,
};
return entityToGroup;
}),
...globals.map((global) => {
const entityToGroup: EntityToGroup = {
type: EntityType.global,
entity: global,
};
return entityToGroup;
}),
], permissions));
}, [collections, globals, permissions]);
useEffect(() => history.listen(() => {
setMenuActive(false);
@@ -65,56 +90,44 @@ const DefaultNav = () => {
)}
</button>
</header>
<div className={`${baseClass}__wrap`}>
<nav className={`${baseClass}__wrap`}>
{Array.isArray(beforeNavLinks) && beforeNavLinks.map((Component, i) => <Component key={i} />)}
<span className={`${baseClass}__label`}>Collections</span>
<nav>
{collections && collections.map((collection, i) => {
const href = `${admin}/collections/${collection.slug}`;
{groups.map(({ label, entities }, key) => {
return (
<NavGroup {...{ key, label }}>
{entities.map(({ entity, type }, i) => {
let entityLabel: string;
let href: string;
let id: string;
if (permissions?.collections?.[collection.slug]?.read.permission) {
return (
<NavLink
id={`nav-${collection.slug}`}
activeClassName="active"
key={i}
to={href}
>
<Chevron />
{collection.labels.plural}
</NavLink>
);
}
return null;
})}
</nav>
{(globals && globals.length > 0) && (
<React.Fragment>
<span className={`${baseClass}__label`}>Globals</span>
<nav>
{globals.map((global, i) => {
const href = `${admin}/globals/${global.slug}`;
if (permissions?.globals?.[global.slug].read.permission) {
return (
<NavLink
id={`nav-global-${global.slug}`}
activeClassName="active"
key={i}
to={href}
>
<Chevron />
{global.label}
</NavLink>
);
if (type === EntityType.collection) {
href = `${admin}/collections/${entity.slug}`;
entityLabel = entity.labels.plural;
id = `nav-${entity.slug}`;
}
return null;
if (type === EntityType.global) {
href = `${admin}/globals/${entity.slug}`;
entityLabel = entity.label;
id = `nav-global-${entity.slug}`;
}
return (
<NavLink
id={id}
className={`${baseClass}__link`}
activeClassName="active"
key={i}
to={href}
>
<Chevron />
{entityLabel}
</NavLink>
);
})}
</nav>
</React.Fragment>
)}
</NavGroup>
);
})}
{Array.isArray(afterNavLinks) && afterNavLinks.map((Component, i) => <Component key={i} />)}
<div className={`${baseClass}__controls`}>
<Localizer />
@@ -131,7 +144,7 @@ const DefaultNav = () => {
<LogOut />
</Link>
</div>
</div>
</nav>
</div>
</aside>
);

View File

@@ -0,0 +1,53 @@
@import '../../../scss/styles.scss';
.nav-group {
width: 100%;
margin-bottom: base(.5);
&__toggle {
cursor: pointer;
color: var(--theme-elevation-400);
background: transparent;
padding-left: 0;
border: 0;
margin-top: base(.25);
width: 100%;
text-align: left;
display: flex;
align-items: flex-start;
padding-right: base(.5);
svg {
flex-shrink: 0;
margin-top: base(-.2);
}
&:hover {
color: var(--theme-elevation-1000);
.stroke {
stroke: var(--theme-elevation-1000);
}
}
}
&__indicator {
margin-left: auto;
.stroke {
stroke: var(--theme-elevation-200);
}
}
&--collapsed {
.collapsible__toggle {
border-bottom-right-radius: $style-radius-s;
border-bottom-left-radius: $style-radius-s;
}
.nav-group__indicator {
transform: rotate(.5turn);
}
}
}

View File

@@ -0,0 +1,91 @@
import React, { useEffect, useState } from 'react';
import AnimateHeight from 'react-animate-height';
import Chevron from '../../icons/Chevron';
import { usePreferences } from '../../utilities/Preferences';
import './index.scss';
const baseClass = 'nav-group';
type Props = {
children: React.ReactNode,
label: string,
}
const NavGroup: React.FC<Props> = ({
children,
label,
}) => {
const [collapsed, setCollapsed] = useState(true);
const [animate, setAnimate] = useState(false);
const { getPreference, setPreference } = usePreferences();
const preferencesKey = `collapsed-${label}-groups`;
useEffect(() => {
if (label) {
const setCollapsedFromPreferences = async () => {
const preferences = await getPreference(preferencesKey) || [];
setCollapsed(preferences.indexOf(label) !== -1);
};
setCollapsedFromPreferences();
}
}, [getPreference, label, preferencesKey]);
if (label) {
const toggleCollapsed = async () => {
setAnimate(true);
let preferences: string[] = await getPreference(preferencesKey) || [];
if (collapsed) {
preferences = preferences.filter((preference) => label !== preference);
} else {
preferences.push(label);
}
setPreference(preferencesKey, preferences);
setCollapsed(!collapsed);
};
return (
<div
id={`nav-group-${label}`}
className={[
`${baseClass}`,
`${label}`,
collapsed && `${baseClass}--collapsed`,
].filter(Boolean)
.join(' ')}
>
<button
type="button"
className={[
`${baseClass}__toggle`,
`${baseClass}__toggle--${collapsed ? 'collapsed' : 'open'}`,
].filter(Boolean)
.join(' ')}
onClick={toggleCollapsed}
>
<div className={`${baseClass}__label`}>
{label}
</div>
<Chevron className={`${baseClass}__indicator`} />
</button>
<AnimateHeight
height={collapsed ? 0 : 'auto'}
duration={animate ? 200 : 0}
>
<div className={`${baseClass}__content`}>
{children}
</div>
</AnimateHeight>
</div>
);
}
return (
<React.Fragment>
{children}
</React.Fragment>
);
};
export default NavGroup;

View File

@@ -1,12 +1,11 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useWindowInfo } from '@faceless-ui/window-info';
import { useScrollInfo } from '@faceless-ui/scroll-info';
import { Props } from './types';
import useThrottledEffect from '../../../hooks/useThrottledEffect';
import PopupButton from './PopupButton';
import useIntersect from '../../../hooks/useIntersect';
import './index.scss';
import useThrottledEffect from '../../../hooks/useThrottledEffect';
const baseClass = 'popup';
@@ -30,36 +29,33 @@ const Popup: React.FC<Props> = (props) => {
boundingRef,
} = props;
const { width: windowWidth, height: windowHeight } = useWindowInfo();
const [intersectionRef, intersectionEntry] = useIntersect({
threshold: 1,
rootMargin: '-100px 0px 0px 0px',
root: boundingRef?.current || null,
});
const buttonRef = useRef(null);
const contentRef = useRef(null);
const [mounted, setMounted] = useState(false);
const [active, setActive] = useState(initActive);
const [verticalAlign, setVerticalAlign] = useState(verticalAlignFromProps);
const [horizontalAlign, setHorizontalAlign] = useState(horizontalAlignFromProps);
const { y: scrollY } = useScrollInfo();
const { height: windowHeight, width: windowWidth } = useWindowInfo();
const setPosition = useCallback(({ horizontal = false, vertical = false }) => {
if (contentRef.current) {
const bounds = contentRef.current.getBoundingClientRect();
const handleClickOutside = useCallback((e) => {
if (contentRef.current.contains(e.target)) {
return;
}
setActive(false);
}, []);
useThrottledEffect(() => {
if (contentRef.current && buttonRef.current) {
const {
left: contentLeftPos,
right: contentRightPos,
top: contentTopPos,
bottom: contentBottomPos,
} = contentRef.current.getBoundingClientRect();
} = bounds;
let boundingTopPos = 100;
let boundingRightPos = windowWidth;
let boundingBottomPos = windowHeight;
let boundingRightPos = window.innerWidth;
let boundingBottomPos = window.innerHeight;
let boundingLeftPos = 0;
if (boundingRef?.current) {
@@ -71,21 +67,39 @@ const Popup: React.FC<Props> = (props) => {
} = boundingRef.current.getBoundingClientRect());
}
if (contentRightPos > boundingRightPos && contentLeftPos > boundingLeftPos) {
setHorizontalAlign('right');
} else if (contentLeftPos < boundingLeftPos && contentRightPos < boundingRightPos) {
setHorizontalAlign('left');
if (horizontal) {
if (contentRightPos > boundingRightPos && contentLeftPos > boundingLeftPos) {
setHorizontalAlign('right');
} else if (contentLeftPos < boundingLeftPos && contentRightPos < boundingRightPos) {
setHorizontalAlign('left');
}
}
if (contentTopPos < boundingTopPos && contentBottomPos < boundingBottomPos) {
setVerticalAlign('bottom');
} else if (contentBottomPos > boundingBottomPos && contentTopPos < boundingTopPos) {
setVerticalAlign('top');
if (vertical) {
if (contentTopPos < boundingTopPos && contentBottomPos < boundingBottomPos) {
setVerticalAlign('bottom');
} else if (contentBottomPos > boundingBottomPos && contentTopPos > boundingTopPos) {
setVerticalAlign('top');
}
}
setMounted(true);
}
}, 500, [scrollY, windowHeight, windowWidth]);
}, [boundingRef]);
const handleClickOutside = useCallback((e) => {
if (contentRef.current.contains(e.target)) {
return;
}
setActive(false);
}, [contentRef]);
useEffect(() => {
setPosition({ horizontal: true });
}, [intersectionEntry, setPosition, windowWidth]);
useEffect(() => {
setPosition({ vertical: true });
}, [intersectionEntry, setPosition, windowHeight]);
useEffect(() => {
if (typeof onToggleOpen === 'function') onToggleOpen(active);
@@ -112,7 +126,7 @@ const Popup: React.FC<Props> = (props) => {
`${baseClass}--color-${color}`,
`${baseClass}--v-align-${verticalAlign}`,
`${baseClass}--h-align-${horizontalAlign}`,
(active && mounted) && `${baseClass}--active`,
(active) && `${baseClass}--active`,
].filter(Boolean).join(' ');
return (
@@ -144,7 +158,7 @@ const Popup: React.FC<Props> = (props) => {
>
<div
className={`${baseClass}__wrap`}
// TODO: color ::after with bg color
ref={intersectionRef}
>
<div
className={`${baseClass}__scroll`}

View File

@@ -89,6 +89,10 @@ div.react-select {
border: $style-stroke-width-s solid var(--theme-elevation-800);
line-height: calc(#{$baseline} - #{$style-stroke-width-s * 2});
margin: base(.25) base(.5) base(.25) 0;
&.draggable {
cursor: grab;
}
}
.rs__multi-value__label {
@@ -112,4 +116,8 @@ div.react-select {
background-color: var(--theme-error-200);
}
}
}
&.rs--is-disabled .rs__control {
background: var(--theme-elevation-200);
}
}

View File

@@ -1,10 +1,53 @@
import React from 'react';
import Select from 'react-select';
import { Props } from './types';
import React, { MouseEventHandler, useCallback } from 'react';
import Select, {
components,
MultiValueProps,
Props as SelectProps,
} from 'react-select';
import {
SortableContainer,
SortableContainerProps,
SortableElement,
SortStartHandler,
SortEndHandler,
SortableHandle,
} from 'react-sortable-hoc';
import { arrayMove } from '../../../../utilities/arrayMove';
import { Props, Value } from './types';
import Chevron from '../../icons/Chevron';
import './index.scss';
const SortableMultiValue = SortableElement(
(props: MultiValueProps<Value>) => {
// this prevents the menu from being opened/closed when the user clicks
// on a value to begin dragging it. ideally, detecting a click (instead of
// a drag) would still focus the control and toggle the menu, but that
// requires some magic with refs that are out of scope for this example
const onMouseDown: MouseEventHandler<HTMLDivElement> = (e) => {
e.preventDefault();
e.stopPropagation();
};
const classes = [
props.className,
!props.isDisabled && 'draggable',
].filter(Boolean).join(' ');
return (
<components.MultiValue
{...props}
className={classes}
innerProps={{ ...props.innerProps, onMouseDown }}
/>
);
},
);
const SortableMultiValueLabel = SortableHandle((props) => <components.MultiValueLabel {...props} />);
const SortableSelect = SortableContainer(Select) as React.ComponentClass<SelectProps<Value, true> & SortableContainerProps>;
const ReactSelect: React.FC<Props> = (props) => {
const {
className,
@@ -16,6 +59,9 @@ const ReactSelect: React.FC<Props> = (props) => {
placeholder,
isSearchable = true,
isClearable,
isMulti,
isSortable,
filterOption = undefined,
} = props;
const classes = [
@@ -24,6 +70,50 @@ const ReactSelect: React.FC<Props> = (props) => {
showError && 'react-select--error',
].filter(Boolean).join(' ');
const onSortStart: SortStartHandler = useCallback(({ helper }) => {
const portalNode = helper;
if (portalNode && portalNode.style) {
portalNode.style.cssText += 'pointer-events: auto; cursor: grabbing;';
}
}, []);
const onSortEnd: SortEndHandler = useCallback(({ oldIndex, newIndex }) => {
onChange(arrayMove(value as Value[], oldIndex, newIndex));
}, [onChange, value]);
if (isMulti && isSortable) {
return (
<SortableSelect
useDragHandle
// react-sortable-hoc props:
axis="xy"
onSortStart={onSortStart}
onSortEnd={onSortEnd}
// small fix for https://github.com/clauderic/react-sortable-hoc/pull/352:
getHelperDimensions={({ node }) => node.getBoundingClientRect()}
// react-select props:
placeholder={placeholder}
{...props}
value={value as Value[]}
onChange={onChange}
disabled={disabled ? 'disabled' : undefined}
className={classes}
classNamePrefix="rs"
options={options}
isSearchable={isSearchable}
isClearable={isClearable}
components={{
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore We're failing to provide a required index prop to SortableElement
MultiValue: SortableMultiValue,
MultiValueLabel: SortableMultiValueLabel,
DropdownIndicator: Chevron,
}}
filterOption={filterOption}
/>
);
}
return (
<Select
placeholder={placeholder}
@@ -37,6 +127,7 @@ const ReactSelect: React.FC<Props> = (props) => {
options={options}
isSearchable={isSearchable}
isClearable={isClearable}
filterOption={filterOption}
/>
);
};

View File

@@ -2,6 +2,11 @@ import { OptionsType, GroupedOptionsType } from 'react-select';
export type Options = OptionsType<Value> | GroupedOptionsType<Value>;
export type OptionType = {
[key: string]: any,
};
export type Value = {
label: string
value: string | null
@@ -16,10 +21,14 @@ export type Props = {
showError?: boolean,
options: Options
isMulti?: boolean,
isSortable?: boolean,
isDisabled?: boolean
onInputChange?: (val: string) => void
onMenuScrollToBottom?: () => void
placeholder?: string
isSearchable?: boolean
isClearable?: boolean
filterOption?:
| (({ label, value, data }: { label: string, value: string, data: OptionType }, search: string) => boolean)
| undefined,
}

View File

@@ -5,7 +5,7 @@ import IDLabel from '../IDLabel';
const baseClass = 'render-title';
const RenderTitle : React.FC<Props> = (props) => {
const RenderTitle: React.FC<Props> = (props) => {
const {
useAsTitle,
title: titleFromProps,
@@ -14,10 +14,8 @@ const RenderTitle : React.FC<Props> = (props) => {
} = props;
const titleFromForm = useTitle(useAsTitle);
const titleFromData = data && data[useAsTitle];
let title = titleFromData;
if (!title) title = titleFromForm;
let title = titleFromForm;
if (!title) title = data?.id;
if (!title) title = fallback;
title = titleFromProps || title;

View File

@@ -25,7 +25,7 @@ const SaveDraft: React.FC = () => {
if (collection) {
action = `${serverURL}${api}/${collection.slug}${id ? `/${id}` : ''}${search}`;
if (id) method = 'PUT';
if (id) method = 'PATCH';
}
if (global) {

View File

@@ -1,11 +1,11 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { useHistory } from 'react-router-dom';
import queryString from 'qs';
import { Props } from './types';
import Search from '../../icons/Search';
import useDebounce from '../../../hooks/useDebounce';
import { useSearchParams } from '../../utilities/SearchParams';
import { Where } from '../../../../types';
import { Where, WhereField } from '../../../../types';
import './index.scss';
@@ -16,32 +16,51 @@ const SearchFilter: React.FC<Props> = (props) => {
fieldName = 'id',
fieldLabel = 'ID',
modifySearchQuery = true,
listSearchableFields,
handleChange,
} = props;
const params = useSearchParams();
const history = useHistory();
const [search, setSearch] = useState(() => params?.where?.[fieldName]?.like || '');
const [search, setSearch] = useState('');
const [previousSearch, setPreviousSearch] = useState('');
const placeholder = useRef(`Search by ${fieldLabel}`);
const debouncedSearch = useDebounce(search, 300);
useEffect(() => {
if (debouncedSearch !== params?.where?.[fieldName]?.like) {
const newWhere = {
...(typeof params?.where === 'object' ? params.where : {}),
[fieldName]: {
like: debouncedSearch,
},
};
const newWhere: Where = { ...typeof params?.where === 'object' ? params.where as Where : {} };
const fieldNamesToSearch = [fieldName, ...(listSearchableFields || []).map(({ name }) => name)];
if (!debouncedSearch) {
delete newWhere[fieldName];
fieldNamesToSearch.forEach((fieldNameToSearch) => {
const hasOrQuery = Array.isArray(newWhere.or);
const existingFieldSearchIndex = hasOrQuery ? newWhere.or.findIndex((condition) => {
return (condition?.[fieldNameToSearch] as WhereField)?.like;
}) : -1;
if (debouncedSearch) {
if (!hasOrQuery) newWhere.or = [];
if (existingFieldSearchIndex > -1) {
(newWhere.or[existingFieldSearchIndex][fieldNameToSearch] as WhereField).like = debouncedSearch;
} else {
newWhere.or.push({
[fieldNameToSearch]: {
like: debouncedSearch,
},
});
}
} else if (existingFieldSearchIndex > -1) {
newWhere.or.splice(existingFieldSearchIndex, 1);
}
});
if (debouncedSearch !== previousSearch) {
if (handleChange) handleChange(newWhere as Where);
if (modifySearchQuery && params?.where?.[fieldName]?.like !== newWhere?.[fieldName]?.like) {
if (modifySearchQuery) {
history.replace({
search: queryString.stringify({
...params,
@@ -50,14 +69,27 @@ const SearchFilter: React.FC<Props> = (props) => {
}),
});
}
setPreviousSearch(debouncedSearch);
}
}, [debouncedSearch, history, fieldName, params, handleChange, modifySearchQuery]);
}, [debouncedSearch, previousSearch, history, fieldName, params, handleChange, modifySearchQuery, listSearchableFields]);
useEffect(() => {
if (listSearchableFields?.length > 0) {
placeholder.current = listSearchableFields.reduce<string>((prev, curr, i) => {
if (i === listSearchableFields.length - 1) {
return `${prev} or ${curr.label || curr.name}`;
}
return `${prev}, ${curr.label || curr.name}`;
}, placeholder.current);
}
}, [listSearchableFields]);
return (
<div className={baseClass}>
<input
className={`${baseClass}__input`}
placeholder={`Search by ${fieldLabel}`}
placeholder={placeholder.current}
type="text"
value={search || ''}
onChange={(e) => setSearch(e.target.value)}

View File

@@ -1,8 +1,10 @@
import { FieldAffectingData } from '../../../../fields/config/types';
import { Where } from '../../../../types';
export type Props = {
fieldName?: string,
fieldLabel?: string,
modifySearchQuery?: boolean
listSearchableFields?: FieldAffectingData[]
handleChange?: (where: Where) => void
}

View File

@@ -15,17 +15,17 @@ import './index.scss';
const baseClass = 'status';
const unPublishModalSlug = 'confirm-un-publish';
const revertModalSlug = 'confirm-revert';
const Status: React.FC<Props> = () => {
const { publishedDoc, unpublishedVersions, collection, global, id, getVersions } = useDocumentInfo();
const { toggle, closeAll: closeAllModals } = useModal();
const { toggleModal } = useModal();
const { serverURL, routes: { api } } = useConfig();
const [processing, setProcessing] = useState(false);
const { reset: resetForm } = useForm();
const locale = useLocale();
const unPublishModalSlug = `confirm-un-publish-${id}`;
const revertModalSlug = `confirm-revert-${id}`;
let statusToRender;
if (unpublishedVersions?.docs?.length > 0 && publishedDoc) {
@@ -55,7 +55,7 @@ const Status: React.FC<Props> = () => {
if (collection) {
url = `${serverURL}${api}/${collection.slug}/${id}?depth=0&locale=${locale}&fallback-locale=null`;
method = 'put';
method = 'patch';
}
if (global) {
url = `${serverURL}${api}/globals/${global.slug}?depth=0&locale=${locale}&fallback-locale=null`;
@@ -92,8 +92,14 @@ const Status: React.FC<Props> = () => {
}
setProcessing(false);
closeAllModals();
}, [closeAllModals, collection, global, serverURL, api, resetForm, id, locale, getVersions, publishedDoc]);
if (action === 'revert') {
toggleModal(revertModalSlug);
}
if (action === 'unpublish') {
toggleModal(unPublishModalSlug);
}
}, [collection, global, publishedDoc, serverURL, api, id, locale, resetForm, getVersions, toggleModal, revertModalSlug, unPublishModalSlug]);
if (statusToRender) {
return (
@@ -104,7 +110,7 @@ const Status: React.FC<Props> = () => {
<React.Fragment>
&nbsp;&mdash;&nbsp;
<Button
onClick={() => toggle(unPublishModalSlug)}
onClick={() => toggleModal(unPublishModalSlug)}
className={`${baseClass}__action`}
buttonStyle="none"
>
@@ -120,7 +126,7 @@ const Status: React.FC<Props> = () => {
<Button
buttonStyle="secondary"
type="button"
onClick={processing ? undefined : () => toggle(unPublishModalSlug)}
onClick={processing ? undefined : () => toggleModal(unPublishModalSlug)}
>
Cancel
</Button>
@@ -137,7 +143,7 @@ const Status: React.FC<Props> = () => {
<React.Fragment>
&nbsp;&mdash;&nbsp;
<Button
onClick={() => toggle(revertModalSlug)}
onClick={() => toggleModal(revertModalSlug)}
className={`${baseClass}__action`}
buttonStyle="none"
>
@@ -153,7 +159,7 @@ const Status: React.FC<Props> = () => {
<Button
buttonStyle="secondary"
type="button"
onClick={processing ? undefined : () => toggle(revertModalSlug)}
onClick={processing ? undefined : () => toggleModal(revertModalSlug)}
>
Cancel
</Button>

View File

@@ -2,7 +2,7 @@ import React from 'react';
export type DescriptionFunction = () => string
export type DescriptionComponent = React.ComponentType
export type DescriptionComponent = React.ComponentType<any>
type Description = string | DescriptionFunction | DescriptionComponent

View File

@@ -14,7 +14,7 @@ const baseClass = 'condition-value-relationship';
const maxResultsPerRequest = 10;
const RelationshipField: React.FC<Props> = (props) => {
const { onChange, value, relationTo, hasMany } = props;
const { onChange, value, relationTo, hasMany, admin: { isSortable } = {} } = props;
const {
serverURL,
@@ -60,7 +60,7 @@ const RelationshipField: React.FC<Props> = (props) => {
const fieldToSearch = collection?.admin?.useAsTitle || 'id';
const searchParam = searchArg ? `&where[${fieldToSearch}][like]=${searchArg}` : '';
const response = await fetch(`${serverURL}${api}/${relation}?limit=${maxResultsPerRequest}&page=${lastLoadedPageToUse}&depth=0${searchParam}`);
const response = await fetch(`${serverURL}${api}/${relation}?limit=${maxResultsPerRequest}&page=${lastLoadedPageToUse}&depth=0${searchParam}`, { credentials: 'include' });
if (response.ok) {
const data: PaginatedDocs<any> = await response.json();
@@ -152,7 +152,7 @@ const RelationshipField: React.FC<Props> = (props) => {
const addOptionByID = useCallback(async (id, relation) => {
if (!errorLoading && id !== 'null') {
const response = await fetch(`${serverURL}${api}/${relation}/${id}?depth=0`);
const response = await fetch(`${serverURL}${api}/${relation}/${id}?depth=0`, { credentials: 'include' });
if (response.ok) {
const data = await response.json();
@@ -253,6 +253,7 @@ const RelationshipField: React.FC<Props> = (props) => {
value={valueToRender}
options={options}
isMulti={hasMany}
isSortable={isSortable}
/>
)}
{errorLoading && (

View File

@@ -62,16 +62,33 @@ const WhereBuilder: React.FC<Props> = (props) => {
const [reducedFields] = useState(() => reduceFields(collection.fields));
useThrottledEffect(() => {
const currentParams = queryString.parse(history.location.search, { ignoreQueryPrefix: true, depth: 10 });
const currentParams = queryString.parse(history.location.search, { ignoreQueryPrefix: true, depth: 10 }) as { where: Where };
const paramsToKeep = typeof currentParams?.where === 'object' && 'or' in currentParams.where ? currentParams.where.or.reduce((keptParams, param) => {
const newParam = { ...param };
if (param.and) {
delete newParam.and;
}
return [
...keptParams,
newParam,
];
}, []) : [];
const newWhereQuery = {
...typeof currentParams?.where === 'object' ? currentParams.where : {},
or: conditions,
or: [
...conditions,
...paramsToKeep,
],
};
if (handleChange) handleChange(newWhereQuery as Where);
if (modifySearchQuery) {
const hasExistingConditions = typeof currentParams?.where === 'object' && 'or' in currentParams.where;
const hasNewWhereConditions = conditions.length > 0;
if (modifySearchQuery && ((hasExistingConditions && !hasNewWhereConditions) || hasNewWhereConditions)) {
history.replace({
search: queryString.stringify({
...currentParams,

View File

@@ -1,6 +1,7 @@
@import '../../../scss/styles';
.field-error.tooltip {
font-family: var(--font-body);
top: 0;
bottom: auto;
left: auto;
@@ -11,4 +12,4 @@
span {
border-top-color: var(--theme-error-500);
}
}
}

View File

@@ -1,7 +1,12 @@
/* eslint-disable no-param-reassign */
import ObjectID from 'bson-objectid';
import { User } from '../../../../../auth';
import { NonPresentationalField, fieldAffectsData, fieldHasSubFields } from '../../../../../fields/config/types';
import {
NonPresentationalField,
fieldAffectsData,
fieldHasSubFields,
tabHasName,
} from '../../../../../fields/config/types';
import getValueWithDefault from '../../../../../fields/getDefaultValue';
import { Fields, Field, Data } from '../types';
import { iterateFields } from './iterateFields';
@@ -213,9 +218,9 @@ export const addFieldStatePromise = async ({
iterateFields({
state,
fields: tab.fields,
data,
data: tabHasName(tab) ? data?.[tab.name] : data,
parentPassesCondition: passesCondition,
path,
path: tabHasName(tab) ? `${path}${tab.name}.` : path,
user,
fieldPromises,
fullData,

View File

@@ -1,11 +1,13 @@
import { createContext, useContext } from 'react';
import { Context } from './types';
import { useContextSelector, createContext as createSelectorContext, useContext as useFullContext } from 'use-context-selector';
import { Context, FormFieldsContext as FormFieldsContextType } from './types';
const FormContext = createContext({} as Context);
const FormWatchContext = createContext({} as Context);
const SubmittedContext = createContext(false);
const ProcessingContext = createContext(false);
const ModifiedContext = createContext(false);
const FormFieldsContext = createSelectorContext<FormFieldsContextType>([{}, () => null]);
const useForm = (): Context => useContext(FormContext);
const useWatchForm = (): Context => useContext(FormWatchContext);
@@ -13,15 +15,21 @@ const useFormSubmitted = (): boolean => useContext(SubmittedContext);
const useFormProcessing = (): boolean => useContext(ProcessingContext);
const useFormModified = (): boolean => useContext(ModifiedContext);
const useFormFields = <Value = unknown>(selector: (context: FormFieldsContextType) => Value): Value => useContextSelector(FormFieldsContext, selector);
const useAllFormFields = (): FormFieldsContextType => useFullContext(FormFieldsContext);
export {
FormContext,
FormWatchContext,
SubmittedContext,
ProcessingContext,
ModifiedContext,
useForm,
useWatchForm,
useFormSubmitted,
useFormProcessing,
useFormModified,
useForm,
FormContext,
FormFieldsContext,
useFormFields,
useAllFormFields,
FormWatchContext,
useWatchForm,
};

View File

@@ -1,13 +1,12 @@
import equal from 'deep-equal';
import ObjectID from 'bson-objectid';
import { unflatten, flatten } from 'flatley';
import flattenFilters from './flattenFilters';
import getSiblingData from './getSiblingData';
import reduceFieldsToValues from './reduceFieldsToValues';
import { Fields } from './types';
import { Field, FieldAction, Fields } from './types';
import deepCopyObject from '../../../../utilities/deepCopyObject';
const unflattenRowsFromState = (state: Fields, path) => {
const unflattenRowsFromState = (state: Fields, path: string) => {
// Take a copy of state
const remainingFlattenedState = { ...state };
@@ -38,7 +37,7 @@ const unflattenRowsFromState = (state: Fields, path) => {
};
};
function fieldReducer(state: Fields, action): Fields {
function fieldReducer(state: Fields, action: FieldAction): Fields {
switch (action.type) {
case 'REPLACE_STATE': {
const newState = {};
@@ -65,7 +64,7 @@ function fieldReducer(state: Fields, action): Fields {
case 'REMOVE': {
const newState = { ...state };
delete newState[action.path];
if (newState[action.path]) delete newState[action.path];
return newState;
}
@@ -187,23 +186,27 @@ function fieldReducer(state: Fields, action): Fields {
}, {});
}
default: {
const newField = {
value: action.value,
valid: action.valid,
errorMessage: action.errorMessage,
disableFormData: action.disableFormData,
initialValue: action.initialValue,
validate: action.validate,
condition: action.condition,
passesCondition: action.passesCondition,
};
case 'UPDATE': {
const newField = Object.entries(action).reduce((field, [key, value]) => {
if (['value', 'valid', 'errorMessage', 'disableFormData', 'initialValue', 'validate', 'condition', 'passesCondition'].includes(key)) {
return {
...field,
[key]: value,
};
}
return field;
}, state[action.path] || {} as Field);
return {
...state,
[action.path]: newField,
};
}
default: {
return state;
}
}
}

View File

@@ -21,7 +21,7 @@ import { Field } from '../../../../fields/config/types';
import buildInitialState from './buildInitialState';
import errorMessages from './errorMessages';
import { Context as FormContextType, GetDataByPath, Props, SubmitOptions } from './types';
import { SubmittedContext, ProcessingContext, ModifiedContext, FormContext, FormWatchContext } from './context';
import { SubmittedContext, ProcessingContext, ModifiedContext, FormContext, FormFieldsContext, FormWatchContext } from './context';
import buildStateFromSchema from './buildStateFromSchema';
import { useOperation } from '../../utilities/OperationProvider';
@@ -63,12 +63,11 @@ const Form: React.FC<Props> = (props) => {
if (formattedInitialData) initialFieldState = formattedInitialData;
if (initialState) initialFieldState = initialState;
// Allow access to initialState for field types such as Blocks and Array
contextRef.current.initialState = initialState;
const [fields, dispatchFields] = useReducer(fieldReducer, {}, () => initialFieldState);
const fieldsReducer = useReducer(fieldReducer, {}, () => initialFieldState);
const [fields, dispatchFields] = fieldsReducer;
contextRef.current.fields = fields;
contextRef.current.dispatchFields = dispatchFields;
const validateForm = useCallback(async () => {
const validatedFieldState = {};
@@ -111,7 +110,7 @@ const Form: React.FC<Props> = (props) => {
}
return isValid;
}, [contextRef, id, user, operation]);
}, [contextRef, id, user, operation, dispatchFields]);
const submit = useCallback(async (options: SubmitOptions = {}, e): Promise<void> => {
const {
@@ -254,6 +253,7 @@ const Form: React.FC<Props> = (props) => {
fieldErrors.forEach((err) => {
dispatchFields({
type: 'UPDATE',
...(contextRef.current?.fields?.[err.field] || {}),
valid: false,
errorMessage: err.message,
@@ -283,6 +283,7 @@ const Form: React.FC<Props> = (props) => {
action,
disableSuccessStatus,
disabled,
dispatchFields,
fields,
handleResponse,
history,
@@ -298,7 +299,6 @@ const Form: React.FC<Props> = (props) => {
const getData = useCallback(() => reduceFieldsToValues(contextRef.current.fields, true), [contextRef]);
const getSiblingData = useCallback((path: string) => getSiblingDataFunc(contextRef.current.fields, path), [contextRef]);
const getDataByPath = useCallback<GetDataByPath>((path: string) => getDataByPathFunc(contextRef.current.fields, path), [contextRef]);
const getUnflattenedValues = useCallback(() => reduceFieldsToValues(contextRef.current.fields), [contextRef]);
const createFormData = useCallback((overrides: any = {}) => {
const data = reduceFieldsToValues(contextRef.current.fields, true);
@@ -328,16 +328,14 @@ const Form: React.FC<Props> = (props) => {
const state = await buildStateFromSchema({ fieldSchema, data, user, id, operation, locale });
contextRef.current = { ...initContextState } as FormContextType;
dispatchFields({ type: 'REPLACE_STATE', state });
}, [id, user, operation, locale]);
}, [id, user, operation, locale, dispatchFields]);
contextRef.current.dispatchFields = dispatchFields;
contextRef.current.submit = submit;
contextRef.current.getFields = getFields;
contextRef.current.getField = getField;
contextRef.current.getData = getData;
contextRef.current.getSiblingData = getSiblingData;
contextRef.current.getDataByPath = getDataByPath;
contextRef.current.getUnflattenedValues = getUnflattenedValues;
contextRef.current.validateForm = validateForm;
contextRef.current.createFormData = createFormData;
contextRef.current.setModified = setModified;
@@ -352,7 +350,7 @@ const Form: React.FC<Props> = (props) => {
contextRef.current = { ...initContextState } as FormContextType;
dispatchFields({ type: 'REPLACE_STATE', state: initialState });
}
}, [initialState]);
}, [initialState, dispatchFields]);
useEffect(() => {
if (initialData) {
@@ -361,12 +359,16 @@ const Form: React.FC<Props> = (props) => {
setFormattedInitialData(builtState);
dispatchFields({ type: 'REPLACE_STATE', state: builtState });
}
}, [initialData]);
}, [initialData, dispatchFields]);
useThrottledEffect(() => {
refreshCookie();
}, 15000, [fields]);
// Re-run form validation every second
// as fields change, because field validations can
// potentially rely on OTHER field values to determine
// if they are valid or not (siblingData, data)
useThrottledEffect(() => {
validateForm();
}, 1000, [validateForm, fields]);
@@ -399,7 +401,9 @@ const Form: React.FC<Props> = (props) => {
<SubmittedContext.Provider value={submitted}>
<ProcessingContext.Provider value={processing}>
<ModifiedContext.Provider value={modified}>
{children}
<FormFieldsContext.Provider value={fieldsReducer}>
{children}
</FormFieldsContext.Provider>
</ModifiedContext.Provider>
</ProcessingContext.Provider>
</SubmittedContext.Provider>

View File

@@ -6,7 +6,6 @@ import {
Submit,
Context,
GetSiblingData,
GetUnflattenedValues,
ValidateForm,
CreateFormData,
SetModified,
@@ -17,7 +16,6 @@ import {
const submit: Submit = () => undefined;
const getSiblingData: GetSiblingData = () => undefined;
const getUnflattenedValues: GetUnflattenedValues = () => ({});
const dispatchFields: DispatchFields = () => undefined;
const validateForm: ValidateForm = () => undefined;
const createFormData: CreateFormData = () => undefined;
@@ -28,12 +26,11 @@ const setSubmitted: SetSubmitted = () => undefined;
const reset: Reset = () => undefined;
const initialContextState: Context = {
getFields: (): Fields => ({ }),
getFields: (): Fields => ({}),
getField: (): Field => undefined,
getData: (): Data => undefined,
getSiblingData,
getDataByPath: () => undefined,
getUnflattenedValues,
validateForm,
createFormData,
submit,
@@ -41,7 +38,6 @@ const initialContextState: Context = {
setModified,
setProcessing,
setSubmitted,
initialState: {},
fields: {},
disabled: false,
formRef: null,

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