Compare commits
686 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b22d91579 | ||
|
|
09b9ce60ad | ||
|
|
e20a07e06f | ||
|
|
0204aa962c | ||
|
|
d46051435f | ||
|
|
0d56cf4496 | ||
|
|
61dae53fc3 | ||
|
|
f5377120c4 | ||
|
|
780106c7f6 | ||
|
|
811e4bb265 | ||
|
|
111cab1564 | ||
|
|
131dd51c39 | ||
|
|
0d333ce7da | ||
|
|
fa7ad6e9ed | ||
|
|
9b6dd0c771 | ||
|
|
a7a0624235 | ||
|
|
8f08269916 | ||
|
|
dbd305acc5 | ||
|
|
83d32e4449 | ||
|
|
78d7aa7707 | ||
|
|
a3ecd7324a | ||
|
|
87525001f7 | ||
|
|
f52836a7e3 | ||
|
|
fe536f0628 | ||
|
|
1de5ac8cba | ||
|
|
2624ad5f7e | ||
|
|
7fb23ab6be | ||
|
|
208e6fe47a | ||
|
|
9a1c1f64c0 | ||
|
|
65fe6c2f95 | ||
|
|
65ade57c94 | ||
|
|
2565005cc0 | ||
|
|
de4538bf5f | ||
|
|
2b80020393 | ||
|
|
1166e85fad | ||
|
|
e5193476c4 | ||
|
|
5dbf276e82 | ||
|
|
a6f3230915 | ||
|
|
0694a09abd | ||
|
|
dceeeaac6a | ||
|
|
bdc92bcdf0 | ||
|
|
06a6056e1c | ||
|
|
4b951279a8 | ||
|
|
3984f8b36c | ||
|
|
b9b3a0dcc7 | ||
|
|
7caa025278 | ||
|
|
27e5dc4d55 | ||
|
|
63fc9b76be | ||
|
|
5c1e2846a2 | ||
|
|
73c45944f4 | ||
|
|
86171c500f | ||
|
|
1010334f76 | ||
|
|
f44869f7cb | ||
|
|
4b0c5cc9a2 | ||
|
|
36b8f46f7e | ||
|
|
d5ecbc96a3 | ||
|
|
760114b718 | ||
|
|
76068637fb | ||
|
|
bed0b1c04e | ||
|
|
8a1cdc3f7d | ||
|
|
462f7f1f42 | ||
|
|
10a657e9d7 | ||
|
|
ae7a94bf47 | ||
|
|
e12bdb320d | ||
|
|
f667937aef | ||
|
|
20c508cb12 | ||
|
|
c00e5e8904 | ||
|
|
c6b00c9aca | ||
|
|
7154a1e35c | ||
|
|
734662a9b4 | ||
|
|
b11aa62001 | ||
|
|
f4c066e475 | ||
|
|
1d7a789604 | ||
|
|
3f74eff0fe | ||
|
|
2a2d33cd0b | ||
|
|
4e61ae84f7 | ||
|
|
165634d127 | ||
|
|
082c6d010c | ||
|
|
62f65d5ffb | ||
|
|
738698edad | ||
|
|
a83064ed97 | ||
|
|
5ef407ba17 | ||
|
|
21fabfcb29 | ||
|
|
521f250688 | ||
|
|
df33ceca79 | ||
|
|
0ffbd2ee0f | ||
|
|
4d64569c34 | ||
|
|
32c025a2fa | ||
|
|
58fa48a4da | ||
|
|
699aa1b60f | ||
|
|
a00c565a62 | ||
|
|
90bac7f4ab | ||
|
|
ec2cda20cf | ||
|
|
1702261a15 | ||
|
|
bd85d621f5 | ||
|
|
61bc485bf1 | ||
|
|
eaf89794ab | ||
|
|
6a6b23d3d6 | ||
|
|
5bb8a6d6d5 | ||
|
|
cc73db9bf0 | ||
|
|
48d2e230a3 | ||
|
|
0ac24bc4f2 | ||
|
|
29e148763a | ||
|
|
354d1527e1 | ||
|
|
ffa56e6c81 | ||
|
|
edcc6b56c8 | ||
|
|
7e2f3e21ba | ||
|
|
0d238085ba | ||
|
|
082bf2fba4 | ||
|
|
56a3ccaee8 | ||
|
|
3f4c66467d | ||
|
|
85ccd80906 | ||
|
|
de539d00b6 | ||
|
|
f37c9eec04 | ||
|
|
9fd3e8d84f | ||
|
|
07c917e33d | ||
|
|
10c51f11c5 | ||
|
|
5431b8ebcc | ||
|
|
e233551d0b | ||
|
|
5d4d1bf60c | ||
|
|
e6bfe00843 | ||
|
|
5c3b20643a | ||
|
|
e9600802a8 | ||
|
|
0aa2e5138c | ||
|
|
27dad40d5c | ||
|
|
646b46528d | ||
|
|
e097e924f2 | ||
|
|
777aaf9afd | ||
|
|
221ec0bb10 | ||
|
|
67ff273a9e | ||
|
|
397d36291d | ||
|
|
a4066e4802 | ||
|
|
88e24ba452 | ||
|
|
c7b377b84c | ||
|
|
05a06a9aa8 | ||
|
|
90a686ea0b | ||
|
|
215df7ff9e | ||
|
|
0758f01120 | ||
|
|
bb7a910377 | ||
|
|
078293833d | ||
|
|
99f61150d6 | ||
|
|
ac64013a07 | ||
|
|
5735b4bbec | ||
|
|
f0374d5772 | ||
|
|
20a9dcf6fe | ||
|
|
d0ef42a16b | ||
|
|
18077d98bf | ||
|
|
cda4792ff7 | ||
|
|
76df2bc75a | ||
|
|
e52b639e64 | ||
|
|
75604e9b6c | ||
|
|
e058933192 | ||
|
|
a12ae0a1b6 | ||
|
|
5e0fd00b4e | ||
|
|
b4a43475ac | ||
|
|
f0bc59dd0c | ||
|
|
c5aba71aa7 | ||
|
|
cac6a8869b | ||
|
|
787691859d | ||
|
|
bf0d20461a | ||
|
|
7da73626ac | ||
|
|
8086120703 | ||
|
|
7afa815467 | ||
|
|
5400500360 | ||
|
|
ccae4014ec | ||
|
|
02279a769d | ||
|
|
c766a5a40e | ||
|
|
7ac2d0dcff | ||
|
|
6dfa1616ea | ||
|
|
8a0411d6b1 | ||
|
|
fc63a692be | ||
|
|
2b9b504573 | ||
|
|
fd9453889e | ||
|
|
dea21a0aae | ||
|
|
12c520c3b1 | ||
|
|
abc0383ecc | ||
|
|
bb4dce09df | ||
|
|
6b932168ce | ||
|
|
681bba82a9 | ||
|
|
4cfde3eba7 | ||
|
|
71633b1c8d | ||
|
|
2eb863aff0 | ||
|
|
5aede8affb | ||
|
|
09dfb9793a | ||
|
|
09897de77d | ||
|
|
0362290d40 | ||
|
|
57ef9fe623 | ||
|
|
38f08d54e3 | ||
|
|
84191ec8fd | ||
|
|
e88be6b251 | ||
|
|
3bec424d05 | ||
|
|
fc56a42fb0 | ||
|
|
05a5cc16bd | ||
|
|
c99b2f35f4 | ||
|
|
8ae7457a3f | ||
|
|
bb8a04dd7b | ||
|
|
a681f5f725 | ||
|
|
613552de0a | ||
|
|
cd38d37242 | ||
|
|
6b6b503ef0 | ||
|
|
f880072697 | ||
|
|
ffc3f10177 | ||
|
|
f9b7e3239e | ||
|
|
09cc61c96a | ||
|
|
a6325c69f4 | ||
|
|
2f1b1a02b1 | ||
|
|
727c5a4a53 | ||
|
|
918d3bd2bc | ||
|
|
2d7bacad09 | ||
|
|
234ed07ad6 | ||
|
|
964fdf779a | ||
|
|
40afb5e8fa | ||
|
|
72c0145816 | ||
|
|
12eb33b5c9 | ||
|
|
ddb238c7e4 | ||
|
|
32980fcafc | ||
|
|
db6315a621 | ||
|
|
6bf0c2aab6 | ||
|
|
3dc9ddcb52 | ||
|
|
352dfa9a09 | ||
|
|
60a3e680dd | ||
|
|
18f98e24e5 | ||
|
|
c599522e44 | ||
|
|
64fef7d380 | ||
|
|
6381d4cf6b | ||
|
|
91016ec206 | ||
|
|
9aeab9548d | ||
|
|
69d7f21b56 | ||
|
|
9de596605b | ||
|
|
650edcf56a | ||
|
|
2c4dedff2c | ||
|
|
870e39961c | ||
|
|
319148ca68 | ||
|
|
6dac68606b | ||
|
|
9716d008a9 | ||
|
|
b5fd68c3dc | ||
|
|
eea9d14749 | ||
|
|
77de6e4b60 | ||
|
|
58f68e3bda | ||
|
|
1ea3763185 | ||
|
|
920251f296 | ||
|
|
dffb554ebe | ||
|
|
7255f6108f | ||
|
|
078392f2c5 | ||
|
|
c2e9407f50 | ||
|
|
a5a4f1490e | ||
|
|
e20555f9cf | ||
|
|
791a222d28 | ||
|
|
cb97090d88 | ||
|
|
923cdc7b6a | ||
|
|
19369b4c33 | ||
|
|
4ec230e8a0 | ||
|
|
7aafe49662 | ||
|
|
33d8ec8a13 | ||
|
|
fb73e772af | ||
|
|
c2692e9b4a | ||
|
|
a0d6fe5ca7 | ||
|
|
7f65e68e82 | ||
|
|
d9f78fc5bf | ||
|
|
b2a5279bcb | ||
|
|
28baba822b | ||
|
|
3b4875b754 | ||
|
|
8ce88deda2 | ||
|
|
4527658027 | ||
|
|
8057c0ecb8 | ||
|
|
eb74a42386 | ||
|
|
d589a90a42 | ||
|
|
bf3d05cb40 | ||
|
|
7bd4859a31 | ||
|
|
9af097c39c | ||
|
|
7f47cdacbb | ||
|
|
d85cbde7a7 | ||
|
|
79284941db | ||
|
|
35c11aa7aa | ||
|
|
5ac2b1fb3d | ||
|
|
0a5e8ab618 | ||
|
|
5ae267f743 | ||
|
|
bc48e92aea | ||
|
|
98af494dd6 | ||
|
|
31487fb022 | ||
|
|
e00a2eeef7 | ||
|
|
c5ea2dcd2a | ||
|
|
e9c2b46c97 | ||
|
|
043d3fffbf | ||
|
|
febe213e80 | ||
|
|
0764f34c64 | ||
|
|
3dbf9ac27c | ||
|
|
eccb4383a1 | ||
|
|
31180bfc2b | ||
|
|
c6105d9ed3 | ||
|
|
3b82d1ccba | ||
|
|
9fe8a09ac9 | ||
|
|
18a59f84ab | ||
|
|
c28f78cb8a | ||
|
|
18baefed0d | ||
|
|
f56eeddff4 | ||
|
|
f5561b4b4f | ||
|
|
d42171d465 | ||
|
|
f3d56fe435 | ||
|
|
52e9e4daa7 | ||
|
|
dde76572e3 | ||
|
|
df4563274b | ||
|
|
6e93bd006e | ||
|
|
814cc9658c | ||
|
|
efc6d0adf1 | ||
|
|
f37fe42c70 | ||
|
|
3359cd4e9b | ||
|
|
2b80c5fda6 | ||
|
|
4c2bcb2933 | ||
|
|
96c1362a5d | ||
|
|
5f3b1aec0a | ||
|
|
942afd96fe | ||
|
|
39ddde082c | ||
|
|
9b384e8192 | ||
|
|
09543831b9 | ||
|
|
eb0b5870a3 | ||
|
|
49c03135a9 | ||
|
|
22ba992b6d | ||
|
|
553d3ebd48 | ||
|
|
f80bda89b6 | ||
|
|
722f2fc88d | ||
|
|
c89d2e0f01 | ||
|
|
7aab4214b7 | ||
|
|
6294333c88 | ||
|
|
98afd9c53b | ||
|
|
6ccca08319 | ||
|
|
f497fdde22 | ||
|
|
d73fdf6572 | ||
|
|
0e6f790734 | ||
|
|
18774c05ea | ||
|
|
b3dda3c070 | ||
|
|
d9e62e9a6d | ||
|
|
3f5518aa3a | ||
|
|
0c12b8dfda | ||
|
|
a53ac62164 | ||
|
|
8fa63c7131 | ||
|
|
5599b8bd30 | ||
|
|
5694258b74 | ||
|
|
a974c908b5 | ||
|
|
6d7d996d61 | ||
|
|
da4d9c5e71 | ||
|
|
c409ac985c | ||
|
|
62072e2d0f | ||
|
|
18e08ad5a7 | ||
|
|
ae8cc7b9ed | ||
|
|
06fd32786d | ||
|
|
2f5c13aac3 | ||
|
|
5025972450 | ||
|
|
e7d3f77560 | ||
|
|
f171adc88d | ||
|
|
e24428c42c | ||
|
|
e849f0f49e | ||
|
|
b011163d90 | ||
|
|
0492603ac7 | ||
|
|
15a488c03f | ||
|
|
77096cb252 | ||
|
|
6efeafe819 | ||
|
|
dabc5749d4 | ||
|
|
79fc3f72a6 | ||
|
|
7c2de28de9 | ||
|
|
83f77d3a05 | ||
|
|
5b71a3cb60 | ||
|
|
04650a86a5 | ||
|
|
69b4f9f59f | ||
|
|
4df979565e | ||
|
|
193cdbbe5e | ||
|
|
a634a5c00c | ||
|
|
c19ccd5df4 | ||
|
|
57c6afa3a9 | ||
|
|
9354d86af2 | ||
|
|
791de301cc | ||
|
|
87ccdae795 | ||
|
|
a412e68e89 | ||
|
|
98e2f2be08 | ||
|
|
b8b5ca17ed | ||
|
|
96ccebdedd | ||
|
|
59da900a1d | ||
|
|
fbc4429648 | ||
|
|
3feec809e6 | ||
|
|
8dd3298f9a | ||
|
|
0c3986917f | ||
|
|
4c11134141 | ||
|
|
45c824c042 | ||
|
|
181d27e33d | ||
|
|
0e225e1ab1 | ||
|
|
5676f22ede | ||
|
|
e9cc178ed7 | ||
|
|
09a3c859c1 | ||
|
|
628d68ed2d | ||
|
|
fe99952561 | ||
|
|
9c28086ba4 | ||
|
|
f8c225316b | ||
|
|
df1f381354 | ||
|
|
939a2923d3 | ||
|
|
0e15f8376c | ||
|
|
c9d9d5269d | ||
|
|
97f8f2b749 | ||
|
|
ea21480230 | ||
|
|
2918bbeeca | ||
|
|
ac1aea23dc | ||
|
|
d478bd0e34 | ||
|
|
1695e4e685 | ||
|
|
20bf4680db | ||
|
|
4f3dd59d25 | ||
|
|
dde3a35b16 | ||
|
|
a964860e55 | ||
|
|
41dcdabe9c | ||
|
|
8ad96452a0 | ||
|
|
9f426913a2 | ||
|
|
e8376a5662 | ||
|
|
08dcd7f8b2 | ||
|
|
03e1b730b5 | ||
|
|
8252c409d4 | ||
|
|
70ae5b1a19 | ||
|
|
0d42060882 | ||
|
|
e35b212781 | ||
|
|
08731594cb | ||
|
|
c439297223 | ||
|
|
88d80aec55 | ||
|
|
6f6289a069 | ||
|
|
af65787862 | ||
|
|
a39cec2b76 | ||
|
|
f20feae4d9 | ||
|
|
2d1169ac66 | ||
|
|
966dd7a40f | ||
|
|
eee835696c | ||
|
|
e95edbd3e4 | ||
|
|
b462fe5f77 | ||
|
|
a1865ed5fb | ||
|
|
209d812239 | ||
|
|
4e3cc2bc05 | ||
|
|
f5731f2fc8 | ||
|
|
29b608d6f8 | ||
|
|
a10dd738c0 | ||
|
|
0fb96ff3e9 | ||
|
|
55488c5c2e | ||
|
|
5345eb993a | ||
|
|
8f67ee4ac3 | ||
|
|
9f8b1d9521 | ||
|
|
d4c4418a3e | ||
|
|
bb5f747052 | ||
|
|
f2f8ffbbb2 | ||
|
|
fab4c0ed76 | ||
|
|
7b3cefd4ef | ||
|
|
ad483da837 | ||
|
|
01d49ccd36 | ||
|
|
7ceadc15a3 | ||
|
|
3caf81d9f7 | ||
|
|
dd6740e109 | ||
|
|
28e4cf6726 | ||
|
|
95e38a568f | ||
|
|
43f1f41179 | ||
|
|
698a9ead04 | ||
|
|
bd3f628d55 | ||
|
|
14d702ff8d | ||
|
|
dd5acae76f | ||
|
|
9de51bbf02 | ||
|
|
9c44a159b9 | ||
|
|
43e3162c7d | ||
|
|
2b81e887ad | ||
|
|
bfd492b7ed | ||
|
|
bf452c23bc | ||
|
|
da3495cb4b | ||
|
|
899a158843 | ||
|
|
163daf5816 | ||
|
|
7fbab16b22 | ||
|
|
17b8335c3c | ||
|
|
93930d1125 | ||
|
|
9cb630f98d | ||
|
|
f695bd8c1d | ||
|
|
8a8222995f | ||
|
|
ca24263c17 | ||
|
|
0bf0dd7d5e | ||
|
|
ef3a70cc38 | ||
|
|
6fe09c8264 | ||
|
|
4616e7fa16 | ||
|
|
d99788f6f7 | ||
|
|
8a1bd762ba | ||
|
|
c760863f8c | ||
|
|
ed5c3b55f1 | ||
|
|
7d0e2820cc | ||
|
|
f381b9261a | ||
|
|
01f170ba73 | ||
|
|
a53e24904f | ||
|
|
423cdbd63f | ||
|
|
e38c0e687f | ||
|
|
9e82f9d02e | ||
|
|
5f9c48494a | ||
|
|
155db092c4 | ||
|
|
87522ff7d6 | ||
|
|
e6b435e88e | ||
|
|
8c78a0a773 | ||
|
|
597f7d3075 | ||
|
|
9da8aca015 | ||
|
|
13aa1eb13d | ||
|
|
918228c9dc | ||
|
|
92a25867ff | ||
|
|
f698594137 | ||
|
|
c15dff4afb | ||
|
|
985db67d48 | ||
|
|
30c5eeaff9 | ||
|
|
fd81085b54 | ||
|
|
9955621f0a | ||
|
|
599a693f5b | ||
|
|
da6a9a2ba7 | ||
|
|
7002752744 | ||
|
|
53ed79ecbc | ||
|
|
1e342f8bee | ||
|
|
d19576978b | ||
|
|
af59822510 | ||
|
|
f45f93ce38 | ||
|
|
0d1ffdcb4b | ||
|
|
b197d780c1 | ||
|
|
45da1369ca | ||
|
|
478d1cf5ee | ||
|
|
ec0b2c6a5b | ||
|
|
3a5881cc48 | ||
|
|
13c857f753 | ||
|
|
5091a40e96 | ||
|
|
1cf45f7fa9 | ||
|
|
b49b531ce4 | ||
|
|
7553e14a8b | ||
|
|
cb984f4d54 | ||
|
|
9630c2c3b2 | ||
|
|
8bfc892f7c | ||
|
|
756d275edf | ||
|
|
bfe7a139af | ||
|
|
95352f47ea | ||
|
|
98a9eb8d23 | ||
|
|
9a62c2ab5d | ||
|
|
9c928d497c | ||
|
|
6f777dd09c | ||
|
|
5777868eba | ||
|
|
063b3b86c8 | ||
|
|
9ef9cca948 | ||
|
|
74ac23dbc6 | ||
|
|
b2145a0a59 | ||
|
|
fe307ecfeb | ||
|
|
80b99653cf | ||
|
|
75aa92952a | ||
|
|
3375963449 | ||
|
|
f91c47bb37 | ||
|
|
60552d9d86 | ||
|
|
857cf088f6 | ||
|
|
5c506a1db7 | ||
|
|
b81c7917a9 | ||
|
|
5802011eea | ||
|
|
c91f4d9e0d | ||
|
|
b5690cf721 | ||
|
|
aaee4d16d1 | ||
|
|
5148a21769 | ||
|
|
ec461e2440 | ||
|
|
ada2b79054 | ||
|
|
3d652b2c9a | ||
|
|
63bc00e8da | ||
|
|
8162b1fc93 | ||
|
|
a009387461 | ||
|
|
8dbf10dc4a | ||
|
|
954cd9437b | ||
|
|
1c532d3719 | ||
|
|
273351103d | ||
|
|
fd15b06773 | ||
|
|
9efb5e881b | ||
|
|
e674bb6779 | ||
|
|
8df93b23b5 | ||
|
|
afc47d92b5 | ||
|
|
d47c8a240a | ||
|
|
9be27927c1 | ||
|
|
e0c1400ee4 | ||
|
|
eb604a566d | ||
|
|
0b2add2e1a | ||
|
|
1ca046532d | ||
|
|
c1843170d6 | ||
|
|
85bf65e40e | ||
|
|
2a2494366b | ||
|
|
b53dbc1a27 | ||
|
|
6c881e3c48 | ||
|
|
17b4251176 | ||
|
|
c7411fd347 | ||
|
|
f32e7f8f4c | ||
|
|
2667c6fa98 | ||
|
|
46309f33a2 | ||
|
|
fb495d7b04 | ||
|
|
f8840ee8b7 | ||
|
|
3c025377f5 | ||
|
|
a01b308c38 | ||
|
|
53c56c537d | ||
|
|
a669ca7099 | ||
|
|
258d4cd62e | ||
|
|
fe2c1e69e5 | ||
|
|
a77dcaa6c2 | ||
|
|
dfd4f75c98 | ||
|
|
322b726be8 | ||
|
|
2978509034 | ||
|
|
d9c1b41e08 | ||
|
|
9538af2e92 | ||
|
|
2eb19876ee | ||
|
|
74fb5397ac | ||
|
|
0630c006d5 | ||
|
|
71a5a525d1 | ||
|
|
5ba0313b8f | ||
|
|
7a775166b2 | ||
|
|
9edf4ae2da | ||
|
|
fcef33ad1f | ||
|
|
cbf18d75a7 | ||
|
|
6f38e5c264 | ||
|
|
ead0568a94 | ||
|
|
5b5acd7471 | ||
|
|
87e79817f7 | ||
|
|
2b969b91b9 | ||
|
|
609bf54c45 | ||
|
|
9a104b5d12 | ||
|
|
65eccb129f | ||
|
|
022bf17b59 | ||
|
|
ae7583401e | ||
|
|
1ca904f164 | ||
|
|
bc884e64dd | ||
|
|
89ceff24fc | ||
|
|
47b2940358 | ||
|
|
facff24abc | ||
|
|
8f10f14bc4 | ||
|
|
3c522930ee | ||
|
|
0da92c20ea | ||
|
|
43db6a491d | ||
|
|
ae77d68f72 | ||
|
|
0e76d3e79d | ||
|
|
f0ae33f780 | ||
|
|
684e23fa2f | ||
|
|
bf2ed1747c | ||
|
|
90a09e268f | ||
|
|
0cc468ad6a | ||
|
|
16b9aab785 | ||
|
|
749388e877 | ||
|
|
17dfa693ba | ||
|
|
2dcb80e681 | ||
|
|
26ab75a356 | ||
|
|
97c31faea1 | ||
|
|
8b167e9a9e | ||
|
|
d9bf8a5fcc | ||
|
|
7b5dd0ac5f | ||
|
|
210295e704 | ||
|
|
ed56971de6 | ||
|
|
298948b203 | ||
|
|
f5b55ddbb4 | ||
|
|
99d083bb09 | ||
|
|
455f2ab183 | ||
|
|
f4127e1820 | ||
|
|
8fa95b7851 | ||
|
|
aefdabfd7e | ||
|
|
470a33f7f9 | ||
|
|
d179427d7b | ||
|
|
599af3f924 | ||
|
|
6e383d9005 | ||
|
|
d74c1e3b93 | ||
|
|
c1814cbc3f | ||
|
|
c0fef9bfde | ||
|
|
1363bec776 | ||
|
|
f3972430f6 | ||
|
|
998cb656a5 | ||
|
|
1a8228c08b | ||
|
|
07f9a06571 | ||
|
|
9c8145238c | ||
|
|
3445f5bdce | ||
|
|
8d3573d734 | ||
|
|
1fc32fa0bb | ||
|
|
255f6e6215 | ||
|
|
1aa649782b | ||
|
|
16d2f400c4 | ||
|
|
4d99594021 | ||
|
|
790c85df5a | ||
|
|
0a98778ec4 | ||
|
|
cc4794e5ac | ||
|
|
e301efdb7b | ||
|
|
a4cf98c460 | ||
|
|
c9e37bcef5 | ||
|
|
deaf5cde5f | ||
|
|
d38b8e746c | ||
|
|
1be0890f03 | ||
|
|
d45ff316c0 | ||
|
|
d2055141be | ||
|
|
846589e303 | ||
|
|
8efc03b325 | ||
|
|
28d8c10a3e | ||
|
|
cddba8d9e7 | ||
|
|
fa02f00503 | ||
|
|
9a095f574b |
@@ -4,7 +4,7 @@ charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.js]
|
||||
[*.{js,ts}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
|
||||
14
.eslintrc.js
14
.eslintrc.js
@@ -1,15 +1,19 @@
|
||||
module.exports = {
|
||||
parser: "babel-eslint",
|
||||
extends: "@trbl",
|
||||
parser: 'babel-eslint',
|
||||
extends: '@trbl',
|
||||
ignorePatterns: [
|
||||
'/**/*.d.ts'
|
||||
],
|
||||
rules: {
|
||||
"import/no-unresolved": [
|
||||
'import/no-unresolved': [
|
||||
2,
|
||||
{
|
||||
ignore: [
|
||||
'payload/config',
|
||||
'payload/unsanitizedConfig',
|
||||
]
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
'no-underscore-dangle': 'off',
|
||||
},
|
||||
};
|
||||
|
||||
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto eol=lf
|
||||
1
.github/workflows/tests.yml
vendored
1
.github/workflows/tests.yml
vendored
@@ -25,6 +25,7 @@ jobs:
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- run: yarn
|
||||
- run: yarn test:client
|
||||
- run: yarn test:int # In-memory db + api tests
|
||||
env:
|
||||
CI: true
|
||||
|
||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -215,15 +215,16 @@ $RECYCLE.BIN/
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
|
||||
# End of https://www.gitignore.io/api/node,macos,windows,webstorm,sublimetext,visualstudiocode
|
||||
|
||||
# Ignores compiled CSS
|
||||
src/**/*.css
|
||||
demo**/*.css
|
||||
dist
|
||||
|
||||
# Ignore all uploads
|
||||
demo/upload
|
||||
demo/media
|
||||
demo/files
|
||||
|
||||
# Ignore build folder
|
||||
build
|
||||
|
||||
# Ignore built components
|
||||
components/index.js
|
||||
components/styles.css
|
||||
|
||||
25
.vscode/launch.json
vendored
25
.vscode/launch.json
vendored
@@ -18,7 +18,30 @@
|
||||
},
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen",
|
||||
"port": 9229
|
||||
"port": 9229,
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Debug Jest Test - Current File",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"runtimeArgs": [
|
||||
"--inspect-brk",
|
||||
"${workspaceRoot}/node_modules/.bin/jest",
|
||||
"${fileBasename}",
|
||||
"--runInBand"
|
||||
],
|
||||
"env": {
|
||||
"PAYLOAD_CONFIG_PATH": "demo/payload.config.js"
|
||||
},
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen",
|
||||
"port": 9229,
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
|
||||
17
README.md
17
README.md
@@ -1,8 +1,21 @@
|
||||
# Payload
|
||||
|
||||
Headless CMS
|
||||
Headless CMS and application framework
|
||||
|
||||
*TODO: More on why to use it and some features*
|
||||
|
||||
## Installation
|
||||
|
||||
`yarn add @payloadcms/payload` or `npm install @payloadcms/payload`
|
||||
|
||||
## Usage
|
||||
|
||||
## Development
|
||||
*TODO: Show basic usage and link to docs*
|
||||
|
||||
## Contributing
|
||||
|
||||
*TODO: Create Contributing.md*
|
||||
|
||||
## License
|
||||
|
||||
*TODO: Create License.md*
|
||||
|
||||
20
babel.config.js
Normal file
20
babel.config.js
Normal file
@@ -0,0 +1,20 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
[
|
||||
require.resolve('@babel/preset-env'),
|
||||
{
|
||||
targets: [
|
||||
'defaults',
|
||||
'not IE 11',
|
||||
'not IE_Mob 11',
|
||||
],
|
||||
},
|
||||
],
|
||||
require.resolve('@babel/preset-react'),
|
||||
],
|
||||
plugins: [
|
||||
require.resolve('@babel/plugin-transform-runtime'),
|
||||
require.resolve('@babel/plugin-proposal-class-properties'),
|
||||
require.resolve('@babel/plugin-proposal-optional-chaining'),
|
||||
],
|
||||
};
|
||||
19
components/forms.js
Normal file
19
components/forms.js
Normal file
@@ -0,0 +1,19 @@
|
||||
export {
|
||||
useForm,
|
||||
useFormFields,
|
||||
useFormSubmitted,
|
||||
useFormProcessing,
|
||||
useFormModified,
|
||||
} from '../src/admin/components/forms/Form/context';
|
||||
|
||||
export { default as useFieldType } from '../src/admin/components/forms/useFieldType';
|
||||
|
||||
export { default as Form } from '../src/admin/components/forms/Form';
|
||||
|
||||
export { default as Text } from '../src/admin/components/forms/field-types/Text';
|
||||
export { default as Group } from '../src/admin/components/forms/field-types/Group';
|
||||
export { default as Select } from '../src/admin/components/forms/field-types/Select';
|
||||
export { default as Checkbox } from '../src/admin/components/forms/field-types/Checkbox';
|
||||
export { default as Submit } from '../src/admin/components/forms/Submit';
|
||||
|
||||
export { default as reduceFieldsToValues } from '../src/admin/components/forms/Form/reduceFieldsToValues';
|
||||
3
components/rich-text.js
Normal file
3
components/rich-text.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export { default as LeafButton } from '../src/admin/components/forms/field-types/RichText/leaves/Button';
|
||||
export { default as ElementButton } from '../src/admin/components/forms/field-types/RichText/elements/Button';
|
||||
export { default as toggleElement } from '../src/admin/components/forms/field-types/RichText/elements/toggle';
|
||||
2
components/views.js
Normal file
2
components/views.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as Edit } from '../src/admin/components/views/collections/Edit/Default';
|
||||
export { default as List } from '../src/admin/components/views/collections/List/Default';
|
||||
@@ -1,9 +0,0 @@
|
||||
import PageList from '../collections/Page/components/List';
|
||||
|
||||
const components = {
|
||||
pages: {
|
||||
List: PageList,
|
||||
},
|
||||
};
|
||||
|
||||
export default components;
|
||||
119
demo/client/components/richText/elements/Button/Button/index.js
Normal file
119
demo/client/components/richText/elements/Button/Button/index.js
Normal file
@@ -0,0 +1,119 @@
|
||||
import React, { Fragment, useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Modal, useModal } from '@faceless-ui/modal';
|
||||
import { Transforms } from 'slate';
|
||||
import { useSlate } from 'slate-react';
|
||||
import MinimalTemplate from '../../../../../../../src/admin/components/templates/Minimal';
|
||||
import { ElementButton } from '../../../../../../../components/rich-text';
|
||||
import X from '../../../../../../../src/admin/components/icons/X';
|
||||
import Button from '../../../../../../../src/admin/components/elements/Button';
|
||||
import { Form, Text, Checkbox, Select, Submit, reduceFieldsToValues } from '../../../../../../../components/forms';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const baseClass = 'button-rich-text-button';
|
||||
|
||||
const initialFormData = {
|
||||
style: 'primary',
|
||||
};
|
||||
|
||||
const insertButton = (editor, { href, label, style, newTab = false }) => {
|
||||
const text = { text: ' ' };
|
||||
const button = {
|
||||
type: 'button',
|
||||
href,
|
||||
style,
|
||||
newTab,
|
||||
label,
|
||||
children: [
|
||||
text,
|
||||
],
|
||||
};
|
||||
|
||||
const nodes = [button, { children: [{ text: '' }] }];
|
||||
|
||||
Transforms.insertNodes(editor, nodes);
|
||||
};
|
||||
|
||||
const ToolbarButton = ({ path }) => {
|
||||
const { open, closeAll } = useModal();
|
||||
const editor = useSlate();
|
||||
|
||||
const handleAddButton = useCallback((fields) => {
|
||||
const data = reduceFieldsToValues(fields);
|
||||
insertButton(editor, data);
|
||||
closeAll();
|
||||
}, [editor, closeAll]);
|
||||
|
||||
const modalSlug = `${path}-add-button`;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<ElementButton
|
||||
className={baseClass}
|
||||
format="button"
|
||||
onClick={() => open(modalSlug)}
|
||||
>
|
||||
Button
|
||||
</ElementButton>
|
||||
<Modal
|
||||
slug={modalSlug}
|
||||
className={`${baseClass}__modal`}
|
||||
>
|
||||
<MinimalTemplate>
|
||||
<header className={`${baseClass}__header`}>
|
||||
<h3>Add button</h3>
|
||||
<Button
|
||||
buttonStyle="none"
|
||||
onClick={closeAll}
|
||||
>
|
||||
<X />
|
||||
</Button>
|
||||
</header>
|
||||
<Form
|
||||
onSubmit={handleAddButton}
|
||||
initialData={initialFormData}
|
||||
>
|
||||
<Text
|
||||
label="Label"
|
||||
name="label"
|
||||
required
|
||||
/>
|
||||
<Text
|
||||
label="URL"
|
||||
name="href"
|
||||
required
|
||||
/>
|
||||
<Select
|
||||
label="Style"
|
||||
name="style"
|
||||
options={[
|
||||
{
|
||||
label: 'Primary',
|
||||
value: 'primary',
|
||||
},
|
||||
{
|
||||
label: 'Secondary',
|
||||
value: 'secondary',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Open in new tab"
|
||||
name="newTab"
|
||||
/>
|
||||
<Submit>
|
||||
Add button
|
||||
</Submit>
|
||||
</Form>
|
||||
</MinimalTemplate>
|
||||
</Modal>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
ToolbarButton.propTypes = {
|
||||
path: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default ToolbarButton;
|
||||
@@ -0,0 +1,33 @@
|
||||
@import '../../../../../../../scss/vars.scss';
|
||||
|
||||
.button-rich-text-button {
|
||||
.btn {
|
||||
margin-right: base(1);
|
||||
}
|
||||
|
||||
&__modal {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
|
||||
&.payload__modal-item--enterDone {
|
||||
@include blur-bg;
|
||||
}
|
||||
}
|
||||
|
||||
&__header {
|
||||
width: 100%;
|
||||
margin-bottom: $baseline;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: base(1.5);
|
||||
height: base(1.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const baseClass = 'rich-text-button';
|
||||
|
||||
const ButtonElement = ({ attributes, children, element }) => {
|
||||
const { style = 'primary', label } = element;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={baseClass}
|
||||
contentEditable={false}
|
||||
>
|
||||
<span
|
||||
{...attributes}
|
||||
className={[
|
||||
`${baseClass}__button`,
|
||||
`${baseClass}__button--${style}`,
|
||||
].join(' ')}
|
||||
>
|
||||
{label}
|
||||
{children}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ButtonElement.defaultProps = {
|
||||
attributes: {},
|
||||
children: null,
|
||||
};
|
||||
|
||||
ButtonElement.propTypes = {
|
||||
attributes: PropTypes.shape({}),
|
||||
children: PropTypes.node,
|
||||
element: PropTypes.shape({
|
||||
style: PropTypes.oneOf(['primary', 'secondary']),
|
||||
label: PropTypes.string,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default ButtonElement;
|
||||
@@ -0,0 +1,19 @@
|
||||
@import '../../../../../../../scss/vars.scss';
|
||||
|
||||
.rich-text-button {
|
||||
margin: $baseline 0;
|
||||
}
|
||||
|
||||
.rich-text-button__button {
|
||||
padding: base(.5) base(1.5);
|
||||
border-radius: $style-radius-s;
|
||||
|
||||
&--primary {
|
||||
background-color: $color-dark-gray;
|
||||
color: white;
|
||||
}
|
||||
|
||||
&--secondary {
|
||||
background-color: $color-light-gray;
|
||||
}
|
||||
}
|
||||
12
demo/client/components/richText/elements/Button/index.js
Normal file
12
demo/client/components/richText/elements/Button/index.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import Button from './Button';
|
||||
import Element from './Element';
|
||||
import plugin from './plugin';
|
||||
|
||||
export default {
|
||||
name: 'button',
|
||||
Button,
|
||||
Element,
|
||||
plugins: [
|
||||
plugin,
|
||||
],
|
||||
};
|
||||
10
demo/client/components/richText/elements/Button/plugin.js
Normal file
10
demo/client/components/richText/elements/Button/plugin.js
Normal file
@@ -0,0 +1,10 @@
|
||||
const withButton = (incomingEditor) => {
|
||||
const editor = incomingEditor;
|
||||
const { isVoid } = editor;
|
||||
|
||||
editor.isVoid = (element) => (element.type === 'button' ? true : isVoid(element));
|
||||
|
||||
return editor;
|
||||
};
|
||||
|
||||
export default withButton;
|
||||
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import { LeafButton } from '../../../../../../../components/rich-text';
|
||||
|
||||
const Button = () => (
|
||||
<LeafButton format="purple-background">
|
||||
Purple Background
|
||||
</LeafButton>
|
||||
);
|
||||
|
||||
export default Button;
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const PurpleBackground = ({ attributes, children }) => (
|
||||
<span
|
||||
{...attributes}
|
||||
style={{ backgroundColor: 'purple' }}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
|
||||
PurpleBackground.defaultProps = {
|
||||
attributes: {},
|
||||
children: null,
|
||||
};
|
||||
|
||||
PurpleBackground.propTypes = {
|
||||
attributes: PropTypes.shape({}),
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
export default PurpleBackground;
|
||||
@@ -0,0 +1,8 @@
|
||||
import button from './Button';
|
||||
import leaf from './Leaf';
|
||||
|
||||
export default {
|
||||
name: 'purple-background',
|
||||
button,
|
||||
leaf,
|
||||
};
|
||||
@@ -20,9 +20,17 @@ module.exports = {
|
||||
admin: () => true,
|
||||
},
|
||||
auth: {
|
||||
tokenExpiration: 7200,
|
||||
tokenExpiration: 7200, // 2 hours
|
||||
verify: false,
|
||||
maxLoginAttempts: 5,
|
||||
lockTime: 600 * 1000, // lock time in ms
|
||||
useAPIKey: true,
|
||||
secureCookie: process.env.NODE_ENV === 'production',
|
||||
depth: 0,
|
||||
cookies: {
|
||||
secure: false,
|
||||
sameSite: 'Lax',
|
||||
domain: undefined,
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
|
||||
@@ -12,6 +12,7 @@ const AllFields = {
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'text',
|
||||
disableScrollOnSuccess: true,
|
||||
},
|
||||
preview: (doc, token) => {
|
||||
if (doc && doc.text) {
|
||||
@@ -151,11 +152,18 @@ const AllFields = {
|
||||
label: 'Array Text 1',
|
||||
type: 'text',
|
||||
required: true,
|
||||
}, {
|
||||
admin: {
|
||||
width: '50%',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'arrayText2',
|
||||
label: 'Array Text 2',
|
||||
type: 'text',
|
||||
required: true,
|
||||
admin: {
|
||||
width: '50%',
|
||||
},
|
||||
access: {
|
||||
read: ({ req: { user } }) => Boolean(user),
|
||||
update: ({ req: { user } }) => checkRole(['admin'], user),
|
||||
@@ -171,14 +179,22 @@ const AllFields = {
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'checkbox',
|
||||
label: 'Checkbox',
|
||||
type: 'checkbox',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'blocks',
|
||||
label: 'Blocks Content',
|
||||
labels: {
|
||||
singular: 'Block',
|
||||
plural: 'Blocks',
|
||||
},
|
||||
name: 'blocks',
|
||||
minRows: 2,
|
||||
singularLabel: 'Block',
|
||||
blocks: [Email, NumberBlock, Quote, CallToAction],
|
||||
localized: true,
|
||||
required: true,
|
||||
@@ -226,6 +242,32 @@ const AllFields = {
|
||||
position: 'sidebar',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'richText',
|
||||
type: 'richText',
|
||||
label: 'Rich Text',
|
||||
required: true,
|
||||
admin: {
|
||||
elements: [
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'blockquote',
|
||||
'ul',
|
||||
'ol',
|
||||
'link',
|
||||
],
|
||||
leaves: [
|
||||
'bold',
|
||||
'italic',
|
||||
'underline',
|
||||
'strikethrough',
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
|
||||
@@ -16,11 +16,25 @@ module.exports = {
|
||||
{
|
||||
name: 'layout',
|
||||
label: 'Layout Blocks',
|
||||
singularLabel: 'Block',
|
||||
labels: {
|
||||
singular: 'Block',
|
||||
plural: 'Blocks',
|
||||
},
|
||||
type: 'blocks',
|
||||
blocks: [Email, NumberBlock, Quote, CallToAction],
|
||||
localized: true,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'nonLocalizedLayout',
|
||||
label: 'Non Localized Layout',
|
||||
labels: {
|
||||
singular: 'Layout',
|
||||
plural: 'Layouts',
|
||||
},
|
||||
type: 'blocks',
|
||||
blocks: [Email, NumberBlock, Quote, CallToAction],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -10,6 +10,9 @@ const Code = {
|
||||
type: 'code',
|
||||
label: 'Code',
|
||||
required: true,
|
||||
admin: {
|
||||
language: 'js',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
const Description = () => <div className="description">fake description field</div>
|
||||
const Description = () => <div className="description">fake description field</div>;
|
||||
|
||||
export default Description;
|
||||
|
||||
@@ -3,16 +3,14 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const Filter = ({ onChange, value }) => {
|
||||
return (
|
||||
<input
|
||||
className="custom-description-filter"
|
||||
type="text"
|
||||
onChange={e => onChange(e.target.value)}
|
||||
value={value}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const Filter = ({ onChange, value }) => (
|
||||
<input
|
||||
className="custom-description-filter"
|
||||
type="text"
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
value={value}
|
||||
/>
|
||||
);
|
||||
|
||||
Filter.defaultProps = {
|
||||
value: '',
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Group } from '../../../../../../../field-types';
|
||||
import { Group } from '../../../../../../../components/forms';
|
||||
|
||||
const CustomGroup = (props) => {
|
||||
return (
|
||||
<div className="custom-group">
|
||||
<Group {...props} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const CustomGroup = (props) => (
|
||||
<div className="custom-group">
|
||||
<Group {...props} />
|
||||
</div>
|
||||
);
|
||||
|
||||
CustomGroup.defaultProps = {
|
||||
value: '',
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import DefaultList from '../../../../../../src/client/components/views/collections/List/Default';
|
||||
import DefaultList from '../../../../../../src/admin/components/views/collections/List/Default';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const CustomListView = (props) => {
|
||||
return (
|
||||
<div className="custom-list">
|
||||
<p>This is a custom Pages list view</p>
|
||||
<p>Sup</p>
|
||||
<DefaultList {...props} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const CustomListView = (props) => (
|
||||
<div className="custom-list">
|
||||
<p>This is a custom Pages list view</p>
|
||||
<p>Sup</p>
|
||||
<DefaultList {...props} />
|
||||
</div>
|
||||
);
|
||||
|
||||
CustomListView.propTypes = {
|
||||
collection: PropTypes.shape({
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
const path = require('path');
|
||||
const DescriptionField = require('./components/fields/Description/Field').default;
|
||||
const DescriptionCell = require('./components/fields/Description/Cell').default;
|
||||
const DescriptionFilter = require('./components/fields/Description/Filter').default;
|
||||
const NestedArrayField = require('./components/fields/NestedArrayCustomField/Field').default;
|
||||
const GroupField = require('./components/fields/Group/Field').default;
|
||||
const NestedGroupField = require('./components/fields/NestedGroupCustomField/Field').default;
|
||||
const NestedText1Field = require('./components/fields/NestedText1/Field').default;
|
||||
const ListView = require('./components/views/List').default;
|
||||
|
||||
module.exports = {
|
||||
slug: 'custom-components',
|
||||
@@ -25,9 +32,9 @@ module.exports = {
|
||||
localized: true,
|
||||
admin: {
|
||||
components: {
|
||||
field: path.resolve(__dirname, 'components/fields/Description/Field/index.js'),
|
||||
cell: path.resolve(__dirname, 'components/fields/Description/Cell/index.js'),
|
||||
filter: path.resolve(__dirname, 'components/fields/Description/Filter/index.js'),
|
||||
Field: DescriptionField,
|
||||
Cell: DescriptionCell,
|
||||
Filter: DescriptionFilter,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -42,7 +49,7 @@ module.exports = {
|
||||
label: 'Nested Array Custom Field',
|
||||
admin: {
|
||||
components: {
|
||||
field: path.resolve(__dirname, 'components/fields/NestedArrayCustomField/Field/index.js'),
|
||||
Field: NestedArrayField,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -54,7 +61,7 @@ module.exports = {
|
||||
type: 'group',
|
||||
admin: {
|
||||
components: {
|
||||
field: path.resolve(__dirname, 'components/fields/Group/Field/index.js'),
|
||||
Field: GroupField,
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
@@ -64,7 +71,7 @@ module.exports = {
|
||||
label: 'Nested Group Custom Field',
|
||||
admin: {
|
||||
components: {
|
||||
field: path.resolve(__dirname, 'components/fields/NestedGroupCustomField/Field/index.js'),
|
||||
Field: NestedGroupField,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -79,7 +86,7 @@ module.exports = {
|
||||
type: 'text',
|
||||
admin: {
|
||||
components: {
|
||||
field: path.resolve(__dirname, 'components/fields/NestedText1/Field/index.js'),
|
||||
Field: NestedText1Field,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
@@ -95,7 +102,7 @@ module.exports = {
|
||||
useAsTitle: 'title',
|
||||
components: {
|
||||
views: {
|
||||
List: path.resolve(__dirname, 'components/views/List/index.js'),
|
||||
List: ListView,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
272
demo/collections/DefaultValueTest.js
Normal file
272
demo/collections/DefaultValueTest.js
Normal file
@@ -0,0 +1,272 @@
|
||||
const checkRole = require('../access/checkRole');
|
||||
const Email = require('../blocks/Email');
|
||||
const Quote = require('../blocks/Quote');
|
||||
const NumberBlock = require('../blocks/Number');
|
||||
const CallToAction = require('../blocks/CallToAction');
|
||||
|
||||
const DefaultValueTest = {
|
||||
slug: 'default-value-test',
|
||||
labels: {
|
||||
singular: 'Default Value Test',
|
||||
plural: 'Default Value Tests',
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'text',
|
||||
},
|
||||
preview: (doc, token) => {
|
||||
if (doc && doc.text) {
|
||||
return `http://localhost:3000/previewable-posts/${doc.text.value}?preview=true&token=${token}`;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
label: 'Text',
|
||||
defaultValue: 'Default Value',
|
||||
unique: true,
|
||||
access: {
|
||||
create: ({ req: { user } }) => checkRole(['admin'], user),
|
||||
update: ({ req: { user } }) => checkRole(['admin'], user),
|
||||
read: ({ req: { user } }) => Boolean(user),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
label: 'Image',
|
||||
relationTo: 'media',
|
||||
},
|
||||
{
|
||||
name: 'select',
|
||||
label: 'Select',
|
||||
type: 'select',
|
||||
options: [{
|
||||
value: 'option-1',
|
||||
label: 'Option 1 Label',
|
||||
}, {
|
||||
value: 'option-2',
|
||||
label: 'Option 2 Label',
|
||||
}, {
|
||||
value: 'option-3',
|
||||
label: 'Option 3 Label',
|
||||
}, {
|
||||
value: 'option-4',
|
||||
label: 'Option 4 Label',
|
||||
}],
|
||||
defaultValue: 'option-1',
|
||||
},
|
||||
{
|
||||
name: 'selectMany',
|
||||
label: 'Select w/ hasMany',
|
||||
type: 'select',
|
||||
options: [{
|
||||
value: 'option-1',
|
||||
label: 'Option 1 Label',
|
||||
}, {
|
||||
value: 'option-2',
|
||||
label: 'Option 2 Label',
|
||||
}, {
|
||||
value: 'option-3',
|
||||
label: 'Option 3 Label',
|
||||
}, {
|
||||
value: 'option-4',
|
||||
label: 'Option 4 Label',
|
||||
}],
|
||||
defaultValue: ['option-1', 'option-4'],
|
||||
hasMany: true,
|
||||
},
|
||||
{
|
||||
name: 'radioGroupExample',
|
||||
label: 'Radio Group Example',
|
||||
type: 'radio',
|
||||
options: [{
|
||||
value: 'option-1',
|
||||
label: 'Options 1 Label',
|
||||
}, {
|
||||
value: 'option-2',
|
||||
label: 'Option 2 Label',
|
||||
}, {
|
||||
value: 'option-3',
|
||||
label: 'Option 3 Label',
|
||||
}],
|
||||
defaultValue: 'option-2',
|
||||
},
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'email',
|
||||
label: 'Email',
|
||||
type: 'email',
|
||||
defaultValue: 'some@email.com',
|
||||
}, {
|
||||
name: 'number',
|
||||
label: 'Number',
|
||||
type: 'number',
|
||||
defaultValue: 5,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
label: 'Group',
|
||||
name: 'group',
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'nestedText1',
|
||||
label: 'Nested Text 1',
|
||||
type: 'text',
|
||||
defaultValue: 'nested default text 1',
|
||||
}, {
|
||||
name: 'nestedText2',
|
||||
label: 'Nested Text 2',
|
||||
type: 'text',
|
||||
defaultValue: 'nested default text 2',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
label: 'Array',
|
||||
name: 'array',
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'arrayText1',
|
||||
label: 'Array Text 1',
|
||||
type: 'text',
|
||||
admin: {
|
||||
width: '50%',
|
||||
},
|
||||
defaultValue: 'default array text',
|
||||
},
|
||||
{
|
||||
name: 'arrayText2',
|
||||
label: 'Array Text 2',
|
||||
type: 'text',
|
||||
admin: {
|
||||
width: '50%',
|
||||
},
|
||||
access: {
|
||||
read: ({ req: { user } }) => Boolean(user),
|
||||
update: ({ req: { user } }) => checkRole(['admin'], user),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'arrayText3',
|
||||
label: 'Array Text 3',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'checkbox',
|
||||
label: 'Checkbox',
|
||||
type: 'checkbox',
|
||||
default: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'blocks',
|
||||
label: 'Blocks Content',
|
||||
name: 'blocks',
|
||||
labels: {
|
||||
singular: 'Block',
|
||||
plural: 'Blocks',
|
||||
},
|
||||
blocks: [Email, NumberBlock, Quote, CallToAction],
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
type: 'relationship',
|
||||
label: 'Relationship to One Collection',
|
||||
name: 'relationship',
|
||||
relationTo: 'conditions',
|
||||
},
|
||||
{
|
||||
type: 'relationship',
|
||||
label: 'Relationship hasMany',
|
||||
name: 'relationshipHasMany',
|
||||
relationTo: 'localized-posts',
|
||||
hasMany: true,
|
||||
},
|
||||
{
|
||||
type: 'relationship',
|
||||
label: 'Relationship to Multiple Collections',
|
||||
name: 'relationshipMultipleCollections',
|
||||
relationTo: ['localized-posts', 'conditions'],
|
||||
},
|
||||
{
|
||||
type: 'textarea',
|
||||
label: 'Textarea',
|
||||
name: 'textarea',
|
||||
defaultValue: 'my textarea text',
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
label: 'Slug',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
localized: true,
|
||||
unique: true,
|
||||
defaultValue: 'my-slug',
|
||||
},
|
||||
{
|
||||
name: 'checkbox',
|
||||
type: 'checkbox',
|
||||
label: 'Checkbox',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
defaultValue: true,
|
||||
},
|
||||
{
|
||||
name: 'richText',
|
||||
type: 'richText',
|
||||
label: 'Rich Text',
|
||||
admin: {
|
||||
elements: [
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'blockquote',
|
||||
'ul',
|
||||
'ol',
|
||||
'link',
|
||||
],
|
||||
leaves: [
|
||||
'bold',
|
||||
'italic',
|
||||
'underline',
|
||||
'strikethrough',
|
||||
],
|
||||
},
|
||||
// defaultValue: 'sooo riiiiiiiich',
|
||||
},
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
|
||||
module.exports = DefaultValueTest;
|
||||
283
demo/collections/DefaultValues.js
Normal file
283
demo/collections/DefaultValues.js
Normal file
@@ -0,0 +1,283 @@
|
||||
const checkRole = require('../access/checkRole');
|
||||
const Email = require('../blocks/Email');
|
||||
const Quote = require('../blocks/Quote');
|
||||
const NumberBlock = require('../blocks/Number');
|
||||
const CallToAction = require('../blocks/CallToAction');
|
||||
|
||||
const DefaultValues = {
|
||||
slug: 'default-values',
|
||||
labels: {
|
||||
singular: 'Default Value Test',
|
||||
plural: 'Default Value Tests',
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'text',
|
||||
},
|
||||
preview: (doc, token) => {
|
||||
if (doc && doc.text) {
|
||||
return `http://localhost:3000/previewable-posts/${doc.text.value}?preview=true&token=${token}`;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
label: 'Text',
|
||||
defaultValue: 'Default Value',
|
||||
unique: true,
|
||||
access: {
|
||||
create: ({ req: { user } }) => checkRole(['admin'], user),
|
||||
update: ({ req: { user } }) => checkRole(['admin'], user),
|
||||
read: ({ req: { user } }) => Boolean(user),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
label: 'Image',
|
||||
relationTo: 'media',
|
||||
},
|
||||
{
|
||||
name: 'select',
|
||||
label: 'Select',
|
||||
type: 'select',
|
||||
options: [{
|
||||
value: 'option-1',
|
||||
label: 'Option 1 Label',
|
||||
}, {
|
||||
value: 'option-2',
|
||||
label: 'Option 2 Label',
|
||||
}, {
|
||||
value: 'option-3',
|
||||
label: 'Option 3 Label',
|
||||
}, {
|
||||
value: 'option-4',
|
||||
label: 'Option 4 Label',
|
||||
}],
|
||||
defaultValue: 'option-1',
|
||||
},
|
||||
{
|
||||
name: 'selectMany',
|
||||
label: 'Select w/ hasMany',
|
||||
type: 'select',
|
||||
options: [{
|
||||
value: 'option-1',
|
||||
label: 'Option 1 Label',
|
||||
}, {
|
||||
value: 'option-2',
|
||||
label: 'Option 2 Label',
|
||||
}, {
|
||||
value: 'option-3',
|
||||
label: 'Option 3 Label',
|
||||
}, {
|
||||
value: 'option-4',
|
||||
label: 'Option 4 Label',
|
||||
}],
|
||||
defaultValue: ['option-1', 'option-4'],
|
||||
hasMany: true,
|
||||
},
|
||||
{
|
||||
name: 'radioGroupExample',
|
||||
label: 'Radio Group Example',
|
||||
type: 'radio',
|
||||
options: [{
|
||||
value: 'option-1',
|
||||
label: 'Options 1 Label',
|
||||
}, {
|
||||
value: 'option-2',
|
||||
label: 'Option 2 Label',
|
||||
}, {
|
||||
value: 'option-3',
|
||||
label: 'Option 3 Label',
|
||||
}],
|
||||
defaultValue: 'option-2',
|
||||
},
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'email',
|
||||
label: 'Email',
|
||||
type: 'email',
|
||||
defaultValue: 'some@email.com',
|
||||
}, {
|
||||
name: 'number',
|
||||
label: 'Number',
|
||||
type: 'number',
|
||||
defaultValue: 5,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
label: 'Group',
|
||||
name: 'group',
|
||||
defaultValue: {
|
||||
nestedText1: 'neat',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'nestedText1',
|
||||
label: 'Nested Text 1',
|
||||
type: 'text',
|
||||
defaultValue: 'nested default text 1',
|
||||
}, {
|
||||
name: 'nestedText2',
|
||||
label: 'Nested Text 2',
|
||||
type: 'text',
|
||||
defaultValue: 'nested default text 2',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
label: 'Array',
|
||||
name: 'array',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
defaultValue: [
|
||||
{
|
||||
arrayText1: 'Get out',
|
||||
},
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'arrayText1',
|
||||
label: 'Array Text 1',
|
||||
type: 'text',
|
||||
admin: {
|
||||
width: '50%',
|
||||
},
|
||||
defaultValue: 'default array text',
|
||||
},
|
||||
{
|
||||
name: 'arrayText2',
|
||||
label: 'Array Text 2',
|
||||
type: 'text',
|
||||
admin: {
|
||||
width: '50%',
|
||||
},
|
||||
access: {
|
||||
read: ({ req: { user } }) => Boolean(user),
|
||||
update: ({ req: { user } }) => checkRole(['admin'], user),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'arrayText3',
|
||||
label: 'Array Text 3',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'checkbox',
|
||||
label: 'Checkbox',
|
||||
type: 'checkbox',
|
||||
default: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'blocks',
|
||||
label: 'Blocks Content',
|
||||
name: 'blocks',
|
||||
labels: {
|
||||
singular: 'Block',
|
||||
plural: 'Blocks',
|
||||
},
|
||||
blocks: [Email, NumberBlock, Quote, CallToAction],
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
type: 'relationship',
|
||||
label: 'Relationship to One Collection',
|
||||
name: 'relationship',
|
||||
relationTo: 'conditions',
|
||||
},
|
||||
{
|
||||
type: 'relationship',
|
||||
label: 'Relationship hasMany',
|
||||
name: 'relationshipHasMany',
|
||||
relationTo: 'localized-posts',
|
||||
hasMany: true,
|
||||
},
|
||||
{
|
||||
type: 'relationship',
|
||||
label: 'Relationship to Multiple Collections',
|
||||
name: 'relationshipMultipleCollections',
|
||||
relationTo: ['localized-posts', 'conditions'],
|
||||
},
|
||||
{
|
||||
type: 'textarea',
|
||||
label: 'Textarea',
|
||||
name: 'textarea',
|
||||
defaultValue: 'my textarea text',
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
label: 'Slug',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
localized: true,
|
||||
unique: true,
|
||||
defaultValue: 'my-slug',
|
||||
},
|
||||
{
|
||||
name: 'checkbox',
|
||||
type: 'checkbox',
|
||||
label: 'Checkbox',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
defaultValue: true,
|
||||
},
|
||||
{
|
||||
name: 'richText',
|
||||
type: 'richText',
|
||||
label: 'Rich Text',
|
||||
admin: {
|
||||
elements: [
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'blockquote',
|
||||
'ul',
|
||||
'ol',
|
||||
'link',
|
||||
],
|
||||
leaves: [
|
||||
'bold',
|
||||
'italic',
|
||||
'underline',
|
||||
'strikethrough',
|
||||
],
|
||||
},
|
||||
defaultValue: [{
|
||||
children: [{ text: 'Cookin now' }],
|
||||
}],
|
||||
},
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
module.exports = DefaultValues;
|
||||
@@ -1,4 +1,3 @@
|
||||
const path = require('path');
|
||||
const checkRole = require('../access/checkRole');
|
||||
|
||||
const access = ({ req: { user } }) => {
|
||||
@@ -15,6 +14,8 @@ const access = ({ req: { user } }) => {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
@@ -25,7 +26,7 @@ module.exports = {
|
||||
},
|
||||
upload: {
|
||||
staticURL: '/files',
|
||||
staticDir: path.resolve(__dirname, '../files'),
|
||||
staticDir: './files',
|
||||
},
|
||||
access: {
|
||||
create: () => true,
|
||||
|
||||
@@ -15,14 +15,6 @@ module.exports = {
|
||||
delete: () => true,
|
||||
},
|
||||
hooks: {
|
||||
beforeCreate: [
|
||||
(operation) => {
|
||||
if (operation.req.headers.hook === 'beforeCreate') {
|
||||
operation.req.body.description += '-beforeCreateSuffix';
|
||||
}
|
||||
return operation.data;
|
||||
},
|
||||
],
|
||||
beforeRead: [
|
||||
(operation) => {
|
||||
if (operation.req.headers.hook === 'beforeRead') {
|
||||
@@ -30,10 +22,10 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
],
|
||||
beforeUpdate: [
|
||||
beforeChange: [
|
||||
(operation) => {
|
||||
if (operation.req.headers.hook === 'beforeUpdate') {
|
||||
operation.req.body.description += '-beforeUpdateSuffix';
|
||||
if (operation.req.headers.hook === 'beforeChange') {
|
||||
operation.req.body.description += '-beforeChangeSuffix';
|
||||
}
|
||||
return operation.data;
|
||||
},
|
||||
@@ -46,14 +38,6 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
],
|
||||
afterCreate: [
|
||||
(operation) => {
|
||||
if (operation.req.headers.hook === 'afterCreate') {
|
||||
operation.doc.afterCreateHook = true;
|
||||
}
|
||||
return operation.doc;
|
||||
},
|
||||
],
|
||||
afterRead: [
|
||||
(operation) => {
|
||||
const { doc } = operation;
|
||||
@@ -62,10 +46,10 @@ module.exports = {
|
||||
return doc;
|
||||
},
|
||||
],
|
||||
afterUpdate: [
|
||||
afterChange: [
|
||||
(operation) => {
|
||||
if (operation.req.headers.hook === 'afterUpdate') {
|
||||
operation.doc.afterUpdateHook = true;
|
||||
if (operation.req.headers.hook === 'afterChange') {
|
||||
operation.doc.afterChangeHook = true;
|
||||
}
|
||||
return operation.doc;
|
||||
},
|
||||
|
||||
36
demo/collections/LocalOperations.js
Normal file
36
demo/collections/LocalOperations.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const LocalOperations = {
|
||||
slug: 'local-operations',
|
||||
labels: {
|
||||
singular: 'Local Operation',
|
||||
plural: 'Local Operations',
|
||||
},
|
||||
hooks: {
|
||||
afterRead: [
|
||||
async ({ req, doc }) => {
|
||||
const formattedData = { ...doc };
|
||||
const localizedPosts = await req.payload.find({
|
||||
collection: 'localized-posts',
|
||||
});
|
||||
|
||||
const blocksGlobal = await req.payload.findGlobal({
|
||||
global: 'blocks-global',
|
||||
});
|
||||
|
||||
formattedData.localizedPosts = localizedPosts;
|
||||
formattedData.blocksGlobal = blocksGlobal;
|
||||
|
||||
return formattedData;
|
||||
},
|
||||
],
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'title',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = LocalOperations;
|
||||
@@ -11,12 +11,13 @@ module.exports = {
|
||||
'priority',
|
||||
'createdAt',
|
||||
],
|
||||
enableRichTextRelationship: true,
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
preview: (doc, token) => {
|
||||
if (doc.title) {
|
||||
if (doc && doc.title) {
|
||||
return `http://localhost:3000/posts/${doc.title.value}?preview=true&token=${token}`;
|
||||
}
|
||||
|
||||
@@ -32,6 +33,12 @@ module.exports = {
|
||||
unique: true,
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'summary',
|
||||
label: 'Summary',
|
||||
type: 'text',
|
||||
index: true,
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
const path = require('path');
|
||||
const checkRole = require('../access/checkRole');
|
||||
|
||||
module.exports = {
|
||||
@@ -10,9 +9,12 @@ module.exports = {
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
admin: {
|
||||
enableRichTextRelationship: true,
|
||||
},
|
||||
upload: {
|
||||
staticURL: '/media',
|
||||
staticDir: path.resolve(__dirname, '../media'),
|
||||
staticDir: './media',
|
||||
adminThumbnail: 'mobile',
|
||||
imageSizes: [
|
||||
{
|
||||
@@ -41,11 +43,6 @@ module.exports = {
|
||||
type: 'text',
|
||||
required: true,
|
||||
localized: true,
|
||||
hooks: {
|
||||
afterRead: [
|
||||
({ value }) => `${value} alt`,
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'sizes',
|
||||
|
||||
@@ -32,7 +32,15 @@ module.exports = {
|
||||
},
|
||||
auth: {
|
||||
tokenExpiration: 300,
|
||||
secureCookie: process.env.NODE_ENV === 'production',
|
||||
verify: true,
|
||||
maxLoginAttempts: 5,
|
||||
lockTime: 600 * 1000, // lock time in ms
|
||||
generateVerificationUrl: (req, token) => `http://localhost:3000/api/verify?token=${token}`,
|
||||
cookies: {
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'Lax',
|
||||
domain: undefined,
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
const Button = require('../client/components/richText/elements/Button').default;
|
||||
const PurpleBackground = require('../client/components/richText/leaves/PurpleBackground').default;
|
||||
|
||||
const RichText = {
|
||||
slug: 'rich-text',
|
||||
labels: {
|
||||
@@ -6,12 +9,27 @@ const RichText = {
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'richText',
|
||||
name: 'defaultRichText',
|
||||
type: 'richText',
|
||||
label: 'Rich Text',
|
||||
label: 'Default Rich Text',
|
||||
required: true,
|
||||
disabledElements: [],
|
||||
disabledMarks: [],
|
||||
},
|
||||
{
|
||||
name: 'customRichText',
|
||||
type: 'richText',
|
||||
label: 'Customized Rich Text',
|
||||
required: true,
|
||||
admin: {
|
||||
elements: [
|
||||
'h2',
|
||||
'h3',
|
||||
Button,
|
||||
],
|
||||
leaves: [
|
||||
'bold',
|
||||
PurpleBackground,
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
43
demo/collections/Select.js
Normal file
43
demo/collections/Select.js
Normal file
@@ -0,0 +1,43 @@
|
||||
const Select = {
|
||||
slug: 'select',
|
||||
labels: {
|
||||
singular: 'Select',
|
||||
plural: 'Selects',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'Select',
|
||||
type: 'select',
|
||||
options: [{
|
||||
value: 'one',
|
||||
label: 'One',
|
||||
}, {
|
||||
value: 'two',
|
||||
label: 'Two',
|
||||
}, {
|
||||
value: 'three',
|
||||
label: 'Three',
|
||||
}],
|
||||
label: 'Select From',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'Radio',
|
||||
type: 'radio',
|
||||
options: [{
|
||||
value: 'one',
|
||||
label: 'One',
|
||||
}, {
|
||||
value: 'two',
|
||||
label: 'Two',
|
||||
}, {
|
||||
value: 'three',
|
||||
label: 'Three',
|
||||
}],
|
||||
label: 'Choose From',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = Select;
|
||||
15
demo/custom-index.html
Normal file
15
demo/custom-index.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="application-name" content="My Payload Application" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<div id="portal"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,4 +1,3 @@
|
||||
const path = require('path');
|
||||
const Admin = require('./collections/Admin');
|
||||
const AllFields = require('./collections/AllFields');
|
||||
const Code = require('./collections/Code');
|
||||
@@ -6,10 +5,12 @@ const Conditions = require('./collections/Conditions');
|
||||
const CustomComponents = require('./collections/CustomComponents');
|
||||
const File = require('./collections/File');
|
||||
const Blocks = require('./collections/Blocks');
|
||||
const DefaultValues = require('./collections/DefaultValues');
|
||||
const HiddenFields = require('./collections/HiddenFields');
|
||||
const Hooks = require('./collections/Hooks');
|
||||
const Localized = require('./collections/Localized');
|
||||
const LocalizedArray = require('./collections/LocalizedArray');
|
||||
const LocalOperations = require('./collections/LocalOperations');
|
||||
const Media = require('./collections/Media');
|
||||
const NestedArrays = require('./collections/NestedArrays');
|
||||
const Preview = require('./collections/Preview');
|
||||
@@ -17,6 +18,7 @@ const PublicUsers = require('./collections/PublicUsers');
|
||||
const RelationshipA = require('./collections/RelationshipA');
|
||||
const RelationshipB = require('./collections/RelationshipB');
|
||||
const RichText = require('./collections/RichText');
|
||||
const Select = require('./collections/Select');
|
||||
const StrictPolicies = require('./collections/StrictPolicies');
|
||||
const Validations = require('./collections/Validations');
|
||||
|
||||
@@ -27,13 +29,24 @@ const GlobalWithStrictAccess = require('./globals/GlobalWithStrictAccess');
|
||||
module.exports = {
|
||||
admin: {
|
||||
user: 'admins',
|
||||
// indexHTML: 'custom-index.html',
|
||||
meta: {
|
||||
titleSuffix: '- Payload Demo',
|
||||
// ogImage: '/static/find-image-here.jpg',
|
||||
// favicon: '/img/whatever.png',
|
||||
},
|
||||
disable: false,
|
||||
components: {
|
||||
layout: {
|
||||
// Sidebar: path.resolve(__dirname, 'client/components/layout/Sidebar/index.js'),
|
||||
},
|
||||
// Nav: () => (
|
||||
// <div>Hello</div>
|
||||
// ),
|
||||
},
|
||||
},
|
||||
email: {
|
||||
transport: 'mock',
|
||||
fromName: 'Payload',
|
||||
fromAddress: 'hello@payloadcms.com',
|
||||
},
|
||||
collections: [
|
||||
Admin,
|
||||
AllFields,
|
||||
@@ -41,11 +54,13 @@ module.exports = {
|
||||
Conditions,
|
||||
CustomComponents,
|
||||
File,
|
||||
DefaultValues,
|
||||
Blocks,
|
||||
HiddenFields,
|
||||
Hooks,
|
||||
Localized,
|
||||
LocalizedArray,
|
||||
LocalOperations,
|
||||
Media,
|
||||
NestedArrays,
|
||||
Preview,
|
||||
@@ -53,6 +68,7 @@ module.exports = {
|
||||
RelationshipA,
|
||||
RelationshipB,
|
||||
RichText,
|
||||
Select,
|
||||
StrictPolicies,
|
||||
Validations,
|
||||
],
|
||||
@@ -63,7 +79,16 @@ module.exports = {
|
||||
],
|
||||
cookiePrefix: 'payload',
|
||||
serverURL: 'http://localhost:3000',
|
||||
cors: ['http://localhost', 'http://localhost:8080', 'http://localhost:8081'],
|
||||
cors: [
|
||||
'http://localhost',
|
||||
'http://localhost:3000',
|
||||
'http://localhost:8080',
|
||||
'http://localhost:8081',
|
||||
],
|
||||
csrf: [
|
||||
'http://localhost:3000',
|
||||
'https://other-app-here.com',
|
||||
],
|
||||
routes: {
|
||||
api: '/api',
|
||||
admin: '/admin',
|
||||
@@ -73,12 +98,21 @@ module.exports = {
|
||||
defaultDepth: 2,
|
||||
compression: {},
|
||||
paths: {
|
||||
scss: path.resolve(__dirname, 'client/scss/overrides.scss'),
|
||||
scss: 'client/scss/overrides.scss',
|
||||
},
|
||||
graphQL: {
|
||||
maxComplexity: 1000,
|
||||
mutations: {},
|
||||
queries: {},
|
||||
disablePlaygroundInProduction: true,
|
||||
},
|
||||
rateLimit: {
|
||||
window: 15 * 60 * 100,
|
||||
max: 100,
|
||||
trustProxy: true,
|
||||
skip: (req) => req.ip === '127.0.0.1',
|
||||
},
|
||||
maxDepth: 10,
|
||||
localization: {
|
||||
locales: [
|
||||
'en',
|
||||
@@ -87,10 +121,14 @@ module.exports = {
|
||||
defaultLocale: 'en',
|
||||
fallback: true,
|
||||
},
|
||||
productionGraphQLPlayground: false,
|
||||
hooks: {
|
||||
afterError: () => {
|
||||
console.error('global error config handler');
|
||||
afterError: (err) => {
|
||||
console.error('global error config handler', err);
|
||||
},
|
||||
},
|
||||
upload: {
|
||||
limits: {
|
||||
fileSize: 10000000, // 10MB
|
||||
},
|
||||
},
|
||||
webpack: (config) => config,
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
/* eslint-disable no-console */
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const Payload = require('../src');
|
||||
const payload = require('../src');
|
||||
const logger = require('../src/utilities/logger')();
|
||||
|
||||
const expressApp = express();
|
||||
|
||||
expressApp.use('/static', express.static(path.resolve(__dirname, 'client/static')));
|
||||
|
||||
const payload = new Payload({
|
||||
email: {
|
||||
provider: 'mock',
|
||||
},
|
||||
payload.init({
|
||||
secret: 'SECRET_KEY',
|
||||
mongoURL: 'mongodb://localhost/payload',
|
||||
express: expressApp,
|
||||
onInit: () => {
|
||||
console.log('Payload is initialized');
|
||||
logger.info('Payload is initialized');
|
||||
// console.log('Payload is initialized');
|
||||
},
|
||||
});
|
||||
|
||||
const externalRouter = express.Router();
|
||||
|
||||
externalRouter.use(payload.authenticate());
|
||||
externalRouter.use(payload.authenticate);
|
||||
|
||||
externalRouter.get('/', (req, res) => {
|
||||
if (req.user) {
|
||||
@@ -33,17 +32,11 @@ externalRouter.get('/', (req, res) => {
|
||||
|
||||
expressApp.use('/external-route', externalRouter);
|
||||
|
||||
exports.payload = payload;
|
||||
|
||||
exports.start = (cb) => {
|
||||
const server = expressApp.listen(3000, async () => {
|
||||
console.log(`listening on ${3000}...`);
|
||||
if (cb) cb();
|
||||
logger.info(`listening on ${3000}...`);
|
||||
|
||||
const creds = await payload.getMockEmailCredentials();
|
||||
console.log(`Mock email account username: ${creds.user}`);
|
||||
console.log(`Mock email account password: ${creds.pass}`);
|
||||
console.log(`Log in to mock email provider at ${creds.web}`);
|
||||
if (cb) cb();
|
||||
});
|
||||
|
||||
return server;
|
||||
|
||||
7
docs/Access-Control/config.mdx
Normal file
7
docs/Access-Control/config.mdx
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Access Control Config
|
||||
label: Config
|
||||
order: 10
|
||||
---
|
||||
|
||||
Talk about how to write access control.
|
||||
7
docs/Access-Control/examples.mdx
Normal file
7
docs/Access-Control/examples.mdx
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Access Control Examples
|
||||
label: Examples
|
||||
order: 20
|
||||
---
|
||||
|
||||
Show examples of how to write access control functions.
|
||||
9
docs/Authentication/accessing-logged-in-user.mdx
Normal file
9
docs/Authentication/accessing-logged-in-user.mdx
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Accessing the User
|
||||
label: Accessing the User
|
||||
order: 40
|
||||
---
|
||||
|
||||
Talk about how to access the User in custom code like hooks and access control. The User is on the req.
|
||||
|
||||
Show code examples.
|
||||
18
docs/Authentication/config.mdx
Normal file
18
docs/Authentication/config.mdx
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
title: Authentication Config
|
||||
label: Config
|
||||
order: 10
|
||||
---
|
||||
|
||||
Talk about how to configure authentication here.
|
||||
|
||||
Need to cover:
|
||||
|
||||
1. What collection to use for Admin panel and how there can only be one
|
||||
1. Can have multiple auth collections
|
||||
1. Token expiration
|
||||
1. Email verification
|
||||
1. Security (max login, lock time)
|
||||
1. API keys
|
||||
1. Depth to populate (performance impact)
|
||||
1. Cookie settings (HTTP only discussion)
|
||||
43
docs/Authentication/operations.mdx
Normal file
43
docs/Authentication/operations.mdx
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
title: Authentication Operations
|
||||
label: Operations
|
||||
order: 30
|
||||
---
|
||||
|
||||
Talk about Auth operations here.
|
||||
|
||||
#### Me
|
||||
|
||||
Returns null or logged in user with token (useful for HTTP only)
|
||||
|
||||
#### Create operation modifications
|
||||
|
||||
Discuss if verification is required, etc etc
|
||||
|
||||
#### Init
|
||||
|
||||
Checks if there have been users created, checks Admin access
|
||||
|
||||
#### Login
|
||||
|
||||
HTTP-only cookies, token response. Discuss max login attempts
|
||||
|
||||
#### Logout
|
||||
|
||||
Removes HTTP-only cookie
|
||||
|
||||
#### Refresh
|
||||
|
||||
Refreshes token (requires valid token)
|
||||
|
||||
#### Register First User
|
||||
|
||||
Allows for anyone to register first user through UI
|
||||
|
||||
#### Forgot Password
|
||||
|
||||
Takes an email, sends email to that address. Discuss how to customize email
|
||||
|
||||
#### Access
|
||||
|
||||
Shows what the user can and cant do.
|
||||
7
docs/Authentication/security.mdx
Normal file
7
docs/Authentication/security.mdx
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Authentication Security
|
||||
label: Security
|
||||
order: 20
|
||||
---
|
||||
|
||||
Talk about how Payload securely authenticates here.
|
||||
7
docs/Authentication/using-middleware.mdx
Normal file
7
docs/Authentication/using-middleware.mdx
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Using the Payload Auth Middleware
|
||||
label: Using the Middleware
|
||||
order: 50
|
||||
---
|
||||
|
||||
Talk about how to use `payload.authenticate()` outside of Payload - show examples
|
||||
7
docs/Components/overview.mdx
Normal file
7
docs/Components/overview.mdx
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Custom Components
|
||||
label: Overview
|
||||
order: 10
|
||||
---
|
||||
|
||||
Talk about how to build custom components. Show a list of all components that can be swapped out.
|
||||
7
docs/Configuration/blocks.mdx
Normal file
7
docs/Configuration/blocks.mdx
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Block Configs
|
||||
label: Blocks
|
||||
order: 40
|
||||
---
|
||||
|
||||
Talk about how to write block configs here.
|
||||
7
docs/Configuration/collections.mdx
Normal file
7
docs/Configuration/collections.mdx
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Collection Configs
|
||||
label: Collections
|
||||
order: 20
|
||||
---
|
||||
|
||||
Talk about how to write collection configs here.
|
||||
7
docs/Configuration/globals.mdx
Normal file
7
docs/Configuration/globals.mdx
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Global Configs
|
||||
label: Globals
|
||||
order: 30
|
||||
---
|
||||
|
||||
Talk about how to write Globals configs here.
|
||||
7
docs/Configuration/main.mdx
Normal file
7
docs/Configuration/main.mdx
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: The Main Config
|
||||
label: Main
|
||||
order: 10
|
||||
---
|
||||
|
||||
Talk about how to write the main config here.
|
||||
298
docs/Fields/overview.mdx
Normal file
298
docs/Fields/overview.mdx
Normal file
@@ -0,0 +1,298 @@
|
||||
---
|
||||
title: Fields Overview
|
||||
label: Overview
|
||||
order: 10
|
||||
---
|
||||
|
||||
<Banner type="info">
|
||||
Fields are the building blocks of Payload. Collections and Globals both use Fields to define the shape of the data that they store. Payload offers a wide-array of field-types - both simple and complex.
|
||||
</Banner>
|
||||
|
||||
## Field types
|
||||
|
||||
The `type` property on a field determines how the input will be rendered in the admin interface, what values it can accept, and how it is presented in the API.
|
||||
|
||||
```js
|
||||
// Collection config
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
name: 'my-field',
|
||||
type: 'text', // highlight-line
|
||||
label: 'Text',
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Simple Types
|
||||
|
||||
#### `text`
|
||||
|
||||
Plain text
|
||||
|
||||
#### `textarea`
|
||||
|
||||
Large blocks of text. No formatting.
|
||||
|
||||
#### `number`
|
||||
|
||||
Simple number input
|
||||
|
||||
#### `checkbox`
|
||||
|
||||
Boolean value that renders a checkbox
|
||||
|
||||
#### `date`
|
||||
|
||||
Renders date-picker
|
||||
|
||||
#### `email`
|
||||
|
||||
Email. Includes validation.
|
||||
|
||||
#### `code`
|
||||
|
||||
Monospaced code block
|
||||
|
||||
#### `richText`
|
||||
|
||||
Large formatted text. Includes rich text editor in the admin interface.
|
||||
|
||||
#### `select`
|
||||
|
||||
Renders a select box with any number of options
|
||||
|
||||
Options:
|
||||
|
||||
- `hasMany` - Allows for multiple options to be selected
|
||||
|
||||
```js
|
||||
{
|
||||
name: 'select',
|
||||
label: 'Select',
|
||||
type: 'select',
|
||||
hasMany: true // Optional
|
||||
options: [
|
||||
{
|
||||
value: 'option-1',
|
||||
label: 'Option 1 Label',
|
||||
}, {
|
||||
value: 'option-2',
|
||||
label: 'Option 2 Label',
|
||||
},
|
||||
],
|
||||
},
|
||||
```
|
||||
|
||||
#### `radio`
|
||||
|
||||
```js
|
||||
{
|
||||
name: 'radioGroup',
|
||||
label: 'Radio Group Example',
|
||||
type: 'radio',
|
||||
options: [
|
||||
{
|
||||
value: 'option-1',
|
||||
label: 'Options 1 Label',
|
||||
},
|
||||
{
|
||||
value: 'option-2',
|
||||
label: 'Option 2 Label',
|
||||
},
|
||||
],
|
||||
},
|
||||
```
|
||||
|
||||
### Complex Types
|
||||
|
||||
More powerful types but require more complex configuration
|
||||
|
||||
#### `upload`
|
||||
|
||||
Allows file and image upload. Useful for uploaded assets that can be referenced and re-used.
|
||||
|
||||
_TODO: Link to Upload configuration docs_
|
||||
|
||||
```js
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
label: 'Image',
|
||||
relationTo: 'media', // A collection with the `upload` properties configured
|
||||
}
|
||||
```
|
||||
|
||||
#### `row`
|
||||
|
||||
Rows allow for deeper nesting of values. They have their own set of fields.
|
||||
|
||||
```js
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
label: 'Text',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'number',
|
||||
label: 'Number',
|
||||
type: 'number',
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
#### `array`
|
||||
|
||||
An array of the specified fields
|
||||
|
||||
Options:
|
||||
|
||||
- `minRows` - Minimum number of items required
|
||||
- `maxRows` - Maximum number of items allowed
|
||||
|
||||
```js
|
||||
{
|
||||
name: 'array',
|
||||
label: 'Array',
|
||||
type: 'array',
|
||||
minRows: 2, // Optional
|
||||
maxRows: 4, // Optional
|
||||
fields: [
|
||||
{
|
||||
name: 'textField',
|
||||
label: 'Text Field',
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'checkbox',
|
||||
label: 'Checkbox',
|
||||
type: 'checkbox'
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### `group`
|
||||
|
||||
A way to group related fields together. They will be rendered as a group in the admin interface.
|
||||
|
||||
```js
|
||||
{
|
||||
type: 'group',
|
||||
label: 'Group',
|
||||
name: 'group',
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'nestedText1',
|
||||
label: 'Nested Text 1',
|
||||
type: 'text',
|
||||
}, {
|
||||
name: 'nestedText2',
|
||||
label: 'Nested Text 2',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
```
|
||||
|
||||
#### `blocks`
|
||||
|
||||
Repeat any type of content any number of times
|
||||
|
||||
The `blocks` property takes an array of the allowed fields
|
||||
|
||||
_TODO: Link to more powerful and complex examples_
|
||||
|
||||
Options:
|
||||
|
||||
- `minRows` - Minimum number of items required
|
||||
- `maxRows` - Maximum number of items allowed
|
||||
|
||||
```js
|
||||
{
|
||||
name: 'page',
|
||||
type: 'blocks',
|
||||
label: 'Page',
|
||||
labels: {
|
||||
singular: 'Page',
|
||||
plural: 'Pages',
|
||||
},
|
||||
minRows: 2, // Optional
|
||||
maxRows: 4, // Optional
|
||||
blocks: [
|
||||
{
|
||||
slug: 'quote'
|
||||
label: 'Quote',
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
slug: 'testimonial',
|
||||
label: 'Testimonial',
|
||||
type: 'textarea'
|
||||
},
|
||||
{
|
||||
slug: 'cta',
|
||||
label: 'Call To Action',
|
||||
type: 'text'
|
||||
}
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
## Common Options
|
||||
|
||||
Most fields have the ability to use the following options
|
||||
|
||||
#### `defaultValue`
|
||||
|
||||
Initial value for the field
|
||||
|
||||
#### `required`
|
||||
|
||||
`true/false` value if the field is required to be populated
|
||||
|
||||
#### `localized`
|
||||
|
||||
`true/false` value to enable localization on the field
|
||||
|
||||
#### `admin`
|
||||
|
||||
The `admin` is an optional object with properties that specify how the field should be represented in the admin interface.
|
||||
|
||||
Properties:
|
||||
|
||||
- `position` - If specified as `sidebar`, the field will show in the admin interface's sidebar
|
||||
- `width` - A percentage value ie. `50%` that will control the max-width of the field
|
||||
- `readOnly` - `true/false` to set the field to read-only after it is saved
|
||||
|
||||
### Common Options Example
|
||||
|
||||
```js
|
||||
// Collection config
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
name: 'my-field',
|
||||
type: 'text',
|
||||
label: 'Text'
|
||||
// highlight-start
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
width: '50%',
|
||||
readOnly: true,
|
||||
}
|
||||
// highlight-end
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
11
docs/GraphQL/config.mdx
Normal file
11
docs/GraphQL/config.mdx
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: GraphQL Overview
|
||||
label: Overview
|
||||
order: 10
|
||||
---
|
||||
|
||||
Go over GraphQL configuration options.
|
||||
|
||||
- Naming conventions
|
||||
- List of all queries and mutations w/ examples
|
||||
- Context
|
||||
7
docs/GraphQL/extending.mdx
Normal file
7
docs/GraphQL/extending.mdx
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Adding your own Queries and Mutations
|
||||
label: Custom Queries and Mutations
|
||||
order: 20
|
||||
---
|
||||
|
||||
Talk about how to add your own queries and mutations.
|
||||
253
docs/Guides/blog-api.mdx
Normal file
253
docs/Guides/blog-api.mdx
Normal file
@@ -0,0 +1,253 @@
|
||||
---
|
||||
title: Blog API From Scratch
|
||||
label: Blog API
|
||||
order: 20
|
||||
---
|
||||
|
||||
In this guide, we will be creating a Blog API from scratch.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Node
|
||||
- npm or yarn
|
||||
- MongoDB server
|
||||
|
||||
## Initial Setup
|
||||
|
||||
Let's get started by creating a project in a blank directory
|
||||
|
||||
```sh
|
||||
yarn init -y
|
||||
```
|
||||
|
||||
We then will install Payload and Express
|
||||
|
||||
```sh
|
||||
yarn add payload express
|
||||
```
|
||||
|
||||
### Server
|
||||
|
||||
Create a file named `server.js` in the root folder with the following contents
|
||||
|
||||
```js
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
|
||||
app.listen(3000, async () => {
|
||||
console.log('Express is now listening for incoming connections on port 3000.');
|
||||
});
|
||||
```
|
||||
|
||||
This is a basic Express application. Now let's pull in Payload and fill in the `init` parameters. `secret` and `mongoURL` can be changed as needed.
|
||||
|
||||
```js
|
||||
const express = require('express');
|
||||
const payload = require('payload'); // highlight-line
|
||||
|
||||
const app = express();
|
||||
|
||||
// highlight-start
|
||||
payload.init({
|
||||
secret: 'SECRET_KEY',
|
||||
mongoURL: 'mongodb://localhost/payload-blog',
|
||||
express: app,
|
||||
})
|
||||
// highlight-end
|
||||
|
||||
app.listen(3000, async () => {
|
||||
console.log('Express is now listening for incoming connections on port 3000.');
|
||||
});
|
||||
```
|
||||
|
||||
### Payload Configuration
|
||||
|
||||
Next, let's get some basics put into our `payload.config.js` file
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
serverURL: 'http://localhost:3000',
|
||||
collections: [],
|
||||
}
|
||||
```
|
||||
|
||||
#### Collections
|
||||
|
||||
We'll need a few different collections for our blog:
|
||||
|
||||
- Posts
|
||||
- Categories
|
||||
- Tags
|
||||
|
||||
The Posts will have the ability to have a single category and multiple tags
|
||||
|
||||
Let's create a `collections` directory and create a `.js` file for each
|
||||
|
||||
```js:title=Posts.js
|
||||
module.exports = {
|
||||
slug: 'posts',
|
||||
labels: {
|
||||
singular: 'Post',
|
||||
plural: 'Posts',
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'content',
|
||||
label: 'Content',
|
||||
type: 'textarea',
|
||||
},
|
||||
{
|
||||
name: 'category',
|
||||
label: 'Category',
|
||||
type: 'relationship',
|
||||
relationTo: 'categories', // Categories Slug // highlight-line
|
||||
},
|
||||
{
|
||||
name: 'tags',
|
||||
label: 'Tags',
|
||||
type: 'relationship',
|
||||
relationTo: 'tags', // Tags Slug // highlight-line
|
||||
hasMany: true, // Allow multiple tags per post
|
||||
},
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
```js:title=Categories.js
|
||||
module.exports = {
|
||||
slug: 'categories',
|
||||
labels: {
|
||||
singular: 'Category',
|
||||
plural: 'Categories',
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'name',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
```js:title=Tags.js
|
||||
module.exports = {
|
||||
slug: 'tags',
|
||||
labels: {
|
||||
singular: 'Tag',
|
||||
plural: 'Tags',
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'name',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
Once these are created, we can pull them into the `collections` array in our `payload.config.js`
|
||||
|
||||
```js:title=payload.config.js
|
||||
const Admins = require('./collections/Admins');
|
||||
// highlight-start
|
||||
const Posts = require('./collections/Posts');
|
||||
const Categories = require('./collections/Categories');
|
||||
const Tags = require('./collections/Tags');
|
||||
// highlight-end
|
||||
|
||||
module.exports = {
|
||||
serverURL: 'http://localhost:3000',
|
||||
admin: {
|
||||
user: 'admins',
|
||||
},
|
||||
collections: [
|
||||
Admins,
|
||||
// highlight-start
|
||||
Posts,
|
||||
Categories,
|
||||
Tags
|
||||
// highlight-end
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### Create Content
|
||||
|
||||
**TODO: Should a guide like this use the Admin interface or `curl`?**
|
||||
|
||||
Navigate to the admin interface at `http://localhost:3000/admin` and create your first user.
|
||||
|
||||
Then create some Categories, Tags, and a Post.
|
||||
|
||||
Let's use `curl` to quickly create some data. The following commands will create 2 tags and 1 category.
|
||||
|
||||
```sh
|
||||
curl -H "Content-Type: application/json" -X POST -d '{"name": "JavaScript"}' http://localhost:3000/api/tags
|
||||
curl -H "Content-Type: application/json" -X POST -d '{"name": "TypeScript"}' http://localhost:3000/api/tags
|
||||
curl -H "Content-Type: application/json" -X POST -d '{"name": "Code"}' http://localhost:3000/api/categories
|
||||
```
|
||||
|
||||
We'll then make a request to create a Post, this will inclue a relationship to the category and tags created previously.
|
||||
|
||||
TODO: Somehow retrieve tag and category IDs then include them in a request to create a Post
|
||||
|
||||
Once complete, navigate to `http://localhost:3000/api/posts`, and you should see something similar to the following:
|
||||
|
||||
```js
|
||||
{
|
||||
"docs": [
|
||||
{
|
||||
"tags": [
|
||||
{
|
||||
"name": "TypeScript",
|
||||
"createdAt": "2020-11-13T11:48:05.993Z",
|
||||
"updatedAt": "2020-11-13T11:48:05.993Z",
|
||||
"id": "5fae72758315da656fb3a8f0"
|
||||
},
|
||||
{
|
||||
"name": "JavaScript",
|
||||
"createdAt": "2020-11-13T11:48:00.064Z",
|
||||
"updatedAt": "2020-11-13T11:48:00.064Z",
|
||||
"id": "5fae72708315da656fb3a8ef"
|
||||
}
|
||||
],
|
||||
"title": "My Title",
|
||||
"content": "This is some content",
|
||||
"category": {
|
||||
"name": "Code",
|
||||
"createdAt": "2020-11-13T11:48:10.351Z",
|
||||
"updatedAt": "2020-11-13T11:48:36.358Z",
|
||||
"id": "5fae727a8315da656fb3a8f1"
|
||||
},
|
||||
"createdAt": "2020-11-13T11:50:14.312Z",
|
||||
"updatedAt": "2020-11-13T11:50:14.312Z",
|
||||
"id": "5fae72f68e314b67609e05d1"
|
||||
}
|
||||
],
|
||||
"totalDocs": 1,
|
||||
"limit": 10,
|
||||
"totalPages": 1,
|
||||
"page": 1,
|
||||
"pagingCounter": 1,
|
||||
"hasPrevPage": false,
|
||||
"hasNextPage": false,
|
||||
"prevPage": null,
|
||||
"nextPage": null
|
||||
}
|
||||
```
|
||||
7
docs/Guides/configure-email.mdx
Normal file
7
docs/Guides/configure-email.mdx
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Configure Email
|
||||
label: Configure Email
|
||||
order: 30
|
||||
---
|
||||
|
||||
TODO: Configure SendGrid or similar
|
||||
320
docs/Hooks/config.mdx
Normal file
320
docs/Hooks/config.mdx
Normal file
@@ -0,0 +1,320 @@
|
||||
---
|
||||
title: Hooks Config
|
||||
label: Config
|
||||
order: 10
|
||||
---
|
||||
|
||||
<Banner type="info">
|
||||
Hooks allow intervention before or after any internal operation.
|
||||
|
||||
Payload has extensive support for hooks, at both the document-level and field-level.
|
||||
</Banner>
|
||||
|
||||
## Hook Types
|
||||
|
||||
- [Collection Hooks](#collection-hooks)
|
||||
- [Field Hooks](#field-hooks)
|
||||
- [Error Hook](#error-hook)
|
||||
|
||||
## Collection Hooks
|
||||
|
||||
Collection can be executed at any point in the modification or retrieval of a collection.
|
||||
|
||||
Collection Hooks available:
|
||||
|
||||
_TODO: Fix anchor links_
|
||||
|
||||
- [beforeOperation](#beforeOperation)
|
||||
- [beforeValidate](#beforeValidate)
|
||||
- [beforeChange](#beforeChange)
|
||||
- [afterChange](#afterChange)
|
||||
- [beforeRead](#beforeRead)
|
||||
- [afterRead](#afterRead)
|
||||
- [beforeDelete](#beforeDelete)
|
||||
- [afterDelete](#afterDelete)
|
||||
|
||||
### Usage
|
||||
|
||||
Configuration of collection hooks is done under a collection config in `payload.config.js`. They can either be created in-line or required from a separate file. Each hook is an array of functions to be run.
|
||||
|
||||
_TODO: Talk about async vs synchronous and concurrency_
|
||||
|
||||
```js
|
||||
// Collection config
|
||||
module.exports = {
|
||||
slug: 'public-user',
|
||||
fields: [
|
||||
{ name: 'name', label: 'Name', type: 'text'},
|
||||
]
|
||||
hooks: {
|
||||
// Before All Operations
|
||||
beforeOperation: [(args) => {...}],
|
||||
|
||||
// Create and Update Operations
|
||||
beforeValidate: [(args) => {...}],
|
||||
beforeChange: [(args) => {...}],
|
||||
afterChange: [(args) => {...}],
|
||||
|
||||
// Read Operations
|
||||
beforeRead: [(args) => {...}],
|
||||
afterRead: [(args) => {...}],
|
||||
|
||||
// Delete Operations
|
||||
beforeDelete: [(args) => {...}],
|
||||
afterDelete: [(args) => {...}],
|
||||
|
||||
// Login Operations
|
||||
beforeLogin: [(args) => {...}],
|
||||
afterLogin: [(args) => {...}],
|
||||
|
||||
// After error
|
||||
afterError: [(args) => {...}],
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Each hook is an array of functions allowing for multiple to be combined as desired.
|
||||
|
||||
Configuration of hooks should be done in your `payload.config.js`
|
||||
|
||||
#### beforeOperation
|
||||
|
||||
Runs before any operation
|
||||
|
||||
```js
|
||||
const beforeOperationHook = ({
|
||||
args, // Original arguments passed into the operation
|
||||
operation, // name of the operation
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
Available Operations: `create`, `read`, `update`, `delete`, `refresh`, `forgotPassword`
|
||||
|
||||
#### beforeValidate
|
||||
|
||||
Runs before the `create` and `update` operations.
|
||||
|
||||
```js
|
||||
const beforeValidateHook = ({
|
||||
data, // incoming document data
|
||||
req, // full express request
|
||||
operation, // name of the operation ie. 'create', 'update'
|
||||
originalDoc, // original document
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
#### beforeChange
|
||||
|
||||
Runs before `create` and `update` operations, after validation.
|
||||
|
||||
```js
|
||||
const beforeChangeHook = ({
|
||||
data, // incoming document data
|
||||
req, // full express request
|
||||
operation, // name of the operation ie. 'create', 'update'
|
||||
originalDoc, // original document
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
#### afterChange
|
||||
|
||||
Runs after `create` and `update` operations.
|
||||
|
||||
```js
|
||||
const afterChangeHook = ({
|
||||
doc, // full document data
|
||||
req, // full express request
|
||||
operation, // name of the operation ie. 'create', 'update'
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
#### beforeRead
|
||||
|
||||
Runs before `find` and `findByID` operations.
|
||||
|
||||
```js
|
||||
const beforeReadHook = ({
|
||||
doc, // full document data
|
||||
req, // full express request
|
||||
query, // JSON formatted query
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
#### afterRead
|
||||
|
||||
Runs after `find` and `findByID` operations.
|
||||
|
||||
```js
|
||||
const afterReadHook = ({
|
||||
doc, // full document data
|
||||
req, // full express request
|
||||
query, // JSON formatted query
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
#### beforeDelete
|
||||
|
||||
Runs before `delete` operation
|
||||
|
||||
```js
|
||||
const beforeDeleteHook = ({
|
||||
req, // full express request
|
||||
id, // id of document to delete
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
#### afterDelete
|
||||
|
||||
Runs after `delete` operation
|
||||
|
||||
```js
|
||||
const afterDeleteHook = ({
|
||||
req, // full express request
|
||||
id, // id of document to delete
|
||||
doc, // deleted document
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
#### beforeLogin
|
||||
|
||||
Runs before `login` operation
|
||||
|
||||
```js
|
||||
const beforeLoginHook = ({
|
||||
req, // full express request
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
#### afterLogin
|
||||
|
||||
Runs after `login` operation
|
||||
|
||||
```js
|
||||
const afterLoginHook = ({
|
||||
req, // full express request
|
||||
user, // user being logged in
|
||||
token, // user token
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
#### afterError
|
||||
|
||||
Runs after an error occurs
|
||||
|
||||
_TODO: example usage_
|
||||
|
||||
## Field Hooks
|
||||
|
||||
Field can be executed at any point in the modification or retrieval of a collection field.
|
||||
|
||||
Field Hooks available:
|
||||
|
||||
- `beforeValidate`
|
||||
- `beforeChange`
|
||||
- `afterChange`
|
||||
- `afterRead`
|
||||
|
||||
### Usage
|
||||
|
||||
```js
|
||||
// Collection config
|
||||
module.exports = {
|
||||
slug: 'public-user',
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
type: 'text',
|
||||
hooks: {
|
||||
// Create and Update Operations
|
||||
beforeValidate: [(args) => {...}],
|
||||
beforeChange: [(args) => {...}],
|
||||
afterChange: [(args) => {...}],
|
||||
|
||||
// Read Operations
|
||||
afterRead: [(args) => {...}],
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
#### beforeValidate
|
||||
|
||||
Runs before the `create` and `update` operations.
|
||||
|
||||
```js
|
||||
const beforeValidateHook = ({
|
||||
value, // field value
|
||||
originalDoc, // original document
|
||||
data, // incoming document data
|
||||
operation, // name of the operation ie. 'create', 'update'
|
||||
req, // full express request
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
#### beforeChange
|
||||
|
||||
Runs before the `create` and `update` operations, after validation.
|
||||
|
||||
```js
|
||||
const beforeChangeHook = ({
|
||||
value, // field value
|
||||
originalDoc, // original document
|
||||
data, // incoming document data
|
||||
operation, // name of the operation ie. 'create', 'update'
|
||||
req, // full express request
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
#### afterChange
|
||||
|
||||
Runs after `create` and `update` operations
|
||||
|
||||
```js
|
||||
const afterChangeHook = ({
|
||||
value, // field value
|
||||
originalDoc, // original document
|
||||
data, // incoming document data
|
||||
operation, // name of the operation ie. 'create', 'update'
|
||||
req, // full express request
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
#### afterRead
|
||||
|
||||
Runs after result as been retrieved from database
|
||||
|
||||
```js
|
||||
const beforeChangeHook = ({
|
||||
value, // field value
|
||||
originalDoc, // original document
|
||||
data, // incoming document data
|
||||
operation, // name of the operation ie. 'create', 'update'
|
||||
req, // full express request
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
## Hook Lifecycle
|
||||
|
||||
Hooks execute in the following order in relation to operations
|
||||
|
||||
`beforeOperation` is a special hook that runs _before all operations_.
|
||||
|
||||
### Create/Update Lifecycle
|
||||
|
||||
1. `beforeValidate` Field Hooks
|
||||
2. `beforeValidate` Collection Hooks
|
||||
3. Validate operation
|
||||
4. `beforeChange` Field Hooks
|
||||
5. `beforeChange` Collection Hooks
|
||||
6. Create/Update against database
|
||||
7. `afterChange` Field Hooks
|
||||
8. `afterChange` Collection Hooks
|
||||
|
||||
### Find Lifecycle
|
||||
|
||||
1. `beforeRead` Collection Hooks
|
||||
2. Find against database
|
||||
3. `afterRead` Field Hooks
|
||||
4. `afterRead` Collection Hooks
|
||||
64
docs/Hooks/examples.mdx
Normal file
64
docs/Hooks/examples.mdx
Normal file
@@ -0,0 +1,64 @@
|
||||
---
|
||||
title: Hooks Examples
|
||||
label: Examples
|
||||
order: 20
|
||||
---
|
||||
|
||||
### Collection Hooks
|
||||
|
||||
#### Set createdBy and lastModifiedBy
|
||||
|
||||
```js
|
||||
beforeChange: [
|
||||
({ req, operation }) => {
|
||||
if (req.user && req.body) {
|
||||
if (operation === 'create') {
|
||||
req.body.createdBy = req.user.id;
|
||||
}
|
||||
req.body.lastModifiedBy = req.user.id;
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
_NOTE: createdBy and lastModifiedBy must be fields on the collection_
|
||||
|
||||
#### Use Local Payload API
|
||||
|
||||
Queries a separate collection and appends to returned result
|
||||
|
||||
```js
|
||||
afterRead: [
|
||||
async ({ req, doc }) => {
|
||||
const formattedData = { ...doc };
|
||||
const posts = await req.payload.find({
|
||||
collection: 'posts',
|
||||
});
|
||||
|
||||
formattedData.posts = posts;
|
||||
|
||||
return formattedData;
|
||||
},
|
||||
],
|
||||
```
|
||||
|
||||
#### Read Request Header
|
||||
|
||||
```js
|
||||
beforeRead: [
|
||||
({ req }) => {
|
||||
if (req.headers.specialheader === 'special') {
|
||||
// Do something...
|
||||
}
|
||||
},
|
||||
],
|
||||
```
|
||||
|
||||
### Field Hooks
|
||||
|
||||
#### Kebab-case Formatter
|
||||
|
||||
```js
|
||||
beforeValidate: [
|
||||
({ value }) => val.replace(/ /g, '-').toLowerCase();
|
||||
]
|
||||
```
|
||||
11
docs/Local-API/overview.mdx
Normal file
11
docs/Local-API/overview.mdx
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Server-Side Operations
|
||||
label: Overview
|
||||
order: 10
|
||||
---
|
||||
|
||||
Talk about how to run server-side operations.
|
||||
|
||||
- Access control
|
||||
- Providing a user
|
||||
|
||||
11
docs/Production/deployment.mdx
Normal file
11
docs/Production/deployment.mdx
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Production Deployment
|
||||
label: Deployment
|
||||
order: 10
|
||||
---
|
||||
|
||||
Talk about how to deploy Payload here.
|
||||
|
||||
- DigitalOcean
|
||||
- If no file storage, Heroku
|
||||
- If file storage, need Heroku extensions
|
||||
10
docs/Production/security.mdx
Normal file
10
docs/Production/security.mdx
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: Production Security Measures
|
||||
label: Security
|
||||
order: 20
|
||||
---
|
||||
|
||||
- Rate limiting
|
||||
- GraphQL Complexity
|
||||
- Limits
|
||||
- Max Depth
|
||||
119
docs/REST-API/config.mdx
Normal file
119
docs/REST-API/config.mdx
Normal file
@@ -0,0 +1,119 @@
|
||||
---
|
||||
title: REST API Overview
|
||||
label: Overview
|
||||
order: 10
|
||||
---
|
||||
|
||||
_TODO: Middleware_
|
||||
|
||||
<Banner type="info">
|
||||
The Payload REST API is created using properties in each collection's config
|
||||
</Banner>
|
||||
|
||||
Access to each collection is mounted on the API route using the collection's `slug` value. For example if a collection's slug is `users`, access to that collection will be mounted on `/api/users`.
|
||||
|
||||
Basic CRUD operations are automatically made available:
|
||||
|
||||
## Endpoints
|
||||
|
||||
### Collections
|
||||
|
||||
| Method | Path | Description |
|
||||
| :------- | :---------------------- | :----------------------------------- |
|
||||
| `GET` | `/api/{collection}` | Get all {collection} documents |
|
||||
| `GET` | `/api/{collection}/:id` | Get a specific {collection} document |
|
||||
| `POST` | `/api/{collection}` | Create a {collection} document |
|
||||
| `DELETE` | `/api/{collection}/:id` | Delete a {collection} document |
|
||||
| `PUT` | `/api/{collection}/:id` | Update a {collection} document |
|
||||
|
||||
### Globals
|
||||
|
||||
| Method | Path | Description |
|
||||
| :------- | :---------------------- | :--------------------- |
|
||||
| `GET` | `/api/globals/{global}` | Get {global} |
|
||||
| `POST` | `/api/globals/{global}` | Create/Update {global} |
|
||||
| `DELETE` | `/api/globals/{global}` | Delete a {global} |
|
||||
|
||||
NOTE: The `/api` prefix can be customized in the [main Payload configuration](../Configuration/main)
|
||||
|
||||
## Responses
|
||||
|
||||
### Collections
|
||||
|
||||
All collection queries return metadata
|
||||
|
||||
#### Example Response
|
||||
|
||||
Collection Query
|
||||
|
||||
```js
|
||||
{
|
||||
// Document Array // highlight-line
|
||||
"docs": [
|
||||
{
|
||||
"title": "Page Title",
|
||||
"description": "Some description text",
|
||||
"priority": 1,
|
||||
"createdAt": "2020-10-17T01:19:29.858Z",
|
||||
"updatedAt": "2020-10-17T01:19:29.858Z",
|
||||
"id": "5f8a46a1dd05db75c3c64760"
|
||||
}
|
||||
],
|
||||
// Metadata // highlight-line
|
||||
"totalDocs": 6,
|
||||
"limit": 1,
|
||||
"totalPages": 6,
|
||||
"page": 1,
|
||||
"pagingCounter": 1,
|
||||
"hasPrevPage": false,
|
||||
"hasNextPage": true,
|
||||
"prevPage": null,
|
||||
"nextPage": 2
|
||||
}
|
||||
```
|
||||
|
||||
Documents queried by id and globals will return a single JSON document, no metadata
|
||||
|
||||
| Property | Description |
|
||||
| :------------ | :--------------------------------------------------------- |
|
||||
| docs | Array of documents in the {collection} |
|
||||
| totalDocs | Total available documents of {collection} |
|
||||
| limit | Limit query parameter. Defaults to `10`, can be configured |
|
||||
| totalPages | Total pages available, based upon the `limit` queried for |
|
||||
| page | Current page |
|
||||
| pagingCounter | `number` for the first doc on the current page |
|
||||
| hasPrevPage | `true/false` if previous page exists |
|
||||
| hasNextPage | `true/false` if next page exists |
|
||||
| prevPage | `number` of previous page. `null` if doesn't exist. |
|
||||
| nextPage | `number` of next page. `null` if doesn't exist. |
|
||||
|
||||
## Query Parameters
|
||||
|
||||
| param | description | example |
|
||||
| :------ | :---------------------------------------------------------------------------------------- | :------------------------------- |
|
||||
| `limit` | Limits the number of documents returned | `?limit=3` |
|
||||
| `page` | Get specific page number | `?page=2` |
|
||||
| `sort` | Sort on a specific field. Use field name for ascending. Prefix with `-` for descending | `?sort=age`, `?sort=-age` |
|
||||
| `depth` | Controls how deep in JSON structure to return in the query. _Follows relationship fields_ | `?depth=3` |
|
||||
| `where` | Exposes many logical operators. _In-depth documentation below_ | `?where[property][equals]=value` |
|
||||
|
||||
### Where
|
||||
|
||||
Query on specific collection field values
|
||||
|
||||
| operator | description | example |
|
||||
| :------------------- | :-------------------------------------------------- | :------------------------------------------- |
|
||||
| `equals` | | `?where[property][equals]=value` |
|
||||
| `not_equals` | | `?where[property][not_equals]=value` |
|
||||
| `greater_than` | | `?where[property][greater_than]=value` |
|
||||
| `greater_than_equal` | | `?where[property][greater_than_equal]=value` |
|
||||
| `less_than` | | `?where[property][less_than]=value` |
|
||||
| `less_than_equal` | | `?where[property][less_than_equal]=value` |
|
||||
| `like` | Partial match against property | `?where[property][like]=value` |
|
||||
| `in` | Query for values in a comma-delimited list | `?where[property][in]=value1,value2` |
|
||||
| `not_in` | Query for values _not in_ in a comma-delimited list | `?where[property][not_in]=value1,value2` |
|
||||
| `exists` | Query for existence of a property | `?where[property][exists]=true` |
|
||||
|
||||
#### Complex Examples
|
||||
|
||||
_TODO: Complex Examples_
|
||||
16
docs/Upload/config.mdx
Normal file
16
docs/Upload/config.mdx
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: Upload Config
|
||||
label: Config
|
||||
order: 10
|
||||
---
|
||||
|
||||
Talk about how to configure uploads here.
|
||||
|
||||
Need to cover:
|
||||
|
||||
1. Can have multiple collections
|
||||
1. Static directory
|
||||
1. How to upload (multipart/form-data)
|
||||
1. Uploading is REST-only
|
||||
1. Access control
|
||||
1. Image sizes
|
||||
39
docs/getting-started/concepts.mdx
Normal file
39
docs/getting-started/concepts.mdx
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
title: Payload Concepts
|
||||
label: Concepts
|
||||
order: 20
|
||||
---
|
||||
|
||||
Payload is based around a small and intuitive set of concepts. Before starting to work with Payload, it's a good idea to familiarize yourself with the following:
|
||||
|
||||
### Config
|
||||
|
||||
<Banner type="info">
|
||||
The Payload config is where you configure everything that Payload does.
|
||||
</Banner>
|
||||
|
||||
By default, the Payload config lives in the root folder of your code and is named `payload.config.js`, but you can customize where you store it. The `config` is plain old JavaScript, meaning you can write full JavaScript functions and even full React components right into your config.
|
||||
|
||||
### Collections
|
||||
|
||||
<Banner type="info">
|
||||
A Collection represents a type of content that Payload will store and can contain many documents.
|
||||
</Banner>
|
||||
|
||||
Payload Collections are defined through Each Collection will map one-to-one with a MongoDB collection automatically based on fields that you define. You can have as many Collections as you need. It's often best practice to write your Collections in separate files,
|
||||
|
||||
### Globals
|
||||
|
||||
<Banner type="info">
|
||||
A Global is a "one-off" piece of content that is perfect for storing navigational structures, themes, top-level meta data, and more.
|
||||
</Banner>
|
||||
|
||||
You can define as many Collections as you need. On each Collection, you can specify the shape of your data through Fields.
|
||||
|
||||
### Fields
|
||||
|
||||
<Banner type="info">
|
||||
Fields are the building blocks of Payload. Collections and Globals both use Fields to define the shape of the data that they store.
|
||||
</Banner>
|
||||
|
||||
Payload comes with [many different types](../fields/overview) of Fields that give you a ton of flexibility while designing your API. Each Field type has its own potential properties that allow you to customize how they work.
|
||||
94
docs/getting-started/installation.mdx
Normal file
94
docs/getting-started/installation.mdx
Normal file
@@ -0,0 +1,94 @@
|
||||
---
|
||||
title: Installation
|
||||
label: Installation
|
||||
order: 30
|
||||
---
|
||||
|
||||
### Requirements
|
||||
|
||||
Payload requires the following software:
|
||||
|
||||
- Yarn or NPM
|
||||
- NodeJS version 10+
|
||||
- A Mongo Database
|
||||
- Express
|
||||
|
||||
Once you are sure that you have all of these dependencies, you can `yarn add payload` or `npm install --save payload` at the root of your project folder.
|
||||
|
||||
The first step is writing a baseline config. The simplest config contains the following:
|
||||
|
||||
```
|
||||
export default {
|
||||
serverURL: 'http://localhost:3000',
|
||||
};
|
||||
```
|
||||
|
||||
Create a new `payload.config.js` file in the root of your project and write the above code into it. This file fully supports ES6.
|
||||
|
||||
Although this is just the bare minimum config, there are *many* more options that you can control here. To reference the full config and all of its options, [click here](/docs/configuration/main).
|
||||
|
||||
### Server
|
||||
|
||||
Now that you've got a baseline Payload config, it's time to initialize Payload. It requires an Express server that you provide, so if you're not familiar with how to set up a baseline Express server, please read up on exactly what Express is and why to use it. Express' own [Documentation](https://expressjs.com/en/starter/hello-world.html) is a good place to start. Otherwise, follow along below for how to build your own Express server to use with Payload.
|
||||
|
||||
1. Run `yarn add express`
|
||||
1. Create a new `server.js` file in the root folder of your app
|
||||
1. Add the following code to `server.js`:
|
||||
|
||||
```
|
||||
const express = require('express');
|
||||
|
||||
const app = express();
|
||||
|
||||
app.listen(3000, async () => {
|
||||
console.log('Express is now listening for incoming connections on port 3000.')
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
This server doesn't do anything just yet. But, after you have this in place, it's time to initialize Payload. Payload is initialized via its `init()` method, which accepts a small set of arguments to tell it how to operate. For a full list of `init` arguments, please consult the [main configuration](/docs/configuration/main#init) docs.
|
||||
|
||||
To initialize Payload, update your `server.js` file with the following code:
|
||||
|
||||
```
|
||||
const express = require('express');
|
||||
const payload = require('payload');
|
||||
|
||||
const app = express();
|
||||
|
||||
payload.init({
|
||||
secret: 'SECRET_KEY',
|
||||
mongoURL: 'mongodb://localhost/payload',
|
||||
express: app,
|
||||
})
|
||||
|
||||
app.listen(3000, async () => {
|
||||
console.log('Express is now listening for incoming connections on port 3000.')
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
As you can see above, the required arguments to the `init` function are as follows:
|
||||
|
||||
##### `secret`
|
||||
|
||||
This is a secure string that will be used to authenticate with Payload. It can be random but should be at least 14 characters and be very difficult to guess. Often, it's smart to store this value in an `env` and set different values for each of your environments (ocal, stage, prod, etc).
|
||||
|
||||
##### `mongoURL`
|
||||
|
||||
This is a fully qualified MongoDB connection string that points to your Mongo database. If you don't have Mongo installed locally, you can [follow these steps for Mac OSX](https://docs.mongodb.com/manual/tutorial/install-mongodb-on-os-x/) and [these steps](https://docs.mongodb.com/manual/tutorial/install-mongodb-on-windows/) for Windows 10. If you want to use a local database and you know you have MongoDB installed locally, a typical connection string will look like this:
|
||||
|
||||
`mongodb://localhost/payload`
|
||||
|
||||
In contrast to running Mongo locally, a popular option is to sign up for a free [MongoDB Atlas account](https://www.mongodb.com/cloud/atlas), which is a fully hosted and cloud-based installation of Mongo that you don't need to ever worry about.
|
||||
|
||||
##### `express`
|
||||
|
||||
This is your Express app as shown above. Payload will tie into your existing `app` and scope all of its functionalities to sub-routers. By default, Payload will add an `/admin` router and an `/api` router, but you can customize these paths.
|
||||
|
||||
### Test it out
|
||||
|
||||
After you've gotten this far, it's time to boot up Payload. At the command line, run `yarn` or `npm install` and then `node server.js` in your application's folder to start up your app and initialize Payload.
|
||||
|
||||
After it starts, you can go to `http://localhost:3000/admin` to create your first Payload user!
|
||||
|
||||
53
docs/getting-started/what-is-payload.mdx
Normal file
53
docs/getting-started/what-is-payload.mdx
Normal file
@@ -0,0 +1,53 @@
|
||||
---
|
||||
title: What is Payload?
|
||||
label: What is Payload?
|
||||
order: 10
|
||||
---
|
||||
|
||||
<Banner type="success">
|
||||
Payload is a headless CMS and application framework. It’s meant to provide a massive boost to your development process, but importantly, stay out of your way as your apps get more complex.
|
||||
</Banner>
|
||||
|
||||
Out of the box, Payload gives you a lot of the things that you often need when developing a new website, web app, or native app:
|
||||
|
||||
- A Mongo database to store your data
|
||||
- A way to store, retrieve, and manipulate data of any shape via full REST and GraphQL APIs
|
||||
- Authentication—complete with commonly required functionality like registration, email verification, login, & password reset
|
||||
- Deep access control to your data, based on document or field-level functions
|
||||
- File storage and access control
|
||||
- A beautiful admin UI that’s generated specifically to suit your data
|
||||
|
||||
## What does "headless" mean?
|
||||
|
||||
A headless CMS is a system that sticks to what it's good at—managing content. It concentrates solely on granting administrators an effective way to author and maintain content, but doesn't control how and where that content is used.
|
||||
|
||||
In this way, the CMS can ensure that its content editing experience is highly polished and effective while avoiding placing creative constraints on designers or restricting development teams. In contrast, traditional content management systems bind the presentation of your content to the storage of your content and severely limit the creativity, development and usability of the content that they manage.
|
||||
|
||||
At this point this concept is [widely](https://en.wikipedia.org/wiki/Headless_content_management_system) [discussed](https://css-tricks.com/what-is-a-headless-cms/) online, and for good reason. The web has become more complicated and with complexity comes the demand for developers to better structure their code. The rise of interface libraries like React and Vue are now the de-facto standard for building modern applications and traditional content management systems are often not designed to make use of them.
|
||||
|
||||
### Why Payload?
|
||||
|
||||
The team behind Payload has been building websites and apps with existing content management systems and application frameworks for over a decade. We know what works and what doesn't about each of the existing solutions, and to this day have found no silver bullet solution.
|
||||
|
||||
**We believe that a CMS should be:**
|
||||
|
||||
- Cost-effective and should save time and effort
|
||||
- Intuitive for developers and content authors alike
|
||||
- Self-hosted however and wherever the application specifies
|
||||
- Designed in code but used with no coding experience
|
||||
- Blazing fast
|
||||
- Secure
|
||||
- Fully flexible and extensible
|
||||
|
||||
Payload is our silver bullet solution. It represents over two years of passionate development and brings everything we need when we build new apps and websites:
|
||||
|
||||
- A beautiful, dynamic, customizable admin UI
|
||||
- Extensible and reusable authentication
|
||||
- Content localization
|
||||
- Local file storage
|
||||
- Extremely flexible access control
|
||||
- Field conditional logic
|
||||
- Block-based layout building
|
||||
- Array field type(s)
|
||||
- Security
|
||||
- and much more
|
||||
@@ -1,5 +1,6 @@
|
||||
const { APIError } = require('./src/errors');
|
||||
const { APIError, Forbidden } = require('./src/errors');
|
||||
|
||||
module.exports = {
|
||||
APIError,
|
||||
Forbidden,
|
||||
};
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export { default as Group } from './src/client/components/forms/field-types/Group';
|
||||
export { default as Select } from './src/client/components/forms/field-types/Select';
|
||||
2
hooks.js
2
hooks.js
@@ -1,2 +0,0 @@
|
||||
export { default as useFieldType } from './src/client/components/forms/useFieldType';
|
||||
export { useForm } from './src/client/components/forms/Form/context';
|
||||
@@ -1,6 +1,15 @@
|
||||
module.exports = {
|
||||
verbose: true,
|
||||
testEnvironment: 'node',
|
||||
globalSetup: '<rootDir>/src/tests/globalSetup.js',
|
||||
globalTeardown: '<rootDir>/src/tests/globalTeardown.js',
|
||||
globalSetup: '<rootDir>/tests/api/globalSetup.js',
|
||||
globalTeardown: '<rootDir>/tests/api/globalTeardown.js',
|
||||
testPathIgnorePatterns: [
|
||||
'node_modules',
|
||||
'src/admin/*',
|
||||
],
|
||||
testTimeout: 15000,
|
||||
moduleNameMapper: {
|
||||
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/src/mocks/fileMock.js',
|
||||
'\\.(css|scss)$': '<rootDir>/src/mocks/emptyModule.js',
|
||||
},
|
||||
};
|
||||
|
||||
10
jest.react.config.js
Normal file
10
jest.react.config.js
Normal file
@@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
verbose: true,
|
||||
testTimeout: 15000,
|
||||
testRegex: '(/src/admin/.*\\.(test|spec))\\.[jt]sx?$',
|
||||
setupFilesAfterEnv: ['<rootDir>/tests/client/globalSetup.js'],
|
||||
moduleNameMapper: {
|
||||
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/src/mocks/fileMock.js',
|
||||
'\\.(css|scss)$': '<rootDir>/src/mocks/emptyModule.js',
|
||||
},
|
||||
};
|
||||
3
logger.js
Normal file
3
logger.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const logger = require('./src/utilities/logger');
|
||||
|
||||
module.exports = logger;
|
||||
94
package.json
94
package.json
@@ -1,40 +1,49 @@
|
||||
{
|
||||
"name": "@payloadcms/payload",
|
||||
"version": "0.0.24",
|
||||
"version": "0.0.141",
|
||||
"description": "CMS and Application Framework",
|
||||
"license": "ISC",
|
||||
"author": "Payload CMS LLC",
|
||||
"main": "index.js",
|
||||
"typings": "types/index.d.ts",
|
||||
"sideEffects": false,
|
||||
"bin": {
|
||||
"payload": "./src/bin/index.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build:components": "webpack --config src/webpack/components.config.js",
|
||||
"build": "cross-env PAYLOAD_CONFIG_PATH=demo/payload.config.js node src/bin/build",
|
||||
"build:analyze": "cross-env PAYLOAD_CONFIG_PATH=demo/payload.config.js PAYLOAD_ANALYZE_BUNDLE=true node src/bin/build",
|
||||
"cov": "npm run core:build && node ./node_modules/jest/bin/jest.js src/tests --coverage",
|
||||
"debug": "nodemon --inspect demo/server.js",
|
||||
"debug": "cross-env PAYLOAD_CONFIG_PATH=demo/payload.config.js nodemon --inspect demo/server.js",
|
||||
"debug:test:int": "node --inspect-brk node_modules/.bin/jest --runInBand",
|
||||
"dev": "cross-env PAYLOAD_CONFIG_PATH=demo/payload.config.js nodemon demo/server.js",
|
||||
"lint": "eslint .",
|
||||
"test:int": "cross-env PAYLOAD_CONFIG_PATH=demo/payload.config.js NODE_ENV=test jest --forceExit --runInBand",
|
||||
"test:unit": "cross-env PAYLOAD_CONFIG_PATH=demo/payload.config.js NODE_ENV=test jest"
|
||||
"test": "yarn test:int && yarn test:client",
|
||||
"test:int": "cross-env PAYLOAD_CONFIG_PATH=demo/payload.config.js NODE_ENV=test DISABLE_LOGGING=true jest --forceExit --runInBand",
|
||||
"test:client": "cross-env PAYLOAD_CONFIG_PATH=demo/payload.config.js NODE_ENV=test jest --config=jest.react.config.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.8.3",
|
||||
"@babel/core": "^7.11.6",
|
||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.8.3",
|
||||
"@babel/plugin-transform-runtime": "^7.8.3",
|
||||
"@babel/plugin-transform-runtime": "^7.11.5",
|
||||
"@babel/preset-env": "^7.8.3",
|
||||
"@babel/preset-react": "^7.8.3",
|
||||
"@babel/runtime": "^7.8.4",
|
||||
"@babel/register": "^7.11.5",
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"@date-io/date-fns": "^1.3.13",
|
||||
"@faceless-ui/collapsibles": "^0.1.0",
|
||||
"@faceless-ui/modal": "^1.0.4",
|
||||
"@faceless-ui/scroll-info": "^1.1.1",
|
||||
"@faceless-ui/window-info": "^1.2.2",
|
||||
"@udecode/slate-plugins": "^0.60.0",
|
||||
"@udecode/slate-plugins": "^0.64.3",
|
||||
"ajv": "^6.12.6",
|
||||
"async-some": "^1.0.2",
|
||||
"autoprefixer": "^9.7.4",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-loader": "^8.0.6",
|
||||
"babel-jest": "^26.3.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"babel-plugin-add-module-exports": "^1.0.4",
|
||||
"body-parser": "^1.19.0",
|
||||
"compression": "^1.7.4",
|
||||
"connect-history-api-fallback": "^1.6.0",
|
||||
@@ -42,46 +51,54 @@
|
||||
"date-fns": "^2.14.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"dotenv": "^6.0.0",
|
||||
"dotenv-webpack": "^1.8.0",
|
||||
"es6-promise": "^4.2.8",
|
||||
"express": "^4.17.1",
|
||||
"express-fileupload": "^1.1.6",
|
||||
"express-graphql": "^0.9.0",
|
||||
"express-rate-limit": "^5.1.3",
|
||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||
"falsey": "^1.0.0",
|
||||
"file-loader": "^1.1.11",
|
||||
"flatley": "^5.2.0",
|
||||
"graphql": "^15.0.0",
|
||||
"graphql-date": "^1.0.3",
|
||||
"graphql-playground-middleware-express": "^1.7.14",
|
||||
"graphql-query-complexity": "^0.7.0",
|
||||
"graphql-scalars": "^1.4.0",
|
||||
"graphql-type-json": "^0.3.1",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"http-status": "^1.4.2",
|
||||
"image-size": "^0.7.5",
|
||||
"ignore-styles": "^5.0.1",
|
||||
"is-hotkey": "^0.1.6",
|
||||
"is-url": "^1.2.4",
|
||||
"isomorphic-fetch": "^2.2.1",
|
||||
"isomorphic-style-loader": "^5.1.0",
|
||||
"jest": "^25.3.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"jwt-decode": "^3.0.0",
|
||||
"method-override": "^3.0.0",
|
||||
"micro-memoize": "^4.0.9",
|
||||
"mini-css-extract-plugin": "^1.0.0",
|
||||
"minimist": "^1.2.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"mkdirp": "^1.0.4",
|
||||
"mongodb-memory-server": "^6.5.2",
|
||||
"mongoose": "^5.8.9",
|
||||
"mongoose-hidden": "^1.8.1",
|
||||
"mongoose-paginate-v2": "^1.3.6",
|
||||
"node-sass": "^4.13.1",
|
||||
"node-sass-chokidar": "^1.4.0",
|
||||
"node-sass": "^4.14.1",
|
||||
"nodemailer": "^6.4.2",
|
||||
"object-to-formdata": "^3.0.9",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.4",
|
||||
"passport": "^0.4.1",
|
||||
"passport-anonymous": "^1.0.1",
|
||||
"passport-headerapikey": "^1.2.1",
|
||||
"passport-jwt": "^4.0.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"passport-local-mongoose": "^6.0.1",
|
||||
"postcss-flexbugs-fixes": "^3.3.1",
|
||||
"postcss-loader": "^2.1.6",
|
||||
"postcss-preset-env": "6.0.6",
|
||||
"pino": "^6.4.1",
|
||||
"pino-pretty": "^4.1.0",
|
||||
"postcss-loader": "^4.0.4",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
"prismjs": "^1.21.0",
|
||||
"probe-image-size": "^5.0.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"qs": "^6.9.1",
|
||||
"qs-middleware": "^1.0.3",
|
||||
@@ -91,43 +108,54 @@
|
||||
"react-datepicker": "^2.13.0",
|
||||
"react-document-meta": "^3.0.0-beta.2",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-hook-form": "^5.7.2",
|
||||
"react-redux": "^7.2.0",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-router-dom": "^5.1.2",
|
||||
"react-router-navigation-prompt": "^1.8.11",
|
||||
"react-select": "^3.0.8",
|
||||
"react-simple-code-editor": "^0.11.0",
|
||||
"react-toastify": "^6.1.0",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"sass": "^1.27.0",
|
||||
"sass-loader": "7.1.0",
|
||||
"sharp": "^0.25.2",
|
||||
"slate": "^0.58.3",
|
||||
"slate": "^0.58.4",
|
||||
"slate-history": "^0.58.3",
|
||||
"slate-hyperscript": "^0.58.3",
|
||||
"slate-react": "^0.58.3",
|
||||
"style-loader": "^0.21.0",
|
||||
"styled-components": "^5.1.1",
|
||||
"uglifyjs-webpack-plugin": "^2.2.0",
|
||||
"terser-webpack-plugin": "^4.2.3",
|
||||
"url-loader": "^1.0.1",
|
||||
"uuid": "^8.1.0",
|
||||
"val-loader": "^2.1.0",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack": "4.44.2",
|
||||
"webpack-bundle-analyzer": "^3.8.0",
|
||||
"webpack-dev-middleware": "^3.7.2",
|
||||
"webpack-hot-middleware": "^2.25.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^5.11.4",
|
||||
"@testing-library/react": "^11.0.4",
|
||||
"@trbl/eslint-config": "^1.2.4",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"cross-env": "^7.0.2",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-loader": "^4.0.2",
|
||||
"eslint-plugin-import": "^2.20.0",
|
||||
"eslint-plugin-jest": "^23.16.0",
|
||||
"eslint-plugin-jest-dom": "^3.0.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.2.1",
|
||||
"eslint-plugin-react": "^7.18.0",
|
||||
"eslint-plugin-react-hooks": "^2.3.0",
|
||||
"faker": "^4.1.0",
|
||||
"form-data": "^3.0.0",
|
||||
"graphql-request": "^2.0.0",
|
||||
"nodemon": "^1.19.4"
|
||||
}
|
||||
"mongodb": "^3.6.2",
|
||||
"nodemon": "^1.19.4",
|
||||
"webpack-cli": "^4.1.0"
|
||||
},
|
||||
"files": [
|
||||
"*.js",
|
||||
"!jest.config.js",
|
||||
"payload.d.ts",
|
||||
"src",
|
||||
"docs",
|
||||
"components",
|
||||
"scss"
|
||||
]
|
||||
}
|
||||
|
||||
1
scss/vars.scss
Normal file
1
scss/vars.scss
Normal file
@@ -0,0 +1 @@
|
||||
@import '../src/admin/scss/vars';
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
import qs from 'qs';
|
||||
|
||||
export const requests = {
|
||||
15
src/admin/assets/images/favicon.svg
Normal file
15
src/admin/assets/images/favicon.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg width="260" height="260" viewBox="0 0 260 260" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
path {
|
||||
fill: #333333;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
path {
|
||||
fill: white;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<path d="M120.59 8.5824L231.788 75.6142V202.829L148.039 251.418V124.203L36.7866 57.2249L120.59 8.5824Z" />
|
||||
<path d="M112.123 244.353V145.073L28.2114 193.769L112.123 244.353Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 437 B |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
BIN
src/admin/assets/images/og-image.png
Normal file
BIN
src/admin/assets/images/og-image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
232
src/admin/components/Routes.js
Normal file
232
src/admin/components/Routes.js
Normal file
@@ -0,0 +1,232 @@
|
||||
import React, { Suspense, lazy, useState, useEffect } from 'react';
|
||||
import {
|
||||
Route, Switch, withRouter, Redirect, useHistory,
|
||||
} from 'react-router-dom';
|
||||
import { useConfig } from './providers/Config';
|
||||
import List from './views/collections/List';
|
||||
import { useAuth } from './providers/Authentication';
|
||||
import DefaultTemplate from './templates/Default';
|
||||
import { requests } from '../api';
|
||||
import Loading from './elements/Loading';
|
||||
|
||||
const Dashboard = lazy(() => import('./views/Dashboard'));
|
||||
const ForgotPassword = lazy(() => import('./views/ForgotPassword'));
|
||||
const Login = lazy(() => import('./views/Login'));
|
||||
const Logout = lazy(() => import('./views/Logout'));
|
||||
const NotFound = lazy(() => import('./views/NotFound'));
|
||||
const Verify = lazy(() => import('./views/Verify'));
|
||||
const CreateFirstUser = lazy(() => import('./views/CreateFirstUser'));
|
||||
const Edit = lazy(() => import('./views/collections/Edit'));
|
||||
const EditGlobal = lazy(() => import('./views/Global'));
|
||||
const ResetPassword = lazy(() => import('./views/ResetPassword'));
|
||||
const Unauthorized = lazy(() => import('./views/Unauthorized'));
|
||||
const Account = lazy(() => import('./views/Account'));
|
||||
|
||||
const Routes = () => {
|
||||
const history = useHistory();
|
||||
const [initialized, setInitialized] = useState(null);
|
||||
const { user, permissions, permissions: { canAccessAdmin } } = useAuth();
|
||||
|
||||
const {
|
||||
admin: { user: userSlug }, routes, collections, globals,
|
||||
} = useConfig();
|
||||
|
||||
useEffect(() => {
|
||||
requests.get(`${routes.api}/${userSlug}/init`).then((res) => res.json().then((data) => {
|
||||
if (data && 'initialized' in data) {
|
||||
setInitialized(data.initialized);
|
||||
}
|
||||
}));
|
||||
}, [routes, userSlug]);
|
||||
|
||||
useEffect(() => {
|
||||
history.replace();
|
||||
}, [history]);
|
||||
|
||||
return (
|
||||
<Suspense fallback={<Loading />}>
|
||||
<Route
|
||||
path={routes.admin}
|
||||
render={({ match }) => {
|
||||
if (initialized === false) {
|
||||
return (
|
||||
<Switch>
|
||||
<Route path={`${match.url}/create-first-user`}>
|
||||
<CreateFirstUser setInitialized={setInitialized} />
|
||||
</Route>
|
||||
<Route>
|
||||
<Redirect to={`${match.url}/create-first-user`} />
|
||||
</Route>
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
|
||||
if (initialized === true) {
|
||||
return (
|
||||
<Switch>
|
||||
<Route path={`${match.url}/login`}>
|
||||
<Login />
|
||||
</Route>
|
||||
<Route path={`${match.url}/logout`}>
|
||||
<Logout />
|
||||
</Route>
|
||||
<Route path={`${match.url}/logout-inactivity`}>
|
||||
<Logout inactivity />
|
||||
</Route>
|
||||
<Route path={`${match.url}/forgot`}>
|
||||
<ForgotPassword />
|
||||
</Route>
|
||||
<Route path={`${match.url}/reset/:token`}>
|
||||
<ResetPassword />
|
||||
</Route>
|
||||
|
||||
{collections.map((collection) => {
|
||||
if (collection?.auth?.verify) {
|
||||
return (
|
||||
<Route
|
||||
key={`${collection.slug}-verify`}
|
||||
path={`${match.url}/${collection.slug}/verify/:token`}
|
||||
exact
|
||||
>
|
||||
<Verify collection={collection} />
|
||||
</Route>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
|
||||
<Route
|
||||
render={() => {
|
||||
if (user) {
|
||||
if (canAccessAdmin) {
|
||||
return (
|
||||
<DefaultTemplate>
|
||||
<Switch>
|
||||
<Route
|
||||
path={`${match.url}/`}
|
||||
exact
|
||||
>
|
||||
<Dashboard />
|
||||
</Route>
|
||||
|
||||
<Route path={`${match.url}/account`}>
|
||||
<Account />
|
||||
</Route>
|
||||
|
||||
{collections.map((collection) => (
|
||||
<Route
|
||||
key={`${collection.slug}-list`}
|
||||
path={`${match.url}/collections/${collection.slug}`}
|
||||
exact
|
||||
render={(routeProps) => {
|
||||
if (permissions?.[collection.slug]?.read?.permission) {
|
||||
return (
|
||||
<List
|
||||
{...routeProps}
|
||||
collection={collection}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <Unauthorized />;
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
{collections.map((collection) => (
|
||||
<Route
|
||||
key={`${collection.slug}-create`}
|
||||
path={`${match.url}/collections/${collection.slug}/create`}
|
||||
exact
|
||||
render={(routeProps) => {
|
||||
if (permissions?.[collection.slug]?.create?.permission) {
|
||||
return (
|
||||
<Edit
|
||||
{...routeProps}
|
||||
collection={collection}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <Unauthorized />;
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
{collections.map((collection) => (
|
||||
<Route
|
||||
key={`${collection.slug}-edit`}
|
||||
path={`${match.url}/collections/${collection.slug}/:id`}
|
||||
exact
|
||||
render={(routeProps) => {
|
||||
if (permissions?.[collection.slug]?.read?.permission) {
|
||||
return (
|
||||
<Edit
|
||||
isEditing
|
||||
{...routeProps}
|
||||
collection={collection}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <Unauthorized />;
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
{globals && globals.map((global) => (
|
||||
<Route
|
||||
key={`${global.slug}`}
|
||||
path={`${match.url}/globals/${global.slug}`}
|
||||
exact
|
||||
render={(routeProps) => {
|
||||
if (permissions?.[global.slug]?.read?.permission) {
|
||||
return (
|
||||
<EditGlobal
|
||||
{...routeProps}
|
||||
global={global}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <Unauthorized />;
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
<Route path={`${match.url}*`}>
|
||||
<NotFound />
|
||||
</Route>
|
||||
</Switch>
|
||||
</DefaultTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
if (canAccessAdmin === false) {
|
||||
return <Unauthorized />;
|
||||
}
|
||||
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (user === undefined) {
|
||||
return <Loading />;
|
||||
}
|
||||
return <Redirect to={`${match.url}/login`} />;
|
||||
}}
|
||||
/>
|
||||
<Route path={`${match.url}*`}>
|
||||
<NotFound />
|
||||
</Route>
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
export default withRouter(Routes);
|
||||
69
src/admin/components/elements/Banner/index.js
Normal file
69
src/admin/components/elements/Banner/index.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const baseClass = 'banner';
|
||||
|
||||
const Banner = ({
|
||||
children, className, to, icon, alignIcon, onClick, type,
|
||||
}) => {
|
||||
const classes = [
|
||||
baseClass,
|
||||
`${baseClass}--type-${type}`,
|
||||
className && className,
|
||||
to && `${baseClass}--has-link`,
|
||||
(to || onClick) && `${baseClass}--has-action`,
|
||||
icon && `${baseClass}--has-icon`,
|
||||
icon && `${baseClass}--align-icon-${alignIcon}`,
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
let RenderedType = 'div';
|
||||
|
||||
if (onClick && !to) RenderedType = 'button';
|
||||
if (to) RenderedType = Link;
|
||||
|
||||
return (
|
||||
<RenderedType
|
||||
className={classes}
|
||||
onClick={onClick}
|
||||
type={RenderedType === 'button' ? 'button' : undefined}
|
||||
to={to || undefined}
|
||||
>
|
||||
{(icon && alignIcon === 'left') && (
|
||||
<React.Fragment>
|
||||
{icon}
|
||||
</React.Fragment>
|
||||
)}
|
||||
{children}
|
||||
{(icon && alignIcon === 'right') && (
|
||||
<React.Fragment>
|
||||
{icon}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</RenderedType>
|
||||
);
|
||||
};
|
||||
|
||||
Banner.defaultProps = {
|
||||
children: undefined,
|
||||
className: '',
|
||||
to: undefined,
|
||||
icon: undefined,
|
||||
alignIcon: 'right',
|
||||
onClick: undefined,
|
||||
type: 'default',
|
||||
};
|
||||
|
||||
Banner.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
icon: PropTypes.node,
|
||||
alignIcon: PropTypes.oneOf(['left', 'right']),
|
||||
onClick: PropTypes.func,
|
||||
to: PropTypes.string,
|
||||
type: PropTypes.oneOf(['error', 'success', 'info', 'default']),
|
||||
};
|
||||
|
||||
export default Banner;
|
||||
74
src/admin/components/elements/Banner/index.scss
Normal file
74
src/admin/components/elements/Banner/index.scss
Normal file
@@ -0,0 +1,74 @@
|
||||
@import '../../../scss/styles.scss';
|
||||
|
||||
.banner {
|
||||
font-size: 1rem;
|
||||
line-height: base(1);
|
||||
border: 0;
|
||||
vertical-align: middle;
|
||||
background: rgba($color-dark-gray, .1);
|
||||
color: $color-dark-gray;
|
||||
border-radius: $style-radius-s;
|
||||
padding: base(.5);
|
||||
margin-bottom: $baseline;
|
||||
|
||||
&--has-action {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&--has-icon {
|
||||
svg {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&--align-icon-left {
|
||||
padding-left: base(.125);
|
||||
}
|
||||
|
||||
&--align-icon-right {
|
||||
padding-right: base(.125);
|
||||
}
|
||||
|
||||
&--type-default {
|
||||
&.button--has-action {
|
||||
&:hover {
|
||||
background: darken($color-dark-gray, .15);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: darken($color-dark-gray, .2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--type-error {
|
||||
background: rgba($color-red, .1);
|
||||
color: $color-red;
|
||||
|
||||
&.button--has-action {
|
||||
&:hover {
|
||||
background: rgba($color-red, .15);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: rgba($color-red, .2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--type-success {
|
||||
background: rgba($color-green, .1);
|
||||
color: darken($color-green, 20%);
|
||||
|
||||
&.button--has-action {
|
||||
&:hover {
|
||||
background: rgba($color-green, .15);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: rgba($color-green, .2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,7 @@ const Button = (props) => {
|
||||
|
||||
function handleClick(event) {
|
||||
if (type !== 'submit' && onClick) event.preventDefault();
|
||||
if (onClick) onClick();
|
||||
if (onClick) onClick(event);
|
||||
}
|
||||
|
||||
const buttonProps = {
|
||||
@@ -52,29 +52,55 @@
|
||||
background-color: $color-dark-gray;
|
||||
color: white;
|
||||
|
||||
&:hover, &:focus {
|
||||
&:hover {
|
||||
background: lighten($color-dark-gray, 5%);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
box-shadow: $focus-box-shadow;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: lighten($color-dark-gray, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
&--style-secondary {
|
||||
box-shadow: inset 0 0 0 $style-stroke-width $color-dark-gray;
|
||||
$base-box-shadow: inset 0 0 0 $style-stroke-width $color-dark-gray;
|
||||
$hover-box-shadow: inset 0 0 0 $style-stroke-width lighten($color-dark-gray, 5%);
|
||||
|
||||
box-shadow: $base-box-shadow;
|
||||
color: $color-dark-gray;
|
||||
background: none;
|
||||
|
||||
&:hover {
|
||||
background: rgba($color-dark-gray, .02);
|
||||
box-shadow: inset 0 0 0 $style-stroke-width lighten($color-dark-gray, 5%);
|
||||
box-shadow: $hover-box-shadow;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow: $hover-box-shadow, $focus-box-shadow;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: lighten($color-light-gray, 7%);
|
||||
}
|
||||
}
|
||||
|
||||
&--style-none {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
|
||||
&:focus {
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: .7;
|
||||
}
|
||||
}
|
||||
|
||||
&--round {
|
||||
@@ -138,6 +164,8 @@
|
||||
@include color-svg($color-dark-gray);
|
||||
background: $color-light-gray;
|
||||
}
|
||||
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
@@ -146,9 +174,4 @@
|
||||
background: lighten($color-dark-gray, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,37 @@
|
||||
const getInitialColumnState = (fields, useAsTitle, defaultColumns) => {
|
||||
let initialColumns = [];
|
||||
|
||||
const hasThumbnail = fields.find((field) => field.type === 'thumbnail');
|
||||
|
||||
if (Array.isArray(defaultColumns) && defaultColumns.length >= 1) {
|
||||
return {
|
||||
columns: defaultColumns,
|
||||
};
|
||||
}
|
||||
|
||||
if (hasThumbnail) {
|
||||
initialColumns.push('thumbnail');
|
||||
}
|
||||
|
||||
if (useAsTitle) {
|
||||
initialColumns.push(useAsTitle);
|
||||
}
|
||||
|
||||
const remainingColumns = fields.filter((field) => field.name !== useAsTitle && field.type !== 'thumbnail')
|
||||
.slice(0, 3 - initialColumns.length).map((field) => field.name);
|
||||
const remainingColumns = fields.reduce((remaining, field) => {
|
||||
if (field.name === useAsTitle) {
|
||||
return remaining;
|
||||
}
|
||||
|
||||
if (!field.name && Array.isArray(field.fields)) {
|
||||
return [
|
||||
...remaining,
|
||||
...field.fields.map((subField) => subField.name),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
...remaining,
|
||||
field.name,
|
||||
];
|
||||
}, []);
|
||||
|
||||
initialColumns = initialColumns.concat(remainingColumns);
|
||||
initialColumns = initialColumns.slice(0, 4);
|
||||
|
||||
return {
|
||||
columns: initialColumns,
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user