Compare commits
147 Commits
db-postgre
...
v2.0.10
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e847061c74 | ||
|
|
48de89794b | ||
|
|
ef4b5d8bfd | ||
|
|
5711d42eca | ||
|
|
a446a788a9 | ||
|
|
df57196d19 | ||
|
|
86c563e4e5 | ||
|
|
734b8c08ed | ||
|
|
68c5a57515 | ||
|
|
d9f0b7bd30 | ||
|
|
5ecfe3da28 | ||
|
|
3e3163e875 | ||
|
|
cfe1698dfd | ||
|
|
5722634660 | ||
|
|
cdbfc9132a | ||
|
|
dd0ac066ce | ||
|
|
8b8ceabbdd | ||
|
|
8da18d3496 | ||
|
|
6cd4df3dc4 | ||
|
|
e74dc8633b | ||
|
|
2f945919a3 | ||
|
|
52dc9177d7 | ||
|
|
cec757d098 | ||
|
|
80e57150a0 | ||
|
|
2969da7402 | ||
|
|
35da1db99f | ||
|
|
a578226d34 | ||
|
|
8c75f32620 | ||
|
|
c3ab8b9115 | ||
|
|
993568a195 | ||
|
|
c3cec64220 | ||
|
|
084e9f0ff8 | ||
|
|
9f0ef9b7da | ||
|
|
c384f490c8 | ||
|
|
45a62ba949 | ||
|
|
1eae5f9c99 | ||
|
|
dfa861557d | ||
|
|
228fd58020 | ||
|
|
150799e10e | ||
|
|
823436a883 | ||
|
|
cbb0ba1a2c | ||
|
|
cb39354a9d | ||
|
|
1625ff244e | ||
|
|
24918fe1d2 | ||
|
|
b8a58666e7 | ||
|
|
2e6a2c8355 | ||
|
|
1fdff92525 | ||
|
|
7da5f6e92a | ||
|
|
67c7572e5f | ||
|
|
e311e8fff9 | ||
|
|
086e50b9b3 | ||
|
|
66ab6c587d | ||
|
|
786fb926c2 | ||
|
|
7d954b11a3 | ||
|
|
3c5044368d | ||
|
|
f129d6c607 | ||
|
|
de2d985405 | ||
|
|
060f3c73fa | ||
|
|
62ae7be113 | ||
|
|
771df061b4 | ||
|
|
09e64c3be8 | ||
|
|
1a006fef19 | ||
|
|
c4cac99875 | ||
|
|
d616772740 | ||
|
|
4b9e87bb4d | ||
|
|
ff5e174497 | ||
|
|
34017e1758 | ||
|
|
23d95526ab | ||
|
|
59b87fdb21 | ||
|
|
f2ac1f7d48 | ||
|
|
8d4f39af5e | ||
|
|
c4ac341d75 | ||
|
|
3eefe8cb21 | ||
|
|
8938f2b7e9 | ||
|
|
ed8a9ffa09 | ||
|
|
b5b487ab90 | ||
|
|
940bfe4f18 | ||
|
|
41dcdd4e01 | ||
|
|
3dbb70a9e6 | ||
|
|
adef360275 | ||
|
|
abebd7f440 | ||
|
|
e9ed969ad9 | ||
|
|
6b1a7f0843 | ||
|
|
e76cd58425 | ||
|
|
888f937e3c | ||
|
|
d61ced9cbd | ||
|
|
ada165a21e | ||
|
|
8d4fd14ff2 | ||
|
|
0363c85dbd | ||
|
|
fa660cd4ef | ||
|
|
4f6eb1e307 | ||
|
|
50860bc8c1 | ||
|
|
52f7890989 | ||
|
|
6200a119f9 | ||
|
|
46eb61d18a | ||
|
|
2766489476 | ||
|
|
0c1102a138 | ||
|
|
e765b96a4e | ||
|
|
01c42d9630 | ||
|
|
f421a2715d | ||
|
|
edd7a8086c | ||
|
|
010db46e08 | ||
|
|
fc8a6e107e | ||
|
|
2cb79f1752 | ||
|
|
5f740a60cc | ||
|
|
27313995cc | ||
|
|
93afe1d000 | ||
|
|
e7ffa2638a | ||
|
|
666765f3fb | ||
|
|
fc14622555 | ||
|
|
0d83d83d3c | ||
|
|
aab2f5f7d2 | ||
|
|
fbdc74ea71 | ||
|
|
141e40ffb9 | ||
|
|
bd2c1c6bf2 | ||
|
|
ba90fdbdfd | ||
|
|
9991fdb8c8 | ||
|
|
15c9ce56c2 | ||
|
|
eecdd0e118 | ||
|
|
f749732a0a | ||
|
|
78e2b518cf | ||
|
|
91362587f0 | ||
|
|
a4c4fc7060 | ||
|
|
db09f4839f | ||
|
|
c71e079fae | ||
|
|
b90785fa8c | ||
|
|
6e340f008f | ||
|
|
e08e681eda | ||
|
|
238db1750c | ||
|
|
0962cd6fcb | ||
|
|
6b436d38e1 | ||
|
|
ae93006446 | ||
|
|
fc722573bf | ||
|
|
b72ba7fe86 | ||
|
|
3dd777343b | ||
|
|
f1f7592eb2 | ||
|
|
3816589e8b | ||
|
|
ba70b8065e | ||
|
|
9b88a47a47 | ||
|
|
9a747bc1eb | ||
|
|
f82723ef33 | ||
|
|
897f7be0f7 | ||
|
|
80f6c7ebe1 | ||
|
|
9da4151642 | ||
|
|
b89cbe5715 | ||
|
|
725a1d35ef | ||
|
|
2fbfb5d305 |
10
.github/workflows/main.yml
vendored
10
.github/workflows/main.yml
vendored
@@ -132,7 +132,12 @@ jobs:
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: E2E Tests
|
||||
run: pnpm test:e2e --part ${{ matrix.part }} --bail
|
||||
uses: nick-fields/retry@v2
|
||||
with:
|
||||
retry_on: error
|
||||
max_attempts: 2
|
||||
timeout_minutes: 15
|
||||
command: pnpm test:e2e --part ${{ matrix.part }} --bail
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
@@ -213,8 +218,9 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pkg:
|
||||
- plugin-cloud
|
||||
- create-payload-app
|
||||
- plugin-cloud
|
||||
- plugin-nested-docs
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"verbose": true,
|
||||
"git": {
|
||||
"commitMessage": "chore(release): v${version}",
|
||||
"requireCleanWorkingDir": true
|
||||
},
|
||||
"github": {
|
||||
"release": true
|
||||
},
|
||||
"npm": {
|
||||
"skipChecks": true
|
||||
},
|
||||
"hooks": {
|
||||
"before:init": ["pnpm i", "pnpm clean", "pnpm test"]
|
||||
},
|
||||
"plugins": {
|
||||
"@release-it/conventional-changelog": {
|
||||
"preset": "angular",
|
||||
"infile": "CHANGELOG.md"
|
||||
}
|
||||
}
|
||||
}
|
||||
16
.release-it.pre.js
Normal file
16
.release-it.pre.js
Normal file
@@ -0,0 +1,16 @@
|
||||
module.exports = {
|
||||
verbose: true,
|
||||
git: {
|
||||
requireCleanWorkingDir: false,
|
||||
commit: false,
|
||||
push: false,
|
||||
tag: false,
|
||||
},
|
||||
npm: {
|
||||
skipChecks: true,
|
||||
tag: 'beta',
|
||||
},
|
||||
hooks: {
|
||||
'before:init': ['pnpm install', 'pnpm clean', 'pnpm build'],
|
||||
},
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"verbose": true,
|
||||
"git": {
|
||||
"requireCleanWorkingDir": false,
|
||||
"commit": false,
|
||||
"push": false,
|
||||
"tag": false
|
||||
},
|
||||
"github": {
|
||||
"release": true
|
||||
},
|
||||
"npm": {
|
||||
"skipChecks": true,
|
||||
"tag": "canary"
|
||||
},
|
||||
"hooks": {
|
||||
"before:init": ["pnpm i", "pnpm clean", "pnpm test"]
|
||||
},
|
||||
"plugins": {
|
||||
"@release-it/conventional-changelog": {
|
||||
"preset": "angular",
|
||||
"infile": "CHANGELOG.md"
|
||||
}
|
||||
}
|
||||
}
|
||||
112
CHANGELOG.md
112
CHANGELOG.md
@@ -1,3 +1,111 @@
|
||||
|
||||
|
||||
## [2.0.8](https://github.com/payloadcms/payload/compare/v2.0.7...v2.0.8) (2023-10-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* allows filterOptions to return null ([c4cac99](https://github.com/payloadcms/payload/commit/c4cac998752730e7084598c92c77789da8c82e0d))
|
||||
* **live-preview:** caches field schema ([#3711](https://github.com/payloadcms/payload/issues/3711)) ([dd0ac06](https://github.com/payloadcms/payload/commit/dd0ac066ce2ed88b85025309303610a95b6089e1))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* autosave time shown minutes only ([#3492](https://github.com/payloadcms/payload/issues/3492)) ([e311e8f](https://github.com/payloadcms/payload/commit/e311e8fff9cd4264d7a71903f63c4fa825a3564d))
|
||||
* blocks within groups in postgres ([45a62ba](https://github.com/payloadcms/payload/commit/45a62ba949aca33b25e0808773a5c2f1cf4adf82))
|
||||
* bug with seeding ecommerce ([993568a](https://github.com/payloadcms/payload/commit/993568a1959ea10f960e35e4ed7a8e06af672a72))
|
||||
* corrects add block index ([#3681](https://github.com/payloadcms/payload/issues/3681)) ([3c50443](https://github.com/payloadcms/payload/commit/3c5044368d5b30c76a2ff20c25b9234ef89dc205))
|
||||
* misc upload crop/focal point updates ([#3580](https://github.com/payloadcms/payload/issues/3580)) ([d616772](https://github.com/payloadcms/payload/commit/d6167727401a01282345e63636560e029ae8e0f3))
|
||||
* renders mobile document controls ([#3695](https://github.com/payloadcms/payload/issues/3695)) ([1625ff2](https://github.com/payloadcms/payload/commit/1625ff244e6e81e6edc0357037c3abc1a3bf8ba7))
|
||||
* some local operations missing req.transactionID ([#3651](https://github.com/payloadcms/payload/issues/3651)) ([150799e](https://github.com/payloadcms/payload/commit/150799e10e580281d1af49388eb142ee9639a002))
|
||||
* **richtext-*:** extra fields not being iterated correctly ([#3693](https://github.com/payloadcms/payload/issues/3693)) ([b8a5866](https://github.com/payloadcms/payload/commit/b8a58666e70f604af1e1cf349bcb4f9add0147e7))
|
||||
* **richtext-*:** link drawer form receiving incorrect field schema ([#3696](https://github.com/payloadcms/payload/issues/3696)) ([cb39354](https://github.com/payloadcms/payload/commit/cb39354a9de3d20960110e453f62c4aa166d8448))
|
||||
* **richtext-lexical:** [#3682](https://github.com/payloadcms/payload/issues/3682) isolated editor container causing z-index issues ([24918fe](https://github.com/payloadcms/payload/commit/24918fe1d2ca251e211632765d370c214cef2a38))
|
||||
* **templates:** user access control ([#3712](https://github.com/payloadcms/payload/issues/3712)) ([8b8ceab](https://github.com/payloadcms/payload/commit/8b8ceabbdd6354761e7d744cacb1192cac3a2427))
|
||||
|
||||
|
||||
## [2.0.6](https://github.com/payloadcms/payload/compare/v2.0.5...v2.0.6) (2023-10-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* document sidebar vertical overflow ([#3639](https://github.com/payloadcms/payload/issues/3639)) ([fcd4c8d](https://github.com/payloadcms/payload/commit/fcd4c8d83040f00d10142ca12ba92616618b966e))
|
||||
* login form clearing out and field spacing ([#3633](https://github.com/payloadcms/payload/issues/3633)) ([4bd01df](https://github.com/payloadcms/payload/commit/4bd01df411e4ad2ccacdcd6de0fb21a8145c3964))
|
||||
* sidebar field permissions ([#3629](https://github.com/payloadcms/payload/issues/3629)) ([c956a85](https://github.com/payloadcms/payload/commit/c956a85252bc7de1686925cc783694383c0ac9be))
|
||||
* preview button conditions ([#3613](https://github.com/payloadcms/payload/issues/3613)) ([beed83b](https://github.com/payloadcms/payload/commit/beed83b231b19090902dd502ff5eab054a67a1a6))
|
||||
* allows drafts to be duplicated ([1a99d66](https://github.com/payloadcms/payload/commit/1a99d66cd0675cf2cb2c4317a121721f35682ff3))
|
||||
|
||||
|
||||
## [2.0.5](https://github.com/payloadcms/payload/compare/v2.0.4...v2.0.5) (2023-10-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* properly renders custom buttons for globals ([#3616](https://github.com/payloadcms/payload/issues/3616)) ([05cc287](https://github.com/payloadcms/payload/commit/05cc2873b4a19e2bd46be778f3610643d3e15d09))
|
||||
* minor type issue in richText validate function ([06a51b3](https://github.com/payloadcms/payload/commit/06a51b3c9b9045b23051807aa03b222b542b46f5))
|
||||
* live preview device size ([#3606](https://github.com/payloadcms/payload/issues/3606)) ([8bbac60](https://github.com/payloadcms/payload/commit/8bbac60e60a6dbe4dc0c7b05edbca7f6f2d1c569))
|
||||
* properly handles nested routes for live preview ([#3586](https://github.com/payloadcms/payload/issues/3586)) ([6486468](https://github.com/payloadcms/payload/commit/64864686c418f9822bf61c45ece078a39e81b4cb))
|
||||
* various stepnav related issues ([#3599](https://github.com/payloadcms/payload/issues/3599)) ([aaf8839](https://github.com/payloadcms/payload/commit/aaf883909c588bae1145ddddc5291a98740c2c03))
|
||||
* database adapter types ([cc56da1](https://github.com/payloadcms/payload/commit/cc56da11d635d11ebbee67e6d0919c7275ede36e))
|
||||
* postgres select fields within groups ([#3570](https://github.com/payloadcms/payload/issues/3570)) ([06e2fa9](https://github.com/payloadcms/payload/commit/06e2fa9d111c18fad3422953082266db9329fc91))
|
||||
* [#3511](https://github.com/payloadcms/payload/issues/3511), documents don't delete their versions ([#3520](https://github.com/payloadcms/payload/issues/3520)) ([e3c7765](https://github.com/payloadcms/payload/commit/e3c776523a64da03a4756ee5ae8a84175090979f))
|
||||
|
||||
### Documentation
|
||||
|
||||
- updates building your own live preview hook ([#3604](https://github.com/payloadcms/payload/issues/3604)) ([15c7f0dbf](https://github.com/payloadcms/payload/commit/15c7f0dbf3ebf5c6a2bb011970dda515a15acb56))
|
||||
- update config overview ([cfd923140](https://github.com/payloadcms/payload/commit/1a006fef19a215d7ef74c71a210fd511727f95bd))
|
||||
|
||||
## [2.0.4](https://github.com/payloadcms/payload/compare/v2.0.3...v2.0.4) (2023-10-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- API tab breadcrumbs and results indentation ([#3564](https://github.com/payloadcms/payload/issues/3564)) ([e0afeec](https://github.com/payloadcms/payload/commit/e0afeeca974d0a0cac7aca8e40f9436449c902b5))
|
||||
- sticky sidebar ([#3563](https://github.com/payloadcms/payload/issues/3563)) ([76e306d](https://github.com/payloadcms/payload/commit/76e306ddd8aae45f03fd4715415d6a44501a9400))
|
||||
- sidebar width when fields have long descriptions ([#3562](https://github.com/payloadcms/payload/issues/3562)) ([cfc78ed](https://github.com/payloadcms/payload/commit/cfc78ed4f58647769f651da5a952fed20cfb217f))
|
||||
- row field margins ([#3558](https://github.com/payloadcms/payload/issues/3558)) ([6d9353b](https://github.com/payloadcms/payload/commit/6d9353b53f4197bae2b15ca95298a87d784c7e76))
|
||||
- removes nested array field configs from array value ([#3549](https://github.com/payloadcms/payload/issues/3549)) ([af892ec](https://github.com/payloadcms/payload/commit/af892ecb0e67777a97206bb5fccf489387ce68fc))
|
||||
- [#3521](https://github.com/payloadcms/payload/issues/3521) ([eb97acd](https://github.com/payloadcms/payload/commit/eb97acd408f128438c2122ab6a6e2930f5b4a1ca))
|
||||
- [#3540](https://github.com/payloadcms/payload/issues/3540) ([2567ac5](https://github.com/payloadcms/payload/commit/2567ac58bac851d0a15ee40db0f5f4737b199a75))
|
||||
- row field width ([#3550](https://github.com/payloadcms/payload/issues/3550)) ([9ff014b](https://github.com/payloadcms/payload/commit/9ff014bbfe08d7b114c11824294f0d59f5f6c2c3))
|
||||
- [#3541](https://github.com/payloadcms/payload/issues/3541) ([e6f0d35](https://github.com/payloadcms/payload/commit/e6f0d3598549a921e36f470adfcbacbaebaea53f))
|
||||
- renders global label as page title ([#3532](https://github.com/payloadcms/payload/issues/3532)) ([ace3e57](https://github.com/payloadcms/payload/commit/ace3e577f6b1cbeb12860dc936c578c2a1f68570))
|
||||
- increases document controls popup list button hitbox ([#3529](https://github.com/payloadcms/payload/issues/3529)) ([f009593](https://github.com/payloadcms/payload/commit/f0095937bafdd85c53c99bcc1d29d3361aa07238))
|
||||
|
||||
### Documentation
|
||||
|
||||
- removes MONGODB_URI ([8bfae6b93](https://github.com/payloadcms/payload/commit/8bfae6b932d6c9bd0c628a203ebf8d24121d66f8))
|
||||
- adds build your own plugin page ([#3184](https://github.com/payloadcms/payload/pull/3184)) ([15f650afd](https://github.com/payloadcms/payload/commit/15f650afdef717d62c162846fec77aa0f326bb43))
|
||||
|
||||
## [2.0.3](https://github.com/payloadcms/payload/compare/v2.0.2...v2.0.3) (2023-10-09)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- webpack export default was not found [#3494](https://github.com/payloadcms/payload/issues/3494) ([be049ce](https://github.com/payloadcms/payload/commit/be049cea0029cf497822e337777b8a86b7d38bed))
|
||||
- postgres generated type id [#3504](https://github.com/payloadcms/payload/issues/3504) ([c90d1fa](https://github.com/payloadcms/payload/commit/c90d1faa7fedd5d902949089fd457c56eed4643d))
|
||||
- hasMany relationships unable to be cleared [#3513](https://github.com/payloadcms/payload/issues/3513) ([5d9384f](https://github.com/payloadcms/payload/commit/5d9384f53052c96403d8c07ae9d05edf3676c4ef))
|
||||
|
||||
### Documentation
|
||||
|
||||
- move payload script mention to top of migrations ([26967fb92](https://github.com/payloadcms/payload/commit/26967fb92))
|
||||
- payload script in package.json ([2f86c196e](https://github.com/payloadcms/payload/commit/2f86c196e))
|
||||
- updates required node version ([70e068b18](https://github.com/payloadcms/payload/commit/70e068b18))
|
||||
- improves custom views (#3499) ([6c17222a6](https://github.com/payloadcms/payload/commit/6c17222a6))
|
||||
|
||||
## [2.0.2](https://github.com/payloadcms/payload/compare/v2.0.1...v2.0.2) (2023-10-09)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- beforeOperation hooks now correctly only run once ([e5d6a75](https://github.com/payloadcms/payload/commit/e5d6a75449acce2e53820a65386f1af78ff1317b))
|
||||
|
||||
## [2.0.1](https://github.com/payloadcms/payload/compare/v2.0.0...v2.0.1) (2023-10-09)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- fix: richtext adapter types (#3497) ([7679e3f0a](https://github.com/payloadcms/payload/commit/7679e3f0aa351832ca933a3978d5931c47375e8b))
|
||||
|
||||
### Documentation
|
||||
|
||||
- remove --save flag for install command ([f7c35df6d](https://github.com/payloadcms/payload/commit/f7c35df6de6817ef33837f60951cd2812431fec7))
|
||||
- updates live preview docs ([ca97f692c](https://github.com/payloadcms/payload/commit/ca97f692c3d470e658e417daf29213b2b2b49e11))
|
||||
- fixes label for rich text overview ([7df1256bf](https://github.com/payloadcms/payload/commit/7df1256bf61daa911089d308cf7c0532d524c9c6))
|
||||
|
||||
## [2.0.0](https://github.com/payloadcms/payload/releases/tag/v2.0.0) (2023-10-09)
|
||||
|
||||
### Features
|
||||
@@ -123,7 +231,7 @@ To avoid any issues, you can pass the `req.transactionID` through to your Local
|
||||
|
||||
### ⚠️ Locales now have more functionality, and in some places, you might need to update custom code
|
||||
|
||||
Payload's locales have become more powerful and now allow you to customize more aspects per locale such as a human-friendly label and if the locale is RTL or not.
|
||||
Payload's locales have become more powerful and now allow you to customize more aspects per locale such as a human-friendly label and if the locale is RTL or not.
|
||||
|
||||
This means that certain functions now return a different shape, such as `useLocale`. This hook used to return a string of the locale code you are currently editing, but it now returns an object with type of `Locale`.
|
||||
|
||||
@@ -218,7 +326,7 @@ To pass connection options for MongoDB, you now need to pass them to `db: mongoo
|
||||
|
||||
### ⚠️ Some types have changed locations
|
||||
|
||||
If you are importing types from Payload, some of their locations may have changed. An example would be Slate-specific types being no longer exported from Payload itself—they are now exported from the `@payloadcms/richtext-slate` package.
|
||||
If you are importing types from Payload, some of their locations may have changed. An example would be Slate-specific types being no longer exported from Payload itself—they are now exported from the `@payloadcms/richtext-slate` package.
|
||||
|
||||
### Recap
|
||||
|
||||
|
||||
@@ -40,20 +40,21 @@ Every Payload Collection can opt-in to supporting Uploads by specifying the `upl
|
||||
|
||||
#### Collection Upload Options
|
||||
|
||||
| Option | Description |
|
||||
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`staticURL`** \* | The URL path to use to access your uploads. Relative path like `/media` will be served by payload. Full path like `https://example.com/media` needs to be served by another web server. |
|
||||
| **`staticDir`** \* | The folder directory to use to store media in. Can be either an absolute path or relative to the directory that contains your config. |
|
||||
| **`adminThumbnail`** | Set the way that the Admin panel will display thumbnails for this Collection. [More](#admin-thumbnails) |
|
||||
| **`crop`** | Set to `false` to disable the cropping tool in the Admin panel. Crop is enabled by default. [More](#crop-and-focal-point-selector) |
|
||||
| **`disableLocalStorage`** | Completely disable uploading files to disk locally. [More](#disabling-local-upload-storage) |
|
||||
| **`focalPoint`** | Set to `false` to disable the focal point selection tool in the Admin panel. The focal point selector is only available when `imageSizes` or `resizeOptions` are defined. [More](#crop-and-focal-point-selector) |
|
||||
| **`formatOptions`** | An object with `format` and `options` that are used with the Sharp image library to format the upload file. [More](https://sharp.pixelplumbing.com/api-output#toformat) |
|
||||
| **`handlers`** | Array of Express request handlers to execute before the built-in Payload static middleware executes. |
|
||||
| **`imageSizes`** | If specified, image uploads will be automatically resized in accordance to these image sizes. [More](#image-sizes) |
|
||||
| **`mimeTypes`** | Restrict mimeTypes in the file picker. Array of valid mimetypes or mimetype wildcards [More](#mimetypes) |
|
||||
| **`staticOptions`** | Set options for `express.static` to use while serving your static files. [More](http://expressjs.com/en/resources/middleware/serve-static.html) format) |
|
||||
| **`resizeOptions`** | An object passed to the the Sharp image library to resize the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize) |
|
||||
| Option | Description |
|
||||
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`staticURL`** \* | The URL path to use to access your uploads. Relative path like `/media` will be served by payload. Full path like `https://example.com/media` needs to be served by another web server. |
|
||||
| **`staticDir`** \* | The folder directory to use to store media in. Can be either an absolute path or relative to the directory that contains your config. |
|
||||
| **`adminThumbnail`** | Set the way that the Admin panel will display thumbnails for this Collection. [More](#admin-thumbnails) |
|
||||
| **`crop`** | Set to `false` to disable the cropping tool in the Admin panel. Crop is enabled by default. [More](#crop-and-focal-point-selector) |
|
||||
| **`disableLocalStorage`** | Completely disable uploading files to disk locally. [More](#disabling-local-upload-storage) |
|
||||
| **`focalPoint`** | Set to `false` to disable the focal point selection tool in the Admin panel. The focal point selector is only available when `imageSizes` or `resizeOptions` are defined. [More](#crop-and-focal-point-selector) |
|
||||
| **`formatOptions`** | An object with `format` and `options` that are used with the Sharp image library to format the upload file. [More](https://sharp.pixelplumbing.com/api-output#toformat) |
|
||||
| **`handlers`** | Array of Express request handlers to execute before the built-in Payload static middleware executes. |
|
||||
| **`imageSizes`** | If specified, image uploads will be automatically resized in accordance to these image sizes. [More](#image-sizes) |
|
||||
| **`mimeTypes`** | Restrict mimeTypes in the file picker. Array of valid mimetypes or mimetype wildcards [More](#mimetypes) |
|
||||
| **`staticOptions`** | Set options for `express.static` to use while serving your static files. [More](http://expressjs.com/en/resources/middleware/serve-static.html) format) |
|
||||
| **`resizeOptions`** | An object passed to the the Sharp image library to resize the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize) |
|
||||
| **`filesRequiredOnCreate`** | Mandate file data on creation, default is true. |
|
||||
|
||||
_An asterisk denotes that a property above is required._
|
||||
|
||||
|
||||
@@ -496,6 +496,16 @@
|
||||
"@jridgewell/trace-mapping" "^0.3.17"
|
||||
jsesc "^2.5.1"
|
||||
|
||||
"@babel/generator@^7.23.0":
|
||||
version "7.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420"
|
||||
integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==
|
||||
dependencies:
|
||||
"@babel/types" "^7.23.0"
|
||||
"@jridgewell/gen-mapping" "^0.3.2"
|
||||
"@jridgewell/trace-mapping" "^0.3.17"
|
||||
jsesc "^2.5.1"
|
||||
|
||||
"@babel/helper-compilation-targets@^7.22.15":
|
||||
version "7.22.15"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52"
|
||||
@@ -507,18 +517,23 @@
|
||||
lru-cache "^5.1.1"
|
||||
semver "^6.3.1"
|
||||
|
||||
"@babel/helper-environment-visitor@^7.22.20":
|
||||
version "7.22.20"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167"
|
||||
integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==
|
||||
|
||||
"@babel/helper-environment-visitor@^7.22.5":
|
||||
version "7.22.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98"
|
||||
integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==
|
||||
|
||||
"@babel/helper-function-name@^7.22.5":
|
||||
version "7.22.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be"
|
||||
integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==
|
||||
"@babel/helper-function-name@^7.23.0":
|
||||
version "7.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759"
|
||||
integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==
|
||||
dependencies:
|
||||
"@babel/template" "^7.22.5"
|
||||
"@babel/types" "^7.22.5"
|
||||
"@babel/template" "^7.22.15"
|
||||
"@babel/types" "^7.23.0"
|
||||
|
||||
"@babel/helper-hoist-variables@^7.22.5":
|
||||
version "7.22.5"
|
||||
@@ -574,6 +589,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz#601fa28e4cc06786c18912dca138cec73b882044"
|
||||
integrity sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ==
|
||||
|
||||
"@babel/helper-validator-identifier@^7.22.20":
|
||||
version "7.22.20"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
|
||||
integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==
|
||||
|
||||
"@babel/helper-validator-option@^7.22.15":
|
||||
version "7.22.15"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040"
|
||||
@@ -602,6 +622,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.16.tgz#180aead7f247305cce6551bea2720934e2fa2c95"
|
||||
integrity sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA==
|
||||
|
||||
"@babel/parser@^7.23.0":
|
||||
version "7.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719"
|
||||
integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==
|
||||
|
||||
"@babel/plugin-syntax-async-generators@^7.8.4":
|
||||
version "7.8.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d"
|
||||
@@ -707,7 +732,7 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
"@babel/template@^7.22.15", "@babel/template@^7.22.5", "@babel/template@^7.3.3":
|
||||
"@babel/template@^7.22.15", "@babel/template@^7.3.3":
|
||||
version "7.22.15"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
|
||||
integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==
|
||||
@@ -717,18 +742,18 @@
|
||||
"@babel/types" "^7.22.15"
|
||||
|
||||
"@babel/traverse@^7.22.15", "@babel/traverse@^7.22.17":
|
||||
version "7.22.17"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.17.tgz#b23c203ab3707e3be816043081b4a994fcacec44"
|
||||
integrity sha512-xK4Uwm0JnAMvxYZxOVecss85WxTEIbTa7bnGyf/+EgCL5Zt3U7htUpEOWv9detPlamGKuRzCqw74xVglDWpPdg==
|
||||
version "7.23.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8"
|
||||
integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.22.13"
|
||||
"@babel/generator" "^7.22.15"
|
||||
"@babel/helper-environment-visitor" "^7.22.5"
|
||||
"@babel/helper-function-name" "^7.22.5"
|
||||
"@babel/generator" "^7.23.0"
|
||||
"@babel/helper-environment-visitor" "^7.22.20"
|
||||
"@babel/helper-function-name" "^7.23.0"
|
||||
"@babel/helper-hoist-variables" "^7.22.5"
|
||||
"@babel/helper-split-export-declaration" "^7.22.6"
|
||||
"@babel/parser" "^7.22.16"
|
||||
"@babel/types" "^7.22.17"
|
||||
"@babel/parser" "^7.23.0"
|
||||
"@babel/types" "^7.23.0"
|
||||
debug "^4.1.0"
|
||||
globals "^11.1.0"
|
||||
|
||||
@@ -741,6 +766,15 @@
|
||||
"@babel/helper-validator-identifier" "^7.22.15"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@babel/types@^7.23.0":
|
||||
version "7.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb"
|
||||
integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==
|
||||
dependencies:
|
||||
"@babel/helper-string-parser" "^7.22.5"
|
||||
"@babel/helper-validator-identifier" "^7.22.20"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@bcherny/json-schema-ref-parser@9.0.9":
|
||||
version "9.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@bcherny/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz#09899d405bc708c0acac0066ae8db5b94d465ca4"
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"@types/prompts": "^2.4.5",
|
||||
"@types/qs": "6.9.7",
|
||||
"@types/react": "18.2.15",
|
||||
"@types/semver": "^7.5.3",
|
||||
"@types/shelljs": "0.8.12",
|
||||
"@types/testing-library__jest-dom": "5.14.8",
|
||||
"chalk": "^5.3.0",
|
||||
@@ -59,6 +60,7 @@
|
||||
"fs-extra": "10.1.0",
|
||||
"get-port": "5.1.1",
|
||||
"glob": "8.1.0",
|
||||
"graphql-request": "6.1.0",
|
||||
"husky": "^8.0.3",
|
||||
"isomorphic-fetch": "3.0.0",
|
||||
"jest": "29.6.4",
|
||||
@@ -73,6 +75,7 @@
|
||||
"prompts": "2.4.2",
|
||||
"qs": "6.11.2",
|
||||
"rimraf": "3.0.2",
|
||||
"semver": "^7.5.4",
|
||||
"shelljs": "0.8.5",
|
||||
"simple-git": "^3.20.0",
|
||||
"slash": "3.0.0",
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"payload": "^2.0.0",
|
||||
"react-dom": "18.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
|
||||
@@ -58,6 +58,9 @@
|
||||
"@types/webpack-hot-middleware": "2.25.6",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"payload": "^2.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"main": "./dist/index.js",
|
||||
"registry": "https://registry.npmjs.org/",
|
||||
|
||||
15
packages/create-payload-app/.swcrc
Normal file
15
packages/create-payload-app/.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"
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,14 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "0.5.2",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"create-payload-app": "bin/cli.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc && pnpm copyfiles",
|
||||
"copyfiles": "copyfiles -u 1 \"src/templates/**\" \"src/lib/common-files/**\" dist",
|
||||
"build": "pnpm build:swc",
|
||||
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
|
||||
"clean": "rimraf dist",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "eslint \"src/**/*.ts\"",
|
||||
"lint:fix": "eslint \"src/**/*.ts\" --fix",
|
||||
"lint-staged": "lint-staged --quiet",
|
||||
"test": "jest",
|
||||
"prepublishOnly": "pnpm test && pnpm clean && pnpm build"
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { BundlerType, CliArgs, DbType, ProjectTemplate } from '../types'
|
||||
import { createProject } from './create-project'
|
||||
import { bundlerPackages, dbPackages, editorPackages } from './packages'
|
||||
import exp from 'constants'
|
||||
import { getValidTemplates } from './templates'
|
||||
|
||||
const projectDir = path.resolve(__dirname, './tmp')
|
||||
describe('createProject', () => {
|
||||
@@ -78,17 +79,20 @@ describe('createProject', () => {
|
||||
})
|
||||
|
||||
describe('db adapters and bundlers', () => {
|
||||
const templates = getValidTemplates()
|
||||
|
||||
it.each([
|
||||
['mongodb', 'webpack'],
|
||||
['postgres', 'webpack'],
|
||||
])('update config and deps: %s, %s', async (db, bundler) => {
|
||||
['blank', 'mongodb', 'webpack'],
|
||||
['blank', 'postgres', 'webpack'],
|
||||
['website', 'mongodb', 'webpack'],
|
||||
['website', 'postgres', 'webpack'],
|
||||
['ecommerce', 'mongodb', 'webpack'],
|
||||
['ecommerce', 'postgres', 'webpack'],
|
||||
])('update config and deps: %s, %s, %s', async (templateName, db, bundler) => {
|
||||
const projectName = 'starter-project'
|
||||
const template: ProjectTemplate = {
|
||||
name: 'blank',
|
||||
type: 'starter',
|
||||
url: 'https://github.com/payloadcms/payload/templates/blank',
|
||||
description: 'Blank Template',
|
||||
}
|
||||
|
||||
const template = templates.find((t) => t.name === templateName)
|
||||
|
||||
await createProject({
|
||||
cliArgs: args,
|
||||
projectName,
|
||||
@@ -124,7 +128,12 @@ describe('createProject', () => {
|
||||
editorReplacement.version,
|
||||
)
|
||||
|
||||
const payloadConfigPath = path.resolve(projectDir, 'src/payload.config.ts')
|
||||
let payloadConfigPath = path.resolve(projectDir, 'src/payload.config.ts')
|
||||
|
||||
// Website and ecommerce templates have payload.config.ts in src/payload
|
||||
if (!fse.existsSync(payloadConfigPath)) {
|
||||
payloadConfigPath = path.resolve(projectDir, 'src/payload/payload.config.ts')
|
||||
}
|
||||
const content = fse.readFileSync(payloadConfigPath, 'utf-8')
|
||||
|
||||
// Check payload.config.ts
|
||||
|
||||
@@ -19,6 +19,5 @@
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.spec.tsx"
|
||||
],
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts", "src/**/*.json"],
|
||||
"references": [{ "path": "../payload" }]
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts", "src/**/*.json"]
|
||||
}
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
"mongodb-memory-server": "8.13.0",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"payload": "^2.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"main": "./dist/index.js",
|
||||
"registry": "https://registry.npmjs.org/",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "0.1.7",
|
||||
"version": "0.1.9",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"repository": "https://github.com/payloadcms/payload",
|
||||
"license": "MIT",
|
||||
@@ -34,6 +34,9 @@
|
||||
"@types/to-snake-case": "1.0.0",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"payload": "^2.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"main": "./dist/index.js",
|
||||
"registry": "https://registry.npmjs.org/",
|
||||
|
||||
@@ -33,7 +33,7 @@ export const findMany = async function find({
|
||||
const db = adapter.sessions[req.transactionID]?.db || adapter.drizzle
|
||||
const table = adapter.tables[tableName]
|
||||
|
||||
let limit = limitArg
|
||||
let limit = limitArg ?? 10
|
||||
let totalDocs: number
|
||||
let totalPages: number
|
||||
let hasPrevPage: boolean
|
||||
@@ -119,7 +119,11 @@ export const findMany = async function find({
|
||||
findManyArgs.where = inArray(adapter.tables[tableName].id, Object.keys(orderedIDMap))
|
||||
} else {
|
||||
findManyArgs.limit = limitArg === 0 ? undefined : limitArg
|
||||
findManyArgs.offset = skip || (page - 1) * limitArg
|
||||
|
||||
const offset = skip || (page - 1) * limitArg
|
||||
|
||||
if (!Number.isNaN(offset)) findManyArgs.offset = offset
|
||||
|
||||
if (where) {
|
||||
findManyArgs.where = where
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ export const traverseFields = ({
|
||||
currentTableName,
|
||||
depth,
|
||||
fields: block.fields,
|
||||
path,
|
||||
path: '',
|
||||
topLevelArgs,
|
||||
topLevelTableName,
|
||||
})
|
||||
|
||||
@@ -148,13 +148,19 @@ export async function parseParams({
|
||||
break
|
||||
}
|
||||
|
||||
const { operator: queryOperator, value: queryValue } = sanitizeQueryValue({
|
||||
const sanitizedQueryValue = sanitizeQueryValue({
|
||||
field,
|
||||
operator,
|
||||
relationOrPath,
|
||||
val,
|
||||
})
|
||||
|
||||
if (sanitizedQueryValue === null) {
|
||||
break
|
||||
}
|
||||
|
||||
const { operator: queryOperator, value: queryValue } = sanitizedQueryValue
|
||||
|
||||
if (queryOperator === 'not_equals' && queryValue !== null) {
|
||||
constraints.push(
|
||||
or(
|
||||
@@ -163,7 +169,10 @@ export async function parseParams({
|
||||
ne<any>(rawColumn || table[columnName], queryValue),
|
||||
),
|
||||
)
|
||||
} else if (
|
||||
break
|
||||
}
|
||||
|
||||
if (
|
||||
(field.type === 'relationship' || field.type === 'upload') &&
|
||||
Array.isArray(queryValue) &&
|
||||
operator === 'not_in'
|
||||
@@ -174,11 +183,13 @@ export async function parseParams({
|
||||
IS
|
||||
NULL`,
|
||||
)
|
||||
} else {
|
||||
constraints.push(
|
||||
operatorMap[queryOperator](rawColumn || table[columnName], queryValue),
|
||||
)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
constraints.push(
|
||||
operatorMap[queryOperator](rawColumn || table[columnName], queryValue),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,11 +42,17 @@ export const sanitizeQueryValue = ({
|
||||
if (val.toLowerCase() === 'false') formattedValue = false
|
||||
}
|
||||
|
||||
if (['all', 'in', 'not_in'].includes(operator) && typeof formattedValue === 'string') {
|
||||
formattedValue = createArrayFromCommaDelineated(formattedValue)
|
||||
if (['all', 'in', 'not_in'].includes(operator)) {
|
||||
if (typeof formattedValue === 'string') {
|
||||
formattedValue = createArrayFromCommaDelineated(formattedValue)
|
||||
|
||||
if (field.type === 'number') {
|
||||
formattedValue = formattedValue.map((arrayVal) => parseFloat(arrayVal))
|
||||
if (field.type === 'number') {
|
||||
formattedValue = formattedValue.map((arrayVal) => parseFloat(arrayVal))
|
||||
}
|
||||
}
|
||||
|
||||
if (!Array.isArray(formattedValue) || formattedValue.length === 0) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-react",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.4",
|
||||
"description": "The official live preview React SDK for Payload",
|
||||
"repository": "https://github.com/payloadcms/payload",
|
||||
"license": "MIT",
|
||||
@@ -17,7 +17,7 @@
|
||||
"prepublishOnly": "pnpm clean && pnpm build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@payloadcms/live-preview": "workspace:*"
|
||||
"@payloadcms/live-preview": "workspace:^0.x"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
@@ -25,6 +25,7 @@
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"payload": "^2.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.4",
|
||||
"description": "The official live preview JavaScript SDK for Payload",
|
||||
"repository": "https://github.com/payloadcms/payload",
|
||||
"license": "MIT",
|
||||
@@ -20,6 +20,9 @@
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"payload": "^2.0.0"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"default": "./src/index.ts",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { mergeData } from '.'
|
||||
|
||||
// For performance reasons, `fieldSchemaJSON` will only be sent once on the initial message
|
||||
// We need to cache this value so that it can be used across subsequent messages
|
||||
// To do this, save `fieldSchemaJSON` when it arrives as a global variable
|
||||
// Send this cached value to `mergeData`, instead of `eventData.fieldSchemaJSON` directly
|
||||
let payloadLivePreviewFieldSchema = undefined // TODO: type this from `fieldSchemaToJSON` return type
|
||||
|
||||
export const handleMessage = async <T>(args: {
|
||||
depth: number
|
||||
event: MessageEvent
|
||||
@@ -11,9 +17,13 @@ export const handleMessage = async <T>(args: {
|
||||
const eventData = JSON.parse(event?.data)
|
||||
|
||||
if (eventData.type === 'payload-live-preview') {
|
||||
if (!payloadLivePreviewFieldSchema && eventData.fieldSchemaJSON) {
|
||||
payloadLivePreviewFieldSchema = eventData.fieldSchemaJSON
|
||||
}
|
||||
|
||||
const mergedData = await mergeData<T>({
|
||||
depth,
|
||||
fieldSchema: eventData.fieldSchemaJSON,
|
||||
fieldSchema: payloadLivePreviewFieldSchema,
|
||||
incomingData: eventData.data,
|
||||
initialData,
|
||||
serverURL,
|
||||
|
||||
@@ -2,11 +2,14 @@ export const ready = (args: { serverURL: string }): void => {
|
||||
const { serverURL } = args
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
// This subscription may have been from either an iframe `src` or `window.open()`
|
||||
// i.e. `window?.opener` || `window?.parent`
|
||||
window?.opener?.postMessage(
|
||||
// This subscription may have been from either an iframe or a popup
|
||||
// We need to report 'ready' to the parent window, whichever it may be
|
||||
// i.e. `window?.opener` for popups, `window?.parent` for iframes
|
||||
const windowToPostTo: Window = window?.opener || window?.parent
|
||||
|
||||
windowToPostTo?.postMessage(
|
||||
JSON.stringify({
|
||||
popupReady: true,
|
||||
ready: true,
|
||||
type: 'payload-live-preview',
|
||||
}),
|
||||
serverURL,
|
||||
|
||||
@@ -9,7 +9,7 @@ export const subscribe = <T>(args: {
|
||||
const { callback, depth, initialData, serverURL } = args
|
||||
|
||||
const onMessage = async (event: MessageEvent) => {
|
||||
const mergedData = await handleMessage({ depth, event, initialData, serverURL })
|
||||
const mergedData = await handleMessage<T>({ depth, event, initialData, serverURL })
|
||||
callback(mergedData)
|
||||
}
|
||||
|
||||
|
||||
51
packages/payload/.release-it.js
Normal file
51
packages/payload/.release-it.js
Normal file
@@ -0,0 +1,51 @@
|
||||
module.exports = {
|
||||
verbose: true,
|
||||
git: {
|
||||
commitMessage: 'chore(release): v${version}',
|
||||
requireCleanWorkingDir: false,
|
||||
tagMatch: 'v*', // payload is tagged normally, other packages are tagged with a prefix
|
||||
},
|
||||
github: {
|
||||
release: true,
|
||||
},
|
||||
npm: {
|
||||
skipChecks: true,
|
||||
},
|
||||
hooks: {
|
||||
'before:init': ['pnpm install', 'pnpm clean', 'pnpm build'], // Assume tests have already been run
|
||||
},
|
||||
plugins: {
|
||||
'@release-it/conventional-changelog': {
|
||||
infile: '../../CHANGELOG.md',
|
||||
preset: {
|
||||
name: 'conventionalcommits',
|
||||
types: [
|
||||
{ type: 'feat', section: 'Features' },
|
||||
{ type: 'feature', section: 'Features' },
|
||||
{ type: 'fix', section: 'Bug Fixes' },
|
||||
{ type: 'docs', section: 'Documentation' },
|
||||
],
|
||||
},
|
||||
writerOpts: {
|
||||
commitGroupsSort: (a, b) => {
|
||||
const groupOrder = ['Features', 'Bug Fixes', 'Documentation']
|
||||
return groupOrder.indexOf(a.title) - groupOrder.indexOf(b.title)
|
||||
},
|
||||
|
||||
// Scoped commits at the end, alphabetical sort
|
||||
commitsSort: (a, b) => {
|
||||
if (a.scope || b.scope) {
|
||||
if (!a.scope) return -1
|
||||
if (!b.scope) return 1
|
||||
return a.scope === b.scope
|
||||
? a.subject.localeCompare(b.subject)
|
||||
: a.scope.localeCompare(b.scope)
|
||||
}
|
||||
|
||||
// Alphabetical sort
|
||||
return a.subject.localeCompare(b.subject)
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload",
|
||||
"version": "2.0.6",
|
||||
"version": "2.0.10",
|
||||
"description": "Node, React and MongoDB Headless CMS and Application Framework",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
@@ -40,8 +40,7 @@
|
||||
"lint": "eslint \"src/**/*.ts\"",
|
||||
"prepublishOnly": "pnpm clean && pnpm build",
|
||||
"pretest": "pnpm build",
|
||||
"release:beta": "release-it pre --preReleaseId=beta --npm.tag=beta --config .release-it.pre.json",
|
||||
"release:canary": "release-it pre --preReleaseId=canary --npm.tag=canary --config .release-it.pre.json",
|
||||
"release:beta": "release-it pre --preReleaseId=beta --npm.tag=beta --config .release-it.pre.js",
|
||||
"release:major": "release-it major",
|
||||
"release:minor": "release-it minor",
|
||||
"release:patch": "release-it patch",
|
||||
@@ -187,7 +186,6 @@
|
||||
"file-loader": "6.2.0",
|
||||
"form-data": "3.0.1",
|
||||
"get-port": "5.1.1",
|
||||
"graphql-request": "6.1.0",
|
||||
"mini-css-extract-plugin": "1.6.2",
|
||||
"node-fetch": "2.6.12",
|
||||
"nodemon": "3.0.1",
|
||||
|
||||
@@ -6,13 +6,13 @@ import { toast } from 'react-toastify'
|
||||
import type { Props } from './types'
|
||||
|
||||
import useDebounce from '../../../hooks/useDebounce'
|
||||
import { formatTimeToNow } from '../../../utilities/formatDate'
|
||||
import { useAllFormFields, useFormModified } from '../../forms/Form/context'
|
||||
import reduceFieldsToValues from '../../forms/Form/reduceFieldsToValues'
|
||||
import { useConfig } from '../../utilities/Config'
|
||||
import { useDocumentInfo } from '../../utilities/DocumentInfo'
|
||||
import { useLocale } from '../../utilities/Locale'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'autosave'
|
||||
|
||||
const Autosave: React.FC<Props> = ({ id, collection, global, publishedDocUpdatedAt }) => {
|
||||
@@ -163,7 +163,7 @@ const Autosave: React.FC<Props> = ({ id, collection, global, publishedDocUpdated
|
||||
{!saving && lastSaved && (
|
||||
<React.Fragment>
|
||||
{t('lastSavedAgo', {
|
||||
distance: Math.round((Number(new Date(lastSaved)) - Number(new Date())) / 1000 / 60),
|
||||
distance: formatTimeToNow(lastSaved, i18n.language),
|
||||
})}
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
@@ -28,10 +28,10 @@
|
||||
gap: var(--base);
|
||||
padding-bottom: 1px;
|
||||
z-index: 1;
|
||||
height: var(--doc-controls-height);
|
||||
}
|
||||
|
||||
&__content {
|
||||
height: var(--doc-controls-height);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
@@ -140,21 +140,21 @@
|
||||
// On mobile, only stick the controls to the top
|
||||
// The timestamps and meta can scroll past
|
||||
// The same container needs to the sticky, though
|
||||
// So we use a static height with a negative top
|
||||
top: calc(var(--doc-controls-height) * -1);
|
||||
// So we use a static height with a negative top equal to the meta height plus top padding
|
||||
top: calc(var(--base) * -2);
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
|
||||
&__wrapper {
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
height: unset;
|
||||
}
|
||||
|
||||
&__content {
|
||||
width: 100%;
|
||||
align-items: flex-start;
|
||||
padding-top: calc(var(--base) / 2);
|
||||
overflow: auto;
|
||||
height: calc(var(--base) * 2);
|
||||
|
||||
// this container has a fixed height
|
||||
// this means the scrollbar (when present) overlaps the content
|
||||
@@ -167,26 +167,52 @@
|
||||
width: auto;
|
||||
gap: calc(var(--base) / 2);
|
||||
margin-left: var(--gutter-h);
|
||||
margin-right: var(--gutter-h);
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: sticky;
|
||||
right: 0;
|
||||
width: calc(var(--base) * 2);
|
||||
height: var(--base);
|
||||
background: linear-gradient(to right, transparent, var(--theme-bg));
|
||||
flex-shrink: 0;
|
||||
z-index: 1111;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__controls-wrapper {
|
||||
width: 100%;
|
||||
transform: translate3d(0, 0, 0);
|
||||
padding: calc(var(--base) / 2) 0;
|
||||
overflow: auto;
|
||||
padding-right: var(--gutter-h);
|
||||
justify-content: space-between;
|
||||
height: var(--doc-controls-height);
|
||||
border-top: 1px solid var(--theme-elevation-100);
|
||||
}
|
||||
|
||||
&__controls {
|
||||
margin-left: var(--gutter-h);
|
||||
margin-right: var(--gutter-h);
|
||||
}
|
||||
padding-left: var(--gutter-h);
|
||||
overflow: auto;
|
||||
|
||||
&__popup {
|
||||
// TODO: the container needs to overflow on mobile
|
||||
// But the popup interferes with this because it requires overflow in order to be visible
|
||||
// So we likely need to outright show the controls nested within the popup on mobile
|
||||
display: none;
|
||||
// do not show scrollbar because the parent container has a static height
|
||||
// this container has a gradient overlay as visual indication of `overflow: scroll`
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: sticky;
|
||||
right: 0;
|
||||
width: calc(var(--base) * 2);
|
||||
height: calc(var(--base) * 1.5);
|
||||
background: linear-gradient(to right, transparent, var(--theme-bg));
|
||||
flex-shrink: 0;
|
||||
z-index: 1111;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ import React, { Fragment } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import type { CollectionPermission, GlobalPermission } from '../../../../auth'
|
||||
import type { SanitizedCollectionConfig, SanitizedGlobalConfig } from '../../../../exports/types'
|
||||
import type { SanitizedCollectionConfig } from '../../../../collections/config/types'
|
||||
import type { SanitizedGlobalConfig } from '../../../../globals/config/types'
|
||||
|
||||
import { formatDate } from '../../../utilities/formatDate'
|
||||
import { useConfig } from '../../utilities/Config'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useModal } from '@faceless-ui/modal'
|
||||
import queryString from 'qs'
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'react-toastify'
|
||||
@@ -16,6 +17,7 @@ import X from '../../icons/X'
|
||||
import { useAuth } from '../../utilities/Auth'
|
||||
import { useConfig } from '../../utilities/Config'
|
||||
import { DocumentInfoProvider, useDocumentInfo } from '../../utilities/DocumentInfo'
|
||||
import { useFormQueryParams } from '../../utilities/FormQueryParams'
|
||||
import { useLocale } from '../../utilities/Locale'
|
||||
import RenderCustomComponent from '../../utilities/RenderCustomComponent'
|
||||
import DefaultEdit from '../../views/collections/Edit/Default'
|
||||
@@ -42,6 +44,8 @@ const Content: React.FC<DocumentDrawerProps> = ({
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [collectionConfig] = useRelatedCollections(collectionSlug)
|
||||
const config = useConfig()
|
||||
const { formQueryParams } = useFormQueryParams()
|
||||
const formattedQueryParams = queryString.stringify(formQueryParams)
|
||||
|
||||
const { admin: { components: { views: { Edit } = {} } = {} } = {} } = collectionConfig
|
||||
|
||||
@@ -117,8 +121,8 @@ const Content: React.FC<DocumentDrawerProps> = ({
|
||||
const apiURL = id ? `${serverURL}${api}/${collectionSlug}/${id}?locale=${locale}` : null
|
||||
|
||||
const action = `${serverURL}${api}/${collectionSlug}${
|
||||
id ? `/${id}` : ''
|
||||
}?locale=${locale}&fallback-locale=null`
|
||||
isEditing ? `/${id}` : ''
|
||||
}?${formattedQueryParams}`
|
||||
|
||||
const hasSavePermission =
|
||||
(isEditing && docPermissions?.update?.permission) ||
|
||||
|
||||
@@ -17,6 +17,24 @@
|
||||
padding: 0;
|
||||
overflow: auto;
|
||||
|
||||
// this container has a gradient overlay as visual indication of `overflow: scroll`
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: sticky;
|
||||
right: 0;
|
||||
width: calc(var(--base) * 2);
|
||||
height: calc(var(--base) * 2);
|
||||
background: linear-gradient(to right, transparent, var(--theme-bg));
|
||||
flex-shrink: 0;
|
||||
z-index: 1111;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&__tabs {
|
||||
padding: 0;
|
||||
margin-left: var(--gutter-h);
|
||||
|
||||
@@ -35,11 +35,6 @@ $header-height: base(5);
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
|
||||
&__cancel,
|
||||
&__save {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
&__toolWrap {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
@@ -50,14 +50,13 @@ export const EditUpload: React.FC<{
|
||||
y: uploadEdits?.focalPoint?.y || 50,
|
||||
})
|
||||
const [checkBounds, setCheckBounds] = useState<boolean>(false)
|
||||
const [originalHeight, setOriginalHeight] = useState<number>(0)
|
||||
const [originalWidth, setOriginalWidth] = useState<number>(0)
|
||||
|
||||
const focalWrapRef = useRef<HTMLDivElement | undefined>()
|
||||
const imageRef = useRef<HTMLImageElement | undefined>()
|
||||
const cropRef = useRef<HTMLDivElement | undefined>()
|
||||
|
||||
const originalHeight = imageRef.current ? imageRef.current.naturalHeight : 0
|
||||
const originalWidth = imageRef.current ? imageRef.current.naturalWidth : 0
|
||||
|
||||
const fineTuneCrop = ({ dimension, value }: { dimension: 'height' | 'width'; value: string }) => {
|
||||
const intValue = parseInt(value)
|
||||
if (dimension === 'width' && intValue >= originalWidth) return null
|
||||
@@ -152,7 +151,15 @@ export const EditUpload: React.FC<{
|
||||
return <div className={`${baseClass}__crop-window`} ref={cropRef} />
|
||||
}}
|
||||
>
|
||||
<img alt={t('upload:setCropArea')} ref={imageRef} src={fileSrcToUse} />
|
||||
<img
|
||||
alt={t('upload:setCropArea')}
|
||||
onLoad={(e) => {
|
||||
setOriginalHeight(e.currentTarget.naturalHeight)
|
||||
setOriginalWidth(e.currentTarget.naturalWidth)
|
||||
}}
|
||||
ref={imageRef}
|
||||
src={fileSrcToUse}
|
||||
/>
|
||||
</ReactCrop>
|
||||
) : (
|
||||
<img alt={t('upload:setFocalPoint')} ref={imageRef} src={fileSrcToUse} />
|
||||
|
||||
@@ -6,16 +6,22 @@
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
position: absolute;
|
||||
position: relative;
|
||||
|
||||
--hamburger-padding: 5px;
|
||||
--hamburger-padding: 8px;
|
||||
--hamburger-size: 9px;
|
||||
--hamburger-line-gap: 3px;
|
||||
|
||||
padding: var(--hamburger-padding);
|
||||
border: 1px solid var(--theme-elevation-350);
|
||||
border: 1px solid var(--theme-elevation-150);
|
||||
color: var(--theme-text);
|
||||
border-radius: 3px;
|
||||
|
||||
&:hover {
|
||||
border: 1px solid var(--theme-elevation-500);
|
||||
background-color: var(--theme-elevation-100);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@@ -92,6 +92,12 @@
|
||||
right: base(2);
|
||||
}
|
||||
|
||||
&--nav-open {
|
||||
.app-header__localizer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__mobile-nav-toggler {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Hamburger } from '../Hamburger'
|
||||
import Localizer from '../Localizer'
|
||||
import { LocalizerLabel } from '../Localizer/LocalizerLabel'
|
||||
import { NavToggler } from '../Nav/NavToggler'
|
||||
import { useNav } from '../Nav/context'
|
||||
import StepNav from '../StepNav'
|
||||
import './index.scss'
|
||||
|
||||
@@ -21,8 +22,10 @@ export const AppHeader: React.FC = (props) => {
|
||||
routes: { admin: adminRoute },
|
||||
} = useConfig()
|
||||
|
||||
const { navOpen } = useNav()
|
||||
|
||||
return (
|
||||
<header className={[baseClass].filter(Boolean).join(' ')}>
|
||||
<header className={[baseClass, navOpen && `${baseClass}--nav-open`].filter(Boolean).join(' ')}>
|
||||
<div className={`${baseClass}__bg`} />
|
||||
<div className={`${baseClass}__content`}>
|
||||
<div className={`${baseClass}__wrapper`}>
|
||||
|
||||
@@ -6,11 +6,5 @@
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
height: var(--app-header-height);
|
||||
width: var(--gutter-h);
|
||||
cursor: pointer;
|
||||
|
||||
@include small-break {
|
||||
width: calc(var(--gutter-h) * 2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,11 +19,21 @@ export const NavContext = React.createContext<NavContextType>({
|
||||
|
||||
export const useNav = () => React.useContext(NavContext)
|
||||
|
||||
const getNavPreference = async (getPreference): Promise<boolean> => {
|
||||
const navPrefs = await getPreference('nav')
|
||||
const preferredState = navPrefs?.open
|
||||
if (typeof preferredState === 'boolean') {
|
||||
return preferredState
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
export const NavProvider: React.FC<{
|
||||
children: React.ReactNode
|
||||
}> = ({ children }) => {
|
||||
const {
|
||||
breakpoints: { l: largeBreak },
|
||||
breakpoints: { l: largeBreak, m: midBreak, s: smallBreak },
|
||||
} = useWindowInfo()
|
||||
|
||||
const { getPreference } = usePreferences()
|
||||
@@ -40,16 +50,11 @@ export const NavProvider: React.FC<{
|
||||
useEffect(() => {
|
||||
if (largeBreak === false) {
|
||||
const setNavFromPreferences = async () => {
|
||||
const navPrefs = await getPreference('nav')
|
||||
const preferredState = navPrefs?.open
|
||||
if (typeof preferredState === 'boolean') {
|
||||
setNavOpen(preferredState)
|
||||
} else {
|
||||
setNavOpen(true)
|
||||
}
|
||||
const preferredState = await getNavPreference(getPreference)
|
||||
setNavOpen(preferredState)
|
||||
}
|
||||
|
||||
setNavFromPreferences()
|
||||
setNavFromPreferences() // eslint-disable-line @typescript-eslint/no-floating-promises
|
||||
}
|
||||
}, [largeBreak, getPreference, setNavOpen])
|
||||
|
||||
@@ -58,7 +63,7 @@ export const NavProvider: React.FC<{
|
||||
useEffect(() => {
|
||||
let unlisten: () => void
|
||||
|
||||
if (largeBreak) {
|
||||
if (midBreak) {
|
||||
unlisten = history.listen(() => {
|
||||
setNavOpen(false)
|
||||
})
|
||||
@@ -67,27 +72,28 @@ export const NavProvider: React.FC<{
|
||||
}
|
||||
|
||||
return () => unlisten && unlisten()
|
||||
}, [history, setNavOpen, largeBreak])
|
||||
|
||||
// on smaller screens where the nav is a modal
|
||||
// close the nav when the user resizes down to a smaller screen
|
||||
useEffect(() => {
|
||||
if (largeBreak) {
|
||||
setNavOpen(false)
|
||||
}
|
||||
}, [largeBreak])
|
||||
}, [history, setNavOpen, midBreak])
|
||||
|
||||
// on open and close, lock the body scroll
|
||||
// do not do this on desktop, the sidebar is not a modal
|
||||
useEffect(() => {
|
||||
if (navRef.current) {
|
||||
if (navOpen && largeBreak) {
|
||||
if (navOpen && midBreak) {
|
||||
disableBodyScroll(navRef.current)
|
||||
} else {
|
||||
enableBodyScroll(navRef.current)
|
||||
}
|
||||
}
|
||||
}, [navOpen, largeBreak])
|
||||
}, [navOpen, midBreak])
|
||||
|
||||
// on smaller screens where the nav is a modal
|
||||
// close the nav when the user resizes down to mobile
|
||||
// the sidebar is a modal on mobile
|
||||
useEffect(() => {
|
||||
if (largeBreak === false || midBreak === false || smallBreak === false) {
|
||||
setNavOpen(false)
|
||||
}
|
||||
}, [largeBreak, midBreak, smallBreak])
|
||||
|
||||
// when the component unmounts, clear all body scroll locks
|
||||
useEffect(() => {
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: var(--app-header-height) base(1.5) base(2) base(1.25);
|
||||
padding: var(--app-header-height) base(1) base(2) base(1);
|
||||
overflow-y: auto;
|
||||
|
||||
// remove the scrollbar here to prevent layout shift as nav groups are toggled
|
||||
@@ -128,12 +128,7 @@
|
||||
|
||||
@include mid-break {
|
||||
&__scroll {
|
||||
padding: var(--app-header-height) base(0.75) base(2) base(0.75);
|
||||
}
|
||||
|
||||
nav a {
|
||||
font-size: base(0.875);
|
||||
line-height: base(1.5);
|
||||
padding: var(--app-header-height) base(0.5) base(2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,9 +136,12 @@
|
||||
&__scroll {
|
||||
padding: var(--app-header-height) var(--gutter-h) base(2);
|
||||
}
|
||||
}
|
||||
|
||||
@include extra-small-break {
|
||||
nav a {
|
||||
font-size: base(0.875);
|
||||
line-height: base(1.5);
|
||||
}
|
||||
|
||||
&--nav-open {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
}
|
||||
|
||||
&__preview {
|
||||
max-height: calc(100% - base(4));
|
||||
max-height: calc(100% - base(6));
|
||||
padding: base(1.5) base(1.5) base(1.5) var(--gutter-h);
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ const PreviewSizes: React.FC<{
|
||||
doc?: Data & {
|
||||
sizes?: FileSizes
|
||||
}
|
||||
updatedAt?: string
|
||||
}> = ({ collection, doc, updatedAt }) => {
|
||||
imageCacheTag?: string
|
||||
}> = ({ collection, doc, imageCacheTag }) => {
|
||||
const {
|
||||
upload: { imageSizes, staticURL },
|
||||
} = collection
|
||||
@@ -39,20 +39,15 @@ const PreviewSizes: React.FC<{
|
||||
const [selectedSize, setSelectedSize] = useState<null | string>(
|
||||
orderedSizes?.[imageSizes[0]?.name]?.filename ? imageSizes[0]?.name : null,
|
||||
)
|
||||
const [appendUrl, setAppendUrl] = useState<boolean>(false)
|
||||
|
||||
const generateImageUrl = (filename) => {
|
||||
const query = appendUrl ? `?${Date.now()}` : ''
|
||||
return `${staticURL}/${filename}${query}`
|
||||
return `${staticURL}/${filename}${imageCacheTag ? `?${imageCacheTag}` : ''}`
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setOrderedSizes(sortSizes(sizes, imageSizes))
|
||||
}, [sizes, imageSizes, updatedAt])
|
||||
}, [sizes, imageSizes, imageCacheTag])
|
||||
|
||||
useEffect(() => {
|
||||
setAppendUrl(true)
|
||||
}, [updatedAt])
|
||||
const mainPreviewSrc = generateImageUrl(`${orderedSizes[selectedSize]?.filename}`)
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
@@ -61,17 +56,15 @@ const PreviewSizes: React.FC<{
|
||||
<div className={`${baseClass}__sizeName`}>{selectedSize}</div>
|
||||
<Meta {...(selectedSize && orderedSizes[selectedSize])} staticURL={staticURL} />
|
||||
</div>
|
||||
<img
|
||||
alt={doc.filename}
|
||||
className={`${baseClass}__preview`}
|
||||
src={generateImageUrl(`${orderedSizes[selectedSize]?.filename}`)}
|
||||
/>
|
||||
<img alt={doc.filename} className={`${baseClass}__preview`} src={mainPreviewSrc} />
|
||||
</div>
|
||||
<div className={`${baseClass}__listWrap`}>
|
||||
<div className={`${baseClass}__list`}>
|
||||
{Object.entries(orderedSizes).map(([key, val]) => {
|
||||
const selected = selectedSize === key
|
||||
if (val?.filename) {
|
||||
const previewSrc = generateImageUrl(val.filename)
|
||||
|
||||
if (previewSrc) {
|
||||
return (
|
||||
<div
|
||||
className={[`${baseClass}__sizeOption`, selected && `${baseClass}--selected`]
|
||||
@@ -88,7 +81,7 @@ const PreviewSizes: React.FC<{
|
||||
tabIndex={0}
|
||||
>
|
||||
<div className={`${baseClass}__image`}>
|
||||
<img alt={val.filename} src={generateImageUrl(val.filename)} />
|
||||
<img alt={val.filename} src={previewSrc} />
|
||||
</div>
|
||||
<div className={`${baseClass}__sizeMeta`}>
|
||||
<div className={`${baseClass}__sizeName`}>{key}</div>
|
||||
|
||||
@@ -25,12 +25,9 @@ const Thumbnail: React.FC<Props> = (props) => {
|
||||
const classes = [baseClass, `${baseClass}--size-${size || 'medium'}`, className].join(' ')
|
||||
|
||||
useEffect(() => {
|
||||
if (doc && collection && thumbnailSRC) {
|
||||
if (thumbnailSRC) {
|
||||
setSrc(`${thumbnailSRC}${imageCacheTag ? `?${imageCacheTag}` : ''}`)
|
||||
}
|
||||
if (fileSrc) {
|
||||
setSrc(fileSrc)
|
||||
}
|
||||
}, [doc, collection, thumbnailSRC, fileSrc, imageCacheTag])
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import type { Props } from './types'
|
||||
@@ -37,7 +37,7 @@ const RenderFields: React.FC<Props> = (props) => {
|
||||
|
||||
const { i18n, t } = useTranslation('general')
|
||||
const [hasRendered, setHasRendered] = useState(Boolean(forceRender))
|
||||
const [intersectionRef, entry] = useIntersect(intersectionObserverOptions)
|
||||
const [intersectionRef, entry] = useIntersect(intersectionObserverOptions, forceRender)
|
||||
|
||||
const isIntersecting = Boolean(entry?.isIntersecting)
|
||||
const isAboveViewport = entry?.boundingClientRect?.top < 0
|
||||
@@ -105,6 +105,7 @@ const RenderFields: React.FC<Props> = (props) => {
|
||||
readOnly,
|
||||
},
|
||||
fieldTypes,
|
||||
forceRender,
|
||||
indexPath:
|
||||
'indexPath' in props ? `${props?.indexPath}.${fieldIndex}` : `${fieldIndex}`,
|
||||
path: field.path || (isFieldAffectingData && 'name' in field ? field.name : ''),
|
||||
|
||||
@@ -24,6 +24,7 @@ type ArrayRowProps = UseDraggableSortableReturn &
|
||||
CustomRowLabel?: RowLabelType
|
||||
addRow: (rowIndex: number) => void
|
||||
duplicateRow: (rowIndex: number) => void
|
||||
forceRender?: boolean
|
||||
hasMaxRows?: boolean
|
||||
moveRow: (fromIndex: number, toIndex: number) => void
|
||||
readOnly?: boolean
|
||||
@@ -40,6 +41,7 @@ export const ArrayRow: React.FC<ArrayRowProps> = ({
|
||||
duplicateRow,
|
||||
fieldTypes,
|
||||
fields,
|
||||
forceRender = false,
|
||||
hasMaxRows,
|
||||
indexPath,
|
||||
labels,
|
||||
@@ -126,10 +128,11 @@ export const ArrayRow: React.FC<ArrayRowProps> = ({
|
||||
path: createNestedFieldPath(path, field),
|
||||
}))}
|
||||
fieldTypes={fieldTypes}
|
||||
forceRender={forceRender}
|
||||
indexPath={indexPath}
|
||||
margins="small"
|
||||
permissions={permissions?.fields}
|
||||
readOnly={readOnly}
|
||||
margins="small"
|
||||
/>
|
||||
</Collapsible>
|
||||
</div>
|
||||
|
||||
@@ -32,6 +32,7 @@ const ArrayFieldType: React.FC<Props> = (props) => {
|
||||
admin: { className, components, condition, description, readOnly },
|
||||
fieldTypes,
|
||||
fields,
|
||||
forceRender = false,
|
||||
indexPath,
|
||||
localized,
|
||||
maxRows,
|
||||
@@ -234,6 +235,7 @@ const ArrayFieldType: React.FC<Props> = (props) => {
|
||||
duplicateRow={duplicateRow}
|
||||
fieldTypes={fieldTypes}
|
||||
fields={fields}
|
||||
forceRender={forceRender}
|
||||
hasMaxRows={hasMaxRows}
|
||||
indexPath={indexPath}
|
||||
labels={labels}
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { ArrayField } from '../../../../../fields/config/types'
|
||||
|
||||
export type Props = Omit<ArrayField, 'type'> & {
|
||||
fieldTypes: FieldTypes
|
||||
forceRender?: boolean
|
||||
indexPath: string
|
||||
label: false | string
|
||||
path?: string
|
||||
|
||||
@@ -24,6 +24,7 @@ type BlockFieldProps = UseDraggableSortableReturn &
|
||||
addRow: (rowIndex: number, blockType: string) => void
|
||||
blockToRender: Block
|
||||
duplicateRow: (rowIndex: number) => void
|
||||
forceRender?: boolean
|
||||
hasMaxRows?: boolean
|
||||
moveRow: (fromIndex: number, toIndex: number) => void
|
||||
readOnly: boolean
|
||||
@@ -40,6 +41,7 @@ export const BlockRow: React.FC<BlockFieldProps> = ({
|
||||
blocks,
|
||||
duplicateRow,
|
||||
fieldTypes,
|
||||
forceRender,
|
||||
hasMaxRows,
|
||||
indexPath,
|
||||
labels,
|
||||
@@ -130,6 +132,7 @@ export const BlockRow: React.FC<BlockFieldProps> = ({
|
||||
path: createNestedFieldPath(path, field),
|
||||
}))}
|
||||
fieldTypes={fieldTypes}
|
||||
forceRender={forceRender}
|
||||
indexPath={indexPath}
|
||||
margins="small"
|
||||
permissions={permissions?.blocks?.[row.blockType]?.fields}
|
||||
|
||||
@@ -37,6 +37,7 @@ const BlocksField: React.FC<Props> = (props) => {
|
||||
admin: { className, condition, description, readOnly },
|
||||
blocks,
|
||||
fieldTypes,
|
||||
forceRender = false,
|
||||
indexPath,
|
||||
label,
|
||||
labels: labelsFromProps,
|
||||
@@ -238,6 +239,7 @@ const BlocksField: React.FC<Props> = (props) => {
|
||||
blocks={blocks}
|
||||
duplicateRow={duplicateRow}
|
||||
fieldTypes={fieldTypes}
|
||||
forceRender={forceRender}
|
||||
hasMaxRows={hasMaxRows}
|
||||
indexPath={indexPath}
|
||||
labels={labels}
|
||||
@@ -297,7 +299,7 @@ const BlocksField: React.FC<Props> = (props) => {
|
||||
</DrawerToggler>
|
||||
<BlocksDrawer
|
||||
addRow={addRow}
|
||||
addRowIndex={value || 0}
|
||||
addRowIndex={rows?.length || 0}
|
||||
blocks={blocks}
|
||||
drawerSlug={drawerSlug}
|
||||
labels={labels}
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { BlockField } from '../../../../../fields/config/types'
|
||||
|
||||
export type Props = Omit<BlockField, 'type'> & {
|
||||
fieldTypes: FieldTypes
|
||||
forceRender?: boolean
|
||||
indexPath: string
|
||||
path?: string
|
||||
permissions: FieldPermissions
|
||||
|
||||
@@ -14,9 +14,9 @@ import { WatchChildErrors } from '../../WatchChildErrors'
|
||||
import withCondition from '../../withCondition'
|
||||
import { useRow } from '../Row/provider'
|
||||
import { useTabs } from '../Tabs/provider'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
import './index.scss'
|
||||
import { GroupProvider, useGroup } from './provider'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
|
||||
const baseClass = 'group-field'
|
||||
|
||||
@@ -26,6 +26,7 @@ const Group: React.FC<Props> = (props) => {
|
||||
admin: { className, description, hideGutter = false, readOnly, style, width },
|
||||
fieldTypes,
|
||||
fields,
|
||||
forceRender = false,
|
||||
indexPath,
|
||||
label,
|
||||
path: pathFromProps,
|
||||
@@ -88,6 +89,7 @@ const Group: React.FC<Props> = (props) => {
|
||||
path: createNestedFieldPath(path, subField),
|
||||
}))}
|
||||
fieldTypes={fieldTypes}
|
||||
forceRender={forceRender}
|
||||
indexPath={indexPath}
|
||||
margins="small"
|
||||
permissions={permissions?.fields}
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { GroupField } from '../../../../../fields/config/types'
|
||||
|
||||
export type Props = Omit<GroupField, 'type'> & {
|
||||
fieldTypes: FieldTypes
|
||||
forceRender?: boolean
|
||||
indexPath: string
|
||||
path?: string
|
||||
permissions: FieldPermissions
|
||||
|
||||
@@ -14,6 +14,7 @@ const Row: React.FC<Props> = (props) => {
|
||||
admin: { className, readOnly },
|
||||
fieldTypes,
|
||||
fields,
|
||||
forceRender = false,
|
||||
indexPath,
|
||||
path,
|
||||
permissions,
|
||||
@@ -28,6 +29,7 @@ const Row: React.FC<Props> = (props) => {
|
||||
path: createNestedFieldPath(path, field),
|
||||
}))}
|
||||
fieldTypes={fieldTypes}
|
||||
forceRender={forceRender}
|
||||
indexPath={indexPath}
|
||||
permissions={permissions}
|
||||
readOnly={readOnly}
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { RowField } from '../../../../../fields/config/types'
|
||||
|
||||
export type Props = Omit<RowField, 'type'> & {
|
||||
fieldTypes: FieldTypes
|
||||
forceRender?: boolean
|
||||
indexPath: string
|
||||
path?: string
|
||||
permissions: FieldPermissions
|
||||
|
||||
@@ -18,9 +18,9 @@ import { createNestedFieldPath } from '../../Form/createNestedFieldPath'
|
||||
import RenderFields from '../../RenderFields'
|
||||
import { WatchChildErrors } from '../../WatchChildErrors'
|
||||
import withCondition from '../../withCondition'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
import './index.scss'
|
||||
import { TabsProvider } from './provider'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
|
||||
const baseClass = 'tabs-field'
|
||||
|
||||
@@ -72,6 +72,7 @@ const TabsField: React.FC<Props> = (props) => {
|
||||
const {
|
||||
admin: { className, readOnly },
|
||||
fieldTypes,
|
||||
forceRender = false,
|
||||
indexPath,
|
||||
path,
|
||||
permissions,
|
||||
@@ -188,7 +189,7 @@ const TabsField: React.FC<Props> = (props) => {
|
||||
}
|
||||
})}
|
||||
fieldTypes={fieldTypes}
|
||||
forceRender
|
||||
forceRender={forceRender}
|
||||
indexPath={indexPath}
|
||||
key={String(activeTabConfig.label)}
|
||||
margins="small"
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { TabsField } from '../../../../../fields/config/types'
|
||||
|
||||
export type Props = Omit<TabsField, 'type'> & {
|
||||
fieldTypes: FieldTypes
|
||||
forceRender?: boolean
|
||||
indexPath: string
|
||||
path?: string
|
||||
permissions: FieldPermissions
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
.graphic-account {
|
||||
vector-effect: non-scaling-stroke;
|
||||
overflow: visible;
|
||||
|
||||
&__bg {
|
||||
fill: var(--theme-elevation-100);
|
||||
fill: var(--theme-elevation-50);
|
||||
stroke: var(--theme-elevation-200);
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
&__head,
|
||||
&__body {
|
||||
fill: var(--theme-elevation-300);
|
||||
fill: var(--theme-elevation-200);
|
||||
}
|
||||
|
||||
&--active {
|
||||
.graphic-account {
|
||||
&__bg {
|
||||
fill: var(--theme-elevation-500);
|
||||
stroke: var(--theme-text);
|
||||
}
|
||||
|
||||
&__head,
|
||||
@@ -25,6 +31,7 @@
|
||||
.graphic-account {
|
||||
&__bg {
|
||||
fill: var(--theme-elevation-200);
|
||||
stroke: var(--theme-elevation-600);
|
||||
}
|
||||
|
||||
&__head,
|
||||
@@ -41,6 +48,12 @@
|
||||
.graphic-account {
|
||||
&__bg {
|
||||
fill: var(--theme-elevation-300);
|
||||
stroke: var(--theme-elevation-600);
|
||||
}
|
||||
|
||||
&__head,
|
||||
&__body {
|
||||
fill: var(--theme-elevation-600);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__nav-toggler {
|
||||
&__nav-toggler-wrapper {
|
||||
position: fixed;
|
||||
z-index: var(--z-modal);
|
||||
top: 0;
|
||||
@@ -40,113 +40,32 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__nav-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
background: var(--theme-overlay);
|
||||
padding: 0;
|
||||
border: 0;
|
||||
z-index: var(--z-modal);
|
||||
cursor: pointer;
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
// on large break, push the document off the screen
|
||||
// show an overlay to prevent clicking on the document
|
||||
@include large-break {
|
||||
width: calc(100% + var(--nav-width));
|
||||
transform: translate3d(0, 0, 0);
|
||||
|
||||
&__wrap {
|
||||
transform: translate3d(calc(var(--nav-width) * -1), 0, 0);
|
||||
}
|
||||
|
||||
&__nav-overlay {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&--nav-open {
|
||||
transform: translate3d(0, 0, 0);
|
||||
|
||||
.template-default {
|
||||
&__wrap {
|
||||
pointer-events: none;
|
||||
transform: translate3d(0, 0, 0);
|
||||
transition: transform var(--nav-trans-time) linear;
|
||||
}
|
||||
|
||||
&__nav-overlay {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__nav-toggler {
|
||||
.hamburger {
|
||||
left: calc(var(--base) * 1.25);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include mid-break {
|
||||
&__nav-toggler {
|
||||
&__nav-toggler-wrapper {
|
||||
.hamburger {
|
||||
left: calc(var(--base) * 0.75);
|
||||
left: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include small-break {
|
||||
&__nav-toggler {
|
||||
.hamburger {
|
||||
left: calc(var(--base) * 0.5);
|
||||
}
|
||||
&.nav-toggler--is-open .hamburger {
|
||||
display: flex;
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include small-break {
|
||||
&__nav-toggler {
|
||||
width: unset;
|
||||
justify-content: unset;
|
||||
|
||||
.hamburger {
|
||||
opacity: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__nav-toggler.nav-toggler--is-open .hamburger {
|
||||
display: flex;
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
@include extra-small-break {
|
||||
&--nav-open {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
&__nav-toggler.nav-toggler--is-open .hamburger {
|
||||
display: none;
|
||||
&__nav-toggler-wrapper {
|
||||
width: unset;
|
||||
justify-content: unset;
|
||||
|
||||
.hamburger {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.template-default {
|
||||
&__wrap {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
&__nav-overlay {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,17 +44,13 @@ const Default: React.FC<Props> = ({ children, className }) => {
|
||||
<div className={`${baseClass}__wrap`}>
|
||||
<AppHeader />
|
||||
{children}
|
||||
<button
|
||||
aria-label={t('close')}
|
||||
className={`${baseClass}__nav-overlay`}
|
||||
onClick={() => setNavOpen(!navOpen)}
|
||||
type="button"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<NavToggler className={`${baseClass}__nav-toggler`} id="nav-toggler">
|
||||
<Hamburger closeIcon="collapse" isActive={navOpen} />
|
||||
</NavToggler>
|
||||
<div className={`${baseClass}__nav-toggler-wrapper`} id="nav-toggler">
|
||||
<NavToggler className={`${baseClass}__nav-toggler`}>
|
||||
<Hamburger closeIcon="collapse" isActive={navOpen} />
|
||||
</NavToggler>
|
||||
</div>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,13 +3,10 @@
|
||||
.global-default-edit {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
--doc-sidebar-width: 325px;
|
||||
|
||||
&--has-sidebar {
|
||||
.global-default-edit {
|
||||
&__main {
|
||||
width: 66.66%;
|
||||
}
|
||||
|
||||
&__edit {
|
||||
[dir='ltr'] & {
|
||||
top: 0;
|
||||
@@ -40,6 +37,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
&__edit {
|
||||
@@ -48,11 +46,16 @@
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
&__auth {
|
||||
margin-bottom: var(--base);
|
||||
}
|
||||
|
||||
&__sidebar-wrap {
|
||||
position: sticky;
|
||||
top: var(--doc-controls-height);
|
||||
width: 33.33%;
|
||||
height: calc(100vh - var(--doc-controls-height));
|
||||
min-width: var(--doc-sidebar-width);
|
||||
}
|
||||
|
||||
&__sidebar {
|
||||
@@ -71,24 +74,16 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--base);
|
||||
padding: calc(var(--base) * 1.5) var(--gutter-h) var(--spacing-view-bottom)
|
||||
calc(var(--base) * 2);
|
||||
padding-top: calc(var(--base) * 1.5);
|
||||
padding-left: calc(var(--base) * 2);
|
||||
padding-right: var(--gutter-h);
|
||||
padding-bottom: var(--spacing-view-bottom);
|
||||
}
|
||||
|
||||
&__label {
|
||||
color: var(--theme-elevation-400);
|
||||
}
|
||||
|
||||
@include large-break {
|
||||
&--no-sidebar {
|
||||
.global-default-edit {
|
||||
&__main {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include mid-break {
|
||||
display: block;
|
||||
|
||||
@@ -130,6 +125,7 @@
|
||||
height: initial;
|
||||
border-left: 0;
|
||||
margin-top: calc(var(--base) / 2);
|
||||
width: var(--doc-sidebar-width);
|
||||
}
|
||||
|
||||
&__form {
|
||||
@@ -158,6 +154,11 @@
|
||||
}
|
||||
|
||||
@include small-break {
|
||||
&__sidebar-wrap {
|
||||
min-width: initial;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__edit {
|
||||
padding-top: calc(var(--base) / 2);
|
||||
}
|
||||
|
||||
@@ -3,21 +3,31 @@ import type { Dispatch } from 'react'
|
||||
import { createContext, useContext } from 'react'
|
||||
|
||||
import type { LivePreviewConfig } from '../../../../../exports/config'
|
||||
import type { fieldSchemaToJSON } from '../../../../../utilities/fieldSchemaToJSON'
|
||||
import type { usePopupWindow } from '../usePopupWindow'
|
||||
import type { SizeReducerAction } from './sizeReducer'
|
||||
|
||||
export interface LivePreviewContextType {
|
||||
appIsReady: boolean
|
||||
breakpoint: LivePreviewConfig['breakpoints'][number]['name']
|
||||
breakpoints: LivePreviewConfig['breakpoints']
|
||||
fieldSchemaJSON?: ReturnType<typeof fieldSchemaToJSON>
|
||||
iframeHasLoaded: boolean
|
||||
iframeRef: React.RefObject<HTMLIFrameElement>
|
||||
isPopupOpen: boolean
|
||||
measuredDeviceSize: {
|
||||
height: number
|
||||
width: number
|
||||
}
|
||||
openPopupWindow: ReturnType<typeof usePopupWindow>['openPopupWindow']
|
||||
popupRef?: React.MutableRefObject<Window | null>
|
||||
previewWindowType: 'iframe' | 'popup'
|
||||
setAppIsReady: (appIsReady: boolean) => void
|
||||
setBreakpoint: (breakpoint: LivePreviewConfig['breakpoints'][number]['name']) => void
|
||||
setHeight: (height: number) => void
|
||||
setIframeHasLoaded: (loaded: boolean) => void
|
||||
setMeasuredDeviceSize: (size: { height: number; width: number }) => void
|
||||
setPreviewWindowType: (previewWindowType: 'iframe' | 'popup') => void
|
||||
setSize: Dispatch<SizeReducerAction>
|
||||
setToolbarPosition: (position: { x: number; y: number }) => void
|
||||
setWidth: (width: number) => void
|
||||
@@ -30,22 +40,31 @@ export interface LivePreviewContextType {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
url: string | undefined
|
||||
zoom: number
|
||||
}
|
||||
|
||||
export const LivePreviewContext = createContext<LivePreviewContextType>({
|
||||
appIsReady: false,
|
||||
breakpoint: undefined,
|
||||
breakpoints: undefined,
|
||||
fieldSchemaJSON: undefined,
|
||||
iframeHasLoaded: false,
|
||||
iframeRef: undefined,
|
||||
isPopupOpen: false,
|
||||
measuredDeviceSize: {
|
||||
height: 0,
|
||||
width: 0,
|
||||
},
|
||||
openPopupWindow: () => {},
|
||||
popupRef: undefined,
|
||||
previewWindowType: 'iframe',
|
||||
setAppIsReady: () => {},
|
||||
setBreakpoint: () => {},
|
||||
setHeight: () => {},
|
||||
setIframeHasLoaded: () => {},
|
||||
setMeasuredDeviceSize: () => {},
|
||||
setPreviewWindowType: () => {},
|
||||
setSize: () => {},
|
||||
setToolbarPosition: () => {},
|
||||
setWidth: () => {},
|
||||
@@ -58,6 +77,7 @@ export const LivePreviewContext = createContext<LivePreviewContextType>({
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
url: undefined,
|
||||
zoom: 1,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,39 +1,48 @@
|
||||
import { DndContext } from '@dnd-kit/core'
|
||||
import React, { useCallback, useEffect } from 'react'
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
|
||||
import type { LivePreviewConfig } from '../../../../../exports/config'
|
||||
import type { Field } from '../../../../../fields/config/types'
|
||||
import type { EditViewProps } from '../../types'
|
||||
import type { usePopupWindow } from '../usePopupWindow'
|
||||
|
||||
import { fieldSchemaToJSON } from '../../../../../utilities/fieldSchemaToJSON'
|
||||
import { customCollisionDetection } from './collisionDetection'
|
||||
import { LivePreviewContext } from './context'
|
||||
import { sizeReducer } from './sizeReducer'
|
||||
|
||||
export type ToolbarProviderProps = EditViewProps & {
|
||||
export type LivePreviewProviderProps = EditViewProps & {
|
||||
appIsReady?: boolean
|
||||
breakpoints?: LivePreviewConfig['breakpoints']
|
||||
children: React.ReactNode
|
||||
deviceSize?: {
|
||||
height: number
|
||||
width: number
|
||||
}
|
||||
popupState: ReturnType<typeof usePopupWindow>
|
||||
isPopupOpen?: boolean
|
||||
openPopupWindow?: ReturnType<typeof usePopupWindow>['openPopupWindow']
|
||||
popupRef?: React.MutableRefObject<Window>
|
||||
url?: string
|
||||
}
|
||||
|
||||
export const LivePreviewProvider: React.FC<ToolbarProviderProps> = (props) => {
|
||||
const { breakpoints, children } = props
|
||||
export const LivePreviewProvider: React.FC<LivePreviewProviderProps> = (props) => {
|
||||
const { breakpoints, children, isPopupOpen, openPopupWindow, popupRef, url } = props
|
||||
|
||||
const [previewWindowType, setPreviewWindowType] = useState<'iframe' | 'popup'>('iframe')
|
||||
|
||||
const [appIsReady, setAppIsReady] = useState(false)
|
||||
|
||||
const iframeRef = React.useRef<HTMLIFrameElement>(null)
|
||||
|
||||
const [iframeHasLoaded, setIframeHasLoaded] = React.useState(false)
|
||||
const [iframeHasLoaded, setIframeHasLoaded] = useState(false)
|
||||
|
||||
const [zoom, setZoom] = React.useState(1)
|
||||
const [zoom, setZoom] = useState(1)
|
||||
|
||||
const [position, setPosition] = React.useState({ x: 0, y: 0 })
|
||||
const [position, setPosition] = useState({ x: 0, y: 0 })
|
||||
|
||||
const [size, setSize] = React.useReducer(sizeReducer, { height: 0, width: 0 })
|
||||
|
||||
const [measuredDeviceSize, setMeasuredDeviceSize] = React.useState({
|
||||
const [measuredDeviceSize, setMeasuredDeviceSize] = useState({
|
||||
height: 0,
|
||||
width: 0,
|
||||
})
|
||||
@@ -41,6 +50,22 @@ export const LivePreviewProvider: React.FC<ToolbarProviderProps> = (props) => {
|
||||
const [breakpoint, setBreakpoint] =
|
||||
React.useState<LivePreviewConfig['breakpoints'][0]['name']>('responsive')
|
||||
|
||||
const [fieldSchemaJSON] = useState(() => {
|
||||
let fields: Field[]
|
||||
|
||||
if ('collection' in props) {
|
||||
const { collection } = props
|
||||
fields = collection.fields
|
||||
}
|
||||
|
||||
if ('global' in props) {
|
||||
const { global } = props
|
||||
fields = global.fields
|
||||
}
|
||||
|
||||
return fieldSchemaToJSON(fields)
|
||||
})
|
||||
|
||||
// The toolbar needs to freely drag and drop around the page
|
||||
const handleDragEnd = (ev) => {
|
||||
// only update position if the toolbar is completely within the preview area
|
||||
@@ -94,24 +119,70 @@ export const LivePreviewProvider: React.FC<ToolbarProviderProps> = (props) => {
|
||||
}
|
||||
}, [breakpoint, breakpoints])
|
||||
|
||||
// Receive the `ready` message from the popup window
|
||||
// This indicates that the app is ready to receive `window.postMessage` events
|
||||
// This is also the only cross-origin way of detecting when a popup window has loaded
|
||||
// Unlike iframe elements which have an `onLoad` handler, there is no way to access `window.open` on popups
|
||||
useEffect(() => {
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
const data = JSON.parse(event.data)
|
||||
|
||||
if (url.startsWith(event.origin) && data.type === 'payload-live-preview' && data.ready) {
|
||||
setAppIsReady(true)
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('message', handleMessage)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('message', handleMessage)
|
||||
}
|
||||
}, [url])
|
||||
|
||||
const handleWindowChange = useCallback(
|
||||
(type: 'iframe' | 'popup') => {
|
||||
setAppIsReady(false)
|
||||
setPreviewWindowType(type)
|
||||
if (type === 'popup') openPopupWindow()
|
||||
},
|
||||
[openPopupWindow],
|
||||
)
|
||||
|
||||
// when the user closes the popup window, switch back to the iframe
|
||||
// the `usePopupWindow` reports the `isPopupOpen` state for us to use here
|
||||
useEffect(() => {
|
||||
if (!isPopupOpen) {
|
||||
handleWindowChange('iframe')
|
||||
}
|
||||
}, [isPopupOpen, handleWindowChange])
|
||||
|
||||
return (
|
||||
<LivePreviewContext.Provider
|
||||
value={{
|
||||
appIsReady,
|
||||
breakpoint,
|
||||
breakpoints,
|
||||
fieldSchemaJSON,
|
||||
iframeHasLoaded,
|
||||
iframeRef,
|
||||
isPopupOpen,
|
||||
measuredDeviceSize,
|
||||
openPopupWindow,
|
||||
popupRef,
|
||||
previewWindowType,
|
||||
setAppIsReady,
|
||||
setBreakpoint,
|
||||
setHeight,
|
||||
setIframeHasLoaded,
|
||||
setMeasuredDeviceSize,
|
||||
setPreviewWindowType: handleWindowChange,
|
||||
setSize,
|
||||
setToolbarPosition: setPosition,
|
||||
setWidth,
|
||||
setZoom,
|
||||
size,
|
||||
toolbarPosition: position,
|
||||
url,
|
||||
zoom,
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@import '../../../../scss/styles.scss';
|
||||
|
||||
.live-preview-iframe {
|
||||
background-color: white;
|
||||
border: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@import '../../../../scss/styles.scss';
|
||||
|
||||
.live-preview-window {
|
||||
background-color: var(--theme-bg);
|
||||
width: 60%;
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
|
||||
import type { LivePreviewConfig } from '../../../../../exports/config'
|
||||
import type { Field } from '../../../../../fields/config/types'
|
||||
import type { EditViewProps } from '../../types'
|
||||
import type { usePopupWindow } from '../usePopupWindow'
|
||||
|
||||
import { fieldSchemaToJSON } from '../../../../../utilities/fieldSchemaToJSON'
|
||||
import { useAllFormFields } from '../../../forms/Form/context'
|
||||
import reduceFieldsToValues from '../../../forms/Form/reduceFieldsToValues'
|
||||
import { LivePreviewProvider } from '../Context'
|
||||
import { useLivePreviewContext } from '../Context/context'
|
||||
import { DeviceContainer } from '../Device'
|
||||
import { IFrame } from '../IFrame'
|
||||
@@ -17,93 +12,81 @@ import './index.scss'
|
||||
|
||||
const baseClass = 'live-preview-window'
|
||||
|
||||
const Preview: React.FC<
|
||||
EditViewProps & {
|
||||
popupState: ReturnType<typeof usePopupWindow>
|
||||
url?: string
|
||||
}
|
||||
> = (props) => {
|
||||
export const LivePreview: React.FC<EditViewProps> = (props) => {
|
||||
const {
|
||||
popupState: { isPopupOpen, popupHasLoaded, popupRef },
|
||||
appIsReady,
|
||||
iframeHasLoaded,
|
||||
iframeRef,
|
||||
popupRef,
|
||||
previewWindowType,
|
||||
setIframeHasLoaded,
|
||||
url,
|
||||
} = props
|
||||
} = useLivePreviewContext()
|
||||
|
||||
const { iframeHasLoaded, iframeRef, setIframeHasLoaded } = useLivePreviewContext()
|
||||
const { breakpoint, fieldSchemaJSON } = useLivePreviewContext()
|
||||
|
||||
const { breakpoint } = useLivePreviewContext()
|
||||
const prevWindowType =
|
||||
React.useRef<ReturnType<typeof useLivePreviewContext>['previewWindowType']>()
|
||||
|
||||
const [fields] = useAllFormFields()
|
||||
|
||||
const [fieldSchemaJSON] = useState(() => {
|
||||
let fields: Field[]
|
||||
|
||||
if ('collection' in props) {
|
||||
const { collection } = props
|
||||
fields = collection.fields
|
||||
}
|
||||
|
||||
if ('global' in props) {
|
||||
const { global } = props
|
||||
fields = global.fields
|
||||
}
|
||||
|
||||
return fieldSchemaToJSON(fields)
|
||||
})
|
||||
|
||||
// The preview could either be an iframe embedded on the page
|
||||
// Or it could be a separate popup window
|
||||
// We need to transmit data to both accordingly
|
||||
useEffect(() => {
|
||||
if (fields && window && 'postMessage' in window) {
|
||||
// For performance, do no reduce fields to values until after the iframe or popup has loaded
|
||||
if (fields && window && 'postMessage' in window && appIsReady) {
|
||||
const values = reduceFieldsToValues(fields, true)
|
||||
|
||||
// TODO: only send `fieldSchemaToJSON` one time
|
||||
// To reduce on large `postMessage` payloads, only send `fieldSchemaToJSON` one time
|
||||
// To do this, the underlying JS function maintains a cache of this value
|
||||
// So we need to send it through each time the window type changes
|
||||
// But only once per window type change, not on every render, because this is a potentially large obj
|
||||
const shouldSendSchema =
|
||||
!prevWindowType.current || prevWindowType.current !== previewWindowType
|
||||
|
||||
prevWindowType.current = previewWindowType
|
||||
|
||||
const message = JSON.stringify({
|
||||
data: values,
|
||||
fieldSchemaJSON,
|
||||
fieldSchemaJSON: shouldSendSchema ? fieldSchemaJSON : undefined,
|
||||
type: 'payload-live-preview',
|
||||
})
|
||||
|
||||
// external window
|
||||
if (isPopupOpen) {
|
||||
setIframeHasLoaded(false)
|
||||
if (popupRef.current) {
|
||||
popupRef.current.postMessage(message, url)
|
||||
}
|
||||
// Post message to external popup window
|
||||
if (previewWindowType === 'popup' && popupRef.current) {
|
||||
popupRef.current.postMessage(message, url)
|
||||
}
|
||||
|
||||
// embedded iframe
|
||||
if (!isPopupOpen) {
|
||||
if (iframeHasLoaded && iframeRef.current) {
|
||||
iframeRef.current.contentWindow?.postMessage(message, url)
|
||||
}
|
||||
// Post message to embedded iframe
|
||||
if (previewWindowType === 'iframe' && iframeRef.current) {
|
||||
iframeRef.current.contentWindow?.postMessage(message, url)
|
||||
}
|
||||
}
|
||||
}, [
|
||||
fields,
|
||||
url,
|
||||
iframeHasLoaded,
|
||||
isPopupOpen,
|
||||
previewWindowType,
|
||||
popupRef,
|
||||
popupHasLoaded,
|
||||
appIsReady,
|
||||
iframeRef,
|
||||
setIframeHasLoaded,
|
||||
fieldSchemaJSON,
|
||||
])
|
||||
|
||||
if (!isPopupOpen) {
|
||||
if (previewWindowType === 'iframe') {
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
baseClass,
|
||||
isPopupOpen && `${baseClass}--popup-open`,
|
||||
breakpoint && breakpoint !== 'responsive' && `${baseClass}--has-breakpoint`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
<div className={`${baseClass}__wrapper`}>
|
||||
<LivePreviewToolbar {...props} iframeRef={iframeRef} url={url} />
|
||||
<LivePreviewToolbar {...props} />
|
||||
<div className={`${baseClass}__main`}>
|
||||
<DeviceContainer>
|
||||
<IFrame ref={iframeRef} setIframeHasLoaded={setIframeHasLoaded} url={url} />
|
||||
@@ -114,29 +97,3 @@ const Preview: React.FC<
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const LivePreview: React.FC<
|
||||
EditViewProps & {
|
||||
livePreviewConfig?: LivePreviewConfig
|
||||
popupState: ReturnType<typeof usePopupWindow>
|
||||
url?: string
|
||||
}
|
||||
> = (props) => {
|
||||
const { livePreviewConfig, url } = props
|
||||
|
||||
const breakpoints: LivePreviewConfig['breakpoints'] = [
|
||||
...(livePreviewConfig?.breakpoints || []),
|
||||
{
|
||||
name: 'responsive',
|
||||
height: '100%',
|
||||
label: 'Responsive',
|
||||
width: '100%',
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<LivePreviewProvider {...props} breakpoints={breakpoints} url={url}>
|
||||
<Preview {...props} />
|
||||
</LivePreviewProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
|
||||
import type { LivePreviewToolbarProps } from '..'
|
||||
import type { EditViewProps } from '../../../types'
|
||||
|
||||
import { X } from '../../../..'
|
||||
import { ExternalLinkIcon } from '../../../../graphics/ExternalLink'
|
||||
@@ -10,13 +10,9 @@ import './index.scss'
|
||||
|
||||
const baseClass = 'live-preview-toolbar-controls'
|
||||
|
||||
export const ToolbarControls: React.FC<LivePreviewToolbarProps> = (props) => {
|
||||
const { breakpoint, breakpoints, setBreakpoint, setZoom, zoom } = useLivePreviewContext()
|
||||
|
||||
const {
|
||||
popupState: { openPopupWindow },
|
||||
url,
|
||||
} = props
|
||||
export const ToolbarControls: React.FC<EditViewProps> = () => {
|
||||
const { breakpoint, breakpoints, setBreakpoint, setPreviewWindowType, setZoom, url, zoom } =
|
||||
useLivePreviewContext()
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
@@ -57,7 +53,15 @@ export const ToolbarControls: React.FC<LivePreviewToolbarProps> = (props) => {
|
||||
<option value={150}>150%</option>
|
||||
<option value={200}>200%</option>
|
||||
</select>
|
||||
<a className={`${baseClass}__external`} href={url} onClick={openPopupWindow} type="button">
|
||||
<a
|
||||
className={`${baseClass}__external`}
|
||||
href={url}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
setPreviewWindowType('popup')
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<ExternalLinkIcon />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useDraggable } from '@dnd-kit/core'
|
||||
import React from 'react'
|
||||
|
||||
import type { ToolbarProviderProps } from '../Context'
|
||||
import type { EditViewProps } from '../../types'
|
||||
|
||||
import DragHandle from '../../../icons/Drag'
|
||||
import { useLivePreviewContext } from '../Context/context'
|
||||
@@ -10,11 +10,7 @@ import './index.scss'
|
||||
|
||||
const baseClass = 'live-preview-toolbar'
|
||||
|
||||
export type LivePreviewToolbarProps = Omit<ToolbarProviderProps, 'children'> & {
|
||||
iframeRef: React.RefObject<HTMLIFrameElement>
|
||||
}
|
||||
|
||||
const DraggableToolbar: React.FC<LivePreviewToolbarProps> = (props) => {
|
||||
const DraggableToolbar: React.FC<EditViewProps> = (props) => {
|
||||
const { toolbarPosition } = useLivePreviewContext()
|
||||
|
||||
const { attributes, listeners, setNodeRef, transform } = useDraggable({
|
||||
@@ -50,7 +46,7 @@ const DraggableToolbar: React.FC<LivePreviewToolbarProps> = (props) => {
|
||||
)
|
||||
}
|
||||
|
||||
const StaticToolbar: React.FC<LivePreviewToolbarProps> = (props) => {
|
||||
const StaticToolbar: React.FC<EditViewProps> = (props) => {
|
||||
return (
|
||||
<div className={[baseClass, `${baseClass}--static`].join(' ')}>
|
||||
<ToolbarControls {...props} />
|
||||
@@ -59,7 +55,7 @@ const StaticToolbar: React.FC<LivePreviewToolbarProps> = (props) => {
|
||||
}
|
||||
|
||||
export const LivePreviewToolbar: React.FC<
|
||||
LivePreviewToolbarProps & {
|
||||
EditViewProps & {
|
||||
draggable?: boolean
|
||||
}
|
||||
> = (props) => {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
.live-preview {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
--gradient: linear-gradient(to left, rgba(0, 0, 0, 0.04) 0%, transparent 100%);
|
||||
|
||||
[dir='rtl'] & {
|
||||
flex-direction: row-reverse;
|
||||
@@ -34,7 +35,7 @@
|
||||
right: 0;
|
||||
width: calc(var(--base) * 2);
|
||||
height: 100%;
|
||||
background: linear-gradient(to left, rgba(0, 0, 0, 0.25) 0%, rgba(0, 0, 0, 0) 100%);
|
||||
background: var(--gradient);
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
@@ -58,6 +59,10 @@
|
||||
&__main {
|
||||
min-height: initial;
|
||||
width: 100%;
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__form {
|
||||
@@ -77,3 +82,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme='dark'] {
|
||||
.live-preview {
|
||||
--gradient: linear-gradient(to left, rgba(0, 0, 0, 0.4) 0%, rgba(0, 0, 0, 0) 100%);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,47 +17,17 @@ import { useDocumentInfo } from '../../utilities/DocumentInfo'
|
||||
import { useLocale } from '../../utilities/Locale'
|
||||
import Meta from '../../utilities/Meta'
|
||||
import { SetStepNav } from '../collections/Edit/SetStepNav'
|
||||
import { LivePreviewProvider } from './Context'
|
||||
import { useLivePreviewContext } from './Context/context'
|
||||
import { LivePreview } from './Preview'
|
||||
import './index.scss'
|
||||
import { usePopupWindow } from './usePopupWindow'
|
||||
|
||||
const baseClass = 'live-preview'
|
||||
|
||||
export const LivePreviewView: React.FC<EditViewProps> = (props) => {
|
||||
const PreviewView: React.FC<EditViewProps> = (props) => {
|
||||
const { i18n, t } = useTranslation('general')
|
||||
const config = useConfig()
|
||||
const documentInfo = useDocumentInfo()
|
||||
const locale = useLocale()
|
||||
|
||||
let livePreviewConfig: LivePreviewConfig = config?.admin?.livePreview
|
||||
|
||||
if ('collection' in props) {
|
||||
livePreviewConfig = {
|
||||
...(livePreviewConfig || {}),
|
||||
...(props?.collection.admin.livePreview || {}),
|
||||
}
|
||||
}
|
||||
|
||||
if ('global' in props) {
|
||||
livePreviewConfig = {
|
||||
...(livePreviewConfig || {}),
|
||||
...(props?.global.admin.livePreview || {}),
|
||||
}
|
||||
}
|
||||
|
||||
const url =
|
||||
typeof livePreviewConfig?.url === 'function'
|
||||
? livePreviewConfig?.url({
|
||||
data: props?.data,
|
||||
documentInfo,
|
||||
locale,
|
||||
})
|
||||
: livePreviewConfig?.url
|
||||
|
||||
const popupState = usePopupWindow({
|
||||
eventType: 'payload-live-preview',
|
||||
url,
|
||||
})
|
||||
const { previewWindowType } = useLivePreviewContext()
|
||||
|
||||
const { apiURL, data, permissions } = props
|
||||
|
||||
@@ -113,14 +83,14 @@ export const LivePreviewView: React.FC<EditViewProps> = (props) => {
|
||||
permissions={permissions}
|
||||
/>
|
||||
<div
|
||||
className={[baseClass, popupState?.isPopupOpen && `${baseClass}--detached`]
|
||||
className={[baseClass, previewWindowType === 'popup' && `${baseClass}--detached`]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
<div
|
||||
className={[
|
||||
`${baseClass}__main`,
|
||||
popupState?.isPopupOpen && `${baseClass}__main--popup-open`,
|
||||
previewWindowType === 'popup' && `${baseClass}__main--popup-open`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
@@ -148,13 +118,67 @@ export const LivePreviewView: React.FC<EditViewProps> = (props) => {
|
||||
)}
|
||||
</Gutter>
|
||||
</div>
|
||||
<LivePreview
|
||||
{...props}
|
||||
livePreviewConfig={livePreviewConfig}
|
||||
popupState={popupState}
|
||||
url={url}
|
||||
/>
|
||||
<LivePreview {...props} />
|
||||
</div>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export const LivePreviewView: React.FC<EditViewProps> = (props) => {
|
||||
const config = useConfig()
|
||||
const documentInfo = useDocumentInfo()
|
||||
const locale = useLocale()
|
||||
|
||||
let livePreviewConfig: LivePreviewConfig = config?.admin?.livePreview
|
||||
|
||||
if ('collection' in props) {
|
||||
livePreviewConfig = {
|
||||
...(livePreviewConfig || {}),
|
||||
...(props?.collection.admin.livePreview || {}),
|
||||
}
|
||||
}
|
||||
|
||||
if ('global' in props) {
|
||||
livePreviewConfig = {
|
||||
...(livePreviewConfig || {}),
|
||||
...(props?.global.admin.livePreview || {}),
|
||||
}
|
||||
}
|
||||
|
||||
const url =
|
||||
typeof livePreviewConfig?.url === 'function'
|
||||
? livePreviewConfig?.url({
|
||||
data: props?.data,
|
||||
documentInfo,
|
||||
locale,
|
||||
})
|
||||
: livePreviewConfig?.url
|
||||
|
||||
const breakpoints: LivePreviewConfig['breakpoints'] = [
|
||||
...(livePreviewConfig?.breakpoints || []),
|
||||
{
|
||||
name: 'responsive',
|
||||
height: '100%',
|
||||
label: 'Responsive',
|
||||
width: '100%',
|
||||
},
|
||||
]
|
||||
|
||||
const { isPopupOpen, openPopupWindow, popupRef } = usePopupWindow({
|
||||
eventType: 'payload-live-preview',
|
||||
url,
|
||||
})
|
||||
|
||||
return (
|
||||
<LivePreviewProvider
|
||||
{...props}
|
||||
breakpoints={breakpoints}
|
||||
isPopupOpen={isPopupOpen}
|
||||
openPopupWindow={openPopupWindow}
|
||||
popupRef={popupRef}
|
||||
url={url}
|
||||
>
|
||||
<PreviewView {...props} />
|
||||
</LivePreviewProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,17 +19,14 @@ export const usePopupWindow = (props: {
|
||||
url: string
|
||||
}): {
|
||||
isPopupOpen: boolean
|
||||
openPopupWindow: (e: React.MouseEvent<HTMLAnchorElement>) => void
|
||||
popupHasLoaded: boolean
|
||||
openPopupWindow: () => void
|
||||
popupRef?: React.MutableRefObject<Window | null>
|
||||
} => {
|
||||
const { eventType, onMessage, url } = props
|
||||
const isReceivingMessage = useRef(false)
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [popupHasLoaded, setPopupHasLoaded] = useState(false)
|
||||
const { serverURL } = useConfig()
|
||||
const popupRef = useRef<Window | null>(null)
|
||||
const hasAttachedMessageListener = useRef(false)
|
||||
|
||||
// Optionally broadcast messages back out to the parent component
|
||||
useEffect(() => {
|
||||
@@ -65,8 +62,10 @@ export const usePopupWindow = (props: {
|
||||
|
||||
// Customize the size, position, and style of the popup window
|
||||
const openPopupWindow = useCallback(
|
||||
(e) => {
|
||||
e.preventDefault()
|
||||
(e?: MouseEvent) => {
|
||||
if (e) {
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
const features = {
|
||||
height: 700,
|
||||
@@ -106,27 +105,6 @@ export const usePopupWindow = (props: {
|
||||
[url],
|
||||
)
|
||||
|
||||
// the only cross-origin way of detecting when a popup window has loaded
|
||||
// we catch a message event that the site rendered within the popup window fires
|
||||
// there is no way in js to add an event listener to a popup window across domains
|
||||
useEffect(() => {
|
||||
if (hasAttachedMessageListener.current) return
|
||||
hasAttachedMessageListener.current = true
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
const data = JSON.parse(event.data)
|
||||
|
||||
if (
|
||||
url.startsWith(event.origin) &&
|
||||
data.type === eventType &&
|
||||
data.popupReady &&
|
||||
!popupHasLoaded
|
||||
) {
|
||||
setPopupHasLoaded(true)
|
||||
}
|
||||
})
|
||||
}, [url, eventType, popupHasLoaded])
|
||||
|
||||
// this is the most stable and widely supported way to check if a popup window is no longer open
|
||||
// we poll its ref every x ms and use the popup window's `closed` property
|
||||
useEffect(() => {
|
||||
@@ -137,7 +115,6 @@ export const usePopupWindow = (props: {
|
||||
if (popupRef.current.closed) {
|
||||
clearInterval(timer)
|
||||
setIsOpen(false)
|
||||
setPopupHasLoaded(false)
|
||||
}
|
||||
}, 1000)
|
||||
} else {
|
||||
@@ -154,7 +131,6 @@ export const usePopupWindow = (props: {
|
||||
return {
|
||||
isPopupOpen: isOpen,
|
||||
openPopupWindow,
|
||||
popupHasLoaded,
|
||||
popupRef,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,10 @@
|
||||
.collection-default-edit {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
--doc-sidebar-width: 500px;
|
||||
|
||||
&--has-sidebar {
|
||||
.collection-default-edit {
|
||||
&__main {
|
||||
width: 66.66%;
|
||||
}
|
||||
|
||||
&__edit {
|
||||
[dir='ltr'] & {
|
||||
top: 0;
|
||||
@@ -40,6 +37,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
&__edit {
|
||||
@@ -55,8 +53,9 @@
|
||||
&__sidebar-wrap {
|
||||
position: sticky;
|
||||
top: var(--doc-controls-height);
|
||||
width: 33.33%;
|
||||
height: calc(100vh - var(--doc-controls-height));
|
||||
width: var(--doc-sidebar-width);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__sidebar {
|
||||
@@ -85,6 +84,10 @@
|
||||
color: var(--theme-elevation-400);
|
||||
}
|
||||
|
||||
@include large-break {
|
||||
--doc-sidebar-width: 350px;
|
||||
}
|
||||
|
||||
@include mid-break {
|
||||
display: block;
|
||||
|
||||
@@ -154,6 +157,10 @@
|
||||
}
|
||||
|
||||
@include small-break {
|
||||
&__sidebar-wrap {
|
||||
min-width: initial;
|
||||
}
|
||||
|
||||
&__edit {
|
||||
padding-top: calc(var(--base) / 2);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ export const DefaultCollectionEdit: React.FC<CollectionEditViewProps> = (props)
|
||||
internalState,
|
||||
isEditing,
|
||||
permissions,
|
||||
updatedAt,
|
||||
} = props
|
||||
|
||||
const { auth, fields, upload } = collection
|
||||
@@ -97,9 +96,7 @@ export const DefaultCollectionEdit: React.FC<CollectionEditViewProps> = (props)
|
||||
verify={auth.verify}
|
||||
/>
|
||||
)}
|
||||
{upload && (
|
||||
<Upload collection={collection} internalState={internalState} updatedAt={updatedAt} />
|
||||
)}
|
||||
{upload && <Upload collection={collection} internalState={internalState} />}
|
||||
<RenderFields
|
||||
className={`${baseClass}__fields`}
|
||||
fieldSchema={fields}
|
||||
|
||||
@@ -201,7 +201,7 @@ export const Upload: React.FC<Props> = (props) => {
|
||||
slug={sizePreviewSlug}
|
||||
title={t('upload:sizesFor', { label: doc?.filename })}
|
||||
>
|
||||
<PreviewSizes collection={collection} doc={doc} updatedAt={updatedAt} />
|
||||
<PreviewSizes collection={collection} doc={doc} />
|
||||
</Drawer>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,10 @@ import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
type Intersect = [setNode: React.Dispatch<Element>, entry: IntersectionObserverEntry]
|
||||
|
||||
const useIntersect = ({ root = null, rootMargin = '0px', threshold = 0 } = {}): Intersect => {
|
||||
const useIntersect = (
|
||||
{ root = null, rootMargin = '0px', threshold = 0 } = {},
|
||||
disable?: boolean,
|
||||
): Intersect => {
|
||||
const [entry, updateEntry] = useState<IntersectionObserverEntry>()
|
||||
const [node, setNode] = useState(null)
|
||||
|
||||
@@ -16,13 +19,16 @@ const useIntersect = ({ root = null, rootMargin = '0px', threshold = 0 } = {}):
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (disable) {
|
||||
return
|
||||
}
|
||||
const { current: currentObserver } = observer
|
||||
currentObserver.disconnect()
|
||||
|
||||
if (node) currentObserver.observe(node)
|
||||
|
||||
return () => currentObserver.disconnect()
|
||||
}, [node])
|
||||
}, [node, disable])
|
||||
|
||||
return [setNode, entry]
|
||||
}
|
||||
|
||||
@@ -40,23 +40,18 @@
|
||||
--spacing-view-bottom: var(--gutter-h);
|
||||
--app-header-height: calc(var(--base) * 3);
|
||||
--doc-controls-height: calc(var(--base) * 3);
|
||||
--nav-width: 300px;
|
||||
--nav-width: 275px;
|
||||
--nav-trans-time: 150ms;
|
||||
|
||||
@include mid-break {
|
||||
--gutter-h: #{base(2)};
|
||||
--app-header-height: calc(var(--base) * 2);
|
||||
--doc-controls-height: calc(var(--base) * 1.5);
|
||||
// set to xs-breakpoint to achieve a seamless transition to full screen
|
||||
--nav-width: var(--breakpoint-xs-width);
|
||||
--doc-controls-height: calc(var(--base) * 2.5);
|
||||
}
|
||||
|
||||
@include small-break {
|
||||
--gutter-h: #{base(0.5)};
|
||||
--spacing-view-bottom: calc(var(--base) * 2);
|
||||
}
|
||||
|
||||
@include extra-small-break {
|
||||
--nav-width: 100vw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { format } from 'date-fns'
|
||||
import { format, formatDistanceToNow } from 'date-fns'
|
||||
import * as Locale from 'date-fns/locale'
|
||||
|
||||
import { getSupportedDateLocale } from './getSupportedDateLocale'
|
||||
@@ -12,3 +12,12 @@ export const formatDate = (
|
||||
const currentLocale = Locale[getSupportedDateLocale(locale)]
|
||||
return format(theDate, pattern, { locale: currentLocale })
|
||||
}
|
||||
|
||||
export const formatTimeToNow = (
|
||||
date: Date | number | string | undefined,
|
||||
locale?: string,
|
||||
): string => {
|
||||
const theDate = new Date(date)
|
||||
const currentLocale = Locale[getSupportedDateLocale(locale)]
|
||||
return formatDistanceToNow(theDate, { locale: currentLocale })
|
||||
}
|
||||
|
||||
@@ -3,10 +3,13 @@ import type { PayloadRequest } from '../../../express/types'
|
||||
import type { Payload } from '../../../payload'
|
||||
|
||||
import { APIError } from '../../../errors'
|
||||
import { setRequestContext } from '../../../express/setRequestContext'
|
||||
import { i18nInit } from '../../../translations/init'
|
||||
import verifyEmail from '../verifyEmail'
|
||||
|
||||
export type Options<T extends keyof GeneratedTypes['collections']> = {
|
||||
collection: T
|
||||
req?: PayloadRequest
|
||||
token: string
|
||||
}
|
||||
|
||||
@@ -14,11 +17,8 @@ async function localVerifyEmail<T extends keyof GeneratedTypes['collections']>(
|
||||
payload: Payload,
|
||||
options: Options<T>,
|
||||
): Promise<boolean> {
|
||||
const { collection: collectionSlug, token } = options
|
||||
|
||||
const req = {
|
||||
payload,
|
||||
} as PayloadRequest
|
||||
const { collection: collectionSlug, req = {} as PayloadRequest, token } = options
|
||||
setRequestContext(options.req)
|
||||
|
||||
const collection = payload.collections[collectionSlug]
|
||||
|
||||
@@ -28,6 +28,10 @@ async function localVerifyEmail<T extends keyof GeneratedTypes['collections']>(
|
||||
)
|
||||
}
|
||||
|
||||
req.payload = payload
|
||||
req.payloadAPI = req.payloadAPI || 'local'
|
||||
req.i18n = i18nInit(payload.config.i18n)
|
||||
|
||||
return verifyEmail({
|
||||
collection,
|
||||
req,
|
||||
|
||||
@@ -69,6 +69,7 @@ async function registerFirstUser<TSlug extends keyof GeneratedTypes['collections
|
||||
data: {
|
||||
_verified: true,
|
||||
},
|
||||
req,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -79,6 +80,7 @@ async function registerFirstUser<TSlug extends keyof GeneratedTypes['collections
|
||||
const { token } = await payload.login({
|
||||
...args,
|
||||
collection: slug,
|
||||
req,
|
||||
})
|
||||
|
||||
const resultToReturn = {
|
||||
|
||||
@@ -163,6 +163,7 @@ const collectionSchema = joi.object().keys({
|
||||
crop: joi.bool(),
|
||||
disableLocalStorage: joi.bool(),
|
||||
focalPoint: joi.bool(),
|
||||
filesRequiredOnCreate: joi.bool(),
|
||||
formatOptions: joi.object().keys({
|
||||
format: joi.string(),
|
||||
options: joi.object(),
|
||||
|
||||
@@ -128,7 +128,8 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
data,
|
||||
overwriteExistingFiles,
|
||||
req,
|
||||
throwOnMissingFile: !shouldSaveDraft,
|
||||
throwOnMissingFile:
|
||||
!shouldSaveDraft && collection.config.upload.filesRequiredOnCreate !== false,
|
||||
})
|
||||
|
||||
data = newFileData
|
||||
|
||||
@@ -24,6 +24,7 @@ export type Options<T extends keyof GeneratedTypes['collections']> = {
|
||||
locale?: string
|
||||
overrideAccess?: boolean
|
||||
page?: number
|
||||
req?: PayloadRequest
|
||||
showHiddenFields?: boolean
|
||||
sort?: string
|
||||
user?: Document
|
||||
@@ -43,6 +44,7 @@ export default async function findVersionsLocal<T extends keyof GeneratedTypes['
|
||||
locale = null,
|
||||
overrideAccess = true,
|
||||
page,
|
||||
req: incomingReq,
|
||||
showHiddenFields,
|
||||
sort,
|
||||
user,
|
||||
@@ -67,6 +69,7 @@ export default async function findVersionsLocal<T extends keyof GeneratedTypes['
|
||||
locale: locale ?? defaultLocale,
|
||||
payload,
|
||||
payloadAPI: 'local',
|
||||
transactionID: incomingReq?.transactionID,
|
||||
user,
|
||||
} as PayloadRequest
|
||||
setRequestContext(req, context)
|
||||
|
||||
@@ -21,6 +21,7 @@ export type Options<T extends keyof GeneratedTypes['collections']> = {
|
||||
id: string
|
||||
locale?: string
|
||||
overrideAccess?: boolean
|
||||
req?: PayloadRequest
|
||||
showHiddenFields?: boolean
|
||||
user?: Document
|
||||
}
|
||||
@@ -37,6 +38,7 @@ export default async function restoreVersionLocal<T extends keyof GeneratedTypes
|
||||
fallbackLocale = null,
|
||||
locale = payload.config.localization ? payload.config.localization?.defaultLocale : null,
|
||||
overrideAccess = true,
|
||||
req: incomingReq,
|
||||
showHiddenFields,
|
||||
user,
|
||||
} = options
|
||||
@@ -59,6 +61,7 @@ export default async function restoreVersionLocal<T extends keyof GeneratedTypes
|
||||
payload,
|
||||
payloadAPI: 'local',
|
||||
t: i18n.t,
|
||||
transactionID: incomingReq?.transactionID,
|
||||
user,
|
||||
} as PayloadRequest
|
||||
setRequestContext(req, context)
|
||||
|
||||
@@ -67,6 +67,7 @@ export type FilterOptionsProps<T = any> = {
|
||||
export type FilterOptions<T = any> =
|
||||
| ((options: FilterOptionsProps<T>) => Promise<Where> | Where)
|
||||
| Where
|
||||
| null
|
||||
|
||||
type Admin = {
|
||||
className?: string
|
||||
|
||||
@@ -258,14 +258,18 @@ const validateFilterOptions: Validate = async (
|
||||
})
|
||||
|
||||
if (valueIDs.length > 0) {
|
||||
const findWhere = {
|
||||
and: [{ id: { in: valueIDs } }],
|
||||
}
|
||||
|
||||
if (optionFilter) findWhere.and.push(optionFilter)
|
||||
|
||||
const result = await payload.find({
|
||||
collection,
|
||||
depth: 0,
|
||||
limit: 0,
|
||||
pagination: false,
|
||||
where: {
|
||||
and: [{ id: { in: valueIDs } }, optionFilter],
|
||||
},
|
||||
where: findWhere,
|
||||
})
|
||||
|
||||
options[collection] = result.docs.map((doc) => doc.id)
|
||||
|
||||
@@ -17,6 +17,7 @@ export type Options<T extends keyof GeneratedTypes['globals']> = {
|
||||
id: string
|
||||
locale?: string
|
||||
overrideAccess?: boolean
|
||||
req?: PayloadRequest
|
||||
showHiddenFields?: boolean
|
||||
slug: T
|
||||
user?: Document
|
||||
@@ -36,6 +37,7 @@ export default async function findVersionByIDLocal<T extends keyof GeneratedType
|
||||
showHiddenFields,
|
||||
slug: globalSlug,
|
||||
user,
|
||||
req: incomingReq,
|
||||
} = options
|
||||
|
||||
const globalConfig = payload.globals.config.find((config) => config.slug === globalSlug)
|
||||
@@ -52,6 +54,7 @@ export default async function findVersionByIDLocal<T extends keyof GeneratedType
|
||||
payload,
|
||||
payloadAPI: 'local',
|
||||
t: i18n.t,
|
||||
transactionID: incomingReq?.transactionID,
|
||||
user,
|
||||
} as PayloadRequest
|
||||
setRequestContext(req)
|
||||
|
||||
@@ -18,6 +18,7 @@ export type Options<T extends keyof GeneratedTypes['globals']> = {
|
||||
locale?: string
|
||||
overrideAccess?: boolean
|
||||
page?: number
|
||||
req?: PayloadRequest
|
||||
showHiddenFields?: boolean
|
||||
slug: T
|
||||
sort?: string
|
||||
@@ -36,6 +37,7 @@ export default async function findVersionsLocal<T extends keyof GeneratedTypes['
|
||||
locale = payload.config.localization ? payload.config.localization?.defaultLocale : null,
|
||||
overrideAccess = true,
|
||||
page,
|
||||
req: incomingReq,
|
||||
showHiddenFields,
|
||||
slug: globalSlug,
|
||||
sort,
|
||||
@@ -57,6 +59,7 @@ export default async function findVersionsLocal<T extends keyof GeneratedTypes['
|
||||
payload,
|
||||
payloadAPI: 'local',
|
||||
t: i18n.t,
|
||||
transactionID: incomingReq?.transactionID,
|
||||
user,
|
||||
} as PayloadRequest
|
||||
setRequestContext(req)
|
||||
|
||||
@@ -15,6 +15,7 @@ export type Options<T extends keyof GeneratedTypes['globals']> = {
|
||||
id: string
|
||||
locale?: string
|
||||
overrideAccess?: boolean
|
||||
req?: PayloadRequest
|
||||
showHiddenFields?: boolean
|
||||
slug: string
|
||||
user?: Document
|
||||
@@ -30,6 +31,7 @@ export default async function restoreVersionLocal<T extends keyof GeneratedTypes
|
||||
fallbackLocale = null,
|
||||
locale = payload.config.localization ? payload.config.localization?.defaultLocale : null,
|
||||
overrideAccess = true,
|
||||
req: incomingReq,
|
||||
showHiddenFields,
|
||||
slug: globalSlug,
|
||||
user,
|
||||
@@ -49,6 +51,7 @@ export default async function restoreVersionLocal<T extends keyof GeneratedTypes
|
||||
payload,
|
||||
payloadAPI: 'local',
|
||||
t: i18n.t,
|
||||
transactionID: incomingReq?.transactionID,
|
||||
user,
|
||||
} as PayloadRequest
|
||||
setRequestContext(req)
|
||||
|
||||
@@ -18,6 +18,7 @@ export type Options<TSlug extends keyof GeneratedTypes['globals']> = {
|
||||
fallbackLocale?: string
|
||||
locale?: string
|
||||
overrideAccess?: boolean
|
||||
req?: PayloadRequest
|
||||
showHiddenFields?: boolean
|
||||
slug: TSlug
|
||||
user?: Document
|
||||
@@ -34,6 +35,7 @@ export default async function updateLocal<TSlug extends keyof GeneratedTypes['gl
|
||||
fallbackLocale = null,
|
||||
locale = payload.config.localization ? payload.config.localization?.defaultLocale : null,
|
||||
overrideAccess = true,
|
||||
req: incomingReq,
|
||||
showHiddenFields,
|
||||
slug: globalSlug,
|
||||
user,
|
||||
@@ -53,6 +55,7 @@ export default async function updateLocal<TSlug extends keyof GeneratedTypes['gl
|
||||
payload,
|
||||
payloadAPI: 'local',
|
||||
t: i18n.t,
|
||||
transactionID: incomingReq?.transactionID,
|
||||
user,
|
||||
} as PayloadRequest
|
||||
setRequestContext(req)
|
||||
|
||||
@@ -333,7 +333,7 @@
|
||||
"currentDocumentStatus": "المستند {{docStatus}} الحالي",
|
||||
"draft": "مسودّة",
|
||||
"draftSavedSuccessfully": "تمّ حفظ المسودّة بنجاح.",
|
||||
"lastSavedAgo": "آخر حفظ في {{distance, relativetime(minutes)}}",
|
||||
"lastSavedAgo": "تم الحفظ آخر مرة قبل {{distance}}",
|
||||
"noFurtherVersionsFound": "لم يتمّ العثور على نسخات أخرى",
|
||||
"noRowsFound": "لم يتمّ العثور على {{label}}",
|
||||
"preview": "معاينة",
|
||||
@@ -368,4 +368,4 @@
|
||||
"viewingVersions": "يتمّ استعراض النُّسَخ ل {{entityLabel}} {{documentTitle}}",
|
||||
"viewingVersionsGlobal": "يتمّ استعراض النُّسَخ للاعداد العامّ {{entityLabel}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,7 +334,7 @@
|
||||
"currentDocumentStatus": "Cari {{docStatus}} sənədi",
|
||||
"draft": "Qaralama",
|
||||
"draftSavedSuccessfully": "Qaralama uğurla yadda saxlandı.",
|
||||
"lastSavedAgo": "Son yadda saxlama: {{distance, relativetime(minutes)}} əvvəl",
|
||||
"lastSavedAgo": "{{distance}} əvvəl son yadda saxlanıldı",
|
||||
"noFurtherVersionsFound": "Başqa versiyalar tapılmadı",
|
||||
"noRowsFound": "Heç bir {{label}} tapılmadı",
|
||||
"preview": "Öncədən baxış",
|
||||
@@ -369,4 +369,4 @@
|
||||
"viewingVersions": "{{entityLabel}} {{documentTitle}} üçün versiyaları göstərir",
|
||||
"viewingVersionsGlobal": "Qlobal {{entityLabel}} üçün versiyaları göstərir"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,7 +333,7 @@
|
||||
"currentDocumentStatus": "Сегашен статус на документа: {{docStatus}}",
|
||||
"draft": "Чернова",
|
||||
"draftSavedSuccessfully": "Чернова запазена успешно.",
|
||||
"lastSavedAgo": "Последно запазен {{distance, relativetime(minutes)}}",
|
||||
"lastSavedAgo": "последно запазено преди {{distance}}",
|
||||
"noFurtherVersionsFound": "Не са открити повече версии",
|
||||
"noRowsFound": "Не е открит {{label}}",
|
||||
"preview": "Предварителен преглед",
|
||||
|
||||
@@ -333,7 +333,7 @@
|
||||
"currentDocumentStatus": "Současný {{docStatus}} dokument",
|
||||
"draft": "Koncept",
|
||||
"draftSavedSuccessfully": "Koncept úspěšně uložen.",
|
||||
"lastSavedAgo": "Naposledy uloženo {{distance, relativetime(minutes)}}",
|
||||
"lastSavedAgo": "Naposledy uloženo před {{distance}}",
|
||||
"noFurtherVersionsFound": "Nenalezeny další verze",
|
||||
"noRowsFound": "Nenalezen {{label}}",
|
||||
"preview": "Náhled",
|
||||
@@ -368,4 +368,4 @@
|
||||
"viewingVersions": "Zobrazuji verze pro {{entityLabel}} {{documentTitle}}",
|
||||
"viewingVersionsGlobal": "Zobrazuji verze pro globální {{entityLabel}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,7 +333,7 @@
|
||||
"currentDocumentStatus": "Aktueller Dokumentenstatus: {{docStatus}}",
|
||||
"draft": "Entwurf",
|
||||
"draftSavedSuccessfully": "Entwurf erfolgreich gespeichert.",
|
||||
"lastSavedAgo": "Zuletzt gespeichert {{distance, relativetime(minutes)}}",
|
||||
"lastSavedAgo": "Zuletzt vor {{distance}} gespeichert",
|
||||
"noFurtherVersionsFound": "Keine weiteren Versionen vorhanden",
|
||||
"noRowsFound": "Kein {{label}} gefunden",
|
||||
"preview": "Vorschau",
|
||||
|
||||
@@ -333,7 +333,7 @@
|
||||
"currentDocumentStatus": "Current {{docStatus}} document",
|
||||
"draft": "Draft",
|
||||
"draftSavedSuccessfully": "Draft saved successfully.",
|
||||
"lastSavedAgo": "Last saved {{distance, relativetime(minutes)}}",
|
||||
"lastSavedAgo": "Last saved {{distance}} ago",
|
||||
"noFurtherVersionsFound": "No further versions found",
|
||||
"noRowsFound": "No {{label}} found",
|
||||
"preview": "Preview",
|
||||
|
||||
@@ -333,7 +333,7 @@
|
||||
"currentDocumentStatus": "Documento {{docStatus}} actual",
|
||||
"draft": "Borrador",
|
||||
"draftSavedSuccessfully": "Borrador guardado con éxito.",
|
||||
"lastSavedAgo": "Último guardado {{distance, relativetime(minutes)}}",
|
||||
"lastSavedAgo": "Guardado por última vez hace {{distance}}",
|
||||
"noFurtherVersionsFound": "No se encontraron más versiones",
|
||||
"noRowsFound": "No encontramos {{label}}",
|
||||
"preview": "Previsualizar",
|
||||
@@ -368,4 +368,4 @@
|
||||
"viewingVersions": "Viendo versiones para {{entityLabel}} {{documentTitle}}",
|
||||
"viewingVersionsGlobal": "Viendo versiones para el global {{entityLabel}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,7 +333,7 @@
|
||||
"currentDocumentStatus": "جاری {{docStatus}} سند",
|
||||
"draft": "پیشنویس",
|
||||
"draftSavedSuccessfully": "پیشنویس با موفقیت ذخیره شد.",
|
||||
"lastSavedAgo": "آخرین ذخیرهسازی {{distance, relativetime(minutes)}}",
|
||||
"lastSavedAgo": "آخرین بار {{distance}} پیش ذخیره شد",
|
||||
"noFurtherVersionsFound": "نگارش دیگری یافت نشد",
|
||||
"noRowsFound": "هیچ {{label}} یافت نشد",
|
||||
"preview": "پیشنمایش",
|
||||
@@ -368,4 +368,4 @@
|
||||
"viewingVersions": "مشاهده نگارشها برای {{entityLabel}} {{documentTitle}}",
|
||||
"viewingVersionsGlobal": "مشاهده نگارشهای کلی {{entityLabel}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,7 +333,7 @@
|
||||
"currentDocumentStatus": "Document {{docStatus}} actuel",
|
||||
"draft": "Brouillon",
|
||||
"draftSavedSuccessfully": "Brouillon enregistré avec succès.",
|
||||
"lastSavedAgo": "Enregistré il y a de cela {{distance, relativetime(minutes)}}",
|
||||
"lastSavedAgo": "Dernière sauvegarde il y a {{distance}}",
|
||||
"noFurtherVersionsFound": "Aucune autre version trouvée",
|
||||
"noRowsFound": "Aucun(e) {{label}} trouvé(e)",
|
||||
"preview": "Aperçu",
|
||||
@@ -368,4 +368,4 @@
|
||||
"viewingVersions": "Affichage des versions de ou du {{entityLabel}} {{documentTitle}}",
|
||||
"viewingVersionsGlobal": "Affichage des versions globales de ou du {{entityLabel}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,7 +333,7 @@
|
||||
"currentDocumentStatus": "Trenutni {{docStatus}} dokumenta",
|
||||
"draft": "Nacrt",
|
||||
"draftSavedSuccessfully": "Nacrt uspješno spremljen.",
|
||||
"lastSavedAgo": "Zadnje spremljeno prije {{distance, relativetime(minutes)}}",
|
||||
"lastSavedAgo": "Zadnji put spremljeno prije {{distance}",
|
||||
"noFurtherVersionsFound": "Nisu pronađene daljnje verzije",
|
||||
"noRowsFound": "{{label}} nije pronađeno",
|
||||
"preview": "Pregled",
|
||||
@@ -368,4 +368,4 @@
|
||||
"viewingVersions": "Pregled verzija za {{entityLabel}} {{documentTitle}}",
|
||||
"viewingVersionsGlobal": "Pregled verzije za globalni {{entityLabel}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,7 +333,7 @@
|
||||
"currentDocumentStatus": "Jelenlegi {{docStatus}} dokumentum",
|
||||
"draft": "Piszkozat",
|
||||
"draftSavedSuccessfully": "A piszkozat sikeresen mentve.",
|
||||
"lastSavedAgo": "Utoljára mentve {{distance, relativetime(minutes)}}",
|
||||
"lastSavedAgo": "Utoljára mentve {{distance}} órája",
|
||||
"noFurtherVersionsFound": "További verziók nem találhatók",
|
||||
"noRowsFound": "Nem található {{label}}",
|
||||
"preview": "Előnézet",
|
||||
@@ -368,4 +368,4 @@
|
||||
"viewingVersions": "A {{entityLabel}} {{documentTitle}} verzióinak megtekintése",
|
||||
"viewingVersionsGlobal": "A globális {{entityLabel}} verzióinak megtekintése"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,7 +334,7 @@
|
||||
"currentDocumentStatus": "Documento {{docStatus}} corrente",
|
||||
"draft": "Bozza",
|
||||
"draftSavedSuccessfully": "Bozza salvata con successo.",
|
||||
"lastSavedAgo": "Ultimo salvataggio {{distance, relativetime(minutes)}}",
|
||||
"lastSavedAgo": "Ultimo salvataggio {{distance}} fa",
|
||||
"noFurtherVersionsFound": "Non sono state trovate ulteriori versioni",
|
||||
"noRowsFound": "Nessun {{label}} trovato",
|
||||
"preview": "Anteprima",
|
||||
@@ -369,4 +369,4 @@
|
||||
"viewingVersions": "Visualizzazione delle versioni per {{entityLabel}} {{documentTitle}}",
|
||||
"viewingVersionsGlobal": "`Visualizzazione delle versioni per {{entityLabel}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user