Compare commits
107 Commits
db-mongodb
...
db-postgre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9854ed60a | ||
|
|
576ee14976 | ||
|
|
bf77cec7e9 | ||
|
|
ab8b2f3fb8 | ||
|
|
db5f3f3ccd | ||
|
|
cece39957f | ||
|
|
157fff0417 | ||
|
|
88e113a545 | ||
|
|
d33afe48fe | ||
|
|
e76df32f09 | ||
|
|
b068f30f51 | ||
|
|
82bd5c656f | ||
|
|
afe8992ca6 | ||
|
|
48a410e294 | ||
|
|
82b88a315f | ||
|
|
cc94078607 | ||
|
|
1c30ad73b6 | ||
|
|
d9442dcce3 | ||
|
|
de92c50847 | ||
|
|
b7f5f932f6 | ||
|
|
bc9e591e37 | ||
|
|
ad38011348 | ||
|
|
d02b1fb084 | ||
|
|
51efe4f39b | ||
|
|
30e535b5b9 | ||
|
|
4bd3bb9400 | ||
|
|
69c93d3c62 | ||
|
|
78aa957043 | ||
|
|
cd06c022c0 | ||
|
|
8299e9fc33 | ||
|
|
9df5ab8a10 | ||
|
|
6979f5a1b1 | ||
|
|
723791b94f | ||
|
|
aa3833ec83 | ||
|
|
2972af2af1 | ||
|
|
857b9a4ac3 | ||
|
|
f829b084ba | ||
|
|
6ae682698d | ||
|
|
622cdb0440 | ||
|
|
cac52da638 | ||
|
|
3cb3c1aceb | ||
|
|
db4aacebb8 | ||
|
|
b735d6aa16 | ||
|
|
fbec3a33e0 | ||
|
|
034be89bdb | ||
|
|
1f2af0963b | ||
|
|
20f1ece2d7 | ||
|
|
2be5ad0eba | ||
|
|
5c58bd322d | ||
|
|
23f3eb1cf0 | ||
|
|
af67749e49 | ||
|
|
43dab5c705 | ||
|
|
9b7e62dc20 | ||
|
|
6e38cc2bcf | ||
|
|
83551bfcaa | ||
|
|
7b44d9d28a | ||
|
|
182d5db6de | ||
|
|
93109ec84a | ||
|
|
4d9e0f35f0 | ||
|
|
19327c8d6d | ||
|
|
831f1ff5be | ||
|
|
a8ac8b4633 | ||
|
|
36b1f5a763 | ||
|
|
24f697219b | ||
|
|
3fccd34abe | ||
|
|
a38f8e93a6 | ||
|
|
84570e6e3b | ||
|
|
5ad8e0edcb | ||
|
|
91bac9c0aa | ||
|
|
33f6edc9d5 | ||
|
|
e1f91f5170 | ||
|
|
0e75dfb5c1 | ||
|
|
1300e264be | ||
|
|
5600125de7 | ||
|
|
22e270f89c | ||
|
|
593f82bcba | ||
|
|
cbf3da1144 | ||
|
|
1a2aab4126 | ||
|
|
d0ba694c80 | ||
|
|
d78df36d9b | ||
|
|
02572d945a | ||
|
|
f05a433320 | ||
|
|
9ce3b3ab29 | ||
|
|
ade637befb | ||
|
|
c31b8dcaa0 | ||
|
|
0ffdcc685f | ||
|
|
881119ba3a | ||
|
|
60372faf36 | ||
|
|
8bca0b0b86 | ||
|
|
87a1d698b2 | ||
|
|
c11600aac3 | ||
|
|
ad01c6784d | ||
|
|
62601c54a7 | ||
|
|
4a144ddc44 | ||
|
|
9152a238d2 | ||
|
|
fc8b835264 | ||
|
|
28ee5e34c3 | ||
|
|
e25886649f | ||
|
|
985796be54 | ||
|
|
bd8b5123b0 | ||
|
|
c380deee4a | ||
|
|
0b12aac895 | ||
|
|
90d3f178ab | ||
|
|
a8c9625cde | ||
|
|
938d069523 | ||
|
|
1a337ec223 | ||
|
|
08f372e6c2 |
70
.github/workflows/main.yml
vendored
70
.github/workflows/main.yml
vendored
@@ -52,14 +52,14 @@ jobs:
|
|||||||
# https://github.com/actions/virtual-environments/issues/1187
|
# https://github.com/actions/virtual-environments/issues/1187
|
||||||
- name: tune linux network
|
- name: tune linux network
|
||||||
run: sudo ethtool -K eth0 tx off rx off
|
run: sudo ethtool -K eth0 tx off rx off
|
||||||
|
|
||||||
- name: Use Node.js 18
|
- name: Use Node.js 20
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 20
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v3
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 8
|
||||||
run_install: false
|
run_install: false
|
||||||
@@ -69,7 +69,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v4
|
||||||
name: Setup pnpm cache
|
name: Setup pnpm cache
|
||||||
with:
|
with:
|
||||||
path: ${{ env.STORE_PATH }}
|
path: ${{ env.STORE_PATH }}
|
||||||
@@ -82,7 +82,7 @@ jobs:
|
|||||||
- run: pnpm run build
|
- run: pnpm run build
|
||||||
|
|
||||||
- name: Cache build
|
- name: Cache build
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ./*
|
path: ./*
|
||||||
key: ${{ github.sha }}-${{ github.run_number }}
|
key: ${{ github.sha }}-${{ github.run_number }}
|
||||||
@@ -108,19 +108,19 @@ jobs:
|
|||||||
- name: tune linux network
|
- name: tune linux network
|
||||||
run: sudo ethtool -K eth0 tx off rx off
|
run: sudo ethtool -K eth0 tx off rx off
|
||||||
|
|
||||||
- name: Use Node.js 18
|
- name: Use Node.js 20
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 20
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v3
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 8
|
||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
- name: Restore build
|
- name: Restore build
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ./*
|
path: ./*
|
||||||
key: ${{ github.sha }}-${{ github.run_number }}
|
key: ${{ github.sha }}-${{ github.run_number }}
|
||||||
@@ -193,19 +193,19 @@ jobs:
|
|||||||
- name: tune linux network
|
- name: tune linux network
|
||||||
run: sudo ethtool -K eth0 tx off rx off
|
run: sudo ethtool -K eth0 tx off rx off
|
||||||
|
|
||||||
- name: Use Node.js 18
|
- name: Use Node.js 20
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 20
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v3
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 8
|
||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
- name: Restore build
|
- name: Restore build
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ./*
|
path: ./*
|
||||||
key: ${{ github.sha }}-${{ github.run_number }}
|
key: ${{ github.sha }}-${{ github.run_number }}
|
||||||
@@ -234,19 +234,19 @@ jobs:
|
|||||||
- name: tune linux network
|
- name: tune linux network
|
||||||
run: sudo ethtool -K eth0 tx off rx off
|
run: sudo ethtool -K eth0 tx off rx off
|
||||||
|
|
||||||
- name: Use Node.js 18
|
- name: Use Node.js 20
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 20
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v3
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 8
|
||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
- name: Restore build
|
- name: Restore build
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ./*
|
path: ./*
|
||||||
key: ${{ github.sha }}-${{ github.run_number }}
|
key: ${{ github.sha }}-${{ github.run_number }}
|
||||||
@@ -278,19 +278,19 @@ jobs:
|
|||||||
- name: tune linux network
|
- name: tune linux network
|
||||||
run: sudo ethtool -K eth0 tx off rx off
|
run: sudo ethtool -K eth0 tx off rx off
|
||||||
|
|
||||||
- name: Use Node.js 18
|
- name: Use Node.js 20
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 20
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v3
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 8
|
||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
- name: Restore build
|
- name: Restore build
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ./*
|
path: ./*
|
||||||
key: ${{ github.sha }}-${{ github.run_number }}
|
key: ${{ github.sha }}-${{ github.run_number }}
|
||||||
@@ -319,19 +319,19 @@ jobs:
|
|||||||
- name: tune linux network
|
- name: tune linux network
|
||||||
run: sudo ethtool -K eth0 tx off rx off
|
run: sudo ethtool -K eth0 tx off rx off
|
||||||
|
|
||||||
- name: Use Node.js 18
|
- name: Use Node.js 20
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 20
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v3
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 8
|
||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
- name: Restore build
|
- name: Restore build
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ./*
|
path: ./*
|
||||||
key: ${{ github.sha }}-${{ github.run_number }}
|
key: ${{ github.sha }}-${{ github.run_number }}
|
||||||
@@ -361,10 +361,10 @@ jobs:
|
|||||||
- name: tune linux network
|
- name: tune linux network
|
||||||
run: sudo ethtool -K eth0 tx off rx off
|
run: sudo ethtool -K eth0 tx off rx off
|
||||||
|
|
||||||
- name: Use Node.js 18
|
- name: Use Node.js 20
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 20
|
||||||
|
|
||||||
- name: Start MongoDB
|
- name: Start MongoDB
|
||||||
uses: supercharge/mongodb-github-action@1.10.0
|
uses: supercharge/mongodb-github-action@1.10.0
|
||||||
|
|||||||
11
.github/workflows/release-canary.yml
vendored
Normal file
11
.github/workflows/release-canary.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
name: release-canary
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Echo
|
||||||
|
run: echo "Register release-canary workflow"
|
||||||
7
.vscode/launch.json
vendored
7
.vscode/launch.json
vendored
@@ -19,6 +19,13 @@
|
|||||||
"PAYLOAD_PUBLIC_CLOUD_STORAGE_ADAPTER": "s3"
|
"PAYLOAD_PUBLIC_CLOUD_STORAGE_ADAPTER": "s3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"command": "pnpm run dev collections-graphql",
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"name": "Run Dev GraphQL",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "node-terminal"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"command": "pnpm run dev fields",
|
"command": "pnpm run dev fields",
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}",
|
||||||
|
|||||||
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@@ -5,21 +5,21 @@
|
|||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": true
|
"source.fixAll.eslint": "explicit"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"[typescriptreact]": {
|
"[typescriptreact]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": true
|
"source.fixAll.eslint": "explicit"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"[javascript]": {
|
"[javascript]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": true
|
"source.fixAll.eslint": "explicit"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"[json]": {
|
"[json]": {
|
||||||
|
|||||||
139
CHANGELOG.md
139
CHANGELOG.md
@@ -1,3 +1,142 @@
|
|||||||
|
## [2.18.3](https://github.com/payloadcms/payload/compare/v2.18.2...v2.18.3) (2024-05-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **db-postgres:** query with like on id columns ([#6416](https://github.com/payloadcms/payload/issues/6416)) ([bf77cec](https://github.com/payloadcms/payload/commit/bf77cec7e9e7db4988e481d464178636203fca32))
|
||||||
|
* **db-postgres:** uuid custom db name ([#6409](https://github.com/payloadcms/payload/issues/6409)) ([db5f3f3](https://github.com/payloadcms/payload/commit/db5f3f3ccdaedd9e8036c3e39fc20650a309a151))
|
||||||
|
* nested `disableListColumn` in rows ([#6412](https://github.com/payloadcms/payload/issues/6412)) ([ab8b2f3](https://github.com/payloadcms/payload/commit/ab8b2f3fb87864484582b7d819ca307888a9449b)), closes [#6407](https://github.com/payloadcms/payload/issues/6407)
|
||||||
|
|
||||||
|
## [2.18.2](https://github.com/payloadcms/payload/compare/v2.18.1...v2.18.2) (2024-05-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* allow focal point when no sizes defined ([#6397](https://github.com/payloadcms/payload/issues/6397)) ([88e113a](https://github.com/payloadcms/payload/commit/88e113a5452300434f690186d10ea02ab159ffc3))
|
||||||
|
|
||||||
|
## [2.18.1](https://github.com/payloadcms/payload/compare/v2.18.0...v2.18.1) (2024-05-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add back explicit crop x and y values ([#6391](https://github.com/payloadcms/payload/issues/6391)) ([e76df32](https://github.com/payloadcms/payload/commit/e76df32f0987cc92dc8d9c693950e650c52576bf))
|
||||||
|
|
||||||
|
## [2.18.0](https://github.com/payloadcms/payload/compare/v2.17.0...v2.18.0) (2024-05-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* store focal point on uploads ([#6364](https://github.com/payloadcms/payload/issues/6364)) ([82b88a3](https://github.com/payloadcms/payload/commit/82b88a315ff1d52f0b19a70224d5c600a3a97eb5))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **db-postgres:** filter with ID not_in AND queries - postgres ([#6358](https://github.com/payloadcms/payload/issues/6358)) ([cc94078](https://github.com/payloadcms/payload/commit/cc940786072c0065f10fdd2893050bddc4595a21)), closes [#5151](https://github.com/payloadcms/payload/issues/5151)
|
||||||
|
* **richtext-lexical:** upload, relationship and block node insertion fails sometimes ([#6390](https://github.com/payloadcms/payload/issues/6390)) ([48a410e](https://github.com/payloadcms/payload/commit/48a410e294598af9c73577a04f86466248f93da0))
|
||||||
|
|
||||||
|
## [2.17.0](https://github.com/payloadcms/payload/compare/v2.16.1...v2.17.0) (2024-05-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* adds misc translations to API view and react select ([#6138](https://github.com/payloadcms/payload/issues/6138)) ([30e535b](https://github.com/payloadcms/payload/commit/30e535b5b929dddead007d8a9adca62808595e2c))
|
||||||
|
|
||||||
|
* **richtext-lexical:** remove LexicalBlock, RichTextFieldRequiredEditor and FieldWithRichTextRequiredEditor types ([#6279](https://github.com/payloadcms/payload/issues/6279)) ([9df5ab8](https://github.com/payloadcms/payload/commit/9df5ab8a10a35ad34615d7e4da024f59ff037e0e))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* appends `editDepth` value to `radio` & `checkbox` IDs when inside drawer ([#6181](https://github.com/payloadcms/payload/issues/6181)) ([69c93d3](https://github.com/payloadcms/payload/commit/69c93d3c62394a5cf995a2eaec9a3ab30e0f77af))
|
||||||
|
* collection labels with locales not working when creating new doc ([#5995](https://github.com/payloadcms/payload/issues/5995)) ([51efe4f](https://github.com/payloadcms/payload/commit/51efe4f39bcaadccb109a2a02a690ca65041ee57))
|
||||||
|
* safely access cookie header for uploads ([#6367](https://github.com/payloadcms/payload/issues/6367)) ([de92c50](https://github.com/payloadcms/payload/commit/de92c50847640661f915455f8db0029873ddc7ab))
|
||||||
|
* step-nav breadcrumbs ellipsis ([#6345](https://github.com/payloadcms/payload/issues/6345)) ([d02b1fb](https://github.com/payloadcms/payload/commit/d02b1fb084e636e49122ad55b25b9c49eb761f1c))
|
||||||
|
*
|
||||||
|
### ⚠ BREAKING CHANGES
|
||||||
|
|
||||||
|
* **richtext-lexical:** remove LexicalBlock, RichTextFieldRequiredEditor and FieldWithRichTextRequiredEditor types (#6279)
|
||||||
|
|
||||||
|
## [2.16.1](https://github.com/payloadcms/payload/compare/v2.16.0...v2.16.1) (2024-05-07)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **richtext-lexical:** add maxDepth property to various lexical features ([#6250](https://github.com/payloadcms/payload/issues/6250)) ([857b9a4](https://github.com/payloadcms/payload/commit/857b9a4ac3236c740458750f156a3a4274eda210)), closes [#6242](https://github.com/payloadcms/payload/issues/6242)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **richtext-lexical:** export missing HorizontalRuleFeature ([#6236](https://github.com/payloadcms/payload/issues/6236)) ([f829b08](https://github.com/payloadcms/payload/commit/f829b084ba9649ef596cce4a7bf6ae8c7ccf57e3))
|
||||||
|
|
||||||
|
## [2.16.0](https://github.com/payloadcms/payload/compare/v2.15.0...v2.16.0) (2024-05-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* adds disableListColumn, disableListFilter to fields admin props ([#6188](https://github.com/payloadcms/payload/issues/6188)) ([db4aace](https://github.com/payloadcms/payload/commit/db4aacebb801f1cc11ef8732f9f3b78475256641))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* graphql upload relations returning null ([#6233](https://github.com/payloadcms/payload/issues/6233)) ([cac52da](https://github.com/payloadcms/payload/commit/cac52da638a0df4356120a2f61c6aaf25641a5ad))
|
||||||
|
* hide drag handles when `admin.isSortable: false` ([#6225](https://github.com/payloadcms/payload/issues/6225)) ([622cdb0](https://github.com/payloadcms/payload/commit/622cdb044002b2c3182c3b0432b51befbfb9b979))
|
||||||
|
* **plugin-form-builder:** hook overrides not working as intended ([#6203](https://github.com/payloadcms/payload/issues/6203)) ([b735d6a](https://github.com/payloadcms/payload/commit/b735d6aa169acca8cb638859d2c8ba43e315f02c))
|
||||||
|
|
||||||
|
## [2.15.0](https://github.com/payloadcms/payload/compare/v2.14.2...v2.15.0) (2024-05-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add isSortable to arrays and blocks ([#5962](https://github.com/payloadcms/payload/issues/5962)) ([5c58bd3](https://github.com/payloadcms/payload/commit/5c58bd322da966fe610959df13dfd49add35a2ef))
|
||||||
|
* use filterOptions in list relationship filter ([#6156](https://github.com/payloadcms/payload/issues/6156)) ([23f3eb1](https://github.com/payloadcms/payload/commit/23f3eb1cf0b75a4044319d7cd3e5000d5b4e42c4))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* bulk publish from collection list ([#6063](https://github.com/payloadcms/payload/issues/6063)) ([84570e6](https://github.com/payloadcms/payload/commit/84570e6e3bbb81fcae80da92b01bc56d09906072))
|
||||||
|
* cascade draft arg in nested GraphQL relationship queries ([#6141](https://github.com/payloadcms/payload/issues/6141)) ([a8ac8b4](https://github.com/payloadcms/payload/commit/a8ac8b463349664f3188ae77217f037da72f796b))
|
||||||
|
* GraphQL nested relationships not respecting req locale ([#6117](https://github.com/payloadcms/payload/issues/6117)) ([3fccd34](https://github.com/payloadcms/payload/commit/3fccd34abe5a332f88f5e950b755cd1d21441fb6))
|
||||||
|
* hide unusable fields from collection filter select ([#6135](https://github.com/payloadcms/payload/issues/6135)) ([2be5ad0](https://github.com/payloadcms/payload/commit/2be5ad0ebafd1d3c1c0567e2085ccfd593f18271))
|
||||||
|
* incorrect `localesNotSaved` translation ([#5996](https://github.com/payloadcms/payload/issues/5996)) ([af67749](https://github.com/payloadcms/payload/commit/af67749e49db92e675b63b52190e562468894706))
|
||||||
|
* **plugin-cloud:** purge cache for all sizes ([#5301](https://github.com/payloadcms/payload/issues/5301)) ([831f1ff](https://github.com/payloadcms/payload/commit/831f1ff5bed7e083cc076e9eb5ff9a2b2f1ed710))
|
||||||
|
* properly adds `readonly` styles to disabled `radio` fields ([#6176](https://github.com/payloadcms/payload/issues/6176)) ([9b7e62d](https://github.com/payloadcms/payload/commit/9b7e62dc20dca7402c6c68dfb8a5995c211993af))
|
||||||
|
* resets filter state when param state change within route ([#6169](https://github.com/payloadcms/payload/issues/6169)) ([6e38cc2](https://github.com/payloadcms/payload/commit/6e38cc2bcfb08b608abcb6aac4b4c1f6eea63428))
|
||||||
|
* **richtext-lexical:** drag and add block handles disappear too quickly for smaller screen sizes. ([#6145](https://github.com/payloadcms/payload/issues/6145)) ([24f6972](https://github.com/payloadcms/payload/commit/24f697219b5071d91a5c37aafb50e2d823b68d4c))
|
||||||
|
* **richtext-lexical:** floating toolbar caret positioned incorrectly for some line heights ([#6151](https://github.com/payloadcms/payload/issues/6151)) ([36b1f5a](https://github.com/payloadcms/payload/commit/36b1f5a763f782c140e62aa062b4077d6efd0738))
|
||||||
|
* sanitizes fields in default edit view for drawer content ([#6175](https://github.com/payloadcms/payload/issues/6175)) ([43dab5c](https://github.com/payloadcms/payload/commit/43dab5c7053831a0c71f3a6860113f653cab674f))
|
||||||
|
* version restoration ([#6039](https://github.com/payloadcms/payload/issues/6039)) ([91bac9c](https://github.com/payloadcms/payload/commit/91bac9c0aa1ff3da052b9c2ad83fa5ac23a16d1d))
|
||||||
|
|
||||||
|
## [2.14.2](https://github.com/payloadcms/payload/compare/v2.14.1...v2.14.2) (2024-04-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **deps:** dedupes react ([#6058](https://github.com/payloadcms/payload/issues/6058)) ([d0ba694](https://github.com/payloadcms/payload/commit/d0ba694c80a1b699c4f2cad98b1f0bde1f0d43ca))
|
||||||
|
|
||||||
|
## [2.14.1](https://github.com/payloadcms/payload/compare/v2.14.0...v2.14.1) (2024-04-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **db-postgres:** cumulative updates ([#6033](https://github.com/payloadcms/payload/issues/6033)) ([c31b8dc](https://github.com/payloadcms/payload/commit/c31b8dcaa0c43132d8a01e0cc43094f466cc9168))
|
||||||
|
* disable api key checkbox does not remove api key ([#6017](https://github.com/payloadcms/payload/issues/6017)) ([0ffdcc6](https://github.com/payloadcms/payload/commit/0ffdcc685f4e917a02e62dbaccec7cc8ebbf695d))
|
||||||
|
* **richtext-lexical:** minimize the amount of times sanitizeFields is called ([#6018](https://github.com/payloadcms/payload/issues/6018)) ([60372fa](https://github.com/payloadcms/payload/commit/60372faf36b7f6d92a61ccbaee0f528e50f5a51a))
|
||||||
|
|
||||||
|
## [2.14.0](https://github.com/payloadcms/payload/compare/v2.13.0...v2.14.0) (2024-04-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add count operation to collections ([#5936](https://github.com/payloadcms/payload/issues/5936)) ([c380dee](https://github.com/payloadcms/payload/commit/c380deee4a1db82bce9fea264060000957a53eee))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* bulk publish ([#6006](https://github.com/payloadcms/payload/issues/6006)) ([c11600a](https://github.com/payloadcms/payload/commit/c11600aac38cd67019765faf2a41e62df13e50cc))
|
||||||
|
* **db-postgres:** extra version suffix added to table names ([#5939](https://github.com/payloadcms/payload/issues/5939)) ([bd8b512](https://github.com/payloadcms/payload/commit/bd8b5123b0991e53eb209315897dbca10d14d45e))
|
||||||
|
* **db-postgres:** Fixes nested groups inside nested blocks ([#5882](https://github.com/payloadcms/payload/issues/5882)) ([e258866](https://github.com/payloadcms/payload/commit/e25886649fce414d5d47918f35ba2d4d2ba59174))
|
||||||
|
* **db-postgres:** row table names were not being built properly - v2 ([#5961](https://github.com/payloadcms/payload/issues/5961)) ([9152a23](https://github.com/payloadcms/payload/commit/9152a238d2982503e7f509350651b0ba3f83b1ec))
|
||||||
|
* header filters ([#5997](https://github.com/payloadcms/payload/issues/5997)) ([ad01c67](https://github.com/payloadcms/payload/commit/ad01c6784d283386dc819dfcd47455cad5accfaa))
|
||||||
|
* min/max attributes missing from number input ([#5779](https://github.com/payloadcms/payload/issues/5779)) ([985796b](https://github.com/payloadcms/payload/commit/985796be54b593af0a4934685ab8621b9badda10))
|
||||||
|
* removes `equals` & `not_equals` operators from fields with `hasMany` ([#5885](https://github.com/payloadcms/payload/issues/5885)) ([a8c9625](https://github.com/payloadcms/payload/commit/a8c9625cdec33476a5da87bcd9f010f9d7fb9a94))
|
||||||
|
|
||||||
## [2.13.0](https://github.com/payloadcms/payload/compare/v2.12.1...v2.13.0) (2024-04-19)
|
## [2.13.0](https://github.com/payloadcms/payload/compare/v2.12.1...v2.13.0) (2024-04-19)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<hr/>
|
<hr/>
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> 🎉 <strong>Payload 2.0 is now available!</strong> Read more in the <a target="_blank" href="https://payloadcms.com/blog/payload-2-0" rel="dofollow"><strong>announcement post</strong></a>.
|
> 🎉 <strong>Payload 3.0 beta released!</strong> You can now deploy Payload fully in any Next.js app folder. Read more in the <a target="_blank" href="https://payloadcms.com/blog/30-beta-install-payload-into-any-nextjs-app-with-one-line" rel="dofollow"><strong>announcement post</strong></a>.
|
||||||
|
|
||||||
<h3>Benefits over a regular CMS</h3>
|
<h3>Benefits over a regular CMS</h3>
|
||||||
<ul>
|
<ul>
|
||||||
|
|||||||
@@ -657,7 +657,7 @@ As your admin customizations gets more complex you may want to share state betwe
|
|||||||
|
|
||||||
### Styling Custom Components
|
### Styling Custom Components
|
||||||
|
|
||||||
Payload exports its SCSS variables and mixins for reuse in your own custom components. This is helpful in cases where you might want to style a custom input similarly to Payload's built-ini styling, so it blends more thoroughly into the existing admin UI.
|
Payload exports its SCSS variables and mixins for reuse in your own custom components. This is helpful in cases where you might want to style a custom input similarly to Payload's built-in styling, so it blends more thoroughly into the existing admin UI.
|
||||||
|
|
||||||
To make use of Payload SCSS variables / mixins to use directly in your own components, you can import them as follows:
|
To make use of Payload SCSS variables / mixins to use directly in your own components, you can import them as follows:
|
||||||
|
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ Example:
|
|||||||
{
|
{
|
||||||
slug: 'customers',
|
slug: 'customers',
|
||||||
auth: {
|
auth: {
|
||||||
forgotPassword: {
|
verify: {
|
||||||
// highlight-start
|
// highlight-start
|
||||||
generateEmailSubject: ({ req, user }) => {
|
generateEmailSubject: ({ req, user }) => {
|
||||||
return `Hey ${user.email}, reset your password!`;
|
return `Hey ${user.email}, reset your password!`;
|
||||||
|
|||||||
@@ -49,7 +49,8 @@ export default buildConfig({
|
|||||||
{
|
{
|
||||||
label: 'Arabic',
|
label: 'Arabic',
|
||||||
code: 'ar',
|
code: 'ar',
|
||||||
// opt-in to setting default text-alignment on Input fields to rtl (right-to-left) when current locale is rtl
|
// opt-in to setting default text-alignment on Input fields to rtl (right-to-left)
|
||||||
|
// when current locale is rtl
|
||||||
rtl: true,
|
rtl: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -134,13 +135,9 @@ to support localization, you need to specify each field that you would like to l
|
|||||||
```js
|
```js
|
||||||
{
|
{
|
||||||
name: 'title',
|
name: 'title',
|
||||||
type
|
type: 'text',
|
||||||
:
|
// highlight-start
|
||||||
'text',
|
localized: true,
|
||||||
// highlight-start
|
|
||||||
localized
|
|
||||||
:
|
|
||||||
true,
|
|
||||||
// highlight-end
|
// highlight-end
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ The initial request made to Payload will begin a new transaction and attach it t
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
const afterChange: CollectionAfterChangeHook = async ({ req }) => {
|
const afterChange: CollectionAfterChangeHook = async ({ req }) => {
|
||||||
// because req.transactionID is assigned from Payload and passed through, my-slug will only persist if the entire request is successful
|
// because req.transactionID is assigned from Payload and passed through,
|
||||||
|
// my-slug will only persist if the entire request is successful
|
||||||
await req.payload.create({
|
await req.payload.create({
|
||||||
req,
|
req,
|
||||||
collection: 'my-slug',
|
collection: 'my-slug',
|
||||||
@@ -60,10 +61,44 @@ const afterChange: CollectionAfterChangeHook = async ({ req }) => {
|
|||||||
|
|
||||||
### Direct Transaction Access
|
### Direct Transaction Access
|
||||||
|
|
||||||
When writing your own scripts or custom endpoints, you may wish to have direct control over transactions. This is useful for interacting with your database outside of Payload's local API.
|
When writing your own scripts or custom endpoints, you may wish to have direct control over transactions. This is useful for interacting with your database in something like a background job, outside the normal request-response flow.
|
||||||
|
|
||||||
The following functions can be used for managing transactions:
|
The following functions can be used for managing transactions:
|
||||||
|
|
||||||
`payload.db.beginTransaction` - Starts a new session and returns a transaction ID for use in other Payload Local API calls.
|
`payload.db.beginTransaction` - Starts a new session and returns a transaction ID for use in other Payload Local API calls. Note that if your database does not support transactions, this will return `null`.\
|
||||||
`payload.db.commitTransaction` - Takes the identifier for the transaction, finalizes any changes.
|
`payload.db.commitTransaction` - Takes the identifier for the transaction, finalizes any changes.\
|
||||||
`payload.db.rollbackTransaction` - Takes the identifier for the transaction, discards any changes.
|
`payload.db.rollbackTransaction` - Takes the identifier for the transaction, discards any changes.
|
||||||
|
|
||||||
|
You can then use the transaction ID with Payload's local API by passing it inside the `PayloadRequest` object.
|
||||||
|
|
||||||
|
Here is an example for a "background job" function, which utilizes the direct transaction API to make sure it either succeeds completely or gets rolled back in case of an error.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
async function allOrNothingJob() {
|
||||||
|
const req = {} as PayloadRequest;
|
||||||
|
req.transactionID = await payload.db.beginTransaction();
|
||||||
|
try {
|
||||||
|
await payload.create({
|
||||||
|
req, // use our manual transaction
|
||||||
|
collection: 'my-slug',
|
||||||
|
data: {
|
||||||
|
some: 'data'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await payload.create({
|
||||||
|
req, // use our manual transaction
|
||||||
|
collection: 'something-else',
|
||||||
|
data: {
|
||||||
|
some: 'data'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log('Everything done.');
|
||||||
|
if (req.transactionID) await payload.db.commitTransaction(req.transactionID);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Oh no, something went wrong!');
|
||||||
|
if (req.transactionID) await payload.db.rollbackTransaction(req.transactionID);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
39
docs/examples/overview.mdx
Normal file
39
docs/examples/overview.mdx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
title: Examples
|
||||||
|
label: Overview
|
||||||
|
order: 10
|
||||||
|
desc:
|
||||||
|
keywords: example, examples, starter, boilerplate, template, templates
|
||||||
|
---
|
||||||
|
|
||||||
|
Payload provides a vast array of examples to help you get started with your project no matter what you are working on. These examples are designed to be easy to get up and running, and to be easy to understand. They showcase nothing more than the specific features being demonstrated, so you can easily decipher what is going on.
|
||||||
|
|
||||||
|
Examples are changing every day, so be sure to check back often to see what new examples have been added. If you have a specific example you would like to see, please feel free to start a new [Discussion](https://github.com/payloadcms/payload/discussions) or open a new [PR](https://github.com/payloadcms/payload/pulls) to add it yourself.
|
||||||
|
|
||||||
|
- [Auth](https://github.com/payloadcms/payload/tree/main/examples/auth)
|
||||||
|
- [Custom Server](https://github.com/payloadcms/payload/tree/main/examples/custom-server)
|
||||||
|
- [Draft Preview](https://github.com/payloadcms/payload/tree/main/examples/draft-preview)
|
||||||
|
- [Email](https://github.com/payloadcms/payload/tree/main/examples/email)
|
||||||
|
- [Form Builder](https://github.com/payloadcms/payload/tree/main/examples/form-builder)
|
||||||
|
- [Hierarchy](https://github.com/payloadcms/payload/tree/main/examples/hierarchy)
|
||||||
|
- [Live Preview](https://github.com/payloadcms/payload/tree/main/examples/live-preview)
|
||||||
|
- [Multi-tenant](https://github.com/payloadcms/payload/tree/main/examples/multi-tenant)
|
||||||
|
- [Nested Docs](https://github.com/payloadcms/payload/tree/main/examples/nested-docs)
|
||||||
|
- [Redirects](https://github.com/payloadcms/payload/tree/main/examples/redirects)
|
||||||
|
- [Tests](https://github.com/payloadcms/payload/tree/main/examples/testing)
|
||||||
|
- [Virtual Fields](https://github.com/payloadcms/payload/tree/main/examples/virtual-fields)
|
||||||
|
- [White-label Admin UI](https://github.com/payloadcms/payload/tree/main/examples/whitelabel)
|
||||||
|
|
||||||
|
Where necessary, some examples include a front-end. Examples that require a front-end share this folder structure:
|
||||||
|
|
||||||
|
```plaintext
|
||||||
|
example/
|
||||||
|
├── payload/
|
||||||
|
├── next-app/
|
||||||
|
├── next-pages/
|
||||||
|
├── react-router/
|
||||||
|
├── vue/
|
||||||
|
├── svelte/
|
||||||
|
```
|
||||||
|
|
||||||
|
Where `payload` is your Payload project, and the other directories are dedicated to their respective front-end framework. We are adding new examples every day, so if your framework of choice is not yet supported in any particular example, please feel free to start a new [Discussion](https://github.com/payloadcms/payload/discussions) or open a new [PR](https://github.com/payloadcms/payload/pulls) to add it yourself.
|
||||||
@@ -59,6 +59,7 @@ properties:
|
|||||||
| Option | Description |
|
| Option | Description |
|
||||||
|---------------------------|----------------------------------------------------------------------------------------------------------------------|
|
|---------------------------|----------------------------------------------------------------------------------------------------------------------|
|
||||||
| **`initCollapsed`** | Set the initial collapsed state |
|
| **`initCollapsed`** | Set the initial collapsed state |
|
||||||
|
| **`isSortable`** | Disable array order sorting by setting this value to `false` |
|
||||||
| **`components.RowLabel`** | Function or React component to be rendered as the label on the array row. Receives `({ data, index, path })` as args |
|
| **`components.RowLabel`** | Function or React component to be rendered as the label on the array row. Receives `({ data, index, path })` as args |
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
@@ -67,6 +68,7 @@ properties:
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { CollectionConfig } from 'payload/types'
|
import { CollectionConfig } from 'payload/types'
|
||||||
|
import { RowLabelArgs } from 'payload/dist/admin/components/forms/RowLabel/types'
|
||||||
|
|
||||||
export const ExampleCollection: CollectionConfig = {
|
export const ExampleCollection: CollectionConfig = {
|
||||||
slug: 'example-collection',
|
slug: 'example-collection',
|
||||||
@@ -101,7 +103,7 @@ export const ExampleCollection: CollectionConfig = {
|
|||||||
],
|
],
|
||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
RowLabel: ({ data, index }) => {
|
RowLabel: ({ data, index }: RowLabelArgs) => {
|
||||||
return data?.title || `Slide ${String(index).padStart(2, '0')}`
|
return data?.title || `Slide ${String(index).padStart(2, '0')}`
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ properties:
|
|||||||
| Option | Description |
|
| Option | Description |
|
||||||
|---------------------|---------------------------------|
|
|---------------------|---------------------------------|
|
||||||
| **`initCollapsed`** | Set the initial collapsed state |
|
| **`initCollapsed`** | Set the initial collapsed state |
|
||||||
|
| **`isSortable`** | Disable block order sorting by setting this value to `false` |
|
||||||
|
|
||||||
### Block configs
|
### Block configs
|
||||||
|
|
||||||
|
|||||||
@@ -163,19 +163,21 @@ Example:
|
|||||||
|
|
||||||
In addition to each field's base configuration, you can define specific traits and properties for fields that only have effect on how they are rendered in the Admin panel. The following properties are available for all fields within the `admin` property:
|
In addition to each field's base configuration, you can define specific traits and properties for fields that only have effect on how they are rendered in the Admin panel. The following properties are available for all fields within the `admin` property:
|
||||||
|
|
||||||
| Option | Description |
|
| Option | Description |
|
||||||
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `condition` | You can programmatically show / hide fields based on what other fields are doing. [Click here](#conditional-logic) for more info. |
|
| `condition` | You can programmatically show / hide fields based on what other fields are doing. [Click here](#conditional-logic) for more info. |
|
||||||
| `components` | All field components can be completely and easily swapped out for custom components that you define. [Click here](#custom-components) for more info. |
|
| `components` | All field components can be completely and easily swapped out for custom components that you define. [Click here](#custom-components) for more info. |
|
||||||
| `description` | Helper text to display with the field to provide more information for the editor user. [Click here](#description) for more info. |
|
| `description` | Helper text to display with the field to provide more information for the editor user. [Click here](#description) for more info. |
|
||||||
| `position` | Specify if the field should be rendered in the sidebar by defining `position: 'sidebar'`. |
|
| `position` | Specify if the field should be rendered in the sidebar by defining `position: 'sidebar'`. |
|
||||||
| `width` | Restrict the width of a field. you can pass any string-based value here, be it pixels, percentages, etc. This property is especially useful when fields are nested within a `Row` type where they can be organized horizontally. |
|
| `width` | Restrict the width of a field. you can pass any string-based value here, be it pixels, percentages, etc. This property is especially useful when fields are nested within a `Row` type where they can be organized horizontally. |
|
||||||
| `style` | Attach raw CSS style properties to the root DOM element of a field. |
|
| `style` | Attach raw CSS style properties to the root DOM element of a field. |
|
||||||
| `className` | Attach a CSS class name to the root DOM element of a field. |
|
| `className` | Attach a CSS class name to the root DOM element of a field. |
|
||||||
| `readOnly` | Setting a field to `readOnly` has no effect on the API whatsoever but disables the admin component's editability to prevent editors from modifying the field's value. |
|
| `readOnly` | Setting a field to `readOnly` has no effect on the API whatsoever but disables the admin component's editability to prevent editors from modifying the field's value. |
|
||||||
| `disabled` | If a field is `disabled`, it is completely omitted from the Admin panel. |
|
| `disabled` | If a field is `disabled`, it is completely omitted from the Admin panel. |
|
||||||
| `disableBulkEdit` | Set `disableBulkEdit` to `true` to prevent fields from appearing in the select options when making edits for multiple documents. |
|
| `disableBulkEdit` | Set `disableBulkEdit` to `true` to prevent fields from appearing in the select options when making edits for multiple documents. |
|
||||||
| `hidden` | Setting a field's `hidden` property on its `admin` config will transform it into a `hidden` input type. Its value will still submit with the Admin panel's requests, but the field itself will not be visible to editors. |
|
| `disableListColumn` | Set `disableListColumn` to `true` to prevent fields from appearing in the list view column selector. |
|
||||||
|
| `disableListFilter` | Set `disableListFilter` to `true` to prevent fields from appearing in the list view filter options. |
|
||||||
|
| `hidden` | Setting a field's `hidden` property on its `admin` config will transform it into a `hidden` input type. Its value will still submit with the Admin panel's requests, but the field itself will not be visible to editors. |
|
||||||
|
|
||||||
### Custom components
|
### Custom components
|
||||||
|
|
||||||
|
|||||||
@@ -43,11 +43,12 @@ export const PublicUser: CollectionConfig = {
|
|||||||
|
|
||||||
**Payload will automatically open up the following queries:**
|
**Payload will automatically open up the following queries:**
|
||||||
|
|
||||||
| Query Name | Operation |
|
| Query Name | Operation |
|
||||||
| ------------------ | ------------------- |
|
| ------------------ | ------------------- |
|
||||||
| **`PublicUser`** | `findByID` |
|
| **`PublicUser`** | `findByID` |
|
||||||
| **`PublicUsers`** | `find` |
|
| **`PublicUsers`** | `find` |
|
||||||
| **`mePublicUser`** | `me` auth operation |
|
| **`countPublicUsers`** | `count` |
|
||||||
|
| **`mePublicUser`** | `me` auth operation |
|
||||||
|
|
||||||
**And the following mutations:**
|
**And the following mutations:**
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ If your Hook simply performs a side-effect, such as updating a CRM, it might be
|
|||||||
|
|
||||||
#### Server-only execution
|
#### Server-only execution
|
||||||
|
|
||||||
Payload Hooks are only triggered on the server. You can safely [remove your hooks](/docs/admin/webpack#aliasing-server-only-modules) from your Admin panel's client-side code by customizing the Webpack config, which not only keeps your Admin bundles' filesize small but also ensures that any server-side only code does not cause problems within browser environments.
|
Payload Hooks are only triggered on the server. You can safely [remove your hooks](/docs/admin/excluding-server-code#aliasing-server-only-modules) from your Admin panel's client-side code by customizing the Webpack config, which not only keeps your Admin bundles' filesize small but also ensures that any server-side only code does not cause problems within browser environments.
|
||||||
|
|
||||||
## Hook Types
|
## Hook Types
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ keywords: live preview, frontend, react, next.js, vue, nuxt.js, svelte, hook, us
|
|||||||
|
|
||||||
While using Live Preview, the Admin panel emits a new `window.postMessage` event every time a change is made to the document. Your front-end application can listen for these events and re-render accordingly.
|
While using Live Preview, the Admin panel emits a new `window.postMessage` event every time a change is made to the document. Your front-end application can listen for these events and re-render accordingly.
|
||||||
|
|
||||||
Wiring your front-end into Live Preview is easy. If your front-end application is built with React or Next.js, use the [`useLivePreview`](#react) React hook that Payload provides. In the future, all other major frameworks like Vue, Svelte, etc will be officially supported. If you are using any of these frameworks today, you can still integrate with Live Preview yourself using the underlying tooling that Payload provides. See [building your own hook](#building-your-own-hook) for more information.
|
Wiring your front-end into Live Preview is easy. If your front-end application is built with React, Next.js, Vue or Nuxt.js, use the `useLivePreview` hook that Payload provides. In the future, all other major frameworks like Svelte will be officially supported. If you are using any of these frameworks today, you can still integrate with Live Preview yourself using the underlying tooling that Payload provides. See [building your own hook](#building-your-own-hook) for more information.
|
||||||
|
|
||||||
By default, all hooks accept the following args:
|
By default, all hooks accept the following args:
|
||||||
|
|
||||||
@@ -32,6 +32,10 @@ And return the following values:
|
|||||||
If your front-end is tightly coupled to required fields, you should ensure that your UI does not break when these fields are removed. For example, if you are rendering something like `data.relatedPosts[0].title`, your page will break once you remove the first related post. To get around this, use conditional logic, optional chaining, or default values in your UI where needed. For example, `data?.relatedPosts?.[0]?.title`.
|
If your front-end is tightly coupled to required fields, you should ensure that your UI does not break when these fields are removed. For example, if you are rendering something like `data.relatedPosts[0].title`, your page will break once you remove the first related post. To get around this, use conditional logic, optional chaining, or default values in your UI where needed. For example, `data?.relatedPosts?.[0]?.title`.
|
||||||
</Banner>
|
</Banner>
|
||||||
|
|
||||||
|
<Banner type="info">
|
||||||
|
If is important that the `depth` argument matches exactly with the depth of your initial page request. The depth property is used to populated relationships and uploads beyond their IDs. See [Depth](../getting-started/concepts#depth) for more information.
|
||||||
|
</Banner>
|
||||||
|
|
||||||
### React
|
### React
|
||||||
|
|
||||||
If your front-end application is built with React or Next.js, you can use the `useLivePreview` hook that Payload provides.
|
If your front-end application is built with React or Next.js, you can use the `useLivePreview` hook that Payload provides.
|
||||||
@@ -69,9 +73,40 @@ export const PageClient: React.FC<{
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
<Banner type="info">
|
### Vue
|
||||||
If is important that the `depth` argument matches exactly with the depth of your initial page request. The depth property is used to populated relationships and uploads beyond their IDs. See [Depth](../getting-started/concepts#depth) for more information.
|
|
||||||
</Banner>
|
If your front-end application is built with Vue 3 or Nuxt 3, you can use the `useLivePreview` composable that Payload provides.
|
||||||
|
|
||||||
|
First, install the `@payloadcms/live-preview-vue` package:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @payloadcms/live-preview-vue
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, use the `useLivePreview` hook in your Vue component:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { PageData } from '~/types';
|
||||||
|
import { defineProps } from 'vue';
|
||||||
|
import { useLivePreview } from '@payloadcms/live-preview-vue';
|
||||||
|
|
||||||
|
// Fetch the initial data on the parent component or using async state
|
||||||
|
const props = defineProps<{ initialData: PageData }>();
|
||||||
|
|
||||||
|
// The hook will take over from here and keep the preview in sync with the changes you make.
|
||||||
|
// The `data` property will contain the live data of the document only when viewed from the Preview view of the Admin UI.
|
||||||
|
const { data } = useLivePreview<PageData>({
|
||||||
|
initialData: props.initialData,
|
||||||
|
serverURL: "<PAYLOAD_SERVER_URL>",
|
||||||
|
depth: 2,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h1>{{ data.title }}</h1>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
## Building your own hook
|
## Building your own hook
|
||||||
|
|
||||||
|
|||||||
@@ -164,6 +164,22 @@ const result = await payload.findByID({
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Count
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Result will be an object with:
|
||||||
|
// {
|
||||||
|
// totalDocs: 10, // count of the documents satisfies query
|
||||||
|
// }
|
||||||
|
const result = await payload.count({
|
||||||
|
collection: 'posts', // required
|
||||||
|
locale: 'en',
|
||||||
|
where: {}, // pass a `where` query here
|
||||||
|
user: dummyUser,
|
||||||
|
overrideAccess: false,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
#### Update by ID
|
#### Update by ID
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ In the template, we have stubbed out a basic `onInitExtension` file that you can
|
|||||||
|
|
||||||
### Webpack
|
### Webpack
|
||||||
|
|
||||||
If any of your files use server only packages such as fs, stripe, nodemailer, etc, they will need to be removed from the browser bundle. To do that, you can [alias the file imports with webpack](https://payloadcms.com/docs/admin/webpack#aliasing-server-only-modules).
|
If any of your files use server only packages such as fs, stripe, nodemailer, etc, they will need to be removed from the browser bundle. To do that, you can [alias the file imports with webpack](https://payloadcms.com/docs/admin/excluding-server-code#aliasing-server-only-modules).
|
||||||
|
|
||||||
When files are bundled for the browser, the import paths are essentially crawled to determine what files to include in the bundle. To prevent the server only files from making it into the bundle, we can alias their import paths to a file that can be included in the browser. This will short-circuit the import path crawling and ensure browser only code is bundled.
|
When files are bundled for the browser, the import paths are essentially crawled to determine what files to include in the bundle. To prevent the server only files from making it into the bundle, we can alias their import paths to a file that can be included in the browser. This will short-circuit the import path crawling and ensure browser only code is bundled.
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,19 @@ Note: Collection slugs must be formatted in kebab-case
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
operation: "Count",
|
||||||
|
method: "GET",
|
||||||
|
path: "/api/{collection-slug}/count",
|
||||||
|
description: "Count the documents",
|
||||||
|
example: {
|
||||||
|
slug: "count",
|
||||||
|
req: true,
|
||||||
|
res: {
|
||||||
|
totalDocs: 10
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
operation: "Create",
|
operation: "Create",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|||||||
@@ -203,7 +203,8 @@ const Pages: CollectionConfig = {
|
|||||||
editor: lexicalEditor({
|
editor: lexicalEditor({
|
||||||
features: ({ defaultFeatures }) => [
|
features: ({ defaultFeatures }) => [
|
||||||
...defaultFeatures,
|
...defaultFeatures,
|
||||||
// The HTMLConverter Feature is the feature which manages the HTML serializers. If you do not pass any arguments to it, it will use the default serializers.
|
// The HTMLConverter Feature is the feature which manages the HTML serializers.
|
||||||
|
// If you do not pass any arguments to it, it will use the default serializers.
|
||||||
HTMLConverterFeature({}),
|
HTMLConverterFeature({}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -270,6 +270,10 @@ const serialize = (children) =>
|
|||||||
text = <em key={i}>{text}</em>;
|
text = <em key={i}>{text}</em>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (node.text === '') {
|
||||||
|
text = <br />;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle other leaf types here...
|
// Handle other leaf types here...
|
||||||
|
|
||||||
return <Fragment key={i}>{text}</Fragment>;
|
return <Fragment key={i}>{text}</Fragment>;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@payloadcms/bundler-vite",
|
"name": "@payloadcms/bundler-vite",
|
||||||
"version": "0.1.6",
|
"version": "0.1.7",
|
||||||
"description": "The officially supported Vite bundler adapter for Payload",
|
"description": "The officially supported Vite bundler adapter for Payload",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"payload": "^2.0.0",
|
"payload": "^2.0.0",
|
||||||
"react-dom": "18.2.0"
|
"react-dom": "^18.0.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@payloadcms/db-mongodb",
|
"name": "@payloadcms/db-mongodb",
|
||||||
"version": "1.5.0",
|
"version": "1.5.1",
|
||||||
"description": "The officially supported MongoDB database adapter for Payload",
|
"description": "The officially supported MongoDB database adapter for Payload",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
49
packages/db-mongodb/src/count.ts
Normal file
49
packages/db-mongodb/src/count.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import type { QueryOptions } from 'mongoose'
|
||||||
|
import type { Count } from 'payload/database'
|
||||||
|
import type { PayloadRequest } from 'payload/types'
|
||||||
|
|
||||||
|
import { flattenWhereToOperators } from 'payload/database'
|
||||||
|
|
||||||
|
import type { MongooseAdapter } from '.'
|
||||||
|
|
||||||
|
import { withSession } from './withSession'
|
||||||
|
|
||||||
|
export const count: Count = async function count(
|
||||||
|
this: MongooseAdapter,
|
||||||
|
{ collection, locale, req = {} as PayloadRequest, where },
|
||||||
|
) {
|
||||||
|
const Model = this.collections[collection]
|
||||||
|
const options: QueryOptions = withSession(this, req.transactionID)
|
||||||
|
|
||||||
|
let hasNearConstraint = false
|
||||||
|
|
||||||
|
if (where) {
|
||||||
|
const constraints = flattenWhereToOperators(where)
|
||||||
|
hasNearConstraint = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = await Model.buildQuery({
|
||||||
|
locale,
|
||||||
|
payload: this.payload,
|
||||||
|
where,
|
||||||
|
})
|
||||||
|
|
||||||
|
// useEstimatedCount is faster, but not accurate, as it ignores any filters. It is thus set to true if there are no filters.
|
||||||
|
const useEstimatedCount = hasNearConstraint || !query || Object.keys(query).length === 0
|
||||||
|
|
||||||
|
if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) {
|
||||||
|
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
|
||||||
|
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
|
||||||
|
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
|
||||||
|
// the correct indexed field
|
||||||
|
options.hint = {
|
||||||
|
_id: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await Model.countDocuments(query, options)
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalDocs: result,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import { createDatabaseAdapter } from 'payload/database'
|
|||||||
import type { CollectionModel, GlobalModel } from './types'
|
import type { CollectionModel, GlobalModel } from './types'
|
||||||
|
|
||||||
import { connect } from './connect'
|
import { connect } from './connect'
|
||||||
|
import { count } from './count'
|
||||||
import { create } from './create'
|
import { create } from './create'
|
||||||
import { createGlobal } from './createGlobal'
|
import { createGlobal } from './createGlobal'
|
||||||
import { createGlobalVersion } from './createGlobalVersion'
|
import { createGlobalVersion } from './createGlobalVersion'
|
||||||
@@ -108,6 +109,7 @@ export function mongooseAdapter({
|
|||||||
collections: {},
|
collections: {},
|
||||||
connectOptions: connectOptions || {},
|
connectOptions: connectOptions || {},
|
||||||
connection: undefined,
|
connection: undefined,
|
||||||
|
count,
|
||||||
disableIndexHints,
|
disableIndexHints,
|
||||||
globals: undefined,
|
globals: undefined,
|
||||||
mongoMemoryServer: undefined,
|
mongoMemoryServer: undefined,
|
||||||
@@ -115,7 +117,6 @@ export function mongooseAdapter({
|
|||||||
transactionOptions: transactionOptions === false ? undefined : transactionOptions,
|
transactionOptions: transactionOptions === false ? undefined : transactionOptions,
|
||||||
url,
|
url,
|
||||||
versions: {},
|
versions: {},
|
||||||
|
|
||||||
// DatabaseAdapter
|
// DatabaseAdapter
|
||||||
beginTransaction: transactionOptions ? beginTransaction : undefined,
|
beginTransaction: transactionOptions ? beginTransaction : undefined,
|
||||||
commitTransaction,
|
commitTransaction,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@payloadcms/db-postgres",
|
"name": "@payloadcms/db-postgres",
|
||||||
"version": "0.7.1",
|
"version": "0.8.4",
|
||||||
"description": "The officially supported Postgres database adapter for Payload",
|
"description": "The officially supported Postgres database adapter for Payload",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
62
packages/db-postgres/src/count.ts
Normal file
62
packages/db-postgres/src/count.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import type { Count } from 'payload/database'
|
||||||
|
import type { SanitizedCollectionConfig } from 'payload/types'
|
||||||
|
|
||||||
|
import { sql } from 'drizzle-orm'
|
||||||
|
import toSnakeCase from 'to-snake-case'
|
||||||
|
|
||||||
|
import type { ChainedMethods } from './find/chainMethods'
|
||||||
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
|
import { chainMethods } from './find/chainMethods'
|
||||||
|
import buildQuery from './queries/buildQuery'
|
||||||
|
|
||||||
|
export const count: Count = async function count(
|
||||||
|
this: PostgresAdapter,
|
||||||
|
{ collection, locale, req, where: whereArg },
|
||||||
|
) {
|
||||||
|
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||||
|
|
||||||
|
const tableName = this.tableNameMap.get(toSnakeCase(collectionConfig.slug))
|
||||||
|
|
||||||
|
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||||
|
const table = this.tables[tableName]
|
||||||
|
|
||||||
|
const { joinAliases, joins, where } = await buildQuery({
|
||||||
|
adapter: this,
|
||||||
|
fields: collectionConfig.fields,
|
||||||
|
locale,
|
||||||
|
tableName,
|
||||||
|
where: whereArg,
|
||||||
|
})
|
||||||
|
|
||||||
|
const selectCountMethods: ChainedMethods = []
|
||||||
|
|
||||||
|
joinAliases.forEach(({ condition, table }) => {
|
||||||
|
selectCountMethods.push({
|
||||||
|
args: [table, condition],
|
||||||
|
method: 'leftJoin',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Object.entries(joins).forEach(([joinTable, condition]) => {
|
||||||
|
if (joinTable) {
|
||||||
|
selectCountMethods.push({
|
||||||
|
args: [this.tables[joinTable], condition],
|
||||||
|
method: 'leftJoin',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const countResult = await chainMethods({
|
||||||
|
methods: selectCountMethods,
|
||||||
|
query: db
|
||||||
|
.select({
|
||||||
|
count: sql<number>`count
|
||||||
|
(DISTINCT ${this.tables[tableName].id})`,
|
||||||
|
})
|
||||||
|
.from(table)
|
||||||
|
.where(where),
|
||||||
|
})
|
||||||
|
|
||||||
|
return { totalDocs: Number(countResult[0].count) }
|
||||||
|
}
|
||||||
@@ -2,8 +2,8 @@ import type { Create } from 'payload/database'
|
|||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import { getTableName } from './schema/getTableName'
|
|
||||||
import { upsertRow } from './upsertRow'
|
import { upsertRow } from './upsertRow'
|
||||||
|
import toSnakeCase from 'to-snake-case'
|
||||||
|
|
||||||
export const create: Create = async function create(
|
export const create: Create = async function create(
|
||||||
this: PostgresAdapter,
|
this: PostgresAdapter,
|
||||||
@@ -12,6 +12,8 @@ export const create: Create = async function create(
|
|||||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||||
const collection = this.payload.collections[collectionSlug].config
|
const collection = this.payload.collections[collectionSlug].config
|
||||||
|
|
||||||
|
const tableName = this.tableNameMap.get(toSnakeCase(collection.slug))
|
||||||
|
|
||||||
const result = await upsertRow({
|
const result = await upsertRow({
|
||||||
adapter: this,
|
adapter: this,
|
||||||
data,
|
data,
|
||||||
@@ -19,10 +21,7 @@ export const create: Create = async function create(
|
|||||||
fields: collection.fields,
|
fields: collection.fields,
|
||||||
operation: 'create',
|
operation: 'create',
|
||||||
req,
|
req,
|
||||||
tableName: getTableName({
|
tableName,
|
||||||
adapter: this,
|
|
||||||
config: collection,
|
|
||||||
}),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import type { CreateGlobalArgs } from 'payload/database'
|
import type { CreateGlobalArgs } from 'payload/database'
|
||||||
import type { PayloadRequest, TypeWithID } from 'payload/types'
|
import type { PayloadRequest, TypeWithID } from 'payload/types'
|
||||||
|
|
||||||
|
import toSnakeCase from 'to-snake-case'
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import { getTableName } from './schema/getTableName'
|
|
||||||
import { upsertRow } from './upsertRow'
|
import { upsertRow } from './upsertRow'
|
||||||
|
|
||||||
export async function createGlobal<T extends TypeWithID>(
|
export async function createGlobal<T extends TypeWithID>(
|
||||||
@@ -13,6 +14,8 @@ export async function createGlobal<T extends TypeWithID>(
|
|||||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||||
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
|
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
|
||||||
|
|
||||||
|
const tableName = this.tableNameMap.get(toSnakeCase(globalConfig.slug))
|
||||||
|
|
||||||
const result = await upsertRow<T>({
|
const result = await upsertRow<T>({
|
||||||
adapter: this,
|
adapter: this,
|
||||||
data,
|
data,
|
||||||
@@ -20,10 +23,7 @@ export async function createGlobal<T extends TypeWithID>(
|
|||||||
fields: globalConfig.fields,
|
fields: globalConfig.fields,
|
||||||
operation: 'create',
|
operation: 'create',
|
||||||
req,
|
req,
|
||||||
tableName: getTableName({
|
tableName,
|
||||||
adapter: this,
|
|
||||||
config: globalConfig,
|
|
||||||
}),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import type { PayloadRequest, TypeWithID } from 'payload/types'
|
|||||||
import { sql } from 'drizzle-orm'
|
import { sql } from 'drizzle-orm'
|
||||||
import { type CreateGlobalVersionArgs } from 'payload/database'
|
import { type CreateGlobalVersionArgs } from 'payload/database'
|
||||||
import { buildVersionGlobalFields } from 'payload/versions'
|
import { buildVersionGlobalFields } from 'payload/versions'
|
||||||
|
import toSnakeCase from 'to-snake-case'
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import { getTableName } from './schema/getTableName'
|
|
||||||
import { upsertRow } from './upsertRow'
|
import { upsertRow } from './upsertRow'
|
||||||
|
|
||||||
export async function createGlobalVersion<T extends TypeWithID>(
|
export async function createGlobalVersion<T extends TypeWithID>(
|
||||||
@@ -16,11 +16,8 @@ export async function createGlobalVersion<T extends TypeWithID>(
|
|||||||
) {
|
) {
|
||||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||||
const global = this.payload.globals.config.find(({ slug }) => slug === globalSlug)
|
const global = this.payload.globals.config.find(({ slug }) => slug === globalSlug)
|
||||||
const tableName = getTableName({
|
|
||||||
adapter: this,
|
const tableName = this.tableNameMap.get(`_${toSnakeCase(global.slug)}${this.versionsSuffix}`)
|
||||||
config: global,
|
|
||||||
versions: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
const result = await upsertRow<TypeWithVersion<T>>({
|
const result = await upsertRow<TypeWithVersion<T>>({
|
||||||
adapter: this,
|
adapter: this,
|
||||||
@@ -40,9 +37,9 @@ export async function createGlobalVersion<T extends TypeWithID>(
|
|||||||
|
|
||||||
if (global.versions.drafts) {
|
if (global.versions.drafts) {
|
||||||
await db.execute(sql`
|
await db.execute(sql`
|
||||||
UPDATE ${table}
|
UPDATE ${table}
|
||||||
SET latest = false
|
SET latest = false
|
||||||
WHERE ${table.id} != ${result.id};
|
WHERE ${table.id} != ${result.id};
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import type { PayloadRequest, TypeWithID } from 'payload/types'
|
|||||||
|
|
||||||
import { sql } from 'drizzle-orm'
|
import { sql } from 'drizzle-orm'
|
||||||
import { buildVersionCollectionFields } from 'payload/versions'
|
import { buildVersionCollectionFields } from 'payload/versions'
|
||||||
|
import toSnakeCase from 'to-snake-case'
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import { getTableName } from './schema/getTableName'
|
|
||||||
import { upsertRow } from './upsertRow'
|
import { upsertRow } from './upsertRow'
|
||||||
|
|
||||||
export async function createVersion<T extends TypeWithID>(
|
export async function createVersion<T extends TypeWithID>(
|
||||||
@@ -21,11 +21,12 @@ export async function createVersion<T extends TypeWithID>(
|
|||||||
) {
|
) {
|
||||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||||
const collection = this.payload.collections[collectionSlug].config
|
const collection = this.payload.collections[collectionSlug].config
|
||||||
const tableName = getTableName({
|
const defaultTableName = toSnakeCase(collection.slug)
|
||||||
adapter: this,
|
|
||||||
config: collection,
|
const tableName = this.tableNameMap.get(`_${defaultTableName}${this.versionsSuffix}`)
|
||||||
versions: true,
|
|
||||||
})
|
const version = { ...versionData }
|
||||||
|
if (version.id) delete version.id
|
||||||
|
|
||||||
const result = await upsertRow<TypeWithVersion<T>>({
|
const result = await upsertRow<TypeWithVersion<T>>({
|
||||||
adapter: this,
|
adapter: this,
|
||||||
@@ -33,7 +34,7 @@ export async function createVersion<T extends TypeWithID>(
|
|||||||
autosave,
|
autosave,
|
||||||
latest: true,
|
latest: true,
|
||||||
parent,
|
parent,
|
||||||
version: versionData,
|
version,
|
||||||
},
|
},
|
||||||
db,
|
db,
|
||||||
fields: buildVersionCollectionFields(collection),
|
fields: buildVersionCollectionFields(collection),
|
||||||
@@ -43,25 +44,18 @@ export async function createVersion<T extends TypeWithID>(
|
|||||||
})
|
})
|
||||||
|
|
||||||
const table = this.tables[tableName]
|
const table = this.tables[tableName]
|
||||||
const relationshipsTable =
|
|
||||||
this.tables[
|
const relationshipsTable = this.tables[`${tableName}${this.relationshipsSuffix}`]
|
||||||
getTableName({
|
|
||||||
adapter: this,
|
|
||||||
config: collection,
|
|
||||||
relationships: true,
|
|
||||||
versions: true,
|
|
||||||
})
|
|
||||||
]
|
|
||||||
|
|
||||||
if (collection.versions.drafts) {
|
if (collection.versions.drafts) {
|
||||||
await db.execute(sql`
|
await db.execute(sql`
|
||||||
UPDATE ${table}
|
UPDATE ${table}
|
||||||
SET latest = false
|
SET latest = false
|
||||||
FROM ${relationshipsTable}
|
FROM ${relationshipsTable}
|
||||||
WHERE ${table.id} = ${relationshipsTable.parent}
|
WHERE ${table.id} = ${relationshipsTable.parent}
|
||||||
AND ${relationshipsTable.path} = ${'parent'}
|
AND ${relationshipsTable.path} = ${'parent'}
|
||||||
AND ${relationshipsTable[`${collectionSlug}ID`]} = ${parent}
|
AND ${relationshipsTable[`${collectionSlug}ID`]} = ${parent}
|
||||||
AND ${table.id} != ${result.id};
|
AND ${table.id} != ${result.id};
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import type { DeleteMany } from 'payload/database'
|
|||||||
import type { PayloadRequest } from 'payload/types'
|
import type { PayloadRequest } from 'payload/types'
|
||||||
|
|
||||||
import { inArray } from 'drizzle-orm'
|
import { inArray } from 'drizzle-orm'
|
||||||
|
import toSnakeCase from 'to-snake-case'
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import { findMany } from './find/findMany'
|
import { findMany } from './find/findMany'
|
||||||
import { getTableName } from './schema/getTableName'
|
|
||||||
|
|
||||||
export const deleteMany: DeleteMany = async function deleteMany(
|
export const deleteMany: DeleteMany = async function deleteMany(
|
||||||
this: PostgresAdapter,
|
this: PostgresAdapter,
|
||||||
@@ -14,7 +14,8 @@ export const deleteMany: DeleteMany = async function deleteMany(
|
|||||||
) {
|
) {
|
||||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||||
const collectionConfig = this.payload.collections[collection].config
|
const collectionConfig = this.payload.collections[collection].config
|
||||||
const tableName = getTableName({ adapter: this, config: collectionConfig })
|
|
||||||
|
const tableName = this.tableNameMap.get(toSnakeCase(collectionConfig.slug))
|
||||||
|
|
||||||
const result = await findMany({
|
const result = await findMany({
|
||||||
adapter: this,
|
adapter: this,
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import type { DeleteOne } from 'payload/database'
|
|||||||
import type { PayloadRequest } from 'payload/types'
|
import type { PayloadRequest } from 'payload/types'
|
||||||
|
|
||||||
import { eq } from 'drizzle-orm'
|
import { eq } from 'drizzle-orm'
|
||||||
|
import toSnakeCase from 'to-snake-case'
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import { buildFindManyArgs } from './find/buildFindManyArgs'
|
import { buildFindManyArgs } from './find/buildFindManyArgs'
|
||||||
import buildQuery from './queries/buildQuery'
|
import buildQuery from './queries/buildQuery'
|
||||||
import { selectDistinct } from './queries/selectDistinct'
|
import { selectDistinct } from './queries/selectDistinct'
|
||||||
import { getTableName } from './schema/getTableName'
|
|
||||||
import { transform } from './transform/read'
|
import { transform } from './transform/read'
|
||||||
|
|
||||||
export const deleteOne: DeleteOne = async function deleteOne(
|
export const deleteOne: DeleteOne = async function deleteOne(
|
||||||
@@ -17,10 +17,9 @@ export const deleteOne: DeleteOne = async function deleteOne(
|
|||||||
) {
|
) {
|
||||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||||
const collection = this.payload.collections[collectionSlug].config
|
const collection = this.payload.collections[collectionSlug].config
|
||||||
const tableName = getTableName({
|
|
||||||
adapter: this,
|
const tableName = this.tableNameMap.get(toSnakeCase(collection.slug))
|
||||||
config: collection,
|
|
||||||
})
|
|
||||||
let docToDelete: Record<string, unknown>
|
let docToDelete: Record<string, unknown>
|
||||||
|
|
||||||
const { joinAliases, joins, selectFields, where } = await buildQuery({
|
const { joinAliases, joins, selectFields, where } = await buildQuery({
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
|
|||||||
|
|
||||||
import { inArray } from 'drizzle-orm'
|
import { inArray } from 'drizzle-orm'
|
||||||
import { buildVersionCollectionFields } from 'payload/versions'
|
import { buildVersionCollectionFields } from 'payload/versions'
|
||||||
|
import toSnakeCase from 'to-snake-case'
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import { findMany } from './find/findMany'
|
import { findMany } from './find/findMany'
|
||||||
import { getTableName } from './schema/getTableName'
|
|
||||||
|
|
||||||
export const deleteVersions: DeleteVersions = async function deleteVersion(
|
export const deleteVersions: DeleteVersions = async function deleteVersion(
|
||||||
this: PostgresAdapter,
|
this: PostgresAdapter,
|
||||||
@@ -16,11 +16,10 @@ export const deleteVersions: DeleteVersions = async function deleteVersion(
|
|||||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||||
|
|
||||||
const tableName = getTableName({
|
const tableName = this.tableNameMap.get(
|
||||||
adapter: this,
|
`_${toSnakeCase(collectionConfig.slug)}${this.versionsSuffix}`,
|
||||||
config: collectionConfig,
|
)
|
||||||
versions: true,
|
|
||||||
})
|
|
||||||
const fields = buildVersionCollectionFields(collectionConfig)
|
const fields = buildVersionCollectionFields(collectionConfig)
|
||||||
|
|
||||||
const { docs } = await findMany({
|
const { docs } = await findMany({
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import type { Find } from 'payload/database'
|
import type { Find } from 'payload/database'
|
||||||
import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
|
import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
|
||||||
|
|
||||||
|
import toSnakeCase from 'to-snake-case'
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import { findMany } from './find/findMany'
|
import { findMany } from './find/findMany'
|
||||||
import { getTableName } from './schema/getTableName'
|
|
||||||
|
|
||||||
export const find: Find = async function find(
|
export const find: Find = async function find(
|
||||||
this: PostgresAdapter,
|
this: PostgresAdapter,
|
||||||
@@ -21,10 +22,8 @@ export const find: Find = async function find(
|
|||||||
) {
|
) {
|
||||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||||
const sort = typeof sortArg === 'string' ? sortArg : collectionConfig.defaultSort
|
const sort = typeof sortArg === 'string' ? sortArg : collectionConfig.defaultSort
|
||||||
const tableName = getTableName({
|
|
||||||
adapter: this,
|
const tableName = this.tableNameMap.get(toSnakeCase(collectionConfig.slug))
|
||||||
config: collectionConfig,
|
|
||||||
})
|
|
||||||
|
|
||||||
return findMany({
|
return findMany({
|
||||||
adapter: this,
|
adapter: this,
|
||||||
|
|||||||
@@ -2,12 +2,11 @@
|
|||||||
import type { Field } from 'payload/types'
|
import type { Field } from 'payload/types'
|
||||||
|
|
||||||
import { fieldAffectsData, tabHasName } from 'payload/types'
|
import { fieldAffectsData, tabHasName } from 'payload/types'
|
||||||
|
import toSnakeCase from 'to-snake-case'
|
||||||
|
|
||||||
import type { PostgresAdapter } from '../types'
|
import type { PostgresAdapter } from '../types'
|
||||||
import type { Result } from './buildFindManyArgs'
|
import type { Result } from './buildFindManyArgs'
|
||||||
|
|
||||||
import { getTableName } from '../schema/getTableName'
|
|
||||||
|
|
||||||
type TraverseFieldArgs = {
|
type TraverseFieldArgs = {
|
||||||
_locales: Record<string, unknown>
|
_locales: Record<string, unknown>
|
||||||
adapter: PostgresAdapter
|
adapter: PostgresAdapter
|
||||||
@@ -79,20 +78,11 @@ export const traverseFields = ({
|
|||||||
with: {},
|
with: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
const arrayTableName = getTableName({
|
const arrayTableName = adapter.tableNameMap.get(
|
||||||
adapter,
|
`${currentTableName}_${path}${toSnakeCase(field.name)}`,
|
||||||
config: field,
|
)
|
||||||
parentTableName: currentTableName,
|
|
||||||
prefix: `${currentTableName}_${path}`,
|
|
||||||
})
|
|
||||||
|
|
||||||
const arrayTableNameWithLocales = getTableName({
|
const arrayTableNameWithLocales = `${arrayTableName}${adapter.localesSuffix}`
|
||||||
adapter,
|
|
||||||
config: field,
|
|
||||||
locales: true,
|
|
||||||
parentTableName: currentTableName,
|
|
||||||
prefix: `${currentTableName}_${path}`,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (adapter.tables[arrayTableNameWithLocales]) withArray.with._locales = _locales
|
if (adapter.tables[arrayTableNameWithLocales]) withArray.with._locales = _locales
|
||||||
currentArgs.with[`${path}${field.name}`] = withArray
|
currentArgs.with[`${path}${field.name}`] = withArray
|
||||||
@@ -142,15 +132,13 @@ export const traverseFields = ({
|
|||||||
with: {},
|
with: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
const tableName = getTableName({
|
const tableName = adapter.tableNameMap.get(
|
||||||
adapter,
|
`${topLevelTableName}_blocks_${toSnakeCase(block.slug)}`,
|
||||||
config: block,
|
)
|
||||||
parentTableName: topLevelTableName,
|
|
||||||
prefix: `${topLevelTableName}_blocks_`,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (adapter.tables[`${tableName}${adapter.localesSuffix}`])
|
if (adapter.tables[`${tableName}${adapter.localesSuffix}`]) {
|
||||||
withBlock.with._locales = _locales
|
withBlock.with._locales = _locales
|
||||||
|
}
|
||||||
topLevelArgs.with[blockKey] = withBlock
|
topLevelArgs.with[blockKey] = withBlock
|
||||||
|
|
||||||
traverseFields({
|
traverseFields({
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
import type { FindGlobal } from 'payload/database'
|
import type { FindGlobal } from 'payload/database'
|
||||||
|
|
||||||
|
import toSnakeCase from 'to-snake-case'
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import { findMany } from './find/findMany'
|
import { findMany } from './find/findMany'
|
||||||
import { getTableName } from './schema/getTableName'
|
|
||||||
|
|
||||||
export const findGlobal: FindGlobal = async function findGlobal(
|
export const findGlobal: FindGlobal = async function findGlobal(
|
||||||
this: PostgresAdapter,
|
this: PostgresAdapter,
|
||||||
{ slug, locale, req, where },
|
{ slug, locale, req, where },
|
||||||
) {
|
) {
|
||||||
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
|
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
|
||||||
const tableName = getTableName({
|
|
||||||
adapter: this,
|
const tableName = this.tableNameMap.get(toSnakeCase(globalConfig.slug))
|
||||||
config: globalConfig,
|
|
||||||
})
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
docs: [doc],
|
docs: [doc],
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import type { FindGlobalVersions } from 'payload/database'
|
|||||||
import type { PayloadRequest, SanitizedGlobalConfig } from 'payload/types'
|
import type { PayloadRequest, SanitizedGlobalConfig } from 'payload/types'
|
||||||
|
|
||||||
import { buildVersionGlobalFields } from 'payload/versions'
|
import { buildVersionGlobalFields } from 'payload/versions'
|
||||||
|
import toSnakeCase from 'to-snake-case'
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import { findMany } from './find/findMany'
|
import { findMany } from './find/findMany'
|
||||||
import { getTableName } from './schema/getTableName'
|
|
||||||
|
|
||||||
export const findGlobalVersions: FindGlobalVersions = async function findGlobalVersions(
|
export const findGlobalVersions: FindGlobalVersions = async function findGlobalVersions(
|
||||||
this: PostgresAdapter,
|
this: PostgresAdapter,
|
||||||
@@ -27,11 +27,10 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
|
|||||||
)
|
)
|
||||||
const sort = typeof sortArg === 'string' ? sortArg : '-createdAt'
|
const sort = typeof sortArg === 'string' ? sortArg : '-createdAt'
|
||||||
|
|
||||||
const tableName = getTableName({
|
const tableName = this.tableNameMap.get(
|
||||||
adapter: this,
|
`_${toSnakeCase(globalConfig.slug)}${this.versionsSuffix}`,
|
||||||
config: globalConfig,
|
)
|
||||||
versions: true,
|
|
||||||
})
|
|
||||||
const fields = buildVersionGlobalFields(globalConfig)
|
const fields = buildVersionGlobalFields(globalConfig)
|
||||||
|
|
||||||
return findMany({
|
return findMany({
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
import type { FindOneArgs } from 'payload/database'
|
import type { FindOneArgs } from 'payload/database'
|
||||||
import type { PayloadRequest, SanitizedCollectionConfig, TypeWithID } from 'payload/types'
|
import type { PayloadRequest, SanitizedCollectionConfig, TypeWithID } from 'payload/types'
|
||||||
|
|
||||||
|
import toSnakeCase from 'to-snake-case'
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import { findMany } from './find/findMany'
|
import { findMany } from './find/findMany'
|
||||||
import { getTableName } from './schema/getTableName'
|
|
||||||
|
|
||||||
export async function findOne<T extends TypeWithID>(
|
export async function findOne<T extends TypeWithID>(
|
||||||
this: PostgresAdapter,
|
this: PostgresAdapter,
|
||||||
{ collection, locale, req = {} as PayloadRequest, where }: FindOneArgs,
|
{ collection, locale, req = {} as PayloadRequest, where }: FindOneArgs,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||||
const tableName = getTableName({
|
|
||||||
adapter: this,
|
const tableName = this.tableNameMap.get(toSnakeCase(collectionConfig.slug))
|
||||||
config: collectionConfig,
|
|
||||||
})
|
|
||||||
|
|
||||||
const { docs } = await findMany({
|
const { docs } = await findMany({
|
||||||
adapter: this,
|
adapter: this,
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import type { FindVersions } from 'payload/database'
|
|||||||
import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
|
import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
|
||||||
|
|
||||||
import { buildVersionCollectionFields } from 'payload/versions'
|
import { buildVersionCollectionFields } from 'payload/versions'
|
||||||
|
import toSnakeCase from 'to-snake-case'
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import { findMany } from './find/findMany'
|
import { findMany } from './find/findMany'
|
||||||
import { getTableName } from './schema/getTableName'
|
|
||||||
|
|
||||||
export const findVersions: FindVersions = async function findVersions(
|
export const findVersions: FindVersions = async function findVersions(
|
||||||
this: PostgresAdapter,
|
this: PostgresAdapter,
|
||||||
@@ -25,11 +25,10 @@ export const findVersions: FindVersions = async function findVersions(
|
|||||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||||
const sort = typeof sortArg === 'string' ? sortArg : collectionConfig.defaultSort
|
const sort = typeof sortArg === 'string' ? sortArg : collectionConfig.defaultSort
|
||||||
|
|
||||||
const tableName = getTableName({
|
const tableName = this.tableNameMap.get(
|
||||||
adapter: this,
|
`_${toSnakeCase(collectionConfig.slug)}${this.versionsSuffix}`,
|
||||||
config: collectionConfig,
|
)
|
||||||
versions: true,
|
|
||||||
})
|
|
||||||
const fields = buildVersionCollectionFields(collectionConfig)
|
const fields = buildVersionCollectionFields(collectionConfig)
|
||||||
|
|
||||||
return findMany({
|
return findMany({
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { createDatabaseAdapter } from 'payload/database'
|
|||||||
import type { Args, PostgresAdapter, PostgresAdapterResult } from './types'
|
import type { Args, PostgresAdapter, PostgresAdapterResult } from './types'
|
||||||
|
|
||||||
import { connect } from './connect'
|
import { connect } from './connect'
|
||||||
|
import { count } from './count'
|
||||||
import { create } from './create'
|
import { create } from './create'
|
||||||
import { createGlobal } from './createGlobal'
|
import { createGlobal } from './createGlobal'
|
||||||
import { createGlobalVersion } from './createGlobalVersion'
|
import { createGlobalVersion } from './createGlobalVersion'
|
||||||
@@ -40,18 +41,18 @@ import { updateVersion } from './updateVersion'
|
|||||||
export type { MigrateDownArgs, MigrateUpArgs } from './types'
|
export type { MigrateDownArgs, MigrateUpArgs } from './types'
|
||||||
|
|
||||||
export function postgresAdapter(args: Args): PostgresAdapterResult {
|
export function postgresAdapter(args: Args): PostgresAdapterResult {
|
||||||
|
const postgresIDType = args.idType || 'serial'
|
||||||
|
const payloadIDType = postgresIDType === 'serial' ? 'number' : 'text'
|
||||||
|
|
||||||
function adapter({ payload }: { payload: Payload }) {
|
function adapter({ payload }: { payload: Payload }) {
|
||||||
const migrationDir = findMigrationDir(args.migrationDir)
|
const migrationDir = findMigrationDir(args.migrationDir)
|
||||||
const idType = args.idType || 'serial'
|
|
||||||
return createDatabaseAdapter<PostgresAdapter>({
|
return createDatabaseAdapter<PostgresAdapter>({
|
||||||
name: 'postgres',
|
name: 'postgres',
|
||||||
|
|
||||||
// Postgres-specific
|
|
||||||
blockTableNames: {},
|
|
||||||
drizzle: undefined,
|
drizzle: undefined,
|
||||||
enums: {},
|
enums: {},
|
||||||
fieldConstraints: {},
|
fieldConstraints: {},
|
||||||
idType,
|
idType: postgresIDType,
|
||||||
localesSuffix: args.localesSuffix || '_locales',
|
localesSuffix: args.localesSuffix || '_locales',
|
||||||
logger: args.logger,
|
logger: args.logger,
|
||||||
pgSchema: undefined,
|
pgSchema: undefined,
|
||||||
@@ -63,6 +64,7 @@ export function postgresAdapter(args: Args): PostgresAdapterResult {
|
|||||||
schema: {},
|
schema: {},
|
||||||
schemaName: args.schemaName,
|
schemaName: args.schemaName,
|
||||||
sessions: {},
|
sessions: {},
|
||||||
|
tableNameMap: new Map<string, string>(),
|
||||||
tables: {},
|
tables: {},
|
||||||
versionsSuffix: args.versionsSuffix || '_v',
|
versionsSuffix: args.versionsSuffix || '_v',
|
||||||
|
|
||||||
@@ -70,15 +72,13 @@ export function postgresAdapter(args: Args): PostgresAdapterResult {
|
|||||||
beginTransaction,
|
beginTransaction,
|
||||||
commitTransaction,
|
commitTransaction,
|
||||||
connect,
|
connect,
|
||||||
|
count,
|
||||||
create,
|
create,
|
||||||
createGlobal,
|
createGlobal,
|
||||||
createGlobalVersion,
|
createGlobalVersion,
|
||||||
createMigration,
|
createMigration,
|
||||||
createVersion,
|
createVersion,
|
||||||
/**
|
defaultIDType: payloadIDType,
|
||||||
* This represents how a default ID is treated in Payload as were a field type
|
|
||||||
*/
|
|
||||||
defaultIDType: idType === 'serial' ? 'number' : 'text',
|
|
||||||
deleteMany,
|
deleteMany,
|
||||||
deleteOne,
|
deleteOne,
|
||||||
deleteVersions,
|
deleteVersions,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload/
|
|||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import { buildTable } from './schema/build'
|
import { buildTable } from './schema/build'
|
||||||
import { getTableName } from './schema/getTableName'
|
import { createTableName } from './schema/createTableName'
|
||||||
|
|
||||||
export const init: Init = async function init(this: PostgresAdapter) {
|
export const init: Init = async function init(this: PostgresAdapter) {
|
||||||
if (this.schemaName) {
|
if (this.schemaName) {
|
||||||
@@ -25,7 +25,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
|
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
|
||||||
const tableName = getTableName({
|
const tableName = createTableName({
|
||||||
adapter: this,
|
adapter: this,
|
||||||
config: collection,
|
config: collection,
|
||||||
})
|
})
|
||||||
@@ -44,10 +44,11 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (collection.versions) {
|
if (collection.versions) {
|
||||||
const versionsTableName = getTableName({
|
const versionsTableName = createTableName({
|
||||||
adapter: this,
|
adapter: this,
|
||||||
config: collection,
|
config: collection,
|
||||||
versions: true,
|
versions: true,
|
||||||
|
versionsCustomName: true,
|
||||||
})
|
})
|
||||||
const versionFields = buildVersionCollectionFields(collection)
|
const versionFields = buildVersionCollectionFields(collection)
|
||||||
|
|
||||||
@@ -67,7 +68,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
this.payload.config.globals.forEach((global) => {
|
this.payload.config.globals.forEach((global) => {
|
||||||
const tableName = getTableName({ adapter: this, config: global })
|
const tableName = createTableName({ adapter: this, config: global })
|
||||||
|
|
||||||
buildTable({
|
buildTable({
|
||||||
adapter: this,
|
adapter: this,
|
||||||
@@ -83,7 +84,12 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (global.versions) {
|
if (global.versions) {
|
||||||
const versionsTableName = getTableName({ adapter: this, config: global, versions: true })
|
const versionsTableName = createTableName({
|
||||||
|
adapter: this,
|
||||||
|
config: global,
|
||||||
|
versions: true,
|
||||||
|
versionsCustomName: true,
|
||||||
|
})
|
||||||
const versionFields = buildVersionGlobalFields(global)
|
const versionFields = buildVersionGlobalFields(global)
|
||||||
|
|
||||||
buildTable({
|
buildTable({
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ import { v4 as uuid } from 'uuid'
|
|||||||
import type { GenericColumn, GenericTable, PostgresAdapter } from '../types'
|
import type { GenericColumn, GenericTable, PostgresAdapter } from '../types'
|
||||||
import type { BuildQueryJoinAliases, BuildQueryJoins } from './buildQuery'
|
import type { BuildQueryJoinAliases, BuildQueryJoins } from './buildQuery'
|
||||||
|
|
||||||
import { getTableName } from '../schema/getTableName'
|
|
||||||
|
|
||||||
type Constraint = {
|
type Constraint = {
|
||||||
columnName: string
|
columnName: string
|
||||||
table: GenericTable | PgTableWithColumns<any>
|
table: GenericTable | PgTableWithColumns<any>
|
||||||
@@ -185,13 +183,7 @@ export const getTableColumnFromPath = ({
|
|||||||
|
|
||||||
case 'group': {
|
case 'group': {
|
||||||
if (locale && field.localized && adapter.payload.config.localization) {
|
if (locale && field.localized && adapter.payload.config.localization) {
|
||||||
newTableName = getTableName({
|
newTableName = `${tableName}${adapter.localesSuffix}`
|
||||||
adapter,
|
|
||||||
config: field,
|
|
||||||
locales: true,
|
|
||||||
parentTableName: tableName,
|
|
||||||
prefix: `${tableName}_`,
|
|
||||||
})
|
|
||||||
|
|
||||||
joins[tableName] = eq(
|
joins[tableName] = eq(
|
||||||
adapter.tables[tableName].id,
|
adapter.tables[tableName].id,
|
||||||
@@ -227,12 +219,9 @@ export const getTableColumnFromPath = ({
|
|||||||
|
|
||||||
case 'select': {
|
case 'select': {
|
||||||
if (field.hasMany) {
|
if (field.hasMany) {
|
||||||
newTableName = getTableName({
|
const newTableName = adapter.tableNameMap.get(
|
||||||
adapter,
|
`${tableName}_${tableNameSuffix}${toSnakeCase(field.name)}`,
|
||||||
config: field,
|
)
|
||||||
parentTableName: `${tableName}_${tableNameSuffix}`,
|
|
||||||
prefix: `${tableName}_${tableNameSuffix}`,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (locale && field.localized && adapter.payload.config.localization) {
|
if (locale && field.localized && adapter.payload.config.localization) {
|
||||||
joins[newTableName] = and(
|
joins[newTableName] = and(
|
||||||
@@ -305,12 +294,10 @@ export const getTableColumnFromPath = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'array': {
|
case 'array': {
|
||||||
newTableName = getTableName({
|
newTableName = adapter.tableNameMap.get(
|
||||||
adapter,
|
`${tableName}_${tableNameSuffix}${toSnakeCase(field.name)}`,
|
||||||
config: field,
|
)
|
||||||
parentTableName: `${tableName}_${tableNameSuffix}`,
|
|
||||||
prefix: `${tableName}_${tableNameSuffix}`,
|
|
||||||
})
|
|
||||||
constraintPath = `${constraintPath}${field.name}.%.`
|
constraintPath = `${constraintPath}${field.name}.%.`
|
||||||
if (locale && field.localized && adapter.payload.config.localization) {
|
if (locale && field.localized && adapter.payload.config.localization) {
|
||||||
joins[newTableName] = and(
|
joins[newTableName] = and(
|
||||||
@@ -357,12 +344,11 @@ export const getTableColumnFromPath = ({
|
|||||||
const blockTypes = Array.isArray(value) ? value : [value]
|
const blockTypes = Array.isArray(value) ? value : [value]
|
||||||
blockTypes.forEach((blockType) => {
|
blockTypes.forEach((blockType) => {
|
||||||
const block = field.blocks.find((block) => block.slug === blockType)
|
const block = field.blocks.find((block) => block.slug === blockType)
|
||||||
newTableName = getTableName({
|
|
||||||
adapter,
|
newTableName = adapter.tableNameMap.get(
|
||||||
config: block,
|
`${tableName}_blocks_${toSnakeCase(block.slug)}`,
|
||||||
parentTableName: tableName,
|
)
|
||||||
prefix: `${tableName}_blocks_`,
|
|
||||||
})
|
|
||||||
joins[newTableName] = eq(
|
joins[newTableName] = eq(
|
||||||
adapter.tables[tableName].id,
|
adapter.tables[tableName].id,
|
||||||
adapter.tables[newTableName]._parentID,
|
adapter.tables[newTableName]._parentID,
|
||||||
@@ -382,13 +368,9 @@ export const getTableColumnFromPath = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const hasBlockField = field.blocks.some((block) => {
|
const hasBlockField = field.blocks.some((block) => {
|
||||||
newTableName = getTableName({
|
newTableName = adapter.tableNameMap.get(`${tableName}_blocks_${toSnakeCase(block.slug)}`)
|
||||||
adapter,
|
|
||||||
config: block,
|
|
||||||
parentTableName: tableName,
|
|
||||||
prefix: `${tableName}_blocks_`,
|
|
||||||
})
|
|
||||||
constraintPath = `${constraintPath}${field.name}.%.`
|
constraintPath = `${constraintPath}${field.name}.%.`
|
||||||
|
|
||||||
let result
|
let result
|
||||||
const blockConstraints = []
|
const blockConstraints = []
|
||||||
const blockSelectFields = {}
|
const blockSelectFields = {}
|
||||||
@@ -461,6 +443,7 @@ export const getTableColumnFromPath = ({
|
|||||||
aliasRelationshipTableName,
|
aliasRelationshipTableName,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Join in the relationships table
|
||||||
if (locale && field.localized && adapter.payload.config.localization) {
|
if (locale && field.localized && adapter.payload.config.localization) {
|
||||||
joinAliases.push({
|
joinAliases.push({
|
||||||
condition: and(
|
condition: and(
|
||||||
@@ -494,10 +477,9 @@ export const getTableColumnFromPath = ({
|
|||||||
|
|
||||||
if (typeof field.relationTo === 'string') {
|
if (typeof field.relationTo === 'string') {
|
||||||
const relationshipConfig = adapter.payload.collections[field.relationTo].config
|
const relationshipConfig = adapter.payload.collections[field.relationTo].config
|
||||||
newTableName = getTableName({
|
|
||||||
adapter,
|
newTableName = adapter.tableNameMap.get(toSnakeCase(relationshipConfig.slug))
|
||||||
config: relationshipConfig,
|
|
||||||
})
|
|
||||||
// parent to relationship join table
|
// parent to relationship join table
|
||||||
relationshipFields = relationshipConfig.fields
|
relationshipFields = relationshipConfig.fields
|
||||||
|
|
||||||
@@ -517,13 +499,13 @@ export const getTableColumnFromPath = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (newCollectionPath === 'value') {
|
} else if (newCollectionPath === 'value') {
|
||||||
const tableColumnsNames = field.relationTo.map(
|
const tableColumnsNames = field.relationTo.map((relationTo) => {
|
||||||
(relationTo) =>
|
const relationTableName = adapter.tableNameMap.get(
|
||||||
`"${aliasRelationshipTableName}"."${getTableName({
|
toSnakeCase(adapter.payload.collections[relationTo].config.slug),
|
||||||
adapter,
|
)
|
||||||
config: adapter.payload.collections[relationTo].config,
|
|
||||||
})}_id"`,
|
return `"${aliasRelationshipTableName}"."${relationTableName}_id"`
|
||||||
)
|
})
|
||||||
return {
|
return {
|
||||||
constraints,
|
constraints,
|
||||||
field,
|
field,
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export async function parseParams({
|
|||||||
// So we need to loop on keys again here to handle each operator independently
|
// So we need to loop on keys again here to handle each operator independently
|
||||||
const pathOperators = where[relationOrPath]
|
const pathOperators = where[relationOrPath]
|
||||||
if (typeof pathOperators === 'object') {
|
if (typeof pathOperators === 'object') {
|
||||||
for (const operator of Object.keys(pathOperators)) {
|
for (let operator of Object.keys(pathOperators)) {
|
||||||
if (validOperators.includes(operator as Operator)) {
|
if (validOperators.includes(operator as Operator)) {
|
||||||
const val = where[relationOrPath][operator]
|
const val = where[relationOrPath][operator]
|
||||||
const {
|
const {
|
||||||
@@ -157,6 +157,13 @@ export async function parseParams({
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
operator === 'like' &&
|
||||||
|
(field.type === 'number' || table[columnName].columnType === 'PgUUID')
|
||||||
|
) {
|
||||||
|
operator = 'equals'
|
||||||
|
}
|
||||||
|
|
||||||
if (operator === 'like') {
|
if (operator === 'like') {
|
||||||
constraints.push(
|
constraints.push(
|
||||||
and(...val.split(' ').map((word) => ilike(table[columnName], `%${word}%`))),
|
and(...val.split(' ').map((word) => ilike(table[columnName], `%${word}%`))),
|
||||||
@@ -195,10 +202,10 @@ export async function parseParams({
|
|||||||
operator === 'not_in'
|
operator === 'not_in'
|
||||||
) {
|
) {
|
||||||
constraints.push(
|
constraints.push(
|
||||||
sql`${notInArray(table[columnName], queryValue)} OR
|
sql`(${notInArray(table[columnName], queryValue)} OR
|
||||||
${table[columnName]}
|
${table[columnName]}
|
||||||
IS
|
IS
|
||||||
NULL`,
|
NULL)`,
|
||||||
)
|
)
|
||||||
|
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -2,26 +2,20 @@ import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
|
|||||||
|
|
||||||
import { type QueryDrafts, combineQueries } from 'payload/database'
|
import { type QueryDrafts, combineQueries } from 'payload/database'
|
||||||
import { buildVersionCollectionFields } from 'payload/versions'
|
import { buildVersionCollectionFields } from 'payload/versions'
|
||||||
|
import toSnakeCase from 'to-snake-case'
|
||||||
|
|
||||||
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import { findMany } from './find/findMany'
|
import { findMany } from './find/findMany'
|
||||||
import { getTableName } from './schema/getTableName'
|
|
||||||
|
|
||||||
export const queryDrafts: QueryDrafts = async function queryDrafts({
|
export const queryDrafts: QueryDrafts = async function queryDrafts(
|
||||||
collection,
|
this: PostgresAdapter,
|
||||||
limit,
|
{ collection, limit, locale, page = 1, pagination, req = {} as PayloadRequest, sort, where },
|
||||||
locale,
|
) {
|
||||||
page = 1,
|
|
||||||
pagination,
|
|
||||||
req = {} as PayloadRequest,
|
|
||||||
sort,
|
|
||||||
where,
|
|
||||||
}) {
|
|
||||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||||
const tableName = getTableName({
|
const tableName = this.tableNameMap.get(
|
||||||
adapter: this,
|
`_${toSnakeCase(collectionConfig.slug)}${this.versionsSuffix}`,
|
||||||
config: collectionConfig,
|
)
|
||||||
versions: true,
|
|
||||||
})
|
|
||||||
const fields = buildVersionCollectionFields(collectionConfig)
|
const fields = buildVersionCollectionFields(collectionConfig)
|
||||||
|
|
||||||
const combinedWhere = combineQueries({ latest: { equals: true } }, where)
|
const combinedWhere = combineQueries({ latest: { equals: true } }, where)
|
||||||
|
|||||||
@@ -1,28 +1,43 @@
|
|||||||
/* eslint-disable no-param-reassign */
|
/* eslint-disable no-param-reassign */
|
||||||
import type { Relation } from 'drizzle-orm'
|
import type { Relation } from 'drizzle-orm'
|
||||||
import type {
|
import type {
|
||||||
|
ForeignKeyBuilder,
|
||||||
IndexBuilder,
|
IndexBuilder,
|
||||||
PgColumnBuilder,
|
PgColumnBuilder,
|
||||||
PgTableWithColumns,
|
PgTableWithColumns,
|
||||||
UniqueConstraintBuilder,
|
UniqueConstraintBuilder,
|
||||||
} from 'drizzle-orm/pg-core'
|
} from 'drizzle-orm/pg-core'
|
||||||
import type { Field } from 'payload/types'
|
import { Field, fieldAffectsData } from 'payload/types'
|
||||||
|
|
||||||
import { relations } from 'drizzle-orm'
|
import { relations } from 'drizzle-orm'
|
||||||
import { index, integer, numeric, serial, timestamp, unique, varchar } from 'drizzle-orm/pg-core'
|
import {
|
||||||
import { fieldAffectsData } from 'payload/types'
|
foreignKey,
|
||||||
|
index,
|
||||||
|
integer,
|
||||||
|
numeric,
|
||||||
|
serial,
|
||||||
|
timestamp,
|
||||||
|
unique,
|
||||||
|
varchar,
|
||||||
|
} from 'drizzle-orm/pg-core'
|
||||||
|
import toSnakeCase from 'to-snake-case'
|
||||||
|
|
||||||
import type { GenericColumns, GenericTable, IDType, PostgresAdapter } from '../types'
|
import type { GenericColumns, GenericTable, IDType, PostgresAdapter } from '../types'
|
||||||
|
|
||||||
import { getTableName } from './getTableName'
|
import { createTableName } from './createTableName'
|
||||||
import { parentIDColumnMap } from './parentIDColumnMap'
|
import { parentIDColumnMap } from './parentIDColumnMap'
|
||||||
import { setColumnID } from './setColumnID'
|
import { setColumnID } from './setColumnID'
|
||||||
import { traverseFields } from './traverseFields'
|
import { traverseFields } from './traverseFields'
|
||||||
|
|
||||||
|
export type BaseExtraConfig = Record<
|
||||||
|
string,
|
||||||
|
(cols: GenericColumns) => ForeignKeyBuilder | IndexBuilder | UniqueConstraintBuilder
|
||||||
|
>
|
||||||
|
|
||||||
type Args = {
|
type Args = {
|
||||||
adapter: PostgresAdapter
|
adapter: PostgresAdapter
|
||||||
baseColumns?: Record<string, PgColumnBuilder>
|
baseColumns?: Record<string, PgColumnBuilder>
|
||||||
baseExtraConfig?: Record<string, (cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder>
|
baseExtraConfig?: BaseExtraConfig
|
||||||
buildNumbers?: boolean
|
buildNumbers?: boolean
|
||||||
buildRelationships?: boolean
|
buildRelationships?: boolean
|
||||||
buildTexts?: boolean
|
buildTexts?: boolean
|
||||||
@@ -66,13 +81,6 @@ export const buildTable = ({
|
|||||||
const columns: Record<string, PgColumnBuilder> = baseColumns
|
const columns: Record<string, PgColumnBuilder> = baseColumns
|
||||||
const indexes: Record<string, (cols: GenericColumns) => IndexBuilder> = {}
|
const indexes: Record<string, (cols: GenericColumns) => IndexBuilder> = {}
|
||||||
|
|
||||||
let hasLocalizedField = false
|
|
||||||
let hasLocalizedRelationshipField = false
|
|
||||||
let hasManyTextField: 'index' | boolean = false
|
|
||||||
let hasManyNumberField: 'index' | boolean = false
|
|
||||||
let hasLocalizedManyTextField = false
|
|
||||||
let hasLocalizedManyNumberField = false
|
|
||||||
|
|
||||||
const localesColumns: Record<string, PgColumnBuilder> = {}
|
const localesColumns: Record<string, PgColumnBuilder> = {}
|
||||||
const localesIndexes: Record<string, (cols: GenericColumns) => IndexBuilder> = {}
|
const localesIndexes: Record<string, (cols: GenericColumns) => IndexBuilder> = {}
|
||||||
let localesTable: GenericTable | PgTableWithColumns<any>
|
let localesTable: GenericTable | PgTableWithColumns<any>
|
||||||
@@ -89,7 +97,7 @@ export const buildTable = ({
|
|||||||
|
|
||||||
const idColType: IDType = setColumnID({ adapter, columns, fields })
|
const idColType: IDType = setColumnID({ adapter, columns, fields })
|
||||||
|
|
||||||
;({
|
const {
|
||||||
hasLocalizedField,
|
hasLocalizedField,
|
||||||
hasLocalizedManyNumberField,
|
hasLocalizedManyNumberField,
|
||||||
hasLocalizedManyTextField,
|
hasLocalizedManyTextField,
|
||||||
@@ -116,7 +124,7 @@ export const buildTable = ({
|
|||||||
rootTableIDColType: rootTableIDColType || idColType,
|
rootTableIDColType: rootTableIDColType || idColType,
|
||||||
rootTableName,
|
rootTableName,
|
||||||
versions,
|
versions,
|
||||||
}))
|
})
|
||||||
|
|
||||||
if (timestamps) {
|
if (timestamps) {
|
||||||
columns.createdAt = timestamp('created_at', {
|
columns.createdAt = timestamp('created_at', {
|
||||||
@@ -141,10 +149,12 @@ export const buildTable = ({
|
|||||||
return config
|
return config
|
||||||
}, {})
|
}, {})
|
||||||
|
|
||||||
return Object.entries(indexes).reduce((acc, [colName, func]) => {
|
const result = Object.entries(indexes).reduce((acc, [colName, func]) => {
|
||||||
acc[colName] = func(cols)
|
acc[colName] = func(cols)
|
||||||
return acc
|
return acc
|
||||||
}, extraConfig)
|
}, extraConfig)
|
||||||
|
|
||||||
|
return result
|
||||||
})
|
})
|
||||||
|
|
||||||
adapter.tables[tableName] = table
|
adapter.tables[tableName] = table
|
||||||
@@ -153,9 +163,7 @@ export const buildTable = ({
|
|||||||
const localeTableName = `${tableName}${adapter.localesSuffix}`
|
const localeTableName = `${tableName}${adapter.localesSuffix}`
|
||||||
localesColumns.id = serial('id').primaryKey()
|
localesColumns.id = serial('id').primaryKey()
|
||||||
localesColumns._locale = adapter.enums.enum__locales('_locale').notNull()
|
localesColumns._locale = adapter.enums.enum__locales('_locale').notNull()
|
||||||
localesColumns._parentID = parentIDColumnMap[idColType]('_parent_id')
|
localesColumns._parentID = parentIDColumnMap[idColType]('_parent_id').notNull()
|
||||||
.references(() => table.id, { onDelete: 'cascade' })
|
|
||||||
.notNull()
|
|
||||||
|
|
||||||
localesTable = adapter.pgSchema.table(localeTableName, localesColumns, (cols) => {
|
localesTable = adapter.pgSchema.table(localeTableName, localesColumns, (cols) => {
|
||||||
return Object.entries(localesIndexes).reduce(
|
return Object.entries(localesIndexes).reduce(
|
||||||
@@ -168,6 +176,11 @@ export const buildTable = ({
|
|||||||
cols._locale,
|
cols._locale,
|
||||||
cols._parentID,
|
cols._parentID,
|
||||||
),
|
),
|
||||||
|
_parentIdFk: foreignKey({
|
||||||
|
name: `${localeTableName}_parent_id_fk`,
|
||||||
|
columns: [cols._parentID],
|
||||||
|
foreignColumns: [table.id],
|
||||||
|
}).onDelete('cascade'),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -189,9 +202,7 @@ export const buildTable = ({
|
|||||||
const columns: Record<string, PgColumnBuilder> = {
|
const columns: Record<string, PgColumnBuilder> = {
|
||||||
id: serial('id').primaryKey(),
|
id: serial('id').primaryKey(),
|
||||||
order: integer('order').notNull(),
|
order: integer('order').notNull(),
|
||||||
parent: parentIDColumnMap[idColType]('parent_id')
|
parent: parentIDColumnMap[idColType]('parent_id').notNull(),
|
||||||
.references(() => table.id, { onDelete: 'cascade' })
|
|
||||||
.notNull(),
|
|
||||||
path: varchar('path').notNull(),
|
path: varchar('path').notNull(),
|
||||||
text: varchar('text'),
|
text: varchar('text'),
|
||||||
}
|
}
|
||||||
@@ -201,19 +212,24 @@ export const buildTable = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
textsTable = adapter.pgSchema.table(textsTableName, columns, (cols) => {
|
textsTable = adapter.pgSchema.table(textsTableName, columns, (cols) => {
|
||||||
const indexes: Record<string, IndexBuilder> = {
|
const config: Record<string, ForeignKeyBuilder | IndexBuilder> = {
|
||||||
orderParentIdx: index(`${textsTableName}_order_parent_idx`).on(cols.order, cols.parent),
|
orderParentIdx: index(`${textsTableName}_order_parent_idx`).on(cols.order, cols.parent),
|
||||||
|
parentFk: foreignKey({
|
||||||
|
name: `${textsTableName}_parent_fk`,
|
||||||
|
columns: [cols.parent],
|
||||||
|
foreignColumns: [table.id],
|
||||||
|
}).onDelete('cascade'),
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasManyTextField === 'index') {
|
if (hasManyTextField === 'index') {
|
||||||
indexes.text_idx = index(`${textsTableName}_text_idx`).on(cols.text)
|
config.text_idx = index(`${textsTableName}_text_idx`).on(cols.text)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasLocalizedManyTextField) {
|
if (hasLocalizedManyTextField) {
|
||||||
indexes.localeParent = index(`${textsTableName}_locale_parent`).on(cols.locale, cols.parent)
|
config.localeParent = index(`${textsTableName}_locale_parent`).on(cols.locale, cols.parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
return indexes
|
return config
|
||||||
})
|
})
|
||||||
|
|
||||||
adapter.tables[textsTableName] = textsTable
|
adapter.tables[textsTableName] = textsTable
|
||||||
@@ -234,9 +250,7 @@ export const buildTable = ({
|
|||||||
id: serial('id').primaryKey(),
|
id: serial('id').primaryKey(),
|
||||||
number: numeric('number'),
|
number: numeric('number'),
|
||||||
order: integer('order').notNull(),
|
order: integer('order').notNull(),
|
||||||
parent: parentIDColumnMap[idColType]('parent_id')
|
parent: parentIDColumnMap[idColType]('parent_id').notNull(),
|
||||||
.references(() => table.id, { onDelete: 'cascade' })
|
|
||||||
.notNull(),
|
|
||||||
path: varchar('path').notNull(),
|
path: varchar('path').notNull(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,22 +259,27 @@ export const buildTable = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
numbersTable = adapter.pgSchema.table(numbersTableName, columns, (cols) => {
|
numbersTable = adapter.pgSchema.table(numbersTableName, columns, (cols) => {
|
||||||
const indexes: Record<string, IndexBuilder> = {
|
const config: Record<string, ForeignKeyBuilder | IndexBuilder> = {
|
||||||
orderParentIdx: index(`${numbersTableName}_order_parent_idx`).on(cols.order, cols.parent),
|
orderParentIdx: index(`${numbersTableName}_order_parent_idx`).on(cols.order, cols.parent),
|
||||||
|
parentFk: foreignKey({
|
||||||
|
name: `${numbersTableName}_parent_fk`,
|
||||||
|
columns: [cols.parent],
|
||||||
|
foreignColumns: [table.id],
|
||||||
|
}).onDelete('cascade'),
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasManyNumberField === 'index') {
|
if (hasManyNumberField === 'index') {
|
||||||
indexes.numberIdx = index(`${numbersTableName}_number_idx`).on(cols.number)
|
config.numberIdx = index(`${numbersTableName}_number_idx`).on(cols.number)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasLocalizedManyNumberField) {
|
if (hasLocalizedManyNumberField) {
|
||||||
indexes.localeParent = index(`${numbersTableName}_locale_parent`).on(
|
config.localeParent = index(`${numbersTableName}_locale_parent`).on(
|
||||||
cols.locale,
|
cols.locale,
|
||||||
cols.parent,
|
cols.parent,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return indexes
|
return config
|
||||||
})
|
})
|
||||||
|
|
||||||
adapter.tables[numbersTableName] = numbersTable
|
adapter.tables[numbersTableName] = numbersTable
|
||||||
@@ -280,9 +299,7 @@ export const buildTable = ({
|
|||||||
const relationshipColumns: Record<string, PgColumnBuilder> = {
|
const relationshipColumns: Record<string, PgColumnBuilder> = {
|
||||||
id: serial('id').primaryKey(),
|
id: serial('id').primaryKey(),
|
||||||
order: integer('order'),
|
order: integer('order'),
|
||||||
parent: parentIDColumnMap[idColType]('parent_id')
|
parent: parentIDColumnMap[idColType]('parent_id').notNull(),
|
||||||
.references(() => table.id, { onDelete: 'cascade' })
|
|
||||||
.notNull(),
|
|
||||||
path: varchar('path').notNull(),
|
path: varchar('path').notNull(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,36 +307,61 @@ export const buildTable = ({
|
|||||||
relationshipColumns.locale = adapter.enums.enum__locales('locale')
|
relationshipColumns.locale = adapter.enums.enum__locales('locale')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const relationExtraConfig: BaseExtraConfig = {}
|
||||||
|
|
||||||
|
const relationshipsTableName = `${tableName}${adapter.relationshipsSuffix}`
|
||||||
|
|
||||||
relationships.forEach((relationTo) => {
|
relationships.forEach((relationTo) => {
|
||||||
const relationshipConfig = adapter.payload.collections[relationTo].config
|
const relationshipConfig = adapter.payload.collections[relationTo].config
|
||||||
const formattedRelationTo = getTableName({
|
const formattedRelationTo = createTableName({
|
||||||
adapter,
|
adapter,
|
||||||
config: relationshipConfig,
|
config: relationshipConfig,
|
||||||
throwValidationError: true,
|
throwValidationError: true,
|
||||||
})
|
})
|
||||||
let colType = adapter.idType === 'uuid' ? 'uuid' : 'integer'
|
let colType = adapter.idType === 'uuid' ? 'uuid' : 'integer'
|
||||||
|
|
||||||
const relatedCollectionCustomID = relationshipConfig.fields.find(
|
const relatedCollectionCustomID = relationshipConfig.fields.find(
|
||||||
(field) => fieldAffectsData(field) && field.name === 'id',
|
(field) => fieldAffectsData(field) && field.name === 'id',
|
||||||
)
|
)
|
||||||
if (relatedCollectionCustomID?.type === 'number') colType = 'numeric'
|
const relatedCollectionCustomIDType = relatedCollectionCustomID?.type
|
||||||
if (relatedCollectionCustomID?.type === 'text') colType = 'varchar'
|
|
||||||
|
if (relatedCollectionCustomIDType === 'number') colType = 'numeric'
|
||||||
|
if (relatedCollectionCustomIDType === 'text') colType = 'varchar'
|
||||||
|
|
||||||
relationshipColumns[`${relationTo}ID`] = parentIDColumnMap[colType](
|
relationshipColumns[`${relationTo}ID`] = parentIDColumnMap[colType](
|
||||||
`${formattedRelationTo}_id`,
|
`${formattedRelationTo}_id`,
|
||||||
).references(() => adapter.tables[formattedRelationTo].id, { onDelete: 'cascade' })
|
)
|
||||||
})
|
|
||||||
|
|
||||||
const relationshipsTableName = `${tableName}${adapter.relationshipsSuffix}`
|
relationExtraConfig[`${relationTo}IdFk`] = (cols) =>
|
||||||
|
foreignKey({
|
||||||
|
name: `${relationshipsTableName}_${toSnakeCase(relationTo)}_fk`,
|
||||||
|
columns: [cols[`${relationTo}ID`]],
|
||||||
|
foreignColumns: [adapter.tables[formattedRelationTo].id],
|
||||||
|
}).onDelete('cascade')
|
||||||
|
})
|
||||||
|
|
||||||
relationshipsTable = adapter.pgSchema.table(
|
relationshipsTable = adapter.pgSchema.table(
|
||||||
relationshipsTableName,
|
relationshipsTableName,
|
||||||
relationshipColumns,
|
relationshipColumns,
|
||||||
(cols) => {
|
(cols) => {
|
||||||
const result: Record<string, unknown> = {
|
const result: Record<string, ForeignKeyBuilder | IndexBuilder> = Object.entries(
|
||||||
order: index(`${relationshipsTableName}_order_idx`).on(cols.order),
|
relationExtraConfig,
|
||||||
parentIdx: index(`${relationshipsTableName}_parent_idx`).on(cols.parent),
|
).reduce(
|
||||||
pathIdx: index(`${relationshipsTableName}_path_idx`).on(cols.path),
|
(config, [key, func]) => {
|
||||||
}
|
config[key] = func(cols)
|
||||||
|
return config
|
||||||
|
},
|
||||||
|
{
|
||||||
|
order: index(`${relationshipsTableName}_order_idx`).on(cols.order),
|
||||||
|
parentFk: foreignKey({
|
||||||
|
name: `${relationshipsTableName}_parent_fk`,
|
||||||
|
columns: [cols.parent],
|
||||||
|
foreignColumns: [table.id],
|
||||||
|
}).onDelete('cascade'),
|
||||||
|
parentIdx: index(`${relationshipsTableName}_parent_idx`).on(cols.parent),
|
||||||
|
pathIdx: index(`${relationshipsTableName}_path_idx`).on(cols.path),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
if (hasLocalizedRelationshipField) {
|
if (hasLocalizedRelationshipField) {
|
||||||
result.localeIdx = index(`${relationshipsTableName}_locale_idx`).on(cols.locale)
|
result.localeIdx = index(`${relationshipsTableName}_locale_idx`).on(cols.locale)
|
||||||
@@ -341,7 +383,7 @@ export const buildTable = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
relationships.forEach((relationTo) => {
|
relationships.forEach((relationTo) => {
|
||||||
const relatedTableName = getTableName({
|
const relatedTableName = createTableName({
|
||||||
adapter,
|
adapter,
|
||||||
config: adapter.payload.collections[relationTo].config,
|
config: adapter.payload.collections[relationTo].config,
|
||||||
throwValidationError: true,
|
throwValidationError: true,
|
||||||
|
|||||||
@@ -14,53 +14,59 @@ type Args = {
|
|||||||
name?: string
|
name?: string
|
||||||
slug?: string
|
slug?: string
|
||||||
}
|
}
|
||||||
/** Localized tables need to be given the locales suffix */
|
|
||||||
locales?: boolean
|
|
||||||
/** For nested tables passed for the user custom dbName functions to handle their own iterations */
|
/** For nested tables passed for the user custom dbName functions to handle their own iterations */
|
||||||
parentTableName?: string
|
parentTableName?: string
|
||||||
/** For sub tables (array for example) this needs to include the parentTableName */
|
/** For sub tables (array for example) this needs to include the parentTableName */
|
||||||
prefix?: string
|
prefix?: string
|
||||||
/** Adds the relationships suffix */
|
|
||||||
relationships?: boolean
|
|
||||||
/** For tables based on fields that could have both enumName and dbName (ie: select with hasMany), default: 'dbName' */
|
/** For tables based on fields that could have both enumName and dbName (ie: select with hasMany), default: 'dbName' */
|
||||||
target?: 'dbName' | 'enumName'
|
target?: 'dbName' | 'enumName'
|
||||||
throwValidationError?: boolean
|
throwValidationError?: boolean
|
||||||
/** Adds the versions suffix, should only be used on the base collection to duplicate suffixing */
|
/** Adds the versions suffix to the default table name - should only be used on the base collection to avoid duplicate suffixing */
|
||||||
versions?: boolean
|
versions?: boolean
|
||||||
|
/** Adds the versions suffix to custom dbName only - this is used while creating blocks / selects / arrays / etc */
|
||||||
|
versionsCustomName?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to name database enums and tables
|
* Used to name database enums and tables
|
||||||
* Returns the table or enum name for a given entity
|
* Returns the table or enum name for a given entity
|
||||||
*/
|
*/
|
||||||
export const getTableName = ({
|
export const createTableName = ({
|
||||||
adapter,
|
adapter,
|
||||||
config: { name, slug },
|
config: { name, slug },
|
||||||
config,
|
config,
|
||||||
locales = false,
|
|
||||||
parentTableName,
|
parentTableName,
|
||||||
prefix = '',
|
prefix = '',
|
||||||
relationships = false,
|
|
||||||
target = 'dbName',
|
target = 'dbName',
|
||||||
throwValidationError = false,
|
throwValidationError = false,
|
||||||
versions = false,
|
versions = false,
|
||||||
|
versionsCustomName = false,
|
||||||
}: Args): string => {
|
}: Args): string => {
|
||||||
let result: string
|
let customNameDefinition = config[target]
|
||||||
let custom = config[target]
|
|
||||||
|
|
||||||
if (!custom && target === 'enumName') {
|
let defaultTableName = `${prefix}${toSnakeCase(name ?? slug)}`
|
||||||
custom = config['dbName']
|
|
||||||
|
if (versions) defaultTableName = `_${defaultTableName}${adapter.versionsSuffix}`
|
||||||
|
|
||||||
|
let customTableNameResult: string
|
||||||
|
|
||||||
|
if (!customNameDefinition && target === 'enumName') {
|
||||||
|
customNameDefinition = config['dbName']
|
||||||
}
|
}
|
||||||
|
|
||||||
if (custom) {
|
if (customNameDefinition) {
|
||||||
result = typeof custom === 'function' ? custom({ tableName: parentTableName }) : custom
|
customTableNameResult =
|
||||||
} else {
|
typeof customNameDefinition === 'function'
|
||||||
result = `${prefix}${toSnakeCase(name ?? slug)}`
|
? customNameDefinition({ tableName: parentTableName })
|
||||||
|
: customNameDefinition
|
||||||
|
|
||||||
|
if (versionsCustomName)
|
||||||
|
customTableNameResult = `_${customTableNameResult}${adapter.versionsSuffix}`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (locales) result = `${result}${adapter.localesSuffix}`
|
const result = customTableNameResult || defaultTableName
|
||||||
if (versions) result = `_${result}${adapter.versionsSuffix}`
|
|
||||||
if (relationships) result = `${result}${adapter.relationshipsSuffix}`
|
adapter.tableNameMap.set(defaultTableName, result)
|
||||||
|
|
||||||
if (!throwValidationError) {
|
if (!throwValidationError) {
|
||||||
return result
|
return result
|
||||||
@@ -71,5 +77,6 @@ export const getTableName = ({
|
|||||||
`Exceeded max identifier length for table or enum name of 63 characters. Invalid name: ${result}`,
|
`Exceeded max identifier length for table or enum name of 63 characters. Invalid name: ${result}`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable no-param-reassign */
|
/* eslint-disable no-param-reassign */
|
||||||
import type { Relation } from 'drizzle-orm'
|
import type { Relation } from 'drizzle-orm'
|
||||||
import type { IndexBuilder, PgColumnBuilder, UniqueConstraintBuilder } from 'drizzle-orm/pg-core'
|
import type { IndexBuilder, PgColumnBuilder } from 'drizzle-orm/pg-core'
|
||||||
import type { Field, TabAsField } from 'payload/types'
|
import type { Field, TabAsField } from 'payload/types'
|
||||||
|
|
||||||
import { relations } from 'drizzle-orm'
|
import { relations } from 'drizzle-orm'
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
PgUUIDBuilder,
|
PgUUIDBuilder,
|
||||||
PgVarcharBuilder,
|
PgVarcharBuilder,
|
||||||
boolean,
|
boolean,
|
||||||
|
foreignKey,
|
||||||
index,
|
index,
|
||||||
integer,
|
integer,
|
||||||
jsonb,
|
jsonb,
|
||||||
@@ -23,11 +24,12 @@ import { fieldAffectsData, optionIsObject } from 'payload/types'
|
|||||||
import toSnakeCase from 'to-snake-case'
|
import toSnakeCase from 'to-snake-case'
|
||||||
|
|
||||||
import type { GenericColumns, IDType, PostgresAdapter } from '../types'
|
import type { GenericColumns, IDType, PostgresAdapter } from '../types'
|
||||||
|
import type { BaseExtraConfig } from './build'
|
||||||
|
|
||||||
import { hasLocalesTable } from '../utilities/hasLocalesTable'
|
import { hasLocalesTable } from '../utilities/hasLocalesTable'
|
||||||
import { buildTable } from './build'
|
import { buildTable } from './build'
|
||||||
import { createIndex } from './createIndex'
|
import { createIndex } from './createIndex'
|
||||||
import { getTableName } from './getTableName'
|
import { createTableName } from './createTableName'
|
||||||
import { idToUUID } from './idToUUID'
|
import { idToUUID } from './idToUUID'
|
||||||
import { parentIDColumnMap } from './parentIDColumnMap'
|
import { parentIDColumnMap } from './parentIDColumnMap'
|
||||||
import { validateExistingBlockIsIdentical } from './validateExistingBlockIsIdentical'
|
import { validateExistingBlockIsIdentical } from './validateExistingBlockIsIdentical'
|
||||||
@@ -221,14 +223,13 @@ export const traverseFields = ({
|
|||||||
|
|
||||||
case 'radio':
|
case 'radio':
|
||||||
case 'select': {
|
case 'select': {
|
||||||
const enumName = getTableName({
|
const enumName = createTableName({
|
||||||
adapter,
|
adapter,
|
||||||
config: field,
|
config: field,
|
||||||
parentTableName: newTableName,
|
parentTableName: newTableName,
|
||||||
prefix: `enum_${newTableName}_`,
|
prefix: `enum_${newTableName}_`,
|
||||||
target: 'enumName',
|
target: 'enumName',
|
||||||
throwValidationError,
|
throwValidationError,
|
||||||
versions,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
adapter.enums[enumName] = pgEnum(
|
adapter.enums[enumName] = pgEnum(
|
||||||
@@ -243,27 +244,27 @@ export const traverseFields = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (field.type === 'select' && field.hasMany) {
|
if (field.type === 'select' && field.hasMany) {
|
||||||
const selectTableName = getTableName({
|
const selectTableName = createTableName({
|
||||||
adapter,
|
adapter,
|
||||||
config: field,
|
config: field,
|
||||||
parentTableName: newTableName,
|
parentTableName: newTableName,
|
||||||
prefix: `${newTableName}_`,
|
prefix: `${newTableName}_`,
|
||||||
throwValidationError,
|
throwValidationError,
|
||||||
versions,
|
|
||||||
})
|
})
|
||||||
const baseColumns: Record<string, PgColumnBuilder> = {
|
const baseColumns: Record<string, PgColumnBuilder> = {
|
||||||
order: integer('order').notNull(),
|
order: integer('order').notNull(),
|
||||||
parent: parentIDColumnMap[parentIDColType]('parent_id')
|
parent: parentIDColumnMap[parentIDColType]('parent_id').notNull(),
|
||||||
.references(() => adapter.tables[parentTableName].id, { onDelete: 'cascade' })
|
|
||||||
.notNull(),
|
|
||||||
value: adapter.enums[enumName]('value'),
|
value: adapter.enums[enumName]('value'),
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseExtraConfig: Record<
|
const baseExtraConfig: BaseExtraConfig = {
|
||||||
string,
|
|
||||||
(cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder
|
|
||||||
> = {
|
|
||||||
orderIdx: (cols) => index(`${selectTableName}_order_idx`).on(cols.order),
|
orderIdx: (cols) => index(`${selectTableName}_order_idx`).on(cols.order),
|
||||||
|
parentFk: (cols) =>
|
||||||
|
foreignKey({
|
||||||
|
name: `${selectTableName}_parent_fk`,
|
||||||
|
columns: [cols.parent],
|
||||||
|
foreignColumns: [adapter.tables[parentTableName].id],
|
||||||
|
}),
|
||||||
parentIdx: (cols) => index(`${selectTableName}_parent_idx`).on(cols.parent),
|
parentIdx: (cols) => index(`${selectTableName}_parent_idx`).on(cols.parent),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,25 +317,28 @@ export const traverseFields = ({
|
|||||||
case 'array': {
|
case 'array': {
|
||||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||||
|
|
||||||
const arrayTableName = getTableName({
|
const arrayTableName = createTableName({
|
||||||
adapter,
|
adapter,
|
||||||
config: field,
|
config: field,
|
||||||
parentTableName: newTableName,
|
parentTableName: newTableName,
|
||||||
prefix: `${newTableName}_`,
|
prefix: `${newTableName}_`,
|
||||||
throwValidationError,
|
throwValidationError,
|
||||||
|
versionsCustomName: versions,
|
||||||
})
|
})
|
||||||
|
|
||||||
const baseColumns: Record<string, PgColumnBuilder> = {
|
const baseColumns: Record<string, PgColumnBuilder> = {
|
||||||
_order: integer('_order').notNull(),
|
_order: integer('_order').notNull(),
|
||||||
_parentID: parentIDColumnMap[parentIDColType]('_parent_id')
|
_parentID: parentIDColumnMap[parentIDColType]('_parent_id').notNull(),
|
||||||
.references(() => adapter.tables[parentTableName].id, { onDelete: 'cascade' })
|
|
||||||
.notNull(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseExtraConfig: Record<
|
const baseExtraConfig: BaseExtraConfig = {
|
||||||
string,
|
|
||||||
(cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder
|
|
||||||
> = {
|
|
||||||
_orderIdx: (cols) => index(`${arrayTableName}_order_idx`).on(cols._order),
|
_orderIdx: (cols) => index(`${arrayTableName}_order_idx`).on(cols._order),
|
||||||
|
_parentIDFk: (cols) =>
|
||||||
|
foreignKey({
|
||||||
|
name: `${arrayTableName}_parent_id_fk`,
|
||||||
|
columns: [cols['_parentID']],
|
||||||
|
foreignColumns: [adapter.tables[parentTableName].id],
|
||||||
|
}).onDelete('cascade'),
|
||||||
_parentIDIdx: (cols) => index(`${arrayTableName}_parent_id_idx`).on(cols._parentID),
|
_parentIDIdx: (cols) => index(`${arrayTableName}_parent_id_idx`).on(cols._parentID),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,28 +406,30 @@ export const traverseFields = ({
|
|||||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||||
|
|
||||||
field.blocks.forEach((block) => {
|
field.blocks.forEach((block) => {
|
||||||
const blockTableName = getTableName({
|
const blockTableName = createTableName({
|
||||||
adapter,
|
adapter,
|
||||||
config: block,
|
config: block,
|
||||||
parentTableName: rootTableName,
|
parentTableName: rootTableName,
|
||||||
prefix: `${rootTableName}_blocks_`,
|
prefix: `${rootTableName}_blocks_`,
|
||||||
throwValidationError,
|
throwValidationError,
|
||||||
|
versionsCustomName: versions,
|
||||||
})
|
})
|
||||||
if (!adapter.tables[blockTableName]) {
|
if (!adapter.tables[blockTableName]) {
|
||||||
const baseColumns: Record<string, PgColumnBuilder> = {
|
const baseColumns: Record<string, PgColumnBuilder> = {
|
||||||
_order: integer('_order').notNull(),
|
_order: integer('_order').notNull(),
|
||||||
_parentID: parentIDColumnMap[rootTableIDColType]('_parent_id')
|
_parentID: parentIDColumnMap[rootTableIDColType]('_parent_id').notNull(),
|
||||||
.references(() => adapter.tables[rootTableName].id, { onDelete: 'cascade' })
|
|
||||||
.notNull(),
|
|
||||||
_path: text('_path').notNull(),
|
_path: text('_path').notNull(),
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseExtraConfig: Record<
|
const baseExtraConfig: BaseExtraConfig = {
|
||||||
string,
|
|
||||||
(cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder
|
|
||||||
> = {
|
|
||||||
_orderIdx: (cols) => index(`${blockTableName}_order_idx`).on(cols._order),
|
_orderIdx: (cols) => index(`${blockTableName}_order_idx`).on(cols._order),
|
||||||
_parentIDIdx: (cols) => index(`${blockTableName}_parent_id_idx`).on(cols._parentID),
|
_parentIDIdx: (cols) => index(`${blockTableName}_parent_id_idx`).on(cols._parentID),
|
||||||
|
_parentIdFk: (cols) =>
|
||||||
|
foreignKey({
|
||||||
|
name: `${blockTableName}_parent_id_fk`,
|
||||||
|
columns: [cols._parentID],
|
||||||
|
foreignColumns: [adapter.tables[rootTableName].id],
|
||||||
|
}).onDelete('cascade'),
|
||||||
_pathIdx: (cols) => index(`${blockTableName}_path_idx`).on(cols._path),
|
_pathIdx: (cols) => index(`${blockTableName}_path_idx`).on(cols._path),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -496,7 +502,6 @@ export const traverseFields = ({
|
|||||||
tableLocales: adapter.tables[`${blockTableName}${adapter.localesSuffix}`],
|
tableLocales: adapter.tables[`${blockTableName}${adapter.localesSuffix}`],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
adapter.blockTableNames[`${rootTableName}.${toSnakeCase(block.slug)}`] = blockTableName
|
|
||||||
rootRelationsToBuild.set(`_blocks_${block.slug}`, blockTableName)
|
rootRelationsToBuild.set(`_blocks_${block.slug}`, blockTableName)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -659,7 +664,7 @@ export const traverseFields = ({
|
|||||||
indexes,
|
indexes,
|
||||||
localesColumns,
|
localesColumns,
|
||||||
localesIndexes,
|
localesIndexes,
|
||||||
newTableName: parentTableName,
|
newTableName,
|
||||||
parentTableName,
|
parentTableName,
|
||||||
relationsToBuild,
|
relationsToBuild,
|
||||||
relationships,
|
relationships,
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ const getFlattenedFieldNames = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fieldHasSubFields(field)) {
|
if (fieldHasSubFields(field)) {
|
||||||
fieldPrefix = 'name' in field ? `${prefix}${field.name}.` : prefix
|
fieldPrefix = 'name' in field ? `${prefix}${field.name}_` : prefix
|
||||||
return [...fieldsToUse, ...getFlattenedFieldNames(field.fields, fieldPrefix)]
|
return [...fieldsToUse, ...getFlattenedFieldNames(field.fields, fieldPrefix)]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ const getFlattenedFieldNames = (
|
|||||||
return [
|
return [
|
||||||
...fieldsToUse,
|
...fieldsToUse,
|
||||||
...field.tabs.reduce((tabFields, tab) => {
|
...field.tabs.reduce((tabFields, tab) => {
|
||||||
fieldPrefix = 'name' in tab ? `${prefix}.${tab.name}` : prefix
|
fieldPrefix = 'name' in tab ? `${prefix}_${tab.name}` : prefix
|
||||||
return [
|
return [
|
||||||
...tabFields,
|
...tabFields,
|
||||||
...(tabHasName(tab)
|
...(tabHasName(tab)
|
||||||
@@ -51,7 +51,7 @@ const getFlattenedFieldNames = (
|
|||||||
return [
|
return [
|
||||||
...fieldsToUse,
|
...fieldsToUse,
|
||||||
{
|
{
|
||||||
name: `${fieldPrefix?.replace('.', '_') || ''}${field.name}`,
|
name: `${fieldPrefix}${field.name}`,
|
||||||
localized: field.localized,
|
localized: field.localized,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -69,6 +69,7 @@ export const validateExistingBlockIsIdentical = ({
|
|||||||
tableLocales,
|
tableLocales,
|
||||||
}: Args): void => {
|
}: Args): void => {
|
||||||
const fieldNames = getFlattenedFieldNames(block.fields)
|
const fieldNames = getFlattenedFieldNames(block.fields)
|
||||||
|
|
||||||
const missingField =
|
const missingField =
|
||||||
// ensure every field from the config is in the matching table
|
// ensure every field from the config is in the matching table
|
||||||
fieldNames.find(({ name, localized }) => {
|
fieldNames.find(({ name, localized }) => {
|
||||||
@@ -84,7 +85,11 @@ export const validateExistingBlockIsIdentical = ({
|
|||||||
|
|
||||||
if (missingField) {
|
if (missingField) {
|
||||||
throw new InvalidConfiguration(
|
throw new InvalidConfiguration(
|
||||||
`The table ${rootTableName} has multiple blocks with slug ${block.slug}, but the schemas do not match. One block includes the field ${typeof missingField === 'string' ? missingField : missingField.name}, while the other block does not.`,
|
`The table ${rootTableName} has multiple blocks with slug ${
|
||||||
|
block.slug
|
||||||
|
}, but the schemas do not match. One block includes the field ${
|
||||||
|
typeof missingField === 'string' ? missingField : missingField.name
|
||||||
|
}, while the other block does not.`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import type { TextField } from 'payload/types'
|
|||||||
type Args = {
|
type Args = {
|
||||||
field: TextField
|
field: TextField
|
||||||
locale?: string
|
locale?: string
|
||||||
textRows: Record<string, unknown>[]
|
|
||||||
ref: Record<string, unknown>
|
ref: Record<string, unknown>
|
||||||
|
textRows: Record<string, unknown>[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const transformHasManyText = ({ field, locale, textRows, ref }: Args) => {
|
export const transformHasManyText = ({ field, locale, ref, textRows }: Args) => {
|
||||||
const result = textRows.map(({ text }) => text)
|
const result = textRows.map(({ text }) => text)
|
||||||
|
|
||||||
if (locale) {
|
if (locale) {
|
||||||
|
|||||||
@@ -48,11 +48,11 @@ export const transform = <T extends TypeWithID>({ config, data, fields }: Transf
|
|||||||
deletions,
|
deletions,
|
||||||
fieldPrefix: '',
|
fieldPrefix: '',
|
||||||
fields,
|
fields,
|
||||||
texts,
|
|
||||||
numbers,
|
numbers,
|
||||||
path: '',
|
path: '',
|
||||||
relationships,
|
relationships,
|
||||||
table: data,
|
table: data,
|
||||||
|
texts,
|
||||||
})
|
})
|
||||||
|
|
||||||
deletions.forEach((deletion) => deletion())
|
deletions.forEach((deletion) => deletion())
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ type Args = {
|
|||||||
data: unknown
|
data: unknown
|
||||||
field: ArrayField
|
field: ArrayField
|
||||||
locale?: string
|
locale?: string
|
||||||
texts: Record<string, unknown>[]
|
|
||||||
numbers: Record<string, unknown>[]
|
numbers: Record<string, unknown>[]
|
||||||
path: string
|
path: string
|
||||||
relationships: Record<string, unknown>[]
|
relationships: Record<string, unknown>[]
|
||||||
@@ -26,6 +25,7 @@ type Args = {
|
|||||||
selects: {
|
selects: {
|
||||||
[tableName: string]: Record<string, unknown>[]
|
[tableName: string]: Record<string, unknown>[]
|
||||||
}
|
}
|
||||||
|
texts: Record<string, unknown>[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const transformArray = ({
|
export const transformArray = ({
|
||||||
@@ -37,14 +37,15 @@ export const transformArray = ({
|
|||||||
data,
|
data,
|
||||||
field,
|
field,
|
||||||
locale,
|
locale,
|
||||||
texts,
|
|
||||||
numbers,
|
numbers,
|
||||||
path,
|
path,
|
||||||
relationships,
|
relationships,
|
||||||
relationshipsToDelete,
|
relationshipsToDelete,
|
||||||
selects,
|
selects,
|
||||||
|
texts,
|
||||||
}: Args) => {
|
}: Args) => {
|
||||||
const newRows: ArrayRowToInsert[] = []
|
const newRows: ArrayRowToInsert[] = []
|
||||||
|
|
||||||
const hasUUID = adapter.tables[arrayTableName]._uuid
|
const hasUUID = adapter.tables[arrayTableName]._uuid
|
||||||
|
|
||||||
if (isArrayOfRows(data)) {
|
if (isArrayOfRows(data)) {
|
||||||
@@ -88,7 +89,6 @@ export const transformArray = ({
|
|||||||
fieldPrefix: '',
|
fieldPrefix: '',
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
locales: newRow.locales,
|
locales: newRow.locales,
|
||||||
texts,
|
|
||||||
numbers,
|
numbers,
|
||||||
parentTableName: arrayTableName,
|
parentTableName: arrayTableName,
|
||||||
path: `${path || ''}${field.name}.${i}.`,
|
path: `${path || ''}${field.name}.${i}.`,
|
||||||
@@ -96,6 +96,7 @@ export const transformArray = ({
|
|||||||
relationshipsToDelete,
|
relationshipsToDelete,
|
||||||
row: newRow.row,
|
row: newRow.row,
|
||||||
selects,
|
selects,
|
||||||
|
texts,
|
||||||
})
|
})
|
||||||
|
|
||||||
newRows.push(newRow)
|
newRows.push(newRow)
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export const transformBlocks = ({
|
|||||||
|
|
||||||
if (field.localized && locale) newRow.row._locale = locale
|
if (field.localized && locale) newRow.row._locale = locale
|
||||||
|
|
||||||
const blockTableName = `${baseTableName}_blocks_${blockType}`
|
const blockTableName = adapter.tableNameMap.get(`${baseTableName}_blocks_${blockType}`)
|
||||||
|
|
||||||
const hasUUID = adapter.tables[blockTableName]._uuid
|
const hasUUID = adapter.tables[blockTableName]._uuid
|
||||||
|
|
||||||
|
|||||||
@@ -27,12 +27,12 @@ export const transformForWrite = ({
|
|||||||
blocks: {},
|
blocks: {},
|
||||||
blocksToDelete: new Set(),
|
blocksToDelete: new Set(),
|
||||||
locales: {},
|
locales: {},
|
||||||
texts: [],
|
|
||||||
numbers: [],
|
numbers: [],
|
||||||
relationships: [],
|
relationships: [],
|
||||||
relationshipsToDelete: [],
|
relationshipsToDelete: [],
|
||||||
row: {},
|
row: {},
|
||||||
selects: {},
|
selects: {},
|
||||||
|
texts: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function is responsible for building up the
|
// This function is responsible for building up the
|
||||||
@@ -48,7 +48,6 @@ export const transformForWrite = ({
|
|||||||
fieldPrefix: '',
|
fieldPrefix: '',
|
||||||
fields,
|
fields,
|
||||||
locales: rowToInsert.locales,
|
locales: rowToInsert.locales,
|
||||||
texts: rowToInsert.texts,
|
|
||||||
numbers: rowToInsert.numbers,
|
numbers: rowToInsert.numbers,
|
||||||
parentTableName: tableName,
|
parentTableName: tableName,
|
||||||
path,
|
path,
|
||||||
@@ -56,6 +55,7 @@ export const transformForWrite = ({
|
|||||||
relationshipsToDelete: rowToInsert.relationshipsToDelete,
|
relationshipsToDelete: rowToInsert.relationshipsToDelete,
|
||||||
row: rowToInsert.row,
|
row: rowToInsert.row,
|
||||||
selects: rowToInsert.selects,
|
selects: rowToInsert.selects,
|
||||||
|
texts: rowToInsert.texts,
|
||||||
})
|
})
|
||||||
|
|
||||||
return rowToInsert
|
return rowToInsert
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ export const transformTexts = ({ baseRow, data, texts }: Args) => {
|
|||||||
data.forEach((val, i) => {
|
data.forEach((val, i) => {
|
||||||
texts.push({
|
texts.push({
|
||||||
...baseRow,
|
...baseRow,
|
||||||
text: val,
|
|
||||||
order: i + 1,
|
order: i + 1,
|
||||||
|
text: val,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import toSnakeCase from 'to-snake-case'
|
|||||||
import type { PostgresAdapter } from '../../types'
|
import type { PostgresAdapter } from '../../types'
|
||||||
import type { ArrayRowToInsert, BlockRowToInsert, RelationshipToDelete } from './types'
|
import type { ArrayRowToInsert, BlockRowToInsert, RelationshipToDelete } from './types'
|
||||||
|
|
||||||
import { getTableName } from '../../schema/getTableName'
|
|
||||||
import { isArrayOfRows } from '../../utilities/isArrayOfRows'
|
import { isArrayOfRows } from '../../utilities/isArrayOfRows'
|
||||||
import { transformArray } from './array'
|
import { transformArray } from './array'
|
||||||
import { transformBlocks } from './blocks'
|
import { transformBlocks } from './blocks'
|
||||||
@@ -89,18 +88,13 @@ export const traverseFields = ({
|
|||||||
let fieldData: unknown
|
let fieldData: unknown
|
||||||
|
|
||||||
if (fieldAffectsData(field)) {
|
if (fieldAffectsData(field)) {
|
||||||
columnName = `${columnPrefix || ''}${getTableName({
|
columnName = `${columnPrefix || ''}${toSnakeCase(field.name)}`
|
||||||
adapter,
|
|
||||||
config: field,
|
|
||||||
// do not pass columnPrefix here because it is required and custom dbNames also need it
|
|
||||||
prefix: '',
|
|
||||||
})}`
|
|
||||||
fieldName = `${fieldPrefix || ''}${field.name}`
|
fieldName = `${fieldPrefix || ''}${field.name}`
|
||||||
fieldData = data[field.name]
|
fieldData = data[field.name]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.type === 'array') {
|
if (field.type === 'array') {
|
||||||
const arrayTableName = `${parentTableName}_${columnName}`
|
const arrayTableName = adapter.tableNameMap.get(`${parentTableName}_${columnName}`)
|
||||||
|
|
||||||
if (!arrays[arrayTableName]) arrays[arrayTableName] = []
|
if (!arrays[arrayTableName]) arrays[arrayTableName] = []
|
||||||
|
|
||||||
@@ -153,8 +147,8 @@ export const traverseFields = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (field.type === 'blocks') {
|
if (field.type === 'blocks') {
|
||||||
field.blocks.forEach((block) => {
|
field.blocks.forEach(({ slug }) => {
|
||||||
blocksToDelete.add(getTableName({ adapter, config: block }))
|
blocksToDelete.add(toSnakeCase(slug))
|
||||||
})
|
})
|
||||||
|
|
||||||
if (field.localized) {
|
if (field.localized) {
|
||||||
@@ -464,7 +458,7 @@ export const traverseFields = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (field.type === 'select' && field.hasMany) {
|
if (field.type === 'select' && field.hasMany) {
|
||||||
const selectTableName = `${parentTableName}_${columnName}`
|
const selectTableName = adapter.tableNameMap.get(`${parentTableName}_${columnName}`)
|
||||||
if (!selects[selectTableName]) selects[selectTableName] = []
|
if (!selects[selectTableName]) selects[selectTableName] = []
|
||||||
|
|
||||||
if (field.localized) {
|
if (field.localized) {
|
||||||
@@ -494,11 +488,7 @@ export const traverseFields = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fieldAffectsData(field)) {
|
if (fieldAffectsData(field)) {
|
||||||
const valuesToTransform: {
|
const valuesToTransform: { localeKey?: string; ref: unknown; value: unknown }[] = []
|
||||||
localeKey?: string
|
|
||||||
ref: unknown
|
|
||||||
value: unknown
|
|
||||||
}[] = []
|
|
||||||
|
|
||||||
if (field.localized) {
|
if (field.localized) {
|
||||||
if (typeof fieldData === 'object' && fieldData !== null) {
|
if (typeof fieldData === 'object' && fieldData !== null) {
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ export type RowToInsert = {
|
|||||||
locales: {
|
locales: {
|
||||||
[locale: string]: Record<string, unknown>
|
[locale: string]: Record<string, unknown>
|
||||||
}
|
}
|
||||||
texts: Record<string, unknown>[]
|
|
||||||
numbers: Record<string, unknown>[]
|
numbers: Record<string, unknown>[]
|
||||||
relationships: Record<string, unknown>[]
|
relationships: Record<string, unknown>[]
|
||||||
relationshipsToDelete: RelationshipToDelete[]
|
relationshipsToDelete: RelationshipToDelete[]
|
||||||
@@ -42,4 +41,5 @@ export type RowToInsert = {
|
|||||||
selects: {
|
selects: {
|
||||||
[tableName: string]: Record<string, unknown>[]
|
[tableName: string]: Record<string, unknown>[]
|
||||||
}
|
}
|
||||||
|
texts: Record<string, unknown>[]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,14 +23,14 @@ import type { Pool, PoolConfig } from 'pg'
|
|||||||
export type DrizzleDB = NodePgDatabase<Record<string, unknown>>
|
export type DrizzleDB = NodePgDatabase<Record<string, unknown>>
|
||||||
|
|
||||||
export type Args = {
|
export type Args = {
|
||||||
localesSuffix?: string
|
|
||||||
idType?: 'serial' | 'uuid'
|
idType?: 'serial' | 'uuid'
|
||||||
|
localesSuffix?: string
|
||||||
logger?: DrizzleConfig['logger']
|
logger?: DrizzleConfig['logger']
|
||||||
migrationDir?: string
|
migrationDir?: string
|
||||||
pool: PoolConfig
|
pool: PoolConfig
|
||||||
push?: boolean
|
push?: boolean
|
||||||
schemaName?: string
|
|
||||||
relationshipsSuffix?: string
|
relationshipsSuffix?: string
|
||||||
|
schemaName?: string
|
||||||
versionsSuffix?: string
|
versionsSuffix?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,10 +61,6 @@ export type DrizzleTransaction = PgTransaction<
|
|||||||
>
|
>
|
||||||
|
|
||||||
export type PostgresAdapter = BaseDatabaseAdapter & {
|
export type PostgresAdapter = BaseDatabaseAdapter & {
|
||||||
/**
|
|
||||||
* Used internally to map the block name to the table name
|
|
||||||
*/
|
|
||||||
blockTableNames: Record<string, string>
|
|
||||||
drizzle: DrizzleDB
|
drizzle: DrizzleDB
|
||||||
enums: Record<string, GenericEnum>
|
enums: Record<string, GenericEnum>
|
||||||
/**
|
/**
|
||||||
@@ -90,6 +86,7 @@ export type PostgresAdapter = BaseDatabaseAdapter & {
|
|||||||
resolve: () => Promise<void>
|
resolve: () => Promise<void>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tableNameMap: Map<string, string>
|
||||||
tables: Record<string, GenericTable | PgTableWithColumns<any>>
|
tables: Record<string, GenericTable | PgTableWithColumns<any>>
|
||||||
versionsSuffix?: string
|
versionsSuffix?: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,10 @@ import type { UpdateOne } from 'payload/database'
|
|||||||
|
|
||||||
import toSnakeCase from 'to-snake-case'
|
import toSnakeCase from 'to-snake-case'
|
||||||
|
|
||||||
import type { ChainedMethods } from './find/chainMethods'
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import buildQuery from './queries/buildQuery'
|
import buildQuery from './queries/buildQuery'
|
||||||
import { selectDistinct } from './queries/selectDistinct'
|
import { selectDistinct } from './queries/selectDistinct'
|
||||||
import { getTableName } from './schema/getTableName'
|
|
||||||
import { upsertRow } from './upsertRow'
|
import { upsertRow } from './upsertRow'
|
||||||
|
|
||||||
export const updateOne: UpdateOne = async function updateOne(
|
export const updateOne: UpdateOne = async function updateOne(
|
||||||
@@ -16,10 +14,7 @@ export const updateOne: UpdateOne = async function updateOne(
|
|||||||
) {
|
) {
|
||||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||||
const collection = this.payload.collections[collectionSlug].config
|
const collection = this.payload.collections[collectionSlug].config
|
||||||
const tableName = getTableName({
|
const tableName = this.tableNameMap.get(toSnakeCase(collection.slug))
|
||||||
adapter: this,
|
|
||||||
config: collection,
|
|
||||||
})
|
|
||||||
const whereToUse = whereArg || { id: { equals: id } }
|
const whereToUse = whereArg || { id: { equals: id } }
|
||||||
let idToUpdate = id
|
let idToUpdate = id
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import type { UpdateGlobalArgs } from 'payload/database'
|
import type { UpdateGlobalArgs } from 'payload/database'
|
||||||
import type { PayloadRequest, TypeWithID } from 'payload/types'
|
import type { PayloadRequest, TypeWithID } from 'payload/types'
|
||||||
|
|
||||||
|
import toSnakeCase from 'to-snake-case'
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import { getTableName } from './schema/getTableName'
|
|
||||||
import { upsertRow } from './upsertRow'
|
import { upsertRow } from './upsertRow'
|
||||||
|
|
||||||
export async function updateGlobal<T extends TypeWithID>(
|
export async function updateGlobal<T extends TypeWithID>(
|
||||||
@@ -12,10 +13,7 @@ export async function updateGlobal<T extends TypeWithID>(
|
|||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||||
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
|
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
|
||||||
const tableName = getTableName({
|
const tableName = this.tableNameMap.get(toSnakeCase(globalConfig.slug))
|
||||||
adapter: this,
|
|
||||||
config: globalConfig,
|
|
||||||
})
|
|
||||||
|
|
||||||
const existingGlobal = await db.query[tableName].findFirst({})
|
const existingGlobal = await db.query[tableName].findFirst({})
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import type { TypeWithVersion, UpdateGlobalVersionArgs } from 'payload/database'
|
|||||||
import type { PayloadRequest, SanitizedGlobalConfig, TypeWithID } from 'payload/types'
|
import type { PayloadRequest, SanitizedGlobalConfig, TypeWithID } from 'payload/types'
|
||||||
|
|
||||||
import { buildVersionGlobalFields } from 'payload/versions'
|
import { buildVersionGlobalFields } from 'payload/versions'
|
||||||
|
import toSnakeCase from 'to-snake-case'
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import buildQuery from './queries/buildQuery'
|
import buildQuery from './queries/buildQuery'
|
||||||
import { getTableName } from './schema/getTableName'
|
|
||||||
import { upsertRow } from './upsertRow'
|
import { upsertRow } from './upsertRow'
|
||||||
|
|
||||||
export async function updateGlobalVersion<T extends TypeWithID>(
|
export async function updateGlobalVersion<T extends TypeWithID>(
|
||||||
@@ -25,11 +25,11 @@ export async function updateGlobalVersion<T extends TypeWithID>(
|
|||||||
({ slug }) => slug === global,
|
({ slug }) => slug === global,
|
||||||
)
|
)
|
||||||
const whereToUse = whereArg || { id: { equals: id } }
|
const whereToUse = whereArg || { id: { equals: id } }
|
||||||
const tableName = getTableName({
|
|
||||||
adapter: this,
|
const tableName = this.tableNameMap.get(
|
||||||
config: globalConfig,
|
`_${toSnakeCase(globalConfig.slug)}${this.versionsSuffix}`,
|
||||||
versions: true,
|
)
|
||||||
})
|
|
||||||
const fields = buildVersionGlobalFields(globalConfig)
|
const fields = buildVersionGlobalFields(globalConfig)
|
||||||
|
|
||||||
const { where } = await buildQuery({
|
const { where } = await buildQuery({
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import type { TypeWithVersion, UpdateVersionArgs } from 'payload/database'
|
|||||||
import type { PayloadRequest, SanitizedCollectionConfig, TypeWithID } from 'payload/types'
|
import type { PayloadRequest, SanitizedCollectionConfig, TypeWithID } from 'payload/types'
|
||||||
|
|
||||||
import { buildVersionCollectionFields } from 'payload/versions'
|
import { buildVersionCollectionFields } from 'payload/versions'
|
||||||
|
import toSnakeCase from 'to-snake-case'
|
||||||
|
|
||||||
import type { PostgresAdapter } from './types'
|
import type { PostgresAdapter } from './types'
|
||||||
|
|
||||||
import buildQuery from './queries/buildQuery'
|
import buildQuery from './queries/buildQuery'
|
||||||
import { getTableName } from './schema/getTableName'
|
|
||||||
import { upsertRow } from './upsertRow'
|
import { upsertRow } from './upsertRow'
|
||||||
|
|
||||||
export async function updateVersion<T extends TypeWithID>(
|
export async function updateVersion<T extends TypeWithID>(
|
||||||
@@ -23,11 +23,10 @@ export async function updateVersion<T extends TypeWithID>(
|
|||||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||||
const whereToUse = whereArg || { id: { equals: id } }
|
const whereToUse = whereArg || { id: { equals: id } }
|
||||||
const tableName = getTableName({
|
const tableName = this.tableNameMap.get(
|
||||||
adapter: this,
|
`_${toSnakeCase(collectionConfig.slug)}${this.versionsSuffix}`,
|
||||||
config: collectionConfig,
|
)
|
||||||
versions: true,
|
|
||||||
})
|
|
||||||
const fields = buildVersionCollectionFields(collectionConfig)
|
const fields = buildVersionCollectionFields(collectionConfig)
|
||||||
|
|
||||||
const { where } = await buildQuery({
|
const { where } = await buildQuery({
|
||||||
|
|||||||
@@ -224,14 +224,14 @@ export const upsertRow = async <T extends TypeWithID>({
|
|||||||
|
|
||||||
if (operation === 'update') {
|
if (operation === 'update') {
|
||||||
for (const blockName of rowToInsert.blocksToDelete) {
|
for (const blockName of rowToInsert.blocksToDelete) {
|
||||||
const blockTableName = adapter.blockTableNames[`${tableName}.${blockName}`]
|
const blockTableName = adapter.tableNameMap.get(`${tableName}_blocks_${blockName}`)
|
||||||
const blockTable = adapter.tables[blockTableName]
|
const blockTable = adapter.tables[blockTableName]
|
||||||
await db.delete(blockTable).where(eq(blockTable._parentID, insertedRow.id))
|
await db.delete(blockTable).where(eq(blockTable._parentID, insertedRow.id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [blockName, blockRows] of Object.entries(blocksToInsert)) {
|
for (const [blockName, blockRows] of Object.entries(blocksToInsert)) {
|
||||||
const blockTableName = adapter.blockTableNames[`${tableName}.${blockName}`]
|
const blockTableName = adapter.tableNameMap.get(`${tableName}_blocks_${blockName}`)
|
||||||
insertedBlockRows[blockName] = await db
|
insertedBlockRows[blockName] = await db
|
||||||
.insert(adapter.tables[blockTableName])
|
.insert(adapter.tables[blockTableName])
|
||||||
.values(blockRows.map(({ row }) => row))
|
.values(blockRows.map(({ row }) => row))
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export const insertArrays = async ({ adapter, arrays, db, parentRows }: Args): P
|
|||||||
// Insert locale rows
|
// Insert locale rows
|
||||||
if (adapter.tables[`${tableName}${adapter.localesSuffix}`] && row.locales.length > 0) {
|
if (adapter.tables[`${tableName}${adapter.localesSuffix}`] && row.locales.length > 0) {
|
||||||
if (!row.locales[0]._parentID) {
|
if (!row.locales[0]._parentID) {
|
||||||
row.locales = row.locales.map((localeRow, i) => {
|
row.locales = row.locales.map((localeRow) => {
|
||||||
if (typeof localeRow._getParentID === 'function') {
|
if (typeof localeRow._getParentID === 'function') {
|
||||||
localeRow._parentID = localeRow._getParentID(insertedRows)
|
localeRow._parentID = localeRow._getParentID(insertedRows)
|
||||||
delete localeRow._getParentID
|
delete localeRow._getParentID
|
||||||
|
|||||||
10
packages/live-preview-vue/.eslintignore
Normal file
10
packages/live-preview-vue/.eslintignore
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
.tmp
|
||||||
|
**/.git
|
||||||
|
**/.hg
|
||||||
|
**/.pnp.*
|
||||||
|
**/.svn
|
||||||
|
**/.yarn/**
|
||||||
|
**/build
|
||||||
|
**/dist/**
|
||||||
|
**/node_modules
|
||||||
|
**/temp
|
||||||
37
packages/live-preview-vue/.eslintrc.js
Normal file
37
packages/live-preview-vue/.eslintrc.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/** @type {import('prettier').Config} */
|
||||||
|
module.exports = {
|
||||||
|
extends: ['@payloadcms'],
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
extends: ['plugin:@typescript-eslint/disable-type-checked'],
|
||||||
|
files: ['*.js', '*.cjs', '*.json', '*.md', '*.yml', '*.yaml'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['package.json', 'tsconfig.json'],
|
||||||
|
rules: {
|
||||||
|
'perfectionist/sort-array-includes': 'off',
|
||||||
|
'perfectionist/sort-astro-attributes': 'off',
|
||||||
|
'perfectionist/sort-classes': 'off',
|
||||||
|
'perfectionist/sort-enums': 'off',
|
||||||
|
'perfectionist/sort-exports': 'off',
|
||||||
|
'perfectionist/sort-imports': 'off',
|
||||||
|
'perfectionist/sort-interfaces': 'off',
|
||||||
|
'perfectionist/sort-jsx-props': 'off',
|
||||||
|
'perfectionist/sort-keys': 'off',
|
||||||
|
'perfectionist/sort-maps': 'off',
|
||||||
|
'perfectionist/sort-named-exports': 'off',
|
||||||
|
'perfectionist/sort-named-imports': 'off',
|
||||||
|
'perfectionist/sort-object-types': 'off',
|
||||||
|
'perfectionist/sort-objects': 'off',
|
||||||
|
'perfectionist/sort-svelte-attributes': 'off',
|
||||||
|
'perfectionist/sort-union-types': 'off',
|
||||||
|
'perfectionist/sort-vue-attributes': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
project: ['./tsconfig.json'],
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
},
|
||||||
|
root: true,
|
||||||
|
}
|
||||||
10
packages/live-preview-vue/.prettierignore
Normal file
10
packages/live-preview-vue/.prettierignore
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
.tmp
|
||||||
|
**/.git
|
||||||
|
**/.hg
|
||||||
|
**/.pnp.*
|
||||||
|
**/.svn
|
||||||
|
**/.yarn/**
|
||||||
|
**/build
|
||||||
|
**/dist/**
|
||||||
|
**/node_modules
|
||||||
|
**/temp
|
||||||
15
packages/live-preview-vue/.swcrc
Normal file
15
packages/live-preview-vue/.swcrc
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/swcrc",
|
||||||
|
"sourceMaps": "inline",
|
||||||
|
"jsc": {
|
||||||
|
"target": "esnext",
|
||||||
|
"parser": {
|
||||||
|
"syntax": "typescript",
|
||||||
|
"tsx": true,
|
||||||
|
"dts": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"module": {
|
||||||
|
"type": "commonjs"
|
||||||
|
}
|
||||||
|
}
|
||||||
49
packages/live-preview-vue/package.json
Normal file
49
packages/live-preview-vue/package.json
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"name": "@payloadcms/live-preview-vue",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "The official live preview Vue SDK for Payload",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/payloadcms/payload.git",
|
||||||
|
"directory": "packages/live-preview-vue"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"homepage": "https://payloadcms.com",
|
||||||
|
"author": "Payload CMS, Inc.",
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"build": "pnpm copyfiles && pnpm build:swc && pnpm build:types",
|
||||||
|
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
|
||||||
|
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
||||||
|
"clean": "rimraf {dist,*.tsbuildinfo}",
|
||||||
|
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
|
||||||
|
"prepublishOnly": "pnpm clean && pnpm build"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@payloadcms/live-preview": "workspace:^0.x"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@payloadcms/eslint-config": "workspace:*",
|
||||||
|
"vue": "^3.0.0",
|
||||||
|
"payload": "workspace:*"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.0.0"
|
||||||
|
},
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"default": "./src/index.ts",
|
||||||
|
"types": "./src/index.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"exports": null,
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"registry": "https://registry.npmjs.org/",
|
||||||
|
"types": "./dist/index.d.ts"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
]
|
||||||
|
}
|
||||||
58
packages/live-preview-vue/src/index.ts
Normal file
58
packages/live-preview-vue/src/index.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import type { Ref } from 'vue'
|
||||||
|
|
||||||
|
import { ready, subscribe, unsubscribe } from '@payloadcms/live-preview'
|
||||||
|
import { onMounted, onUnmounted, ref } from 'vue'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vue composable to implement Payload CMS Live Preview.
|
||||||
|
*
|
||||||
|
* {@link https://payloadcms.com/docs/live-preview/frontend View the documentation}
|
||||||
|
*/
|
||||||
|
export const useLivePreview = <T>(props: {
|
||||||
|
apiRoute?: string
|
||||||
|
depth?: number
|
||||||
|
initialData: T
|
||||||
|
serverURL: string
|
||||||
|
}): {
|
||||||
|
data: Ref<T>
|
||||||
|
isLoading: Ref<boolean>
|
||||||
|
} => {
|
||||||
|
const { apiRoute, depth, initialData, serverURL } = props
|
||||||
|
const data = ref(initialData) as Ref<T>
|
||||||
|
const isLoading = ref(true)
|
||||||
|
const hasSentReadyMessage = ref(false)
|
||||||
|
|
||||||
|
const onChange = (mergedData: T) => {
|
||||||
|
data.value = mergedData
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
let subscription: (event: MessageEvent) => void
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
subscription = subscribe({
|
||||||
|
apiRoute,
|
||||||
|
callback: onChange,
|
||||||
|
depth,
|
||||||
|
initialData,
|
||||||
|
serverURL,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!hasSentReadyMessage.value) {
|
||||||
|
hasSentReadyMessage.value = true
|
||||||
|
|
||||||
|
ready({
|
||||||
|
serverURL,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
unsubscribe(subscription)
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
isLoading,
|
||||||
|
}
|
||||||
|
}
|
||||||
25
packages/live-preview-vue/tsconfig.json
Normal file
25
packages/live-preview-vue/tsconfig.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true, // Make sure typescript knows that this module depends on their references
|
||||||
|
"noEmit": false /* Do not emit outputs. */,
|
||||||
|
"emitDeclarationOnly": true,
|
||||||
|
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
|
||||||
|
"rootDir": "./src" /* Specify the root folder within your source files. */,
|
||||||
|
"jsx": "react"
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"dist",
|
||||||
|
"build",
|
||||||
|
"tests",
|
||||||
|
"test",
|
||||||
|
"node_modules",
|
||||||
|
".eslintrc.js",
|
||||||
|
"src/**/*.spec.js",
|
||||||
|
"src/**/*.spec.jsx",
|
||||||
|
"src/**/*.spec.ts",
|
||||||
|
"src/**/*.spec.tsx"
|
||||||
|
],
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts", "src/**/*.json"],
|
||||||
|
"references": [{ "path": "../payload" }] // db-mongodb depends on payload
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "payload",
|
"name": "payload",
|
||||||
"version": "2.13.0",
|
"version": "2.18.3",
|
||||||
"description": "Node, React and MongoDB Headless CMS and Application Framework",
|
"description": "Node, React and MongoDB Headless CMS and Application Framework",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
@@ -118,11 +118,11 @@
|
|||||||
"process": "0.11.10",
|
"process": "0.11.10",
|
||||||
"qs": "6.11.2",
|
"qs": "6.11.2",
|
||||||
"qs-middleware": "1.0.3",
|
"qs-middleware": "1.0.3",
|
||||||
"react": "18.2.0",
|
"react": "^18.0.0",
|
||||||
"react-animate-height": "2.1.2",
|
"react-animate-height": "2.1.2",
|
||||||
"react-datepicker": "4.16.0",
|
"react-datepicker": "4.16.0",
|
||||||
"react-diff-viewer-continued": "3.2.6",
|
"react-diff-viewer-continued": "3.2.6",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "^18.0.0",
|
||||||
"react-helmet": "6.1.0",
|
"react-helmet": "6.1.0",
|
||||||
"react-i18next": "11.18.6",
|
"react-i18next": "11.18.6",
|
||||||
"react-image-crop": "10.1.8",
|
"react-image-crop": "10.1.8",
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { ConfigProvider } from './components/utilities/Config'
|
|||||||
import { CustomProvider } from './components/utilities/CustomProvider'
|
import { CustomProvider } from './components/utilities/CustomProvider'
|
||||||
import { DocumentEventsProvider } from './components/utilities/DocumentEvents'
|
import { DocumentEventsProvider } from './components/utilities/DocumentEvents'
|
||||||
import { I18n } from './components/utilities/I18n'
|
import { I18n } from './components/utilities/I18n'
|
||||||
|
import { LanguageWrap } from './components/utilities/LanguageWrap'
|
||||||
import { LoadingOverlayProvider } from './components/utilities/LoadingOverlay'
|
import { LoadingOverlayProvider } from './components/utilities/LoadingOverlay'
|
||||||
import { LocaleProvider } from './components/utilities/Locale'
|
import { LocaleProvider } from './components/utilities/Locale'
|
||||||
import { PreferencesProvider } from './components/utilities/Preferences'
|
import { PreferencesProvider } from './components/utilities/Preferences'
|
||||||
@@ -46,21 +47,23 @@ const Root = ({ config: incomingConfig }: { config?: SanitizedConfig }) => {
|
|||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<PreferencesProvider>
|
<PreferencesProvider>
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<SearchParamsProvider>
|
<LanguageWrap>
|
||||||
<LocaleProvider>
|
<SearchParamsProvider>
|
||||||
<StepNavProvider>
|
<LocaleProvider>
|
||||||
<LoadingOverlayProvider>
|
<StepNavProvider>
|
||||||
<DocumentEventsProvider>
|
<LoadingOverlayProvider>
|
||||||
<NavProvider>
|
<DocumentEventsProvider>
|
||||||
<CustomProvider>
|
<NavProvider>
|
||||||
<Routes />
|
<CustomProvider>
|
||||||
</CustomProvider>
|
<Routes />
|
||||||
</NavProvider>
|
</CustomProvider>
|
||||||
</DocumentEventsProvider>
|
</NavProvider>
|
||||||
</LoadingOverlayProvider>
|
</DocumentEventsProvider>
|
||||||
</StepNavProvider>
|
</LoadingOverlayProvider>
|
||||||
</LocaleProvider>
|
</StepNavProvider>
|
||||||
</SearchParamsProvider>
|
</LocaleProvider>
|
||||||
|
</SearchParamsProvider>
|
||||||
|
</LanguageWrap>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
<ModalContainer />
|
<ModalContainer />
|
||||||
</PreferencesProvider>
|
</PreferencesProvider>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export const ArrayAction: React.FC<Props> = ({
|
|||||||
duplicateRow,
|
duplicateRow,
|
||||||
hasMaxRows,
|
hasMaxRows,
|
||||||
index,
|
index,
|
||||||
|
isSortable,
|
||||||
moveRow,
|
moveRow,
|
||||||
removeRow,
|
removeRow,
|
||||||
rowCount,
|
rowCount,
|
||||||
@@ -33,7 +34,7 @@ export const ArrayAction: React.FC<Props> = ({
|
|||||||
render={({ close }) => {
|
render={({ close }) => {
|
||||||
return (
|
return (
|
||||||
<PopupList.ButtonGroup buttonSize="small">
|
<PopupList.ButtonGroup buttonSize="small">
|
||||||
{index !== 0 && (
|
{isSortable && index !== 0 && (
|
||||||
<PopupList.Button
|
<PopupList.Button
|
||||||
className={`${baseClass}__action ${baseClass}__move-up`}
|
className={`${baseClass}__action ${baseClass}__move-up`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -47,7 +48,7 @@ export const ArrayAction: React.FC<Props> = ({
|
|||||||
{t('moveUp')}
|
{t('moveUp')}
|
||||||
</PopupList.Button>
|
</PopupList.Button>
|
||||||
)}
|
)}
|
||||||
{index < rowCount - 1 && (
|
{isSortable && index < rowCount - 1 && (
|
||||||
<PopupList.Button
|
<PopupList.Button
|
||||||
className={`${baseClass}__action`}
|
className={`${baseClass}__action`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ export type Props = {
|
|||||||
duplicateRow: (current: number) => void
|
duplicateRow: (current: number) => void
|
||||||
hasMaxRows: boolean
|
hasMaxRows: boolean
|
||||||
index: number
|
index: number
|
||||||
|
isSortable: boolean
|
||||||
moveRow: (from: number, to: number) => void
|
moveRow: (from: number, to: number) => void
|
||||||
removeRow: (index: number) => void
|
removeRow: (index: number) => void
|
||||||
rowCount: number
|
rowCount: number
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import './index.scss'
|
|||||||
const baseClass = 'column-selector'
|
const baseClass = 'column-selector'
|
||||||
|
|
||||||
const ColumnSelector: React.FC<Props> = (props) => {
|
const ColumnSelector: React.FC<Props> = (props) => {
|
||||||
const { collection } = props
|
const { slug } = props
|
||||||
|
|
||||||
const { columns, moveColumn, toggleColumn } = useTableColumns()
|
const { columns, moveColumn, toggleColumn } = useTableColumns()
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ const ColumnSelector: React.FC<Props> = (props) => {
|
|||||||
draggable
|
draggable
|
||||||
icon={active ? <X /> : <Plus />}
|
icon={active ? <X /> : <Plus />}
|
||||||
id={accessor}
|
id={accessor}
|
||||||
key={`${collection.slug}-${col.name || i}${editDepth ? `-${editDepth}-` : ''}${uuid}`}
|
key={`${slug}-${col.name || i}${editDepth ? `-${editDepth}-` : ''}${uuid}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
toggleColumn(accessor)
|
toggleColumn(accessor)
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { SanitizedCollectionConfig } from '../../../../collections/config/types'
|
import type { SanitizedCollectionConfig } from '../../../../collections/config/types'
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
collection: SanitizedCollectionConfig
|
slug: SanitizedCollectionConfig['slug']
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { CollectionPermission, GlobalPermission } from '../../../../auth'
|
|||||||
import type { SanitizedCollectionConfig } from '../../../../collections/config/types'
|
import type { SanitizedCollectionConfig } from '../../../../collections/config/types'
|
||||||
import type { SanitizedGlobalConfig } from '../../../../globals/config/types'
|
import type { SanitizedGlobalConfig } from '../../../../globals/config/types'
|
||||||
|
|
||||||
|
import { getTranslation } from '../../../../utilities/getTranslation'
|
||||||
import { formatDate } from '../../../utilities/formatDate'
|
import { formatDate } from '../../../utilities/formatDate'
|
||||||
import { useConfig } from '../../utilities/Config'
|
import { useConfig } from '../../utilities/Config'
|
||||||
import { useDocumentInfo } from '../../utilities/DocumentInfo'
|
import { useDocumentInfo } from '../../utilities/DocumentInfo'
|
||||||
@@ -63,6 +64,12 @@ export const DocumentControls: React.FC<{
|
|||||||
collection && id && !disableActions && (hasCreatePermission || hasDeletePermission),
|
collection && id && !disableActions && (hasCreatePermission || hasDeletePermission),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const collectionLabel = () => {
|
||||||
|
const label = collection?.labels?.singular
|
||||||
|
if (!label) return t('document')
|
||||||
|
return typeof label === 'string' ? label : getTranslation(label, i18n)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Gutter className={baseClass}>
|
<Gutter className={baseClass}>
|
||||||
<div className={`${baseClass}__wrapper`}>
|
<div className={`${baseClass}__wrapper`}>
|
||||||
@@ -71,12 +78,7 @@ export const DocumentControls: React.FC<{
|
|||||||
{collection && !isEditing && !isAccountView && (
|
{collection && !isEditing && !isAccountView && (
|
||||||
<li className={`${baseClass}__list-item`}>
|
<li className={`${baseClass}__list-item`}>
|
||||||
<p className={`${baseClass}__value`}>
|
<p className={`${baseClass}__value`}>
|
||||||
{t('creatingNewLabel', {
|
{t('creatingNewLabel', { label: collectionLabel() })}
|
||||||
label:
|
|
||||||
typeof collection?.labels?.singular === 'string'
|
|
||||||
? collection.labels.singular
|
|
||||||
: t('document'),
|
|
||||||
})}
|
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ const Duplicate: React.FC<Props> = ({ id, slug, collection }) => {
|
|||||||
if (localeErrors.length > 0) {
|
if (localeErrors.length > 0) {
|
||||||
toast.error(
|
toast.error(
|
||||||
`
|
`
|
||||||
${t('error:localesNotSaved', { count: localeErrors.length })}
|
${t('error:localesNotSaved_other', { count: localeErrors.length })}
|
||||||
${localeErrors.join(', ')}
|
${localeErrors.join(', ')}
|
||||||
`,
|
`,
|
||||||
{ autoClose: 5000 },
|
{ autoClose: 5000 },
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const EditUpload: React.FC<{
|
|||||||
imageCacheTag?: string
|
imageCacheTag?: string
|
||||||
showCrop?: boolean
|
showCrop?: boolean
|
||||||
showFocalPoint?: boolean
|
showFocalPoint?: boolean
|
||||||
}> = ({ fileName, fileSrc, imageCacheTag, showCrop, showFocalPoint }) => {
|
}> = ({ doc, fileName, fileSrc, imageCacheTag, showCrop, showFocalPoint }) => {
|
||||||
const { closeModal } = useModal()
|
const { closeModal } = useModal()
|
||||||
const { t } = useTranslation(['general', 'upload'])
|
const { t } = useTranslation(['general', 'upload'])
|
||||||
const { formQueryParams, setFormQueryParams } = useFormQueryParams()
|
const { formQueryParams, setFormQueryParams } = useFormQueryParams()
|
||||||
@@ -45,10 +45,11 @@ export const EditUpload: React.FC<{
|
|||||||
y: uploadEdits?.crop?.y || 0,
|
y: uploadEdits?.crop?.y || 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
const [pointPosition, setPointPosition] = useState<{ x: number; y: number }>({
|
const [focalPosition, setFocalPosition] = useState<{ x: number; y: number }>({
|
||||||
x: uploadEdits?.focalPoint?.x || 50,
|
x: uploadEdits?.focalPoint?.x || doc.focalX || 50,
|
||||||
y: uploadEdits?.focalPoint?.y || 50,
|
y: uploadEdits?.focalPoint?.y || doc.focalY || 50,
|
||||||
})
|
})
|
||||||
|
|
||||||
const [checkBounds, setCheckBounds] = useState<boolean>(false)
|
const [checkBounds, setCheckBounds] = useState<boolean>(false)
|
||||||
const [originalHeight, setOriginalHeight] = useState<number>(0)
|
const [originalHeight, setOriginalHeight] = useState<number>(0)
|
||||||
const [originalWidth, setOriginalWidth] = useState<number>(0)
|
const [originalWidth, setOriginalWidth] = useState<number>(0)
|
||||||
@@ -72,10 +73,16 @@ export const EditUpload: React.FC<{
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const fineTuneFocalPoint = ({ coordinate, value }: { coordinate: 'x' | 'y'; value: string }) => {
|
const fineTuneFocalPosition = ({
|
||||||
|
coordinate,
|
||||||
|
value,
|
||||||
|
}: {
|
||||||
|
coordinate: 'x' | 'y'
|
||||||
|
value: string
|
||||||
|
}) => {
|
||||||
const intValue = parseInt(value)
|
const intValue = parseInt(value)
|
||||||
if (intValue >= 0 && intValue <= 100) {
|
if (intValue >= 0 && intValue <= 100) {
|
||||||
setPointPosition((prevPosition) => ({ ...prevPosition, [coordinate]: intValue }))
|
setFocalPosition((prevPosition) => ({ ...prevPosition, [coordinate]: intValue }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,14 +91,14 @@ export const EditUpload: React.FC<{
|
|||||||
...formQueryParams,
|
...formQueryParams,
|
||||||
uploadEdits: {
|
uploadEdits: {
|
||||||
crop: crop || undefined,
|
crop: crop || undefined,
|
||||||
focalPoint: pointPosition ? pointPosition : undefined,
|
focalPoint: focalPosition ? focalPosition : undefined,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
closeModal(editDrawerSlug)
|
closeModal(editDrawerSlug)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onDragEnd = React.useCallback(({ x, y }) => {
|
const onDragEnd = React.useCallback(({ x, y }) => {
|
||||||
setPointPosition({ x, y })
|
setFocalPosition({ x, y })
|
||||||
setCheckBounds(false)
|
setCheckBounds(false)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@@ -104,7 +111,7 @@ export const EditUpload: React.FC<{
|
|||||||
((boundsRect.left - containerRect.left + boundsRect.width / 2) / containerRect.width) * 100
|
((boundsRect.left - containerRect.left + boundsRect.width / 2) / containerRect.width) * 100
|
||||||
const yCenter =
|
const yCenter =
|
||||||
((boundsRect.top - containerRect.top + boundsRect.height / 2) / containerRect.height) * 100
|
((boundsRect.top - containerRect.top + boundsRect.height / 2) / containerRect.height) * 100
|
||||||
setPointPosition({ x: xCenter, y: yCenter })
|
setFocalPosition({ x: xCenter, y: yCenter })
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileSrcToUse = imageCacheTag ? `${fileSrc}?${imageCacheTag}` : fileSrc
|
const fileSrcToUse = imageCacheTag ? `${fileSrc}?${imageCacheTag}` : fileSrc
|
||||||
@@ -180,7 +187,7 @@ export const EditUpload: React.FC<{
|
|||||||
checkBounds={showCrop ? checkBounds : false}
|
checkBounds={showCrop ? checkBounds : false}
|
||||||
className={`${baseClass}__focalPoint`}
|
className={`${baseClass}__focalPoint`}
|
||||||
containerRef={focalWrapRef}
|
containerRef={focalWrapRef}
|
||||||
initialPosition={pointPosition}
|
initialPosition={focalPosition}
|
||||||
onDragEnd={onDragEnd}
|
onDragEnd={onDragEnd}
|
||||||
setCheckBounds={showCrop ? setCheckBounds : false}
|
setCheckBounds={showCrop ? setCheckBounds : false}
|
||||||
>
|
>
|
||||||
@@ -251,13 +258,13 @@ export const EditUpload: React.FC<{
|
|||||||
<div className={`${baseClass}__inputsWrap`}>
|
<div className={`${baseClass}__inputsWrap`}>
|
||||||
<Input
|
<Input
|
||||||
name="X %"
|
name="X %"
|
||||||
onChange={(value) => fineTuneFocalPoint({ coordinate: 'x', value })}
|
onChange={(value) => fineTuneFocalPosition({ coordinate: 'x', value })}
|
||||||
value={pointPosition.x.toFixed(0)}
|
value={focalPosition.x.toFixed(0)}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
name="Y %"
|
name="Y %"
|
||||||
onChange={(value) => fineTuneFocalPoint({ coordinate: 'y', value })}
|
onChange={(value) => fineTuneFocalPosition({ coordinate: 'y', value })}
|
||||||
value={pointPosition.y.toFixed(0)}
|
value={focalPosition.y.toFixed(0)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { fieldAffectsData } from '../../../../fields/config/types'
|
|||||||
import { getTranslation } from '../../../../utilities/getTranslation'
|
import { getTranslation } from '../../../../utilities/getTranslation'
|
||||||
import Chevron from '../../icons/Chevron'
|
import Chevron from '../../icons/Chevron'
|
||||||
import { useSearchParams } from '../../utilities/SearchParams'
|
import { useSearchParams } from '../../utilities/SearchParams'
|
||||||
import Button from '../Button'
|
|
||||||
import ColumnSelector from '../ColumnSelector'
|
import ColumnSelector from '../ColumnSelector'
|
||||||
import DeleteMany from '../DeleteMany'
|
import DeleteMany from '../DeleteMany'
|
||||||
import EditMany from '../EditMany'
|
import EditMany from '../EditMany'
|
||||||
@@ -50,6 +49,8 @@ export const ListControls: React.FC<Props> = (props) => {
|
|||||||
const params = useSearchParams()
|
const params = useSearchParams()
|
||||||
const shouldInitializeWhereOpened = validateWhereQuery(params?.where)
|
const shouldInitializeWhereOpened = validateWhereQuery(params?.where)
|
||||||
|
|
||||||
|
const hasWhereParam = React.useRef(Boolean(params?.where))
|
||||||
|
|
||||||
const [textFieldsToBeSearched, setFieldsToBeSearched] = useState(
|
const [textFieldsToBeSearched, setFieldsToBeSearched] = useState(
|
||||||
getTextFieldsToBeSearched(listSearchableFields, fields),
|
getTextFieldsToBeSearched(listSearchableFields, fields),
|
||||||
)
|
)
|
||||||
@@ -65,6 +66,15 @@ export const ListControls: React.FC<Props> = (props) => {
|
|||||||
setFieldsToBeSearched(getTextFieldsToBeSearched(listSearchableFields, fields))
|
setFieldsToBeSearched(getTextFieldsToBeSearched(listSearchableFields, fields))
|
||||||
}, [listSearchableFields, fields])
|
}, [listSearchableFields, fields])
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (hasWhereParam.current && !params?.where) {
|
||||||
|
setVisibleDrawer(undefined)
|
||||||
|
hasWhereParam.current = false
|
||||||
|
} else if (params?.where) {
|
||||||
|
hasWhereParam.current = true
|
||||||
|
}
|
||||||
|
}, [setVisibleDrawer, params?.where])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={baseClass}>
|
<div className={baseClass}>
|
||||||
<div className={`${baseClass}__wrap`}>
|
<div className={`${baseClass}__wrap`}>
|
||||||
@@ -136,7 +146,7 @@ export const ListControls: React.FC<Props> = (props) => {
|
|||||||
height={visibleDrawer === 'columns' ? 'auto' : 0}
|
height={visibleDrawer === 'columns' ? 'auto' : 0}
|
||||||
id={`${baseClass}-columns`}
|
id={`${baseClass}-columns`}
|
||||||
>
|
>
|
||||||
<ColumnSelector collection={collection} />
|
<ColumnSelector slug={collection.slug} />
|
||||||
</AnimateHeight>
|
</AnimateHeight>
|
||||||
)}
|
)}
|
||||||
<AnimateHeight
|
<AnimateHeight
|
||||||
@@ -147,6 +157,7 @@ export const ListControls: React.FC<Props> = (props) => {
|
|||||||
<WhereBuilder
|
<WhereBuilder
|
||||||
collection={collection}
|
collection={collection}
|
||||||
handleChange={handleWhereChange}
|
handleChange={handleWhereChange}
|
||||||
|
key={String(hasWhereParam.current && !params?.where)}
|
||||||
modifySearchQuery={modifySearchQuery}
|
modifySearchQuery={modifySearchQuery}
|
||||||
/>
|
/>
|
||||||
</AnimateHeight>
|
</AnimateHeight>
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ export const LoadingOverlay: React.FC<Props> = ({
|
|||||||
show = true,
|
show = true,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation('general')
|
const { t } = useTranslation('general')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={[
|
className={[
|
||||||
@@ -59,25 +58,25 @@ type UseLoadingOverlayToggleT = {
|
|||||||
}
|
}
|
||||||
export const LoadingOverlayToggle: React.FC<UseLoadingOverlayToggleT> = ({
|
export const LoadingOverlayToggle: React.FC<UseLoadingOverlayToggleT> = ({
|
||||||
name: key,
|
name: key,
|
||||||
|
type = 'fullscreen',
|
||||||
loadingText,
|
loadingText,
|
||||||
show,
|
show,
|
||||||
type = 'fullscreen',
|
|
||||||
}) => {
|
}) => {
|
||||||
const { toggleLoadingOverlay } = useLoadingOverlay()
|
const { toggleLoadingOverlay } = useLoadingOverlay()
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
toggleLoadingOverlay({
|
toggleLoadingOverlay({
|
||||||
|
type,
|
||||||
isLoading: show,
|
isLoading: show,
|
||||||
key,
|
key,
|
||||||
loadingText: loadingText || undefined,
|
loadingText: loadingText || undefined,
|
||||||
type,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
toggleLoadingOverlay({
|
toggleLoadingOverlay({
|
||||||
|
type,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
key,
|
key,
|
||||||
type,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [show, toggleLoadingOverlay, key, type, loadingText])
|
}, [show, toggleLoadingOverlay, key, type, loadingText])
|
||||||
@@ -94,10 +93,10 @@ type FormLoadingOverlayToggleT = {
|
|||||||
}
|
}
|
||||||
export const FormLoadingOverlayToggle: React.FC<FormLoadingOverlayToggleT> = ({
|
export const FormLoadingOverlayToggle: React.FC<FormLoadingOverlayToggleT> = ({
|
||||||
name,
|
name,
|
||||||
|
type = 'fullscreen',
|
||||||
action,
|
action,
|
||||||
formIsLoading = false,
|
formIsLoading = false,
|
||||||
loadingSuffix,
|
loadingSuffix,
|
||||||
type = 'fullscreen',
|
|
||||||
}) => {
|
}) => {
|
||||||
const isProcessing = useFormProcessing()
|
const isProcessing = useFormProcessing()
|
||||||
const { i18n, t } = useTranslation('general')
|
const { i18n, t } = useTranslation('general')
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import './index.scss'
|
|||||||
const baseClass = 'publish-many'
|
const baseClass = 'publish-many'
|
||||||
|
|
||||||
const PublishMany: React.FC<Props> = (props) => {
|
const PublishMany: React.FC<Props> = (props) => {
|
||||||
const { collection: { labels: { plural }, slug, versions } = {}, resetParams } = props
|
const { collection: { slug, labels: { plural }, versions } = {}, resetParams } = props
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routes: { api },
|
routes: { api },
|
||||||
@@ -27,7 +27,7 @@ const PublishMany: React.FC<Props> = (props) => {
|
|||||||
const { permissions } = useAuth()
|
const { permissions } = useAuth()
|
||||||
const { toggleModal } = useModal()
|
const { toggleModal } = useModal()
|
||||||
const { i18n, t } = useTranslation('version')
|
const { i18n, t } = useTranslation('version')
|
||||||
const { count, getQueryParams, selectAll } = useSelection()
|
const { getQueryParams, selectAll } = useSelection()
|
||||||
const [submitted, setSubmitted] = useState(false)
|
const [submitted, setSubmitted] = useState(false)
|
||||||
|
|
||||||
const collectionPermissions = permissions?.collections?.[slug]
|
const collectionPermissions = permissions?.collections?.[slug]
|
||||||
@@ -41,9 +41,11 @@ const PublishMany: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
const handlePublish = useCallback(() => {
|
const handlePublish = useCallback(() => {
|
||||||
setSubmitted(true)
|
setSubmitted(true)
|
||||||
requests
|
void requests
|
||||||
.patch(
|
.patch(
|
||||||
`${serverURL}${api}/${slug}${getQueryParams({ _status: { not_equals: 'published' } })}`,
|
`${serverURL}${api}/${slug}${getQueryParams({
|
||||||
|
_status: { not_equals: 'published' },
|
||||||
|
})}&draft=true`,
|
||||||
{
|
{
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
_status: 'published',
|
_status: 'published',
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ const SelectAdapter: React.FC<ReactSelectAdapterProps> = (props) => {
|
|||||||
isCreatable,
|
isCreatable,
|
||||||
isLoading,
|
isLoading,
|
||||||
isSearchable = true,
|
isSearchable = true,
|
||||||
noOptionsMessage,
|
noOptionsMessage = () => t('general:noOptions'),
|
||||||
numberOnly = false,
|
numberOnly = false,
|
||||||
onChange,
|
onChange,
|
||||||
onMenuOpen,
|
onMenuOpen,
|
||||||
@@ -50,6 +50,8 @@ const SelectAdapter: React.FC<ReactSelectAdapterProps> = (props) => {
|
|||||||
value,
|
value,
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
|
const loadingMessage = () => t('general:loading') + '...'
|
||||||
|
|
||||||
const classes = [className, 'react-select', showError && 'react-select--error']
|
const classes = [className, 'react-select', showError && 'react-select--error']
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(' ')
|
.join(' ')
|
||||||
@@ -79,6 +81,7 @@ const SelectAdapter: React.FC<ReactSelectAdapterProps> = (props) => {
|
|||||||
isClearable={isClearable}
|
isClearable={isClearable}
|
||||||
isDisabled={disabled}
|
isDisabled={disabled}
|
||||||
isSearchable={isSearchable}
|
isSearchable={isSearchable}
|
||||||
|
loadingMessage={loadingMessage}
|
||||||
menuPlacement="auto"
|
menuPlacement="auto"
|
||||||
noOptionsMessage={noOptionsMessage}
|
noOptionsMessage={noOptionsMessage}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
@@ -148,6 +151,7 @@ const SelectAdapter: React.FC<ReactSelectAdapterProps> = (props) => {
|
|||||||
isClearable={isClearable}
|
isClearable={isClearable}
|
||||||
isDisabled={disabled}
|
isDisabled={disabled}
|
||||||
isSearchable={isSearchable}
|
isSearchable={isSearchable}
|
||||||
|
loadingMessage={loadingMessage}
|
||||||
menuPlacement="auto"
|
menuPlacement="auto"
|
||||||
noOptionsMessage={noOptionsMessage}
|
noOptionsMessage={noOptionsMessage}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
.step-nav {
|
.step-nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
gap: calc(var(--base) / 2);
|
gap: calc(var(--base) / 2);
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
@@ -56,8 +57,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
max-width: base(8);
|
max-width: base(8);
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ import React, {
|
|||||||
} from 'react'
|
} from 'react'
|
||||||
|
|
||||||
import type { SanitizedCollectionConfig } from '../../../../collections/config/types'
|
import type { SanitizedCollectionConfig } from '../../../../collections/config/types'
|
||||||
import type { Field } from '../../../../fields/config/types'
|
|
||||||
import type { Props as CellProps } from '../../views/collections/List/Cell/types'
|
import type { Props as CellProps } from '../../views/collections/List/Cell/types'
|
||||||
import type { ListPreferences } from '../../views/collections/List/types'
|
import type { ListPreferences } from '../../views/collections/List/types'
|
||||||
import type { Column } from '../Table/types'
|
import type { Column } from '../Table/types'
|
||||||
import type { Action } from './columnReducer'
|
import type { Action } from './columnReducer'
|
||||||
|
|
||||||
|
import { type Field, fieldHasSubFields } from '../../../../fields/config/types'
|
||||||
import { usePreferences } from '../../utilities/Preferences'
|
import { usePreferences } from '../../utilities/Preferences'
|
||||||
import formatFields from '../../views/collections/List/formatFields'
|
import formatFields from '../../views/collections/List/formatFields'
|
||||||
import buildColumns from './buildColumns'
|
import buildColumns from './buildColumns'
|
||||||
@@ -33,6 +33,19 @@ export const TableColumnContext = createContext<ITableColumns>({} as ITableColum
|
|||||||
|
|
||||||
export const useTableColumns = (): ITableColumns => useContext(TableColumnContext)
|
export const useTableColumns = (): ITableColumns => useContext(TableColumnContext)
|
||||||
|
|
||||||
|
const filterTableFields = (fields: Field[]): Field[] => {
|
||||||
|
return fields.reduce((acc, field) => {
|
||||||
|
if (fieldHasSubFields(field)) {
|
||||||
|
field = {
|
||||||
|
...field,
|
||||||
|
fields: filterTableFields(field.fields),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!field.admin?.disableListColumn) acc.push(field)
|
||||||
|
return acc
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
||||||
export const TableColumnsProvider: React.FC<{
|
export const TableColumnsProvider: React.FC<{
|
||||||
cellProps?: Partial<CellProps>[]
|
cellProps?: Partial<CellProps>[]
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
@@ -50,9 +63,10 @@ export const TableColumnsProvider: React.FC<{
|
|||||||
const hasInitialized = useRef(false)
|
const hasInitialized = useRef(false)
|
||||||
const { getPreference, setPreference } = usePreferences()
|
const { getPreference, setPreference } = usePreferences()
|
||||||
const [formattedFields] = useState<Field[]>(() => formatFields(collection))
|
const [formattedFields] = useState<Field[]>(() => formatFields(collection))
|
||||||
|
const filteredFields = filterTableFields(formattedFields)
|
||||||
|
|
||||||
const [tableColumns, dispatchTableColumns] = useReducer(columnReducer, {}, () => {
|
const [tableColumns, dispatchTableColumns] = useReducer(columnReducer, {}, () => {
|
||||||
const initialColumns = getInitialColumnState(formattedFields, useAsTitle, defaultColumns)
|
const initialColumns = getInitialColumnState(filteredFields, useAsTitle, defaultColumns)
|
||||||
|
|
||||||
return buildColumns({
|
return buildColumns({
|
||||||
cellProps,
|
cellProps,
|
||||||
@@ -77,13 +91,14 @@ export const TableColumnsProvider: React.FC<{
|
|||||||
|
|
||||||
const currentPreferences = await getPreference<ListPreferences>(preferenceKey)
|
const currentPreferences = await getPreference<ListPreferences>(preferenceKey)
|
||||||
prevCollection.current = collection.slug
|
prevCollection.current = collection.slug
|
||||||
const initialColumns = getInitialColumnState(formattedFields, useAsTitle, defaultColumns)
|
const initialColumns = getInitialColumnState(filteredFields, useAsTitle, defaultColumns)
|
||||||
const newCols = currentPreferences?.columns || initialColumns
|
const newCols = currentPreferences?.columns || initialColumns
|
||||||
|
|
||||||
dispatchTableColumns({
|
dispatchTableColumns({
|
||||||
|
type: 'set',
|
||||||
payload: {
|
payload: {
|
||||||
cellProps,
|
cellProps,
|
||||||
collection: { ...collection, fields: formatFields(collection) },
|
collection: { ...collection, fields: filteredFields },
|
||||||
columns: newCols.map((column) => {
|
columns: newCols.map((column) => {
|
||||||
// 'string' is for backwards compatibility
|
// 'string' is for backwards compatibility
|
||||||
// the preference used to be stored as an array of strings
|
// the preference used to be stored as an array of strings
|
||||||
@@ -96,14 +111,13 @@ export const TableColumnsProvider: React.FC<{
|
|||||||
return column
|
return column
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
type: 'set',
|
|
||||||
})
|
})
|
||||||
|
|
||||||
hasInitialized.current = true
|
hasInitialized.current = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sync()
|
void sync()
|
||||||
}, [
|
}, [
|
||||||
preferenceKey,
|
preferenceKey,
|
||||||
setPreference,
|
setPreference,
|
||||||
@@ -113,7 +127,7 @@ export const TableColumnsProvider: React.FC<{
|
|||||||
defaultColumns,
|
defaultColumns,
|
||||||
collection,
|
collection,
|
||||||
cellProps,
|
cellProps,
|
||||||
formattedFields,
|
filteredFields,
|
||||||
])
|
])
|
||||||
|
|
||||||
// /////////////////////////////////////
|
// /////////////////////////////////////
|
||||||
@@ -133,6 +147,7 @@ export const TableColumnsProvider: React.FC<{
|
|||||||
const setActiveColumns = useCallback(
|
const setActiveColumns = useCallback(
|
||||||
(columns: string[]) => {
|
(columns: string[]) => {
|
||||||
dispatchTableColumns({
|
dispatchTableColumns({
|
||||||
|
type: 'set',
|
||||||
payload: {
|
payload: {
|
||||||
// onSelect,
|
// onSelect,
|
||||||
cellProps,
|
cellProps,
|
||||||
@@ -142,7 +157,6 @@ export const TableColumnsProvider: React.FC<{
|
|||||||
active: true,
|
active: true,
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
type: 'set',
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
[collection, cellProps],
|
[collection, cellProps],
|
||||||
@@ -153,13 +167,13 @@ export const TableColumnsProvider: React.FC<{
|
|||||||
const { fromIndex, toIndex } = args
|
const { fromIndex, toIndex } = args
|
||||||
|
|
||||||
dispatchTableColumns({
|
dispatchTableColumns({
|
||||||
|
type: 'move',
|
||||||
payload: {
|
payload: {
|
||||||
cellProps,
|
cellProps,
|
||||||
collection: { ...collection, fields: formatFields(collection) },
|
collection: { ...collection, fields: formatFields(collection) },
|
||||||
fromIndex,
|
fromIndex,
|
||||||
toIndex,
|
toIndex,
|
||||||
},
|
},
|
||||||
type: 'move',
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
[collection, cellProps],
|
[collection, cellProps],
|
||||||
@@ -168,12 +182,12 @@ export const TableColumnsProvider: React.FC<{
|
|||||||
const toggleColumn = useCallback(
|
const toggleColumn = useCallback(
|
||||||
(column: string) => {
|
(column: string) => {
|
||||||
dispatchTableColumns({
|
dispatchTableColumns({
|
||||||
|
type: 'toggle',
|
||||||
payload: {
|
payload: {
|
||||||
cellProps,
|
cellProps,
|
||||||
collection: { ...collection, fields: formatFields(collection) },
|
collection: { ...collection, fields: formatFields(collection) },
|
||||||
column,
|
column,
|
||||||
},
|
},
|
||||||
type: 'toggle',
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
[collection, cellProps],
|
[collection, cellProps],
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import type { Where } from 'payload/types'
|
||||||
|
|
||||||
|
import qs from 'qs'
|
||||||
import React, { useCallback, useEffect, useReducer, useState } from 'react'
|
import React, { useCallback, useEffect, useReducer, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
@@ -6,6 +9,7 @@ import type { Option } from '../../../ReactSelect/types'
|
|||||||
import type { GetResults, Props, ValueWithRelation } from './types'
|
import type { GetResults, Props, ValueWithRelation } from './types'
|
||||||
|
|
||||||
import useDebounce from '../../../../../hooks/useDebounce'
|
import useDebounce from '../../../../../hooks/useDebounce'
|
||||||
|
import { useAuth } from '../../../../utilities/Auth'
|
||||||
import { useConfig } from '../../../../utilities/Config'
|
import { useConfig } from '../../../../utilities/Config'
|
||||||
import ReactSelect from '../../../ReactSelect'
|
import ReactSelect from '../../../ReactSelect'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
@@ -16,7 +20,15 @@ const baseClass = 'condition-value-relationship'
|
|||||||
const maxResultsPerRequest = 10
|
const maxResultsPerRequest = 10
|
||||||
|
|
||||||
const RelationshipField: React.FC<Props> = (props) => {
|
const RelationshipField: React.FC<Props> = (props) => {
|
||||||
const { admin: { isSortable } = {}, disabled, hasMany, onChange, relationTo, value } = props
|
const {
|
||||||
|
admin: { isSortable } = {},
|
||||||
|
disabled,
|
||||||
|
filterOptions,
|
||||||
|
hasMany,
|
||||||
|
onChange,
|
||||||
|
relationTo,
|
||||||
|
value,
|
||||||
|
} = props
|
||||||
|
|
||||||
const {
|
const {
|
||||||
collections,
|
collections,
|
||||||
@@ -33,11 +45,12 @@ const RelationshipField: React.FC<Props> = (props) => {
|
|||||||
const [hasLoadedFirstOptions, setHasLoadedFirstOptions] = useState(false)
|
const [hasLoadedFirstOptions, setHasLoadedFirstOptions] = useState(false)
|
||||||
const debouncedSearch = useDebounce(search, 300)
|
const debouncedSearch = useDebounce(search, 300)
|
||||||
const { i18n, t } = useTranslation('general')
|
const { i18n, t } = useTranslation('general')
|
||||||
|
const { user } = useAuth()
|
||||||
|
|
||||||
const addOptions = useCallback(
|
const addOptions = useCallback(
|
||||||
(data, relation) => {
|
(data, relation) => {
|
||||||
const collection = collections.find((coll) => coll.slug === relation)
|
const collection = collections.find((coll) => coll.slug === relation)
|
||||||
dispatchOptions({ collection, data, hasMultipleRelations, i18n, relation, type: 'ADD' })
|
dispatchOptions({ type: 'ADD', collection, data, hasMultipleRelations, i18n, relation })
|
||||||
},
|
},
|
||||||
[collections, hasMultipleRelations, i18n],
|
[collections, hasMultipleRelations, i18n],
|
||||||
)
|
)
|
||||||
@@ -61,23 +74,66 @@ const RelationshipField: React.FC<Props> = (props) => {
|
|||||||
let resultsFetched = 0
|
let resultsFetched = 0
|
||||||
|
|
||||||
if (!errorLoading) {
|
if (!errorLoading) {
|
||||||
relationsToFetch.reduce(async (priorRelation, relation) => {
|
void relationsToFetch.reduce(async (priorRelation, relation) => {
|
||||||
await priorRelation
|
await priorRelation
|
||||||
|
|
||||||
if (resultsFetched < 10) {
|
if (resultsFetched < 10) {
|
||||||
|
const search: Record<string, unknown> & { where: Where } = {
|
||||||
|
depth: 0,
|
||||||
|
limit: maxResultsPerRequest,
|
||||||
|
page: lastLoadedPageToUse,
|
||||||
|
where: { and: [] },
|
||||||
|
}
|
||||||
const collection = collections.find((coll) => coll.slug === relation)
|
const collection = collections.find((coll) => coll.slug === relation)
|
||||||
const fieldToSearch = collection?.admin?.useAsTitle || 'id'
|
const fieldToSearch = collection?.admin?.useAsTitle || 'id'
|
||||||
const searchParam = searchArg ? `&where[${fieldToSearch}][like]=${searchArg}` : ''
|
// add search arg to where object
|
||||||
|
if (searchArg) {
|
||||||
const response = await fetch(
|
search.where.and.push({
|
||||||
`${serverURL}${api}/${relation}?limit=${maxResultsPerRequest}&page=${lastLoadedPageToUse}&depth=0${searchParam}`,
|
[fieldToSearch]: {
|
||||||
{
|
like: searchArg,
|
||||||
credentials: 'include',
|
|
||||||
headers: {
|
|
||||||
'Accept-Language': i18n.language,
|
|
||||||
},
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// call the filterOptions function if it exists passing in the collection
|
||||||
|
if (filterOptions) {
|
||||||
|
const optionFilter =
|
||||||
|
typeof filterOptions === 'function'
|
||||||
|
? await filterOptions({
|
||||||
|
// data and siblingData are empty since we cannot fetch with the values covering the
|
||||||
|
// entire list this limitation means that filterOptions functions using a document's
|
||||||
|
// data are unsupported in the whereBuilder
|
||||||
|
id: undefined,
|
||||||
|
data: {},
|
||||||
|
relationTo: collection.slug,
|
||||||
|
siblingData: {},
|
||||||
|
user,
|
||||||
|
})
|
||||||
|
: filterOptions
|
||||||
|
if (typeof optionFilter === 'object') {
|
||||||
|
search.where.and.push(optionFilter)
|
||||||
|
}
|
||||||
|
if (optionFilter === false) {
|
||||||
|
// no options will be returned
|
||||||
|
setLastFullyLoadedRelation(relations.indexOf(relation))
|
||||||
|
|
||||||
|
// If there are more relations to search, need to reset lastLoadedPage to 1
|
||||||
|
// both locally within function and state
|
||||||
|
if (relations.indexOf(relation) + 1 < relations.length) {
|
||||||
|
lastLoadedPageToUse = 1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search.where.and.length === 0) {
|
||||||
|
delete search.where
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`${serverURL}${api}/${relation}?${qs.stringify(search)}`, {
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
'Accept-Language': i18n.language,
|
||||||
},
|
},
|
||||||
)
|
})
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data: PaginatedDocs = await response.json()
|
const data: PaginatedDocs = await response.json()
|
||||||
@@ -103,7 +159,18 @@ const RelationshipField: React.FC<Props> = (props) => {
|
|||||||
}, Promise.resolve())
|
}, Promise.resolve())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[i18n, relationTo, errorLoading, collections, serverURL, api, addOptions, t],
|
[
|
||||||
|
relationTo,
|
||||||
|
errorLoading,
|
||||||
|
collections,
|
||||||
|
filterOptions,
|
||||||
|
serverURL,
|
||||||
|
api,
|
||||||
|
i18n.language,
|
||||||
|
user,
|
||||||
|
addOptions,
|
||||||
|
t,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
const findOptionsByValue = useCallback((): Option | Option[] => {
|
const findOptionsByValue = useCallback((): Option | Option[] => {
|
||||||
|
|||||||
@@ -52,9 +52,9 @@ const Condition: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch({
|
dispatch({
|
||||||
|
type: 'update',
|
||||||
andIndex,
|
andIndex,
|
||||||
orIndex,
|
orIndex,
|
||||||
type: 'update',
|
|
||||||
value: debouncedValue || '',
|
value: debouncedValue || '',
|
||||||
})
|
})
|
||||||
}, [debouncedValue, dispatch, orIndex, andIndex])
|
}, [debouncedValue, dispatch, orIndex, andIndex])
|
||||||
@@ -80,10 +80,10 @@ const Condition: React.FC<Props> = (props) => {
|
|||||||
isClearable={false}
|
isClearable={false}
|
||||||
onChange={(field) => {
|
onChange={(field) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
andIndex: andIndex,
|
|
||||||
field: field?.value,
|
|
||||||
orIndex: orIndex,
|
|
||||||
type: 'update',
|
type: 'update',
|
||||||
|
andIndex,
|
||||||
|
field: field?.value,
|
||||||
|
orIndex,
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
options={fields}
|
options={fields}
|
||||||
@@ -96,10 +96,10 @@ const Condition: React.FC<Props> = (props) => {
|
|||||||
isClearable={false}
|
isClearable={false}
|
||||||
onChange={(operator) => {
|
onChange={(operator) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
|
type: 'update',
|
||||||
andIndex,
|
andIndex,
|
||||||
operator: operator.value,
|
operator: operator.value,
|
||||||
orIndex,
|
orIndex,
|
||||||
type: 'update',
|
|
||||||
})
|
})
|
||||||
setInternalOperatorField(operator.value)
|
setInternalOperatorField(operator.value)
|
||||||
}}
|
}}
|
||||||
@@ -134,9 +134,9 @@ const Condition: React.FC<Props> = (props) => {
|
|||||||
iconStyle="with-border"
|
iconStyle="with-border"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
dispatch({
|
dispatch({
|
||||||
|
type: 'remove',
|
||||||
andIndex,
|
andIndex,
|
||||||
orIndex,
|
orIndex,
|
||||||
type: 'remove',
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
round
|
round
|
||||||
@@ -148,11 +148,11 @@ const Condition: React.FC<Props> = (props) => {
|
|||||||
iconStyle="with-border"
|
iconStyle="with-border"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
dispatch({
|
dispatch({
|
||||||
|
type: 'add',
|
||||||
andIndex: andIndex + 1,
|
andIndex: andIndex + 1,
|
||||||
field: fields[0].value,
|
field: fields[0].value,
|
||||||
orIndex,
|
orIndex,
|
||||||
relation: 'and',
|
relation: 'and',
|
||||||
type: 'add',
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
round
|
round
|
||||||
|
|||||||
@@ -77,6 +77,15 @@ const contains = {
|
|||||||
value: 'contains',
|
value: 'contains',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const filterOperators = (operators, hasMany = false) => {
|
||||||
|
if (hasMany) {
|
||||||
|
return operators.filter(
|
||||||
|
(operator) => operator.value !== 'equals' && operator.value !== 'not_equals',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return operators
|
||||||
|
}
|
||||||
|
|
||||||
const fieldTypeConditions = {
|
const fieldTypeConditions = {
|
||||||
checkbox: {
|
checkbox: {
|
||||||
component: 'Text',
|
component: 'Text',
|
||||||
@@ -100,7 +109,7 @@ const fieldTypeConditions = {
|
|||||||
},
|
},
|
||||||
number: {
|
number: {
|
||||||
component: 'Number',
|
component: 'Number',
|
||||||
operators: [...base, ...numeric],
|
operators: (hasMany) => filterOperators([...base, ...numeric], hasMany),
|
||||||
},
|
},
|
||||||
point: {
|
point: {
|
||||||
component: 'Point',
|
component: 'Point',
|
||||||
@@ -120,11 +129,11 @@ const fieldTypeConditions = {
|
|||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
component: 'Select',
|
component: 'Select',
|
||||||
operators: [...base],
|
operators: (hasMany) => filterOperators([...base], hasMany),
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
component: 'Text',
|
component: 'Text',
|
||||||
operators: [...base, like, contains],
|
operators: (hasMany) => filterOperators([...base, like, contains], hasMany),
|
||||||
},
|
},
|
||||||
textarea: {
|
textarea: {
|
||||||
component: 'Text',
|
component: 'Text',
|
||||||
|
|||||||
@@ -22,9 +22,19 @@ const baseClass = 'where-builder'
|
|||||||
|
|
||||||
const reduceFields = (fields, i18n) =>
|
const reduceFields = (fields, i18n) =>
|
||||||
flattenTopLevelFields(fields).reduce((reduced, field) => {
|
flattenTopLevelFields(fields).reduce((reduced, field) => {
|
||||||
|
let operators = []
|
||||||
|
|
||||||
if (typeof fieldTypes[field.type] === 'object') {
|
if (typeof fieldTypes[field.type] === 'object') {
|
||||||
|
if (typeof fieldTypes[field.type].operators === 'function') {
|
||||||
|
operators = fieldTypes[field.type].operators(
|
||||||
|
'hasMany' in field && field.hasMany ? true : false,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
operators = fieldTypes[field.type].operators
|
||||||
|
}
|
||||||
|
|
||||||
const operatorKeys = new Set()
|
const operatorKeys = new Set()
|
||||||
const operators = fieldTypes[field.type].operators.reduce((acc, operator) => {
|
const filteredOperators = operators.reduce((acc, operator) => {
|
||||||
if (!operatorKeys.has(operator.value)) {
|
if (!operatorKeys.has(operator.value)) {
|
||||||
operatorKeys.add(operator.value)
|
operatorKeys.add(operator.value)
|
||||||
return [
|
return [
|
||||||
@@ -42,12 +52,14 @@ const reduceFields = (fields, i18n) =>
|
|||||||
label: getTranslation(field.label || field.name, i18n),
|
label: getTranslation(field.label || field.name, i18n),
|
||||||
value: field.name,
|
value: field.name,
|
||||||
...fieldTypes[field.type],
|
...fieldTypes[field.type],
|
||||||
operators,
|
operators: filteredOperators,
|
||||||
props: {
|
props: {
|
||||||
...field,
|
...field,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (field.admin?.disableListFilter) return reduced
|
||||||
|
|
||||||
return [...reduced, formattedField]
|
return [...reduced, formattedField]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +197,7 @@ const WhereBuilder: React.FC<Props> = (props) => {
|
|||||||
iconStyle="with-border"
|
iconStyle="with-border"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (reducedFields.length > 0)
|
if (reducedFields.length > 0)
|
||||||
dispatchConditions({ field: reducedFields[0].value, type: 'add' })
|
dispatchConditions({ type: 'add', field: reducedFields[0].value })
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('or')}
|
{t('or')}
|
||||||
@@ -203,7 +215,7 @@ const WhereBuilder: React.FC<Props> = (props) => {
|
|||||||
iconStyle="with-border"
|
iconStyle="with-border"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (reducedFields.length > 0)
|
if (reducedFields.length > 0)
|
||||||
dispatchConditions({ field: reducedFields[0].value, type: 'add' })
|
dispatchConditions({ type: 'add', field: reducedFields[0].value })
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('addFilter')}
|
{t('addFilter')}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ type ArrayRowProps = UseDraggableSortableReturn &
|
|||||||
duplicateRow: (rowIndex: number) => void
|
duplicateRow: (rowIndex: number) => void
|
||||||
forceRender?: boolean
|
forceRender?: boolean
|
||||||
hasMaxRows?: boolean
|
hasMaxRows?: boolean
|
||||||
|
isSortable: boolean
|
||||||
moveRow: (fromIndex: number, toIndex: number) => void
|
moveRow: (fromIndex: number, toIndex: number) => void
|
||||||
readOnly?: boolean
|
readOnly?: boolean
|
||||||
removeRow: (rowIndex: number) => void
|
removeRow: (rowIndex: number) => void
|
||||||
@@ -44,6 +45,7 @@ export const ArrayRow: React.FC<ArrayRowProps> = ({
|
|||||||
forceRender = false,
|
forceRender = false,
|
||||||
hasMaxRows,
|
hasMaxRows,
|
||||||
indexPath,
|
indexPath,
|
||||||
|
isSortable,
|
||||||
labels,
|
labels,
|
||||||
listeners,
|
listeners,
|
||||||
moveRow,
|
moveRow,
|
||||||
@@ -94,6 +96,7 @@ export const ArrayRow: React.FC<ArrayRowProps> = ({
|
|||||||
duplicateRow={duplicateRow}
|
duplicateRow={duplicateRow}
|
||||||
hasMaxRows={hasMaxRows}
|
hasMaxRows={hasMaxRows}
|
||||||
index={rowIndex}
|
index={rowIndex}
|
||||||
|
isSortable={isSortable}
|
||||||
moveRow={moveRow}
|
moveRow={moveRow}
|
||||||
removeRow={removeRow}
|
removeRow={removeRow}
|
||||||
rowCount={rowCount}
|
rowCount={rowCount}
|
||||||
@@ -103,11 +106,15 @@ export const ArrayRow: React.FC<ArrayRowProps> = ({
|
|||||||
className={classNames}
|
className={classNames}
|
||||||
collapsed={row.collapsed}
|
collapsed={row.collapsed}
|
||||||
collapsibleStyle={fieldHasErrors ? 'error' : 'default'}
|
collapsibleStyle={fieldHasErrors ? 'error' : 'default'}
|
||||||
dragHandleProps={{
|
dragHandleProps={
|
||||||
id: row.id,
|
isSortable
|
||||||
attributes,
|
? {
|
||||||
listeners,
|
id: row.id,
|
||||||
}}
|
attributes,
|
||||||
|
listeners,
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
header={
|
header={
|
||||||
<div className={`${baseClass}__row-header`}>
|
<div className={`${baseClass}__row-header`}>
|
||||||
<RowLabel
|
<RowLabel
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const baseClass = 'array-field'
|
|||||||
const ArrayFieldType: React.FC<Props> = (props) => {
|
const ArrayFieldType: React.FC<Props> = (props) => {
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
admin: { className, components, condition, description, readOnly },
|
admin: { className, components, condition, description, isSortable = true, readOnly },
|
||||||
fieldTypes,
|
fieldTypes,
|
||||||
fields,
|
fields,
|
||||||
forceRender = false,
|
forceRender = false,
|
||||||
@@ -113,7 +113,7 @@ const ArrayFieldType: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
const duplicateRow = useCallback(
|
const duplicateRow = useCallback(
|
||||||
(rowIndex: number) => {
|
(rowIndex: number) => {
|
||||||
dispatchFields({ path, rowIndex, type: 'DUPLICATE_ROW' })
|
dispatchFields({ type: 'DUPLICATE_ROW', path, rowIndex })
|
||||||
setModified(true)
|
setModified(true)
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -133,7 +133,7 @@ const ArrayFieldType: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
const moveRow = useCallback(
|
const moveRow = useCallback(
|
||||||
(moveFromIndex: number, moveToIndex: number) => {
|
(moveFromIndex: number, moveToIndex: number) => {
|
||||||
dispatchFields({ moveFromIndex, moveToIndex, path, type: 'MOVE_ROW' })
|
dispatchFields({ type: 'MOVE_ROW', moveFromIndex, moveToIndex, path })
|
||||||
setModified(true)
|
setModified(true)
|
||||||
},
|
},
|
||||||
[dispatchFields, path, setModified],
|
[dispatchFields, path, setModified],
|
||||||
@@ -141,14 +141,14 @@ const ArrayFieldType: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
const toggleCollapseAll = useCallback(
|
const toggleCollapseAll = useCallback(
|
||||||
(collapsed: boolean) => {
|
(collapsed: boolean) => {
|
||||||
dispatchFields({ collapsed, path, setDocFieldPreferences, type: 'SET_ALL_ROWS_COLLAPSED' })
|
dispatchFields({ type: 'SET_ALL_ROWS_COLLAPSED', collapsed, path, setDocFieldPreferences })
|
||||||
},
|
},
|
||||||
[dispatchFields, path, setDocFieldPreferences],
|
[dispatchFields, path, setDocFieldPreferences],
|
||||||
)
|
)
|
||||||
|
|
||||||
const setCollapse = useCallback(
|
const setCollapse = useCallback(
|
||||||
(rowID: string, collapsed: boolean) => {
|
(rowID: string, collapsed: boolean) => {
|
||||||
dispatchFields({ collapsed, path, rowID, setDocFieldPreferences, type: 'SET_ROW_COLLAPSED' })
|
dispatchFields({ type: 'SET_ROW_COLLAPSED', collapsed, path, rowID, setDocFieldPreferences })
|
||||||
},
|
},
|
||||||
[dispatchFields, path, setDocFieldPreferences],
|
[dispatchFields, path, setDocFieldPreferences],
|
||||||
)
|
)
|
||||||
@@ -227,7 +227,7 @@ const ArrayFieldType: React.FC<Props> = (props) => {
|
|||||||
onDragEnd={({ moveFromIndex, moveToIndex }) => moveRow(moveFromIndex, moveToIndex)}
|
onDragEnd={({ moveFromIndex, moveToIndex }) => moveRow(moveFromIndex, moveToIndex)}
|
||||||
>
|
>
|
||||||
{rows.map((row, i) => (
|
{rows.map((row, i) => (
|
||||||
<DraggableSortableItem disabled={readOnly} id={row.id} key={row.id}>
|
<DraggableSortableItem disabled={readOnly || !isSortable} id={row.id} key={row.id}>
|
||||||
{(draggableSortableItemProps) => (
|
{(draggableSortableItemProps) => (
|
||||||
<ArrayRow
|
<ArrayRow
|
||||||
{...draggableSortableItemProps}
|
{...draggableSortableItemProps}
|
||||||
@@ -239,6 +239,7 @@ const ArrayFieldType: React.FC<Props> = (props) => {
|
|||||||
forceRender={forceRender}
|
forceRender={forceRender}
|
||||||
hasMaxRows={hasMaxRows}
|
hasMaxRows={hasMaxRows}
|
||||||
indexPath={indexPath}
|
indexPath={indexPath}
|
||||||
|
isSortable={isSortable}
|
||||||
labels={labels}
|
labels={labels}
|
||||||
moveRow={moveRow}
|
moveRow={moveRow}
|
||||||
path={path}
|
path={path}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ type BlockFieldProps = UseDraggableSortableReturn &
|
|||||||
duplicateRow: (rowIndex: number) => void
|
duplicateRow: (rowIndex: number) => void
|
||||||
forceRender?: boolean
|
forceRender?: boolean
|
||||||
hasMaxRows?: boolean
|
hasMaxRows?: boolean
|
||||||
|
isSortable?: boolean
|
||||||
moveRow: (fromIndex: number, toIndex: number) => void
|
moveRow: (fromIndex: number, toIndex: number) => void
|
||||||
readOnly: boolean
|
readOnly: boolean
|
||||||
removeRow: (rowIndex: number) => void
|
removeRow: (rowIndex: number) => void
|
||||||
@@ -44,6 +45,7 @@ export const BlockRow: React.FC<BlockFieldProps> = ({
|
|||||||
forceRender,
|
forceRender,
|
||||||
hasMaxRows,
|
hasMaxRows,
|
||||||
indexPath,
|
indexPath,
|
||||||
|
isSortable,
|
||||||
labels,
|
labels,
|
||||||
listeners,
|
listeners,
|
||||||
moveRow,
|
moveRow,
|
||||||
@@ -90,6 +92,7 @@ export const BlockRow: React.FC<BlockFieldProps> = ({
|
|||||||
blocks={blocks}
|
blocks={blocks}
|
||||||
duplicateRow={duplicateRow}
|
duplicateRow={duplicateRow}
|
||||||
hasMaxRows={hasMaxRows}
|
hasMaxRows={hasMaxRows}
|
||||||
|
isSortable={isSortable}
|
||||||
labels={labels}
|
labels={labels}
|
||||||
moveRow={moveRow}
|
moveRow={moveRow}
|
||||||
removeRow={removeRow}
|
removeRow={removeRow}
|
||||||
@@ -101,11 +104,15 @@ export const BlockRow: React.FC<BlockFieldProps> = ({
|
|||||||
className={classNames}
|
className={classNames}
|
||||||
collapsed={row.collapsed}
|
collapsed={row.collapsed}
|
||||||
collapsibleStyle={fieldHasErrors ? 'error' : 'default'}
|
collapsibleStyle={fieldHasErrors ? 'error' : 'default'}
|
||||||
dragHandleProps={{
|
dragHandleProps={
|
||||||
id: row.id,
|
isSortable
|
||||||
attributes,
|
? {
|
||||||
listeners,
|
id: row.id,
|
||||||
}}
|
attributes,
|
||||||
|
listeners,
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
header={
|
header={
|
||||||
<div className={`${baseClass}__block-header`}>
|
<div className={`${baseClass}__block-header`}>
|
||||||
<span className={`${baseClass}__block-number`}>
|
<span className={`${baseClass}__block-number`}>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export const RowActions: React.FC<{
|
|||||||
blocks: Block[]
|
blocks: Block[]
|
||||||
duplicateRow: (rowIndex: number, blockType: string) => void
|
duplicateRow: (rowIndex: number, blockType: string) => void
|
||||||
hasMaxRows?: boolean
|
hasMaxRows?: boolean
|
||||||
|
isSortable?: boolean
|
||||||
labels: Labels
|
labels: Labels
|
||||||
moveRow: (fromIndex: number, toIndex: number) => void
|
moveRow: (fromIndex: number, toIndex: number) => void
|
||||||
removeRow: (rowIndex: number) => void
|
removeRow: (rowIndex: number) => void
|
||||||
@@ -25,6 +26,7 @@ export const RowActions: React.FC<{
|
|||||||
blocks,
|
blocks,
|
||||||
duplicateRow,
|
duplicateRow,
|
||||||
hasMaxRows,
|
hasMaxRows,
|
||||||
|
isSortable,
|
||||||
labels,
|
labels,
|
||||||
moveRow,
|
moveRow,
|
||||||
removeRow,
|
removeRow,
|
||||||
@@ -59,6 +61,7 @@ export const RowActions: React.FC<{
|
|||||||
duplicateRow={() => duplicateRow(rowIndex, blockType)}
|
duplicateRow={() => duplicateRow(rowIndex, blockType)}
|
||||||
hasMaxRows={hasMaxRows}
|
hasMaxRows={hasMaxRows}
|
||||||
index={rowIndex}
|
index={rowIndex}
|
||||||
|
isSortable={isSortable}
|
||||||
moveRow={moveRow}
|
moveRow={moveRow}
|
||||||
removeRow={removeRow}
|
removeRow={removeRow}
|
||||||
rowCount={rowCount}
|
rowCount={rowCount}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const BlocksField: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
admin: { className, condition, description, readOnly },
|
admin: { className, condition, description, isSortable = true, readOnly },
|
||||||
blocks,
|
blocks,
|
||||||
fieldTypes,
|
fieldTypes,
|
||||||
forceRender = false,
|
forceRender = false,
|
||||||
@@ -230,7 +230,7 @@ const BlocksField: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
if (blockToRender) {
|
if (blockToRender) {
|
||||||
return (
|
return (
|
||||||
<DraggableSortableItem disabled={readOnly} id={row.id} key={row.id}>
|
<DraggableSortableItem disabled={readOnly || !isSortable} id={row.id} key={row.id}>
|
||||||
{(draggableSortableItemProps) => (
|
{(draggableSortableItemProps) => (
|
||||||
<BlockRow
|
<BlockRow
|
||||||
{...draggableSortableItemProps}
|
{...draggableSortableItemProps}
|
||||||
@@ -242,6 +242,7 @@ const BlocksField: React.FC<Props> = (props) => {
|
|||||||
forceRender={forceRender}
|
forceRender={forceRender}
|
||||||
hasMaxRows={hasMaxRows}
|
hasMaxRows={hasMaxRows}
|
||||||
indexPath={indexPath}
|
indexPath={indexPath}
|
||||||
|
isSortable={isSortable}
|
||||||
labels={labels}
|
labels={labels}
|
||||||
moveRow={moveRow}
|
moveRow={moveRow}
|
||||||
path={path}
|
path={path}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { Props } from './types'
|
|||||||
|
|
||||||
import { checkbox } from '../../../../../fields/validations'
|
import { checkbox } from '../../../../../fields/validations'
|
||||||
import { getTranslation } from '../../../../../utilities/getTranslation'
|
import { getTranslation } from '../../../../../utilities/getTranslation'
|
||||||
|
import { useEditDepth } from '../../../utilities/EditDepth'
|
||||||
import DefaultError from '../../Error'
|
import DefaultError from '../../Error'
|
||||||
import FieldDescription from '../../FieldDescription'
|
import FieldDescription from '../../FieldDescription'
|
||||||
import useField from '../../useField'
|
import useField from '../../useField'
|
||||||
@@ -41,6 +42,8 @@ const Checkbox: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
const path = pathFromProps || name
|
const path = pathFromProps || name
|
||||||
|
|
||||||
|
const editDepth = useEditDepth()
|
||||||
|
|
||||||
const memoizedValidate = useCallback(
|
const memoizedValidate = useCallback(
|
||||||
(value, options) => {
|
(value, options) => {
|
||||||
return validate(value, { ...options, required })
|
return validate(value, { ...options, required })
|
||||||
@@ -62,7 +65,7 @@ const Checkbox: React.FC<Props> = (props) => {
|
|||||||
}
|
}
|
||||||
}, [onChange, readOnly, setValue, value])
|
}, [onChange, readOnly, setValue, value])
|
||||||
|
|
||||||
const fieldID = `field-${path.replace(/\./g, '__')}`
|
const fieldID = `field-${path.replace(/\./g, '__')}${editDepth > 1 ? `-${editDepth}` : ''}`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user