Compare commits

..

20 Commits

Author SHA1 Message Date
Elliot DeNolf
62fa22cb24 chore(release): payload/2.21.0 [skip ci] 2024-06-14 15:37:16 -04:00
Sjoert
3b4bb3065a fix(login): use correct time for isLocked check (#6052)
Fixes #4950
 
The time given to the `isLocked` function is an ISO string because of
the implementation of the [`incrementLoginAttempts`
function](d78df36d9b/packages/payload/src/auth/strategies/local/incrementLoginAttempts.ts (L47)),
even though it expects milliseconds since epoch. For this reason the
`isLocked` function will never return `true`.
2024-06-14 14:49:36 -04:00
Jarrod Flesch
4e0725f7c6 chore: fixes view height (#6779)
Fixes issue where view height was not 100% by default. Regression from
https://github.com/payloadcms/payload/pull/4769
2024-06-14 12:53:26 -04:00
Jarrod Flesch
ff70fd9813 feat: draft validation (#6746)
Allows draft validation to be enabled at the config level.

You can enable this by:

```ts
// ...collectionConfig
versions: {
  drafts: {
    validate: true // defaults to false
  }
}
```
2024-06-13 11:08:04 -04:00
Patrik
e40570bd0d fix: unable to save animated file types with undefined image sizes (#6733)
## Description

Fixes #6727 

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

## Type of change

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

## Checklist:

- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] Existing test suite passes locally with my changes
2024-06-13 09:41:24 -04:00
Jessica Chowdhury
b7e852993b fix: adjust version status pill when unpublished (#6744)
## Description

Versions that have been published then unpublished still showed the
`current published version` pill - these need to be `previously
published`.

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

## Type of change

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

## Checklist:

- [X] Existing test suite passes locally with my changes
2024-06-12 13:24:35 -04:00
Elliot DeNolf
ab97590879 chore(release): payload/2.20.0 [skip ci] 2024-06-11 19:00:22 -04:00
Jessica Chowdhury
ed86b15242 feat(ui): updates draft/published version pills (#6732) 2024-06-11 18:18:32 -04:00
Elliot DeNolf
d58631c12c chore: add v2 tag to issue template 2024-06-11 16:08:11 -04:00
Jessica Chowdhury
37c8386a51 fix: withinCollapsible should be undefined by default (#6666)
## Description

Closes #6658

`withinCollapsible` from the collapsible provider is `true` by default,
should be undefined.
2024-06-11 15:48:46 -04:00
Patrik
2f9ed34d13 fix: only use metadata.pages for height if animated (#6729)
## Description

### Issue: 

Non-animated webp / gif files were using `metadata.pages` to calculate
it's resized heights for `imageSizes` or `cropping`.

### Fix: 

It should only use this to calculate it's height if the file's
`metadata` contains `metadata.pages`. Non-animated webps and gifs would
not have this.

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

## Type of change

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

## Checklist:

- [x] Existing test suite passes locally with my changes
2024-06-11 13:46:12 -04:00
Patrik
921a5c065d fix: create sharp file for fileHasAdjustments files or fileIsAnimated files (#6710)
## Description

V3 PR [here](https://github.com/payloadcms/payload/pull/6708)

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

## Type of change

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

## Checklist:

- [x] Existing test suite passes locally with my changes
2024-06-11 10:57:59 -04:00
Patrik
e3003b443f fix: adds multi select inputs for number & text fields in where builder (#6662)
## Description

### Issue: 
The `in` & `not_in` operators were not properly working for `number` &
`text` fields as this operator requires an `array` of values for it's
input.

### Fix: 
Conditionally renders a multi select input for `number` & `text` fields
when filtering by `in` & `not_in` operators.

Also, improves the UX of the where builder by now clearing the `params`
from the where query when a user clears the `value` from the filter
value input or when updating the `operator` in the operator dropdown.
2024-06-10 16:08:25 -04:00
Patrik
8a622984e7 fix: handles localized nested relationship fields in versions (#6679)
## Description

### Issue: 

When `localization` is `true` and a `relationship` field is nested, in
versions the title of the relationship field was returning as `[Object
object]` instead of the correct locale version.

Before:
![Screenshot 2024-06-07 at 3 45
32 PM](https://github.com/payloadcms/payload/assets/35232443/fe5df9fd-f16b-4231-8ad4-b76eb795f6bf)

### Fix:

Recursively loop through fields and find the desired field regardless of
its nesting level.

After:
![Screenshot 2024-06-07 at 3 45
48 PM](https://github.com/payloadcms/payload/assets/35232443/d5fc942f-b26f-4bd9-a679-6b77b3e7ec75)

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

## Type of change

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

## Checklist:

- [x] Existing test suite passes locally with my changes
2024-06-10 16:04:52 -04:00
Patrik
507e0954b2 fix: removes array & blocks & group fields from sort (#6574)
## Description

Fixes #6469 

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

## Type of change

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

## Checklist:

- [x] Existing test suite passes locally with my changes
2024-06-10 14:08:48 -04:00
wkd
63bc6ae52f fix: enable SaveDraft button when creating new documents (#6672)
## Description

Fixes #6671, which looks like a regression introduced in v2.19.0 that
disables the "save draft" button when creating new documents.


8f03cd7c78 (diff-b7c978f47b1f3beff95c78ad95078e600624cbcd7ac10f9378cc4ad6803db244L75-R79)
2024-06-10 13:38:37 -04:00
Elliot DeNolf
d016fbd2a5 chore(templates): update lock files (#6707)
Update template lock files

Fixes #6692
2024-06-10 11:00:46 -04:00
Jacob Fletcher
9525511e8b fix: live preview device position when using zoom (#6667) 2024-06-07 09:47:35 -04:00
Elliot DeNolf
1e834e58a4 chore(release): payload/2.19.3 [skip ci] 2024-06-07 09:44:02 -04:00
Jarrod Flesch
373cb00139 fix: scopes uploadEdits to documents, hoists action to doc provider (#6664)
Fixes https://github.com/payloadcms/payload/issues/6545

### Description
Correctly scopes upload edits to a single doc, previously they were
stored on the top level document.

- Removes formQueryParams in favor of an upload edit provider.
- Hoists the document `action` up to the doc provider
2024-06-07 09:12:19 -04:00
115 changed files with 8560 additions and 7672 deletions

View File

@@ -1,6 +1,6 @@
name: Bug Report
description: Create a bug report for Payload
labels: ['status: needs-triage']
labels: ['status: needs-triage', 'v2']
body:
- type: markdown
attributes:

7
.vscode/launch.json vendored
View File

@@ -54,6 +54,13 @@
"request": "launch",
"type": "node-terminal"
},
{
"command": "pnpm run dev field-error-states",
"cwd": "${workspaceFolder}",
"name": "Run Dev Field Error States",
"request": "launch",
"type": "node-terminal"
},
{
"command": "pnpm run dev uploads",
"cwd": "${workspaceFolder}",

View File

@@ -1,3 +1,43 @@
## [2.21.0](https://github.com/payloadcms/payload/compare/v2.20.0...v2.21.0) (2024-06-14)
### Features
* draft validation ([#6746](https://github.com/payloadcms/payload/issues/6746)) ([ff70fd9](https://github.com/payloadcms/payload/commit/ff70fd9813ec7dc14bf54d3457c25e145fe01699))
### Bug Fixes
* adjust version status pill when unpublished ([#6744](https://github.com/payloadcms/payload/issues/6744)) ([b7e8529](https://github.com/payloadcms/payload/commit/b7e852993beaaa465e38caa36e75e870819516b5))
* use correct time for isLocked check ([#6052](https://github.com/payloadcms/payload/issues/6052)) ([3b4bb30](https://github.com/payloadcms/payload/commit/3b4bb3065a6d2ac2f7d6aafd0fb4a35b580f3662))
* unable to save animated file types with undefined image sizes ([#6733](https://github.com/payloadcms/payload/issues/6733)) ([e40570b](https://github.com/payloadcms/payload/commit/e40570bd0d64660bb2ae5b5785b4c85c684ca9ab)), closes [#6727](https://github.com/payloadcms/payload/issues/6727)
## [2.20.0](https://github.com/payloadcms/payload/compare/v2.19.3...v2.20.0) (2024-06-11)
### Features
* **ui:** updates draft/published version pills ([#6732](https://github.com/payloadcms/payload/issues/6732)) ([ed86b15](https://github.com/payloadcms/payload/commit/ed86b15242ccbd737f3fe80103afc7d8c4cc6915))
### Bug Fixes
* adds multi select inputs for `number` & `text` fields in where builder ([#6662](https://github.com/payloadcms/payload/issues/6662)) ([e3003b4](https://github.com/payloadcms/payload/commit/e3003b443fd3b472670ade228c174c7f755b1732))
* create sharp file for `fileHasAdjustments` files or `fileIsAnimated` files ([#6710](https://github.com/payloadcms/payload/issues/6710)) ([921a5c0](https://github.com/payloadcms/payload/commit/921a5c065d6089f0118ad8be7adbf75614c8db9c))
* enable SaveDraft button when creating new documents ([#6672](https://github.com/payloadcms/payload/issues/6672)) ([63bc6ae](https://github.com/payloadcms/payload/commit/63bc6ae52f63adf98f442c2d7992461e7f5f86e4)), closes [#6671](https://github.com/payloadcms/payload/issues/6671) [/github.com/payloadcms/payload/commit/8f03cd7c789eda7613ddced0d45a32afe49b1e01#diff-b7c978f47b1f3beff95c78ad95078e600624cbcd7ac10f9378cc4ad6803db244L75-R79](https://github.com/payloadcms//github.com/payloadcms/payload/commit/8f03cd7c789eda7613ddced0d45a32afe49b1e01/issues/diff-b7c978f47b1f3beff95c78ad95078e600624cbcd7ac10f9378cc4ad6803db244L75-R79)
* handles localized nested relationship fields in versions ([#6679](https://github.com/payloadcms/payload/issues/6679)) ([8a62298](https://github.com/payloadcms/payload/commit/8a622984e7ce4a2439d5d63e2689404652f8c96b))
* live preview device position when using zoom ([#6667](https://github.com/payloadcms/payload/issues/6667)) ([9525511](https://github.com/payloadcms/payload/commit/9525511e8bc6bc3fbd7e21e36596404604f1c109))
* only use `metadata.pages` for height if animated ([#6729](https://github.com/payloadcms/payload/issues/6729)) ([2f9ed34](https://github.com/payloadcms/payload/commit/2f9ed34d13d94070db40b1eabd04655d2e13e094))
* removes `array` & `blocks` & `group` fields from sort ([#6574](https://github.com/payloadcms/payload/issues/6574)) ([507e095](https://github.com/payloadcms/payload/commit/507e0954b2743012f0b53c396d49461120a02b1a)), closes [#6469](https://github.com/payloadcms/payload/issues/6469)
* withinCollapsible should be undefined by default ([#6666](https://github.com/payloadcms/payload/issues/6666)) ([37c8386](https://github.com/payloadcms/payload/commit/37c8386a51172966057df3477517d160e1c4a9a7)), closes [#6658](https://github.com/payloadcms/payload/issues/6658)
## [2.19.3](https://github.com/payloadcms/payload/compare/v2.19.2...v2.19.3) (2024-06-07)
### Bug Fixes
* scopes uploadEdits to documents, hoists action to doc provider ([#6664](https://github.com/payloadcms/payload/issues/6664)) ([373cb00](https://github.com/payloadcms/payload/commit/373cb0013902b52aba455542e10402316da4b2f4))
## [2.19.2](https://github.com/payloadcms/payload/compare/v2.19.1...v2.19.2) (2024-06-06)

View File

@@ -1,6 +1,6 @@
---
title: Implementing Live Preview in your app
label: Frontend Implementation
label: Frontend
order: 20
desc: Learn how to implement Live Preview in your front-end application.
keywords: live preview, frontend, react, next.js, vue, nuxt.js, svelte, hook, useLivePreview
@@ -275,7 +275,7 @@ const { data } = useLivePreview<PageType>({
})
```
### Iframe refuses to connect
#### Iframe refuses to connect
If your front-end application has set a [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) (CSP) that blocks the Admin Panel from loading your front-end application, the iframe will not be able to load your site. To resolve this, you can whitelist the Admin Panel's domain in your CSP by setting the `frame-ancestors` directive:

View File

@@ -22,6 +22,7 @@ Collections and Globals both support the same options for configuring drafts. Yo
| Draft Option | Description |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `autosave` | Enable `autosave` to automatically save progress while documents are edited. To enable, set to `true` or pass an object with [options](/docs/versions/autosave). |
| `validate` | Set `validate` to `true` to validate draft documents when saved. Default is `false`. |
### Database changes

View File

@@ -1,6 +1,6 @@
{
"name": "payload",
"version": "2.19.2",
"version": "2.21.0",
"description": "Node, React and MongoDB Headless CMS and Application Framework",
"license": "MIT",
"main": "./dist/index.js",

View File

@@ -7,8 +7,14 @@ import type { Props } from './types'
import useDebounce from '../../../hooks/useDebounce'
import { formatTimeToNow } from '../../../utilities/formatDate'
import { useAllFormFields, useFormModified } from '../../forms/Form/context'
import {
useAllFormFields,
useForm,
useFormModified,
useFormSubmitted,
} from '../../forms/Form/context'
import reduceFieldsToValues from '../../forms/Form/reduceFieldsToValues'
import { reduceFieldsToValuesWithValidation } from '../../forms/Form/reduceFieldsToValuesWithValidation'
import { useConfig } from '../../utilities/Config'
import { useDocumentInfo } from '../../utilities/DocumentInfo'
import { useLocale } from '../../utilities/Locale'
@@ -24,10 +30,15 @@ const Autosave: React.FC<Props> = ({ id, collection, global, publishedDocUpdated
const [fields] = useAllFormFields()
const modified = useFormModified()
const { code: locale } = useLocale()
const { replace } = useHistory()
const submitted = useFormSubmitted()
const { dispatchFields, setSubmitted } = useForm()
const history = useHistory()
const { i18n, t } = useTranslation('version')
let interval = 800
const validateDrafts =
(collection?.versions.drafts && collection.versions?.drafts?.validate) ||
(global?.versions.drafts && global.versions?.drafts?.validate)
if (collection?.versions.drafts && collection.versions?.drafts?.autosave)
interval = collection.versions.drafts.autosave.interval
if (global?.versions.drafts && global.versions?.drafts?.autosave)
@@ -66,7 +77,7 @@ const Autosave: React.FC<Props> = ({ id, collection, global, publishedDocUpdated
if (res.status === 201) {
const json = await res.json()
replace(`${admin}/collections/${collection.slug}/${json.doc.id}`, {
history.replace(`${admin}/collections/${collection.slug}/${json.doc.id}`, {
state: {
data: json.doc,
},
@@ -74,19 +85,22 @@ const Autosave: React.FC<Props> = ({ id, collection, global, publishedDocUpdated
} else {
toast.error(t('error:autosaving'))
}
}, [i18n, serverURL, api, collection, locale, replace, admin, t])
}, [serverURL, api, collection?.slug, locale, i18n.language, history, admin, t])
useEffect(() => {
// If no ID, but this is used for a collection doc,
// Immediately save it and set lastSaved
if (!id && collection) {
createCollectionDoc()
void createCollectionDoc()
}
}, [id, collection, createCollectionDoc])
// When debounced fields change, autosave
useEffect(() => {
const abortController = new AbortController()
let autosaveTimeout = undefined
const autosave = async () => {
if (modified) {
setSaving(true)
@@ -105,8 +119,82 @@ const Autosave: React.FC<Props> = ({ id, collection, global, publishedDocUpdated
}
if (url) {
setTimeout(async () => {
autosaveTimeout = setTimeout(async () => {
if (modifiedRef.current) {
const { data, valid } = {
...reduceFieldsToValuesWithValidation(fieldRef.current, true),
}
data._status = 'draft'
const skipSubmission = submitted && !valid && validateDrafts
if (!skipSubmission) {
const res = await fetch(url, {
body: JSON.stringify(data),
credentials: 'include',
headers: {
'Accept-Language': i18n.language,
'Content-Type': 'application/json',
},
method,
signal: abortController.signal,
})
if (res.status === 200) {
const newDate = new Date()
setLastSaved(newDate.getTime())
void getVersions()
}
if (validateDrafts && res.status === 400) {
const json = await res.json()
if (Array.isArray(json.errors)) {
const [fieldErrors, nonFieldErrors] = json.errors.reduce(
([fieldErrs, nonFieldErrs], err) => {
const newFieldErrs = []
const newNonFieldErrs = []
if (err?.message) {
newNonFieldErrs.push(err)
}
if (Array.isArray(err?.data)) {
err.data.forEach((dataError) => {
if (dataError?.field) {
newFieldErrs.push(dataError)
} else {
newNonFieldErrs.push(dataError)
}
})
}
return [
[...fieldErrs, ...newFieldErrs],
[...nonFieldErrs, ...newNonFieldErrs],
]
},
[[], []],
)
fieldErrors.forEach((err) => {
dispatchFields({
type: 'UPDATE',
errorMessage: err.message,
path: err.field,
valid: false,
})
})
nonFieldErrors.forEach((err) => {
toast.error(err.message || i18n.t('error:unknown'))
})
setSubmitted(true)
setSaving(false)
return
}
}
}
const body = {
...reduceFieldsToValues(fieldRef.current, true),
_status: 'draft',
@@ -124,7 +212,7 @@ const Autosave: React.FC<Props> = ({ id, collection, global, publishedDocUpdated
if (res.status === 200) {
setLastSaved(new Date().getTime())
getVersions()
void getVersions()
}
}
@@ -134,7 +222,13 @@ const Autosave: React.FC<Props> = ({ id, collection, global, publishedDocUpdated
}
}
autosave()
void autosave()
return () => {
clearTimeout(autosaveTimeout)
if (abortController.signal) abortController.abort()
setSaving(false)
}
}, [
i18n,
debouncedFields,
@@ -147,6 +241,10 @@ const Autosave: React.FC<Props> = ({ id, collection, global, publishedDocUpdated
getVersions,
localeRef,
modifiedRef,
submitted,
validateDrafts,
setSubmitted,
dispatchFields,
])
useEffect(() => {

View File

@@ -10,7 +10,7 @@ const Context = createContext({
collapsed: false,
isVisible: true,
toggle: () => {},
withinCollapsible: true,
withinCollapsible: false,
})
export const CollapsibleProvider: React.FC<{
@@ -18,7 +18,7 @@ export const CollapsibleProvider: React.FC<{
collapsed?: boolean
toggle: () => void
withinCollapsible?: boolean
}> = ({ children, collapsed, toggle, withinCollapsible = true }) => {
}> = ({ children, collapsed, toggle, withinCollapsible }) => {
const { collapsed: parentIsCollapsed, isVisible } = useCollapsible()
const contextValue = React.useMemo((): ContextType => {

View File

@@ -48,13 +48,18 @@ export const DocumentControls: React.FC<{
permissions,
} = props
const { publishedDoc } = useDocumentInfo()
const { slug, publishedDoc } = useDocumentInfo()
const {
admin: { dateFormat },
collections,
globals,
routes: { admin: adminRoute },
} = useConfig()
const collectionConfig = collections.find((coll) => coll.slug === slug)
const globalConfig = globals.find((global) => global.slug === slug)
const { i18n, t } = useTranslation('general')
const hasCreatePermission = 'create' in permissions && permissions.create?.permission
@@ -70,6 +75,9 @@ export const DocumentControls: React.FC<{
return typeof label === 'string' ? label : getTranslation(label, i18n)
}
const unsavedDraftWithValidations =
!id && collectionConfig?.versions?.drafts && collectionConfig.versions?.drafts.validate
return (
<Gutter className={baseClass}>
<div className={`${baseClass}__wrapper`}>
@@ -93,8 +101,10 @@ export const DocumentControls: React.FC<{
<Status />
</li>
)}
{((collection?.versions?.drafts && collection?.versions?.drafts?.autosave) ||
(global?.versions?.drafts && global?.versions?.drafts?.autosave)) &&
{((collectionConfig?.versions?.drafts &&
collectionConfig?.versions?.drafts?.autosave &&
!unsavedDraftWithValidations) ||
(globalConfig?.versions?.drafts && globalConfig?.versions?.drafts?.autosave)) &&
hasSavePermission && (
<li className={`${baseClass}__list-item`}>
<Autosave
@@ -168,8 +178,11 @@ export const DocumentControls: React.FC<{
<React.Fragment>
{collection?.versions?.drafts || global?.versions?.drafts ? (
<React.Fragment>
{((collection?.versions?.drafts && !collection?.versions?.drafts?.autosave) ||
(global?.versions?.drafts && !global?.versions?.drafts?.autosave)) && (
{((collectionConfig?.versions?.drafts &&
!collectionConfig?.versions?.drafts?.autosave) ||
unsavedDraftWithValidations ||
(globalConfig?.versions?.drafts &&
!globalConfig?.versions?.drafts?.autosave)) && (
<SaveDraft
CustomComponent={
collection?.admin?.components?.edit?.SaveDraftButton ||

View File

@@ -1,5 +1,4 @@
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'
@@ -18,7 +17,6 @@ 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'
@@ -45,12 +43,10 @@ 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
const { id, docPermissions, getDocPreferences } = useDocumentInfo()
const { id, action, docPermissions, getDocPreferences } = useDocumentInfo()
// If they are replacing the entire edit view, use that.
// Else let the DefaultEdit determine what to render.
@@ -90,7 +86,7 @@ const Content: React.FC<DocumentDrawerProps> = ({
setInternalState(state)
}
awaitInitialState()
void awaitInitialState()
hasInitializedState.current = true
}, [data, fields, id, user, locale, isLoadingDocument, t, getDocPreferences, config])
@@ -111,10 +107,6 @@ const Content: React.FC<DocumentDrawerProps> = ({
const apiURL = id ? `${serverURL}${api}/${collectionSlug}/${id}?locale=${locale}` : null
const action = `${serverURL}${api}/${collectionSlug}${
isEditing ? `/${id}` : ''
}?${formattedQueryParams}`
const hasSavePermission =
(isEditing && docPermissions?.update?.permission) ||
(!isEditing && (docPermissions as CollectionPermission)?.create?.permission)

View File

@@ -7,7 +7,7 @@ import 'react-image-crop/dist/ReactCrop.css'
import type { Data } from '../../forms/Form/types'
import Plus from '../../icons/Plus'
import { useFormQueryParams } from '../../utilities/FormQueryParams'
import { useUploadEdits } from '../../utilities/UploadEdits'
import { editDrawerSlug } from '../../views/collections/Edit/Upload'
import Button from '../Button'
import './index.scss'
@@ -35,8 +35,8 @@ export const EditUpload: React.FC<{
}> = ({ doc, fileName, fileSrc, imageCacheTag, showCrop, showFocalPoint }) => {
const { closeModal } = useModal()
const { t } = useTranslation(['general', 'upload'])
const { formQueryParams, setFormQueryParams } = useFormQueryParams()
const { uploadEdits } = formQueryParams || {}
const { updateUploadEdits, uploadEdits } = useUploadEdits()
const [crop, setCrop] = useState<CropType>({
height: uploadEdits?.crop?.height || 100,
unit: '%',
@@ -87,12 +87,9 @@ export const EditUpload: React.FC<{
}
const saveEdits = () => {
setFormQueryParams({
...formQueryParams,
uploadEdits: {
crop: crop || undefined,
focalPoint: focalPosition ? focalPosition : undefined,
},
updateUploadEdits({
crop: crop || undefined,
focalPoint: focalPosition ? focalPosition : undefined,
})
closeModal(editDrawerSlug)
}

View File

@@ -67,13 +67,10 @@ export const ListControls: React.FC<Props> = (props) => {
}, [listSearchableFields, fields])
React.useEffect(() => {
if (hasWhereParam.current && !params?.where) {
if (!params?.limit) {
setVisibleDrawer(undefined)
hasWhereParam.current = false
} else if (params?.where) {
hasWhereParam.current = true
}
}, [setVisibleDrawer, params?.where])
}, [setVisibleDrawer, params?.limit])
return (
<div className={baseClass}>

View File

@@ -8,7 +8,6 @@ import { useConfig } from '../../utilities/Config'
import { useDocumentInfo } from '../../utilities/DocumentInfo'
import { useEditDepth } from '../../utilities/EditDepth'
import { useLocale } from '../../utilities/Locale'
import { useOperation } from '../../utilities/OperationProvider'
import RenderCustomComponent from '../../utilities/RenderCustomComponent'
const baseClass = 'save-draft'
@@ -70,13 +69,16 @@ export const SaveDraft: React.FC<Props> = ({ CustomComponent }) => {
} = useConfig()
const { submit } = useForm()
const { id, collection, global } = useDocumentInfo()
const operation = useOperation()
const modified = useFormModified()
const { code: locale } = useLocale()
const { t } = useTranslation('version')
const canSaveDraft = operation === 'update' && modified
const canSaveDraft = modified
const validateDrafts =
(collection?.versions.drafts && collection.versions?.drafts?.validate) ||
(global?.versions.drafts && global.versions?.drafts?.validate)
const saveDraft = useCallback(async () => {
const search = `?locale=${locale}&depth=0&fallback-locale=null&draft=true`
@@ -98,9 +100,9 @@ export const SaveDraft: React.FC<Props> = ({ CustomComponent }) => {
overrides: {
_status: 'draft',
},
skipValidation: true,
skipValidation: !validateDrafts,
})
}, [submit, collection, global, serverURL, api, locale, id])
}, [submit, collection, global, serverURL, api, locale, id, validateDrafts])
return (
<RenderCustomComponent

View File

@@ -43,6 +43,11 @@ const buildColumns = ({
}
const props = cellProps?.[colIndex] || {}
const fieldAffectsDataSubFields =
field &&
field.type &&
(field.type === 'array' || field.type === 'group' || field.type === 'blocks')
const disableListFilter =
field.admin && 'disableListFilter' in field.admin ? field.admin.disableListFilter : false
@@ -57,11 +62,7 @@ const buildColumns = ({
components: {
Heading: (
<SortColumn
disable={
('disableSort' in field && Boolean(field.disableSort)) ||
fieldIsPresentationalOnly(field) ||
undefined
}
disable={fieldAffectsDataSubFields || fieldIsPresentationalOnly(field) || undefined}
label={field.label || field.name}
name={field.name}
/>

View File

@@ -3,20 +3,78 @@ import { useTranslation } from 'react-i18next'
import type { Props } from './types'
import ReactSelect from '../../../ReactSelect'
import './index.scss'
const baseClass = 'condition-value-number'
const NumberField: React.FC<Props> = ({ disabled, onChange, value }) => {
const { t } = useTranslation('general')
return (
const NumberField: React.FC<Props> = ({ disabled, onChange, operator, value }) => {
const { t } = useTranslation()
const isMulti = ['in', 'not_in'].includes(operator)
const [valueToRender, setValueToRender] = React.useState<
{ id: string; label: string; value: { value: number } }[]
>([])
const onSelect = React.useCallback(
(selectedOption) => {
let newValue
if (!selectedOption) {
newValue = []
} else if (isMulti) {
if (Array.isArray(selectedOption)) {
newValue = selectedOption.map((option) => Number(option.value?.value || option.value))
} else {
newValue = [Number(selectedOption.value?.value || selectedOption.value)]
}
}
onChange(newValue)
},
[isMulti, onChange],
)
React.useEffect(() => {
if (Array.isArray(value)) {
setValueToRender(
value.map((val, index) => {
return {
id: `${val}${index}`, // append index to avoid duplicate keys but allow duplicate numbers
label: `${val}`,
value: {
toString: () => `${val}${index}`,
value: (val as any)?.value || val,
},
}
}),
)
} else {
setValueToRender([])
}
}, [value])
return isMulti ? (
<ReactSelect
disabled={disabled}
isClearable
isCreatable
isMulti={isMulti}
isSortable
numberOnly
onChange={onSelect}
options={[]}
placeholder={t('general:enterAValue')}
value={valueToRender || []}
/>
) : (
<input
className={baseClass}
disabled={disabled}
onChange={(e) => onChange(e.target.value)}
placeholder={t('enterAValue')}
placeholder={t('general:enterAValue')}
type="number"
value={value}
value={value as number}
/>
)
}

View File

@@ -1,5 +1,8 @@
import type { Operator } from '../../../../../../types'
export type Props = {
disabled?: boolean
onChange: (e: string) => void
value: string
operator: Operator
value: number | number[]
}

View File

@@ -3,18 +3,75 @@ import { useTranslation } from 'react-i18next'
import type { Props } from './types'
import ReactSelect from '../../../ReactSelect'
import './index.scss'
const baseClass = 'condition-value-text'
const Text: React.FC<Props> = ({ disabled, onChange, value }) => {
const { t } = useTranslation('general')
return (
const Text: React.FC<Props> = ({ disabled, onChange, operator, value }) => {
const { t } = useTranslation()
const isMulti = ['in', 'not_in'].includes(operator)
const [valueToRender, setValueToRender] = React.useState<
{ id: string; label: string; value: { value: string } }[]
>([])
const onSelect = React.useCallback(
(selectedOption) => {
let newValue
if (!selectedOption) {
newValue = []
} else if (isMulti) {
if (Array.isArray(selectedOption)) {
newValue = selectedOption.map((option) => option.value?.value || option.value)
} else {
newValue = [selectedOption.value?.value || selectedOption.value]
}
}
onChange(newValue)
},
[isMulti, onChange],
)
React.useEffect(() => {
if (Array.isArray(value)) {
setValueToRender(
value.map((val, index) => {
return {
id: `${val}${index}`, // append index to avoid duplicate keys but allow duplicate numbers
label: `${val}`,
value: {
toString: () => `${val}${index}`,
value: (val as any)?.value || val,
},
}
}),
)
} else {
setValueToRender([])
}
}, [value])
return isMulti ? (
<ReactSelect
disabled={disabled}
isClearable
isCreatable
isMulti={isMulti}
isSortable
onChange={onSelect}
options={[]}
placeholder={t('general:enterAValue')}
value={valueToRender || []}
/>
) : (
<input
className={baseClass}
disabled={disabled}
onChange={(e) => onChange(e.target.value)}
placeholder={t('enterAValue')}
placeholder={t('general:enterAValue')}
type="text"
value={value || ''}
/>

View File

@@ -1,5 +1,8 @@
import type { Operator } from '../../../../../../types'
export type Props = {
disabled?: boolean
onChange: (val: string) => void
value: string
operator: Operator
value: string | string[]
}

View File

@@ -102,6 +102,7 @@ const Condition: React.FC<Props> = (props) => {
orIndex,
})
setInternalOperatorField(operator.value)
setInternalValue('') // Reset value when operator changes
}}
options={activeField.operators}
value={

View File

@@ -34,7 +34,7 @@ const reduceFields = (fields, i18n) =>
}
const operatorKeys = new Set()
const filteredOperators = operators.reduce((acc, operator) => {
const reducedOperators = operators.reduce((acc, operator) => {
if (!operatorKeys.has(operator.value)) {
operatorKeys.add(operator.value)
return [
@@ -52,7 +52,7 @@ const reduceFields = (fields, i18n) =>
label: getTranslation(field.label || field.name, i18n),
value: field.name,
...fieldTypes[field.type],
operators: filteredOperators,
operators: reducedOperators,
props: {
...field,
},
@@ -135,6 +135,26 @@ const WhereBuilder: React.FC<Props> = (props) => {
or: [...conditions, ...paramsToKeep],
}
const reducedQuery = {
or: newWhereQuery.or.map((orCondition) => {
const andConditions = (orCondition.and || []).map((andCondition) => {
const reducedCondition = {}
Object.entries(andCondition).forEach(([fieldName, fieldValue]) => {
Object.entries(fieldValue).forEach(([operatorKey, operatorValue]) => {
reducedCondition[fieldName] = {}
reducedCondition[fieldName][operatorKey] = !operatorValue
? undefined
: operatorValue
})
})
return reducedCondition
})
return {
and: andConditions,
}
}),
}
if (handleChange) handleChange(newWhereQuery as Where)
const hasExistingConditions =
@@ -149,7 +169,7 @@ const WhereBuilder: React.FC<Props> = (props) => {
{
...currentParams,
page: 1,
where: newWhereQuery,
where: reducedQuery,
},
{ addQueryPrefix: true },
),

View File

@@ -50,9 +50,15 @@ const reducer = (state: Where[], action: Action): Where[] => {
)[0] || [undefined, undefined]
if (operator) {
const existingOperator = Object.keys(existingCondition)[0]
const newValue =
existingOperator && existingOperator !== operator
? undefined
: Object.values(existingCondition)[0]
newState[orIndex].and[andIndex] = {
[existingFieldName]: {
[operator]: Object.values(existingCondition)[0],
[operator]: newValue,
},
}
}

View File

@@ -149,9 +149,9 @@ const Form: React.FC<Props> = (props) => {
if (!stateMatches) {
dispatchFields({
type: 'UPDATE',
path,
rows: newRows,
type: 'UPDATE',
})
}
})
@@ -204,7 +204,7 @@ const Form: React.FC<Props> = (props) => {
await Promise.all(validationPromises)
if (!isDeepEqual(contextRef.current.fields, validatedFieldState)) {
dispatchFields({ state: validatedFieldState, type: 'REPLACE_STATE' })
dispatchFields({ type: 'REPLACE_STATE', state: validatedFieldState })
}
return isValid
@@ -299,8 +299,8 @@ const Form: React.FC<Props> = (props) => {
destination.state = {
status: [
{
message: json.message,
type: 'success',
message: json.message,
},
],
}
@@ -481,11 +481,11 @@ const Form: React.FC<Props> = (props) => {
})
dispatchFields({
type: 'ADD_ROW',
blockType: data?.blockType,
path,
rowIndex,
subFieldState,
type: 'ADD_ROW',
})
}
},
@@ -494,7 +494,7 @@ const Form: React.FC<Props> = (props) => {
const removeFieldRow: Context['removeFieldRow'] = useCallback(
({ path, rowIndex }) => {
dispatchFields({ path, rowIndex, type: 'REMOVE_ROW' })
dispatchFields({ type: 'REMOVE_ROW', path, rowIndex })
},
[dispatchFields],
)
@@ -520,11 +520,11 @@ const Form: React.FC<Props> = (props) => {
user,
})
dispatchFields({
type: 'REPLACE_ROW',
blockType: data?.blockType,
path,
rowIndex,
subFieldState,
type: 'REPLACE_ROW',
})
}
},
@@ -589,7 +589,7 @@ const Form: React.FC<Props> = (props) => {
})
contextRef.current = { ...initContextState } as FormContextType
setModified(false)
dispatchFields({ state, type: 'REPLACE_STATE' })
dispatchFields({ type: 'REPLACE_STATE', state })
},
[id, user, operation, locale, t, dispatchFields, getDocPreferences, config],
)
@@ -598,7 +598,7 @@ const Form: React.FC<Props> = (props) => {
(state: Fields) => {
contextRef.current = { ...initContextState } as FormContextType
setModified(false)
dispatchFields({ state, type: 'REPLACE_STATE' })
dispatchFields({ type: 'REPLACE_STATE', state })
},
[dispatchFields],
)
@@ -630,7 +630,7 @@ const Form: React.FC<Props> = (props) => {
useEffect(() => {
if (initialState) {
contextRef.current = { ...initContextState } as FormContextType
dispatchFields({ state: initialState, type: 'REPLACE_STATE' })
dispatchFields({ type: 'REPLACE_STATE', state: initialState })
}
}, [initialState, dispatchFields])
@@ -639,7 +639,7 @@ const Form: React.FC<Props> = (props) => {
contextRef.current = { ...initContextState } as FormContextType
const builtState = buildInitialState(initialData)
setFormattedInitialData(builtState)
dispatchFields({ state: builtState, type: 'REPLACE_STATE' })
dispatchFields({ type: 'REPLACE_STATE', state: builtState })
}
}, [initialData, dispatchFields])

View File

@@ -0,0 +1,42 @@
import flatleyImport from 'flatley'
import type { Data, Fields } from './types'
const { unflatten: flatleyUnflatten } = flatleyImport
type ReturnType = {
data: Data
valid: boolean
}
/**
* Reduce flattened form fields (Fields) to just map to the respective values instead of the full FormField object
*
* @param unflatten This also unflattens the data if `unflatten` is true. The unflattened data should match the original data structure
* @param ignoreDisableFormData - if true, will include fields that have `disableFormData` set to true, for example, blocks or arrays fields.
*
*/
export const reduceFieldsToValuesWithValidation = (
fields: Fields,
unflatten?: boolean,
ignoreDisableFormData?: boolean,
): ReturnType => {
const state: ReturnType = {
data: {},
valid: true,
}
if (!fields) return state
Object.keys(fields).forEach((key) => {
if (ignoreDisableFormData === true || !fields[key]?.disableFormData) {
state.data[key] = fields[key]?.value
if (!fields[key].valid) state.valid = false
}
})
if (unflatten) {
state.data = flatleyUnflatten(state.data, { safe: true })
}
return state
}

View File

@@ -1,4 +1,5 @@
import qs from 'qs'
import QueryString from 'qs'
import React, { createContext, useCallback, useContext, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useParams } from 'react-router-dom'
@@ -14,12 +15,13 @@ import { useAuth } from '../Auth'
import { useConfig } from '../Config'
import { useLocale } from '../Locale'
import { usePreferences } from '../Preferences'
import { UploadEditsProvider, useUploadEdits } from '../UploadEdits'
const Context = createContext({} as ContextType)
export const useDocumentInfo = (): ContextType => useContext(Context)
export const DocumentInfoProvider: React.FC<Props> = ({
const DocumentInfo: React.FC<Props> = ({
id: idFromProps,
children,
collection,
@@ -37,6 +39,7 @@ export const DocumentInfoProvider: React.FC<Props> = ({
const { i18n } = useTranslation()
const { permissions } = useAuth()
const { code } = useLocale()
const { uploadEdits } = useUploadEdits()
const [publishedDoc, setPublishedDoc] = useState<TypeWithID & TypeWithTimestamps>(null)
const [versions, setVersions] = useState<PaginatedDocs<Version>>(null)
const [unpublishedVersions, setUnpublishedVersions] = useState<PaginatedDocs<Version>>(null)
@@ -256,16 +259,31 @@ export const DocumentInfoProvider: React.FC<Props> = ({
)
useEffect(() => {
getVersions()
void getVersions()
}, [getVersions])
useEffect(() => {
getDocPermissions()
void getDocPermissions()
}, [getDocPermissions])
const action: string = React.useMemo(() => {
const docURL = `${baseURL}${pluralType === 'globals' ? `/globals` : ''}/${slug}${id ? `/${id}` : ''}`
const params = {
depth: 0,
'fallback-locale': 'null',
locale: code,
uploadEdits: uploadEdits || undefined,
}
return `${docURL}${QueryString.stringify(params, {
addQueryPrefix: true,
})}`
}, [baseURL, code, pluralType, id, slug, uploadEdits])
const value: ContextType = {
id,
slug,
action,
collection,
docPermissions,
getDocPermissions,
@@ -281,3 +299,11 @@ export const DocumentInfoProvider: React.FC<Props> = ({
return <Context.Provider value={value}>{children}</Context.Provider>
}
export const DocumentInfoProvider: React.FC<Props> = (props) => {
return (
<UploadEditsProvider>
<DocumentInfo {...props} />
</UploadEditsProvider>
)
}

View File

@@ -15,6 +15,7 @@ export type Version = TypeWithVersion<any>
export type DocumentPermissions = CollectionPermission | GlobalPermission
export type ContextType = {
action: string
collection?: SanitizedCollectionConfig
docPermissions: DocumentPermissions
getDocPermissions: () => Promise<void>

View File

@@ -1,21 +0,0 @@
import { createContext, useContext } from 'react'
import type { UploadEdits } from '../../views/collections/Edit/types'
export type QueryParamTypes = {
depth: number
'fallback-locale': string
locale: string
uploadEdits?: UploadEdits
}
export const FormQueryParams = createContext(
{} as {
formQueryParams: QueryParamTypes
setFormQueryParams: (params: QueryParamTypes) => void
},
)
export const useFormQueryParams = (): {
formQueryParams: QueryParamTypes
setFormQueryParams: (params: QueryParamTypes) => void
} => useContext(FormQueryParams)

View File

@@ -0,0 +1,8 @@
.script-language {
height: 100%;
& > *,
& > * > * {
letter-spacing: 0 !important;
}
}

View File

@@ -10,5 +10,9 @@ export const LanguageWrap: React.FC<{ children?: React.ReactNode }> = ({ childre
const currentLanguage = i18n?.language
const isScriptLanguage = currentLanguage && scriptLanguages.includes(currentLanguage)
return <div className={isScriptLanguage ? `script-language` : ''}>{children}</div>
if (isScriptLanguage) {
return <div className="script-language">{children}</div>
}
return <React.Fragment>{children}</React.Fragment>
}

View File

@@ -0,0 +1,28 @@
import React from 'react'
import type { UploadEdits } from '../../../../uploads/types'
export type UploadEditsContext = {
updateUploadEdits: (edits: UploadEdits) => void
uploadEdits: UploadEdits
}
const Context = React.createContext<UploadEditsContext>({
updateUploadEdits: undefined,
uploadEdits: undefined,
})
export const UploadEditsProvider = ({ children }) => {
const [uploadEdits, setUploadEdits] = React.useState<UploadEdits>(undefined)
const updateUploadEdits = (edits: UploadEdits) => {
setUploadEdits((prevEdits) => ({
...(prevEdits || {}),
...(edits || {}),
}))
}
return <Context.Provider value={{ updateUploadEdits, uploadEdits }}>{children}</Context.Provider>
}
export const useUploadEdits = (): UploadEditsContext => React.useContext(Context)

View File

@@ -27,8 +27,14 @@ const GlobalView: React.FC<IndexProps> = (props) => {
const { permissions, user } = useAuth()
const [initialState, setInitialState] = useState<Fields>()
const [updatedAt, setUpdatedAt] = useState<string>()
const { docPermissions, getDocPermissions, getDocPreferences, getVersions, preferencesKey } =
useDocumentInfo()
const {
action,
docPermissions,
getDocPermissions,
getDocPreferences,
getVersions,
preferencesKey,
} = useDocumentInfo()
const { getPreference } = usePreferences()
const { t } = useTranslation()
const config = useConfig()
@@ -49,8 +55,8 @@ const GlobalView: React.FC<IndexProps> = (props) => {
updatedAt: json?.result?.updatedAt || new Date().toISOString(),
})
getVersions()
getDocPermissions()
void getVersions()
void getDocPermissions()
setUpdatedAt(json?.result?.updatedAt)
const preferences = await getDocPreferences()
@@ -109,7 +115,7 @@ const GlobalView: React.FC<IndexProps> = (props) => {
setInitialState(state)
}
if (dataToRender) awaitInitialState()
if (dataToRender) void awaitInitialState()
}, [
dataToRender,
fields,
@@ -125,7 +131,7 @@ const GlobalView: React.FC<IndexProps> = (props) => {
const isLoading = !initialState || !docPermissions || isLoadingData
const componentProps: DefaultGlobalViewProps = {
action: `${serverURL}${api}/globals/${slug}?locale=${locale}&fallback-locale=null`,
action,
apiURL: `${serverURL}${api}/globals/${slug}?locale=${locale}${
global.versions?.drafts ? '&draft=true' : ''
}`,

View File

@@ -9,16 +9,20 @@ export const DeviceContainer: React.FC<{
const { children } = props
const deviceFrameRef = React.useRef<HTMLDivElement>(null)
const outerFrameRef = React.useRef<HTMLDivElement>(null)
const { breakpoint, setMeasuredDeviceSize, size, zoom } = useLivePreviewContext()
const { breakpoint, setMeasuredDeviceSize, size: desiredSize, zoom } = useLivePreviewContext()
// Keep an accurate measurement of the actual device size as it is truly rendered
// This is helpful when `sizes` are non-number units like percentages, etc.
const { size: measuredDeviceSize } = useResize(deviceFrameRef)
const { size: measuredDeviceSize } = useResize(deviceFrameRef.current)
const { size: outerFrameSize } = useResize(outerFrameRef.current)
let deviceIsLargerThanFrame: boolean = false
// Sync the measured device size with the context so that other components can use it
// This happens from the bottom up so that as this component mounts and unmounts,
// Its size is freshly populated again upon re-mounting, i.e. going from iframe->popup->iframe
// its size is freshly populated again upon re-mounting, i.e. going from iframe->popup->iframe
useEffect(() => {
if (measuredDeviceSize) {
setMeasuredDeviceSize(measuredDeviceSize)
@@ -33,13 +37,34 @@ export const DeviceContainer: React.FC<{
if (
typeof zoom === 'number' &&
typeof size.width === 'number' &&
typeof size.height === 'number'
typeof desiredSize.width === 'number' &&
typeof desiredSize.height === 'number' &&
typeof measuredDeviceSize.width === 'number' &&
typeof measuredDeviceSize.height === 'number'
) {
const scaledWidth = size.width / zoom
const difference = scaledWidth - size.width
x = `${difference / 2}px`
margin = '0 auto'
const scaledDesiredWidth = desiredSize.width / zoom
const scaledDeviceWidth = measuredDeviceSize.width * zoom
const scaledDeviceDifferencePixels = scaledDesiredWidth - desiredSize.width
deviceIsLargerThanFrame = scaledDeviceWidth > outerFrameSize.width
if (deviceIsLargerThanFrame) {
if (zoom > 1) {
const differenceFromDeviceToFrame = measuredDeviceSize.width - outerFrameSize.width
if (differenceFromDeviceToFrame < 0) x = `${differenceFromDeviceToFrame / 2}px`
else x = '0'
} else {
x = '0'
}
} else {
if (zoom >= 1) {
x = `${scaledDeviceDifferencePixels / 2}px`
} else {
const differenceFromDeviceToFrame = outerFrameSize.width - scaledDeviceWidth
x = `${differenceFromDeviceToFrame / 2}px`
margin = '0'
}
}
}
}
@@ -47,21 +72,29 @@ export const DeviceContainer: React.FC<{
let height = zoom ? `${100 / zoom}%` : '100%'
if (breakpoint !== 'responsive') {
width = `${size?.width / (typeof zoom === 'number' ? zoom : 1)}px`
height = `${size?.height / (typeof zoom === 'number' ? zoom : 1)}px`
width = `${desiredSize?.width / (typeof zoom === 'number' ? zoom : 1)}px`
height = `${desiredSize?.height / (typeof zoom === 'number' ? zoom : 1)}px`
}
return (
<div
ref={deviceFrameRef}
ref={outerFrameRef}
style={{
height,
margin,
transform: `translate3d(${x}, 0, 0)`,
width,
height: '100%',
width: '100%',
}}
>
{children}
<div
ref={deviceFrameRef}
style={{
height,
margin,
transform: `translate3d(${x}, 0, 0)`,
width,
}}
>
{children}
</div>
</div>
)
}

View File

@@ -10,17 +10,25 @@ import { formatDate } from '../../../../utilities/formatDate'
import ReactSelect from '../../../elements/ReactSelect'
import { fieldBaseClass } from '../../../forms/field-types/shared'
import { useConfig } from '../../../utilities/Config'
import { mostRecentVersionOption, publishedVersionOption } from '../shared'
import { renderPill } from '../../Versions/cells/AutosaveCell'
import './index.scss'
const baseClass = 'compare-version'
const maxResultsPerRequest = 10
const maxResultsPerRequest = 100
const baseOptions = [mostRecentVersionOption]
const baseOptions = []
const CompareVersion: React.FC<Props> = (props) => {
const { baseURL, onChange, parentID, publishedDoc, value, versionID } = props
const {
baseURL,
latestDraftVersion,
latestPublishedVersion,
onChange,
parentID,
value,
versionID,
} = props
const {
admin: { dateFormat },
@@ -70,30 +78,61 @@ const CompareVersion: React.FC<Props> = (props) => {
if (response.ok) {
const data: PaginatedDocs = await response.json()
if (data.docs.length > 0) {
setOptions((existingOptions) => [
...existingOptions,
...data.docs.map((doc) => ({
label: formatDate(doc.updatedAt, dateFormat, i18n?.language),
const versionInfo = {
draft: {
currentLabel: t('version:currentDraft'),
latestVersion: latestDraftVersion,
pillStyle: undefined,
previousLabel: t('version:draft'),
},
published: {
currentLabel: t('version:currentPublishedVersion'),
latestVersion: latestPublishedVersion,
pillStyle: 'success',
previousLabel: t('version:previouslyPublished'),
},
}
const additionalOptions = data.docs.map((doc) => {
const status = doc.version._status
const { currentLabel, latestVersion, pillStyle, previousLabel } =
versionInfo[status] || {}
return {
label: (
<div>
{formatDate(doc.updatedAt, dateFormat, i18n?.language)}
&nbsp;&nbsp;
{renderPill(doc, latestVersion, currentLabel, previousLabel, pillStyle)}
</div>
),
value: doc.id,
})),
])
}
})
setOptions(additionalOptions)
setLastLoadedPage(data.page)
}
} else {
setErrorLoading(t('error:unspecific'))
}
},
[dateFormat, baseURL, parentID, versionID, t, i18n],
[dateFormat, baseURL, parentID, versionID, t, i18n, latestDraftVersion, latestPublishedVersion],
)
useEffect(() => {
getResults({ lastLoadedPage: 1 })
void getResults({ lastLoadedPage: 1 })
}, [getResults])
const filteredOptions = options.filter(
(option, index, self) => self.findIndex((t) => t.value === option.value) === index,
)
useEffect(() => {
if (publishedDoc?._status === 'published')
setOptions((currentOptions) => [publishedVersionOption, ...currentOptions])
}, [publishedDoc])
if (filteredOptions.length > 0 && !value) {
onChange(filteredOptions[0])
}
}, [filteredOptions, value, onChange])
return (
<div
@@ -108,9 +147,9 @@ const CompareVersion: React.FC<Props> = (props) => {
isSearchable={false}
onChange={onChange}
onMenuScrollToBottom={() => {
getResults({ lastLoadedPage: lastLoadedPage + 1 })
void getResults({ lastLoadedPage: lastLoadedPage + 1 })
}}
options={options}
options={filteredOptions}
placeholder={t('selectVersionToCompare')}
value={value}
/>

View File

@@ -4,9 +4,10 @@ import type { CompareOption } from '../types'
export type Props = {
baseURL: string
latestDraftVersion?: string
latestPublishedVersion?: string
onChange: (val: CompareOption) => void
parentID?: string
publishedDoc: any
value: CompareOption
versionID: string
}

View File

@@ -6,11 +6,9 @@ import type { SanitizedCollectionConfig } from '../../../../../../../collections
import type { RelationshipField } from '../../../../../../../fields/config/types'
import type { Props } from '../types'
import {
fieldAffectsData,
fieldIsPresentationalOnly,
} from '../../../../../../../fields/config/types'
import { fieldAffectsData } from '../../../../../../../fields/config/types'
import { getTranslation } from '../../../../../../../utilities/getTranslation'
import { useUseTitleField } from '../../../../../../hooks/useUseAsTitle'
import { useConfig } from '../../../../../utilities/Config'
import { useLocale } from '../../../../../utilities/Locale'
import Label from '../../Label'
@@ -49,9 +47,10 @@ const generateLabelFromValue = (
if (relatedCollection) {
const useAsTitle = relatedCollection?.admin?.useAsTitle
const useAsTitleField = relatedCollection.fields.find(
(f) => fieldAffectsData(f) && !fieldIsPresentationalOnly(f) && f.name === useAsTitle,
)
// eslint-disable-next-line react-hooks/rules-of-hooks
const useAsTitleField = useUseTitleField(relatedCollection)
let titleFieldIsLocalized = false
if (useAsTitleField && fieldAffectsData(useAsTitleField))

View File

@@ -27,7 +27,6 @@ import fieldComponents from './RenderFieldsToDiff/fields'
import Restore from './Restore'
import SelectLocales from './SelectLocales'
import './index.scss'
import { mostRecentVersionOption } from './shared'
const baseClass = 'view-version'
@@ -46,11 +45,11 @@ const VersionView: React.FC<Props> = ({ collection, global }) => {
params: { id, versionID },
} = useRouteMatch<{ id?: string; versionID: string }>()
const [compareValue, setCompareValue] = useState<CompareOption>(mostRecentVersionOption)
const [compareValue, setCompareValue] = useState<CompareOption>()
const [localeOptions] = useState<Option[]>(() => {
if (localization && localization?.locales) {
return localization.locales.map(({ code, label }) => ({
label: label,
label,
value: code,
}))
}
@@ -70,6 +69,8 @@ const VersionView: React.FC<Props> = ({ collection, global }) => {
let compareBaseURL: string
let slug: string
let parentID: string
const [latestDraftVersion, setLatestDraftVersion] = useState(undefined)
const [latestPublishedVersion, setLatestPublishedVersion] = useState(undefined)
if (collection) {
;({ slug } = collection)
@@ -92,10 +93,7 @@ const VersionView: React.FC<Props> = ({ collection, global }) => {
fieldPermissions = permissions.globals[global.slug].fields
}
const compareFetchURL =
compareValue?.value === 'mostRecent' || compareValue?.value === 'published'
? originalDocFetchURL
: `${compareBaseURL}/${compareValue.value}`
const compareFetchURL = compareValue?.value && `${compareBaseURL}/${compareValue.value}`
const [{ data: doc, isError }] = usePayloadAPI(versionFetchURL, {
initialParams: { depth: 1, locale: '*' },
@@ -110,6 +108,39 @@ const VersionView: React.FC<Props> = ({ collection, global }) => {
initialParams: { depth: 1, draft: 'true', locale: '*' },
})
const sharedParams = (status) => {
return {
depth: 0,
limit: 1,
sort: '-updatedAt',
where: {
'version._status': {
equals: status,
},
},
}
}
const [{ data: draft }] = usePayloadAPI(compareBaseURL, {
initialParams: { ...sharedParams('draft') },
})
const [{ data: published }] = usePayloadAPI(compareBaseURL, {
initialParams: { ...sharedParams('published') },
})
useEffect(() => {
const formattedPublished = published?.docs?.length > 0 && published?.docs[0]
const formattedDraft = draft?.docs?.length > 0 && draft?.docs[0]
if (!formattedPublished || !formattedDraft) return
const publishedNewerThanDraft = formattedPublished?.updatedAt > formattedDraft?.updatedAt
setLatestDraftVersion(publishedNewerThanDraft ? undefined : formattedDraft?.id)
setLatestPublishedVersion(formattedPublished.latest ? formattedPublished?.id : undefined)
}, [draft, published])
useEffect(() => {
let nav: StepNavItem[] = []
@@ -192,18 +223,18 @@ const VersionView: React.FC<Props> = ({ collection, global }) => {
let metaTitle: string
let metaDesc: string
const formattedCreatedAt = doc?.createdAt
? formatDate(doc.createdAt, dateFormat, i18n?.language)
const versionCreatedAt = doc?.updatedAt
? formatDate(doc.updatedAt, dateFormat, i18n?.language)
: ''
if (collection) {
const useAsTitle = collection?.admin?.useAsTitle || 'id'
metaTitle = `${t('version')} - ${formattedCreatedAt} - ${doc[useAsTitle]} - ${entityLabel}`
metaTitle = `${t('version')} - ${versionCreatedAt} - ${doc[useAsTitle]} - ${entityLabel}`
metaDesc = t('viewingVersion', { documentTitle: doc[useAsTitle], entityLabel })
}
if (global) {
metaTitle = `${t('version')} - ${formattedCreatedAt} - ${entityLabel}`
metaTitle = `${t('version')} - ${versionCreatedAt} - ${entityLabel}`
metaDesc = t('viewingVersionGlobal', { entityLabel })
}
@@ -234,14 +265,14 @@ const VersionView: React.FC<Props> = ({ collection, global }) => {
})}
</p>
<header className={`${baseClass}__header`}>
<h2>{formattedCreatedAt}</h2>
<h2>{versionCreatedAt}</h2>
{canUpdate && (
<Restore
className={`${baseClass}__restore`}
collection={collection}
global={global}
originalDocID={id}
versionDate={formattedCreatedAt}
versionDate={versionCreatedAt}
versionID={versionID}
/>
)}
@@ -250,9 +281,10 @@ const VersionView: React.FC<Props> = ({ collection, global }) => {
<div className={`${baseClass}__controls`}>
<CompareVersion
baseURL={compareBaseURL}
latestDraftVersion={latestDraftVersion}
latestPublishedVersion={latestPublishedVersion}
onChange={setCompareValue}
parentID={parentID}
publishedDoc={publishedDoc}
value={compareValue}
versionID={versionID}
/>

View File

@@ -1,9 +0,0 @@
export const mostRecentVersionOption = {
label: 'Most recent',
value: 'mostRecent',
}
export const publishedVersionOption = {
label: 'Most recently published',
value: 'published',
}

View File

@@ -17,7 +17,17 @@ import './index.scss'
const baseClass = 'versions'
export const DefaultVersionsView: React.FC<Props> = (props) => {
const { id, collection, data, entityLabel, global, isLoadingVersions, versionsData } = props
const {
id,
collection,
data,
entityLabel,
global,
isLoadingVersions,
latestDraftVersion,
latestPublishedVersion,
versionsData,
} = props
const { t } = useTranslation('version')
@@ -58,7 +68,13 @@ export const DefaultVersionsView: React.FC<Props> = (props) => {
})}
</div> */}
<Table
columns={buildVersionColumns(collection, global, t)}
columns={buildVersionColumns(
collection,
global,
t,
latestDraftVersion,
latestPublishedVersion,
)}
data={versionsData?.docs}
/>
<div className={`${baseClass}__page-controls`}>

View File

@@ -0,0 +1,62 @@
'use client'
import React, { Fragment } from 'react'
import { useTranslation } from 'react-i18next'
import { Pill } from '../../../'
type AutosaveCellProps = {
latestDraftVersion?: string
latestPublishedVersion?: string
rowData: any
}
export const renderPill = (data, latestVersion, currentLabel, previousLabel, pillStyle) => {
return (
<React.Fragment>
{data?.id === latestVersion ? (
<Pill pillStyle={pillStyle}>{currentLabel}</Pill>
) : (
<Pill>{previousLabel}</Pill>
)}
&nbsp;&nbsp;
</React.Fragment>
)
}
export const AutosaveCell: React.FC<AutosaveCellProps> = ({
latestDraftVersion,
latestPublishedVersion,
rowData,
}) => {
const { t } = useTranslation()
const status = rowData?.version._status
const versionInfo = {
draft: {
currentLabel: t('version:currentDraft'),
latestVersion: latestDraftVersion,
pillStyle: undefined,
previousLabel: t('version:draft'),
},
published: {
currentLabel: t('version:currentPublishedVersion'),
latestVersion: latestPublishedVersion,
pillStyle: 'success',
previousLabel: t('version:previouslyPublished'),
},
}
const { currentLabel, latestVersion, pillStyle, previousLabel } = versionInfo[status] || {}
return (
<Fragment>
{rowData?.autosave && (
<React.Fragment>
<Pill>{t('version:autosave')}</Pill>
&nbsp;&nbsp;
</React.Fragment>
)}
{status && renderPill(rowData, latestVersion, currentLabel, previousLabel, pillStyle)}
</Fragment>
)
}

View File

@@ -8,10 +8,10 @@ import type { SanitizedCollectionConfig } from '../../../../collections/config/t
import type { SanitizedGlobalConfig } from '../../../../globals/config/types'
import type { Column } from '../../elements/Table/types'
import { Pill } from '../..'
import { formatDate } from '../../../utilities/formatDate'
import SortColumn from '../../elements/SortColumn'
import { useConfig } from '../../utilities/Config'
import { AutosaveCell } from './cells/AutosaveCell'
type CreatedAtCellProps = {
collection?: SanitizedCollectionConfig
@@ -45,6 +45,8 @@ export const buildVersionColumns = (
collection: SanitizedCollectionConfig,
global: SanitizedGlobalConfig,
t: TFunction,
latestDraftVersion?: string,
latestPublishedVersion?: string,
): Column[] => [
{
name: '',
@@ -73,24 +75,16 @@ export const buildVersionColumns = (
accessor: 'autosave',
active: true,
components: {
Heading: <SortColumn disable label={t('type')} name="autosave" />,
renderCell: (row) => (
<TextCell>
{row?.autosave && (
<React.Fragment>
<Pill>{t('autosave')}</Pill>
&nbsp;&nbsp;
</React.Fragment>
)}
{row?.version._status === 'published' && (
<React.Fragment>
<Pill pillStyle="success">{t('published')}</Pill>
&nbsp;&nbsp;
</React.Fragment>
)}
{row?.version._status === 'draft' && <Pill>{t('draft')}</Pill>}
</TextCell>
),
Heading: <SortColumn disable label={t('status')} name="autosave" />,
renderCell: (row) => {
return (
<AutosaveCell
latestDraftVersion={latestDraftVersion}
latestPublishedVersion={latestPublishedVersion}
rowData={row}
/>
)
},
},
label: '',
},

View File

@@ -36,6 +36,8 @@ const VersionsView: React.FC<IndexProps> = (props) => {
let entityLabel: string
let slug: string
let editURL: string
const [latestDraftVersion, setLatestDraftVersion] = useState(undefined)
const [latestPublishedVersion, setLatestPublishedVersion] = useState(undefined)
if (collection) {
;({ slug } = collection)
@@ -92,6 +94,38 @@ const VersionsView: React.FC<IndexProps> = (props) => {
const [{ data: versionsData, isLoading: isLoadingVersions }, { setParams }] =
usePayloadAPI(fetchURL)
const sharedParams = (status) => {
return {
depth: 0,
limit: 1,
sort: '-updatedAt',
where: {
'version._status': {
equals: status,
},
},
}
}
const [{ data: draft }] = usePayloadAPI(fetchURL, {
initialParams: { ...sharedParams('draft') },
})
const [{ data: published }] = usePayloadAPI(fetchURL, {
initialParams: { ...sharedParams('published') },
})
useEffect(() => {
const formattedPublished = published?.docs?.length > 0 && published?.docs[0]
const formattedDraft = draft?.docs?.length > 0 && draft?.docs[0]
if (!formattedPublished || !formattedDraft) return
const publishedNewerThanDraft = formattedPublished?.updatedAt > formattedDraft?.updatedAt
setLatestDraftVersion(publishedNewerThanDraft ? undefined : formattedDraft?.id)
setLatestPublishedVersion(formattedPublished.latest ? formattedPublished?.id : undefined)
}, [draft, published])
useEffect(() => {
const params = {
depth: 1,
@@ -158,6 +192,8 @@ const VersionsView: React.FC<IndexProps> = (props) => {
global,
isLoading,
isLoadingVersions,
latestDraftVersion,
latestPublishedVersion,
user,
versionsData,
}}

View File

@@ -17,5 +17,7 @@ export type Props = IndexProps & {
id: string
isLoading: boolean
isLoadingVersions: boolean
latestDraftVersion?: string
latestPublishedVersion?: string
versionsData: PaginatedDocs<Version>
}

View File

@@ -46,7 +46,6 @@ const DefaultEditView: React.FC<DefaultEditViewProps> = (props) => {
} = props
const { setViewActions } = useActions()
const { reportUpdate } = useDocumentEvents()
const { auth } = collection

View File

@@ -1,11 +1,9 @@
import queryString from 'qs'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useHistory, useRouteMatch } from 'react-router-dom'
import type { CollectionPermission } from '../../../../../auth'
import type { Fields } from '../../../forms/Form/types'
import type { QueryParamTypes } from '../../../utilities/FormQueryParams'
import type { DefaultEditViewProps } from './Default'
import type { IndexProps } from './types'
@@ -16,7 +14,6 @@ import { useAuth } from '../../../utilities/Auth'
import { useConfig } from '../../../utilities/Config'
import { useDocumentInfo } from '../../../utilities/DocumentInfo'
import { EditDepthContext } from '../../../utilities/EditDepth'
import { FormQueryParams } from '../../../utilities/FormQueryParams'
import { useLocale } from '../../../utilities/Locale'
import RenderCustomComponent from '../../../utilities/RenderCustomComponent'
import NotFound from '../../NotFound'
@@ -32,15 +29,6 @@ const EditView: React.FC<IndexProps> = (props) => {
const [fields] = useState(() => formatFields(incomingCollection, isEditing))
const [collection] = useState(() => ({ ...incomingCollection, fields }))
const [redirect, setRedirect] = useState<string>()
const [formQueryParams, setFormQueryParams] = useState<QueryParamTypes>({
depth: 0,
'fallback-locale': 'null',
locale: '',
uploadEdits: undefined,
})
const formattedQueryParams = queryString.stringify(formQueryParams)
const { code: locale } = useLocale()
const config = useConfig()
@@ -56,7 +44,8 @@ const EditView: React.FC<IndexProps> = (props) => {
const [updatedAt, setUpdatedAt] = useState<string>()
const { permissions, user } = useAuth()
const userRef = useRef(user)
const { docPermissions, getDocPermissions, getDocPreferences, getVersions } = useDocumentInfo()
const { action, docPermissions, getDocPermissions, getDocPreferences, getVersions } =
useDocumentInfo()
const { t } = useTranslation('general')
const [{ data, isError, isLoading: isLoadingData }, { refetchData }] = usePayloadAPI(
@@ -87,20 +76,16 @@ const EditView: React.FC<IndexProps> = (props) => {
)
const onSave = useCallback(
async (json: { doc }) => {
getVersions()
getDocPermissions()
(json: { doc }) => {
void getVersions()
void getDocPermissions()
setUpdatedAt(json?.doc?.updatedAt)
if (!isEditing) {
setRedirect(`${admin}/collections/${collection.slug}/${json?.doc?.id}`)
} else {
buildState(json.doc, {
void buildState(json.doc, {
fieldSchema: collection.fields,
})
setFormQueryParams((params) => ({
...params,
uploadEdits: undefined,
}))
}
},
[admin, getVersions, isEditing, buildState, getDocPermissions, collection],
@@ -108,15 +93,15 @@ const EditView: React.FC<IndexProps> = (props) => {
useEffect(() => {
if (fields && (isEditing ? data : true)) {
const awaitInternalState = async () => {
const awaitInternalState = () => {
setUpdatedAt(data?.updatedAt)
buildState(data, {
void buildState(data, {
fieldSchema: fields,
operation: isEditing ? 'update' : 'create',
})
}
awaitInternalState()
void awaitInternalState()
}
}, [isEditing, data, buildState, fields])
@@ -126,13 +111,6 @@ const EditView: React.FC<IndexProps> = (props) => {
}
}, [history, redirect])
useEffect(() => {
setFormQueryParams((params) => ({
...params,
locale,
}))
}, [locale])
useEffect(() => {
if (history.location.state?.refetchDocumentData) {
void refetchData()
@@ -147,10 +125,6 @@ const EditView: React.FC<IndexProps> = (props) => {
collection.versions.drafts ? '&draft=true' : ''
}`
const action = `${serverURL}${api}/${collectionSlug}${
isEditing ? `/${id}` : ''
}?${formattedQueryParams}`
const hasSavePermission =
(isEditing && docPermissions?.update?.permission) ||
(!isEditing && (docPermissions as CollectionPermission)?.create?.permission)
@@ -177,13 +151,11 @@ const EditView: React.FC<IndexProps> = (props) => {
return (
<EditDepthContext.Provider value={1}>
<FormQueryParams.Provider value={{ formQueryParams, setFormQueryParams }}>
<RenderCustomComponent
CustomComponent={typeof Edit === 'function' ? Edit : undefined}
DefaultComponent={DefaultEdit}
componentProps={componentProps}
/>
</FormQueryParams.Provider>
<RenderCustomComponent
CustomComponent={typeof Edit === 'function' ? Edit : undefined}
DefaultComponent={DefaultEdit}
componentProps={componentProps}
/>
</EditDepthContext.Provider>
)
}

View File

@@ -200,11 +200,4 @@ dialog {
z-index: var(--z-modal);
}
.script-language {
& > *,
& > * > * {
letter-spacing: 0 !important;
}
}
@import '~payload-user-css';

View File

@@ -11,15 +11,13 @@ interface Resize {
size?: Size
}
export const useResize = (ref: React.MutableRefObject<HTMLElement>): Resize => {
export const useResize = (element: HTMLElement): Resize => {
const [size, setSize] = useState<Size>()
useEffect(() => {
let observer: any // eslint-disable-line
const { current: currentRef } = ref
if (currentRef) {
if (element) {
observer = new ResizeObserver((entries) => {
entries.forEach((entry) => {
const {
@@ -52,15 +50,15 @@ export const useResize = (ref: React.MutableRefObject<HTMLElement>): Resize => {
})
})
observer.observe(currentRef)
observer.observe(element)
}
return () => {
if (observer) {
observer.unobserve(currentRef)
observer.unobserve(element)
}
}
}, [ref])
}, [element])
return {
size,

View File

@@ -1,2 +1,5 @@
const isLocked = (date: number): boolean => !!(date && date > Date.now())
const isLocked = (date: number): boolean => {
if (!date) return false
return date > Date.now()
}
export default isLocked

View File

@@ -105,7 +105,7 @@ async function login<TSlug extends keyof GeneratedTypes['collections']>(
throw new AuthenticationError(req.t)
}
if (user && isLocked(user.lockUntil)) {
if (user && isLocked(new Date(user.lockUntil).getTime())) {
throw new LockedAuth(req.t)
}

View File

@@ -46,12 +46,12 @@ const sanitizeCollection = (
if (!hasUpdatedAt) {
sanitized.fields.push({
name: 'updatedAt',
type: 'date',
admin: {
disableBulkEdit: true,
hidden: true,
},
label: translations['general:updatedAt'],
type: 'date',
})
}
if (!hasCreatedAt) {
@@ -62,9 +62,9 @@ const sanitizeCollection = (
hidden: true,
},
// The default sort for list view is createdAt. Thus, enabling indexing by default, is a major performance improvement, especially for large or a large amount of collections.
type: 'date',
index: true,
label: translations['general:createdAt'],
type: 'date',
})
}
}
@@ -82,6 +82,7 @@ const sanitizeCollection = (
if (sanitized.versions.drafts === true) {
sanitized.versions.drafts = {
autosave: false,
validate: false,
}
}
@@ -91,6 +92,10 @@ const sanitizeCollection = (
}
}
if (sanitized.versions.drafts.validate === undefined) {
sanitized.versions.drafts.validate = false
}
sanitized.fields = mergeBaseFields(sanitized.fields, baseVersionFields)
}
}

View File

@@ -228,6 +228,7 @@ const collectionSchema = joi.object().keys({
interval: joi.number(),
}),
),
validate: joi.boolean(),
}),
joi.boolean(),
),

View File

@@ -204,7 +204,10 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
global: null,
operation: 'create',
req,
skipValidation: shouldSaveDraft,
skipValidation:
shouldSaveDraft &&
collectionConfig.versions.drafts &&
!collectionConfig.versions.drafts.validate,
})
// /////////////////////////////////////

View File

@@ -271,7 +271,10 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
operation: 'update',
req,
skipValidation:
Boolean(collectionConfig.versions?.drafts) && data._status !== 'published',
shouldSaveDraft &&
collectionConfig.versions.drafts &&
!collectionConfig.versions.drafts.validate &&
data._status !== 'published',
})
// /////////////////////////////////////

View File

@@ -244,7 +244,11 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
global: null,
operation: 'update',
req,
skipValidation: Boolean(collectionConfig.versions?.drafts) && data._status !== 'published',
skipValidation:
shouldSaveDraft &&
collectionConfig.versions.drafts &&
!collectionConfig.versions.drafts.validate &&
data._status !== 'published',
})
// /////////////////////////////////////

View File

@@ -42,6 +42,7 @@ const sanitizeGlobals = (config: Config): SanitizedGlobalConfig[] => {
if (sanitizedGlobal.versions.drafts === true) {
sanitizedGlobal.versions.drafts = {
autosave: false,
validate: false,
}
}
@@ -51,6 +52,10 @@ const sanitizeGlobals = (config: Config): SanitizedGlobalConfig[] => {
}
}
if (sanitizedGlobal.versions.drafts.validate === undefined) {
sanitizedGlobal.versions.drafts.validate = false
}
sanitizedGlobal.fields = mergeBaseFields(sanitizedGlobal.fields, baseVersionFields)
}
}
@@ -72,23 +77,23 @@ const sanitizeGlobals = (config: Config): SanitizedGlobalConfig[] => {
if (!hasUpdatedAt) {
sanitizedGlobal.fields.push({
name: 'updatedAt',
type: 'date',
admin: {
disableBulkEdit: true,
hidden: true,
},
label: translations['general:updatedAt'],
type: 'date',
})
}
if (!hasCreatedAt) {
sanitizedGlobal.fields.push({
name: 'createdAt',
type: 'date',
admin: {
disableBulkEdit: true,
hidden: true,
},
label: translations['general:createdAt'],
type: 'date',
})
}

View File

@@ -79,6 +79,7 @@ const globalSchema = joi
interval: joi.number(),
}),
),
validate: joi.boolean(),
}),
joi.boolean(),
),

View File

@@ -168,7 +168,8 @@ async function update<TSlug extends keyof GeneratedTypes['globals']>(
global: globalConfig,
operation: 'update',
req,
skipValidation: shouldSaveDraft,
skipValidation:
shouldSaveDraft && globalConfig.versions.drafts && !globalConfig.versions.drafts.validate,
})
// /////////////////////////////////////

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "تأكيد إلغاء النّشر",
"confirmVersionRestoration": "تأكيد إستعادة النّسخة",
"currentDocumentStatus": "المستند {{docStatus}} الحالي",
"currentDraft": "مسودة حالية",
"currentPublishedVersion": "النسخة المنشورة الحالية",
"draft": "مسودّة",
"draftSavedSuccessfully": "تمّ حفظ المسودّة بنجاح.",
"lastSavedAgo": "تم الحفظ آخر مرة قبل {{distance}}",
"noFurtherVersionsFound": "لم يتمّ العثور على نسخات أخرى",
"noRowsFound": "لم يتمّ العثور على {{label}}",
"preview": "معاينة",
"previouslyPublished": "نشر سابقا",
"problemRestoringVersion": "حدث خطأ في استعادة هذه النّسخة",
"publish": "نشر",
"publishChanges": "نشر التّغييرات",
@@ -378,4 +381,4 @@
"viewingVersions": "يتمّ استعراض النُّسَخ ل {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "يتمّ استعراض النُّسَخ للاعداد العامّ {{entityLabel}}"
}
}
}

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "Dərcdən çıxartmağı təsdiq edin",
"confirmVersionRestoration": "Versiyanın bərpasını təsdiq edin",
"currentDocumentStatus": "Cari {{docStatus}} sənədi",
"currentDraft": "Cari Müsvedde",
"currentPublishedVersion": "Hazırki Nəşr Versiyası",
"draft": "Qaralama",
"draftSavedSuccessfully": "Qaralama uğurla yadda saxlandı.",
"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ış",
"previouslyPublished": "Əvvəlki nəşr olunmuş",
"problemRestoringVersion": "Bu versiyanın bərpasında problem yaşandı",
"publish": "Dərc et",
"publishChanges": "Dəyişiklikləri dərc et",
@@ -378,4 +381,4 @@
"viewingVersions": "{{entityLabel}} {{documentTitle}} üçün versiyaları göstərir",
"viewingVersionsGlobal": "Qlobal {{entityLabel}} üçün versiyaları göstərir"
}
}
}

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "Потвърди скриване",
"confirmVersionRestoration": "Потвърди възстановяване на версия",
"currentDocumentStatus": "Сегашен статус на документа: {{docStatus}}",
"currentDraft": "Текуща версия",
"currentPublishedVersion": "Текуща публикувана версия",
"draft": "Чернова",
"draftSavedSuccessfully": "Чернова запазена успешно.",
"lastSavedAgo": "последно запазено преди {{distance}}",
"noFurtherVersionsFound": "Не са открити повече версии",
"noRowsFound": "Не е открит {{label}}",
"preview": "Предварителен преглед",
"previouslyPublished": "Предишно публикувано",
"problemRestoringVersion": "Имаше проблем при възстановяването на тази версия",
"publish": "Публикувай",
"publishChanges": "Публикувай промените",
@@ -378,4 +381,4 @@
"viewingVersions": "Гледане на версии за {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "Гледане на версии за глобалния документ {{entityLabel}}"
}
}
}

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "Potvrdit zrušení publikování",
"confirmVersionRestoration": "Potvrdit obnovení verze",
"currentDocumentStatus": "Současný {{docStatus}} dokument",
"currentDraft": "Aktuální koncept",
"currentPublishedVersion": "Aktuálně publikovaná verze",
"draft": "Koncept",
"draftSavedSuccessfully": "Koncept úspěšně uložen.",
"lastSavedAgo": "Naposledy uloženo před {{distance}}",
"noFurtherVersionsFound": "Nenalezeny další verze",
"noRowsFound": "Nenalezen {{label}}",
"preview": "Náhled",
"previouslyPublished": "Dříve publikováno",
"problemRestoringVersion": "Při obnovování této verze došlo k problému",
"publish": "Publikovat",
"publishChanges": "Publikovat změny",
@@ -378,4 +381,4 @@
"viewingVersions": "Zobrazuji verze pro {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "Zobrazuji verze pro globální {{entityLabel}}"
}
}
}

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "Setzen auf Entwurf bestätigen",
"confirmVersionRestoration": " Wiederherstellung der Version bestätigen",
"currentDocumentStatus": "Aktueller Dokumentenstatus: {{docStatus}}",
"currentDraft": "Aktueller Entwurf",
"currentPublishedVersion": "Aktuell veröffentlichte Version",
"draft": "Entwurf",
"draftSavedSuccessfully": "Entwurf erfolgreich gespeichert.",
"lastSavedAgo": "Zuletzt vor {{distance}} gespeichert",
"noFurtherVersionsFound": "Keine weiteren Versionen vorhanden",
"noRowsFound": "Kein {{label}} gefunden",
"preview": "Vorschau",
"previouslyPublished": "Zuvor veröffentlicht",
"problemRestoringVersion": "Es gab ein Problem bei der Wiederherstellung dieser Version",
"publish": "Veröffentlichen",
"publishChanges": "Änderungen veröffentlichen",
@@ -378,4 +381,4 @@
"viewingVersions": "Betrachte Versionen für {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "`Betrachte Versionen für das Globale Dokument {{entityLabel}}"
}
}
}

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "Confirm unpublish",
"confirmVersionRestoration": "Confirm version Restoration",
"currentDocumentStatus": "Current {{docStatus}} document",
"currentDraft": "Current Draft",
"currentPublishedVersion": "Current Published Version",
"draft": "Draft",
"draftSavedSuccessfully": "Draft saved successfully.",
"lastSavedAgo": "Last saved {{distance}} ago",
"noFurtherVersionsFound": "No further versions found",
"noRowsFound": "No {{label}} found",
"preview": "Preview",
"previouslyPublished": "Previously Published",
"problemRestoringVersion": "There was a problem restoring this version",
"publish": "Publish",
"publishChanges": "Publish changes",

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "Confirmar despublicado",
"confirmVersionRestoration": "Confirmar restauración de versión",
"currentDocumentStatus": "Documento {{docStatus}} actual",
"currentDraft": "Borrador Actual",
"currentPublishedVersion": "Versión Publicada Actualmente",
"draft": "Borrador",
"draftSavedSuccessfully": "Borrador guardado con éxito.",
"lastSavedAgo": "Guardado por última vez hace {{distance}}",
"noFurtherVersionsFound": "No se encontraron más versiones",
"noRowsFound": "No encontramos {{label}}",
"preview": "Previsualizar",
"previouslyPublished": "Publicado anteriormente",
"problemRestoringVersion": "Ocurrió un problema al restaurar esta versión",
"publish": "Publicar",
"publishChanges": "Publicar cambios",
@@ -378,4 +381,4 @@
"viewingVersions": "Viendo versiones para {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "Viendo versiones para el global {{entityLabel}}"
}
}
}

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "تأیید لغو انتشار",
"confirmVersionRestoration": "تأیید بازیابی نگارش",
"currentDocumentStatus": "جاری {{docStatus}} سند",
"currentDraft": "پیش نویس فعلی",
"currentPublishedVersion": "نسخه منتشر شده فعلی",
"draft": "پیش‌نویس",
"draftSavedSuccessfully": "پیش‌نویس با موفقیت ذخیره شد.",
"lastSavedAgo": "آخرین بار {{distance}} پیش ذخیره شد",
"noFurtherVersionsFound": "نگارش دیگری یافت نشد",
"noRowsFound": "هیچ {{label}} یافت نشد",
"preview": "پیش‌نمایش",
"previouslyPublished": "قبلا منتشر شده",
"problemRestoringVersion": "مشکلی در بازیابی این نگارش وجود دارد",
"publish": "انتشار",
"publishChanges": "انتشار تغییرات",
@@ -378,4 +381,4 @@
"viewingVersions": "مشاهده نگارش‌ها برای {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "مشاهده نگارش‌های کلی {{entityLabel}}"
}
}
}

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "Confirmer lannulation",
"confirmVersionRestoration": "Confirmer la restauration de la version",
"currentDocumentStatus": "Document {{docStatus}} actuel",
"currentDraft": "Projet actuel",
"currentPublishedVersion": "Version Publiée Actuelle",
"draft": "Brouillon",
"draftSavedSuccessfully": "Brouillon enregistré avec succès.",
"lastSavedAgo": "Dernière sauvegarde il y a {{distance}}",
"noFurtherVersionsFound": "Aucune autre version trouvée",
"noRowsFound": "Aucun(e) {{label}} trouvé(e)",
"preview": "Aperçu",
"previouslyPublished": "Précédemment publié",
"problemRestoringVersion": "Un problème est survenu lors de la restauration de cette version",
"publish": "Publier",
"publishChanges": "Publier les modifications",
@@ -378,4 +381,4 @@
"viewingVersions": "Affichage des versions de ou du {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "Affichage des versions globales de ou du {{entityLabel}}"
}
}
}

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "Potvrdite poništavanje objave",
"confirmVersionRestoration": "Potvrdite vraćanje verzije",
"currentDocumentStatus": "Trenutni {{docStatus}} dokumenta",
"currentDraft": "Trenutačni nacrt",
"currentPublishedVersion": "Trenutačno objavljena verzija",
"draft": "Nacrt",
"draftSavedSuccessfully": "Nacrt uspješno spremljen.",
"lastSavedAgo": "Zadnji put spremljeno prije {{distance}",
"noFurtherVersionsFound": "Nisu pronađene daljnje verzije",
"noRowsFound": "{{label}} nije pronađeno",
"preview": "Pregled",
"previouslyPublished": "Prethodno objavljeno",
"problemRestoringVersion": "Nastao je problem pri vraćanju ove verzije",
"publish": "Objaviti",
"publishChanges": "Objavi promjene",
@@ -378,4 +381,4 @@
"viewingVersions": "Pregled verzija za {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "Pregled verzije za globalni {{entityLabel}}"
}
}
}

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "A közzététel visszavonásának megerősítése",
"confirmVersionRestoration": "Verzió-visszaállítás megerősítése",
"currentDocumentStatus": "Jelenlegi {{docStatus}} dokumentum",
"currentDraft": "Jelenlegi vázlat",
"currentPublishedVersion": "Aktuálisan Kiadott Verzió",
"draft": "Piszkozat",
"draftSavedSuccessfully": "A piszkozat sikeresen mentve.",
"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",
"previouslyPublished": "Korábban Megjelent",
"problemRestoringVersion": "Hiba történt a verzió visszaállításakor",
"publish": "Közzététel",
"publishChanges": "Módosítások közzététele",
@@ -378,4 +381,4 @@
"viewingVersions": "A {{entityLabel}} {{documentTitle}} verzióinak megtekintése",
"viewingVersionsGlobal": "A globális {{entityLabel}} verzióinak megtekintése"
}
}
}

View File

@@ -342,12 +342,15 @@
"confirmUnpublish": "Conferma annullamento della pubblicazione",
"confirmVersionRestoration": "Conferma il ripristino della versione",
"currentDocumentStatus": "Documento {{docStatus}} corrente",
"currentDraft": "Bozza Corrente",
"currentPublishedVersion": "Versione Pubblicata Corrente",
"draft": "Bozza",
"draftSavedSuccessfully": "Bozza salvata con successo.",
"lastSavedAgo": "Ultimo salvataggio {{distance}} fa",
"noFurtherVersionsFound": "Non sono state trovate ulteriori versioni",
"noRowsFound": "Nessun {{label}} trovato",
"preview": "Anteprima",
"previouslyPublished": "Pubblicato in precedenza",
"problemRestoringVersion": "Si è verificato un problema durante il ripristino di questa versione",
"publish": "Pubblicare",
"publishChanges": "Pubblica modifiche",
@@ -379,4 +382,4 @@
"viewingVersions": "Visualizzazione delle versioni per {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "`Visualizzazione delle versioni per {{entityLabel}}"
}
}
}

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "非公開の確認",
"confirmVersionRestoration": "バージョン復元の確認",
"currentDocumentStatus": "現在の {{docStatus}} データ",
"currentDraft": "現行のドラフト",
"currentPublishedVersion": "現在公開されているバージョン",
"draft": "ドラフト",
"draftSavedSuccessfully": "下書きは正常に保存されました。",
"lastSavedAgo": "{{distance}}前に最後に保存されました",
"noFurtherVersionsFound": "その他のバージョンは見つかりませんでした。",
"noRowsFound": "{{label}} は未設定です",
"preview": "プレビュー",
"previouslyPublished": "以前に公開された",
"problemRestoringVersion": "このバージョンの復元に問題がありました。",
"publish": "公開する",
"publishChanges": "変更内容を公開",
@@ -378,4 +381,4 @@
"viewingVersions": "表示バージョン: {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "表示バージョン: グローバルな {{entityLabel}}"
}
}
}

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "게시 해제하기",
"confirmVersionRestoration": "버전 복원하기",
"currentDocumentStatus": "현재 {{docStatus}} 문서",
"currentDraft": "현재 초안",
"currentPublishedVersion": "현재 게시된 버전",
"draft": "초안",
"draftSavedSuccessfully": "초안이 저장되었습니다.",
"lastSavedAgo": "마지막으로 저장한지 {{distance}} 전",
"noFurtherVersionsFound": "더 이상의 버전을 찾을 수 없습니다.",
"noRowsFound": "{{label}}을(를) 찾을 수 없음",
"preview": "미리보기",
"previouslyPublished": "이전에 발표된",
"problemRestoringVersion": "이 버전을 복원하는 중 문제가 발생했습니다.",
"publish": "게시",
"publishChanges": "변경 사항 게시",
@@ -378,4 +381,4 @@
"viewingVersions": "{{entityLabel}} {{documentTitle}}에 대한 버전 보기",
"viewingVersionsGlobal": "글로벌 {{entityLabel}}에 대한 버전 보기"
}
}
}

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "အများဆိုင်ကို ဖျက်ရန် အတည်ပြုပါ။",
"confirmVersionRestoration": "ဗားရှင်းပြန်လည် အသုံးပြုခြင်းကို အတည်ပြုပါ။",
"currentDocumentStatus": "လက်ရှိ {{docStatus}} ဖိုင်",
"currentDraft": "လက်ရှိမှန်ကန့်သတ်",
"currentPublishedVersion": "Versi yang Diterbitkan Saat Ini",
"draft": "မူကြမ်း",
"draftSavedSuccessfully": "မူကြမ်းကို အောင်မြင်စွာ သိမ်းဆည်းပြီးပါပြီ။",
"lastSavedAgo": "နောက်ဆုံး သိမ်းချက် {{distance}} ကြာပြီး",
"noFurtherVersionsFound": "နောက်ထပ်ဗားရှင်းများ မတွေ့ပါ။",
"noRowsFound": "{{label}} အားမတွေ့ပါ။",
"preview": "နမူနာပြရန်",
"previouslyPublished": "Sebelum ini diterbitkan",
"problemRestoringVersion": "ဤဗားရှင်းကို ပြန်လည်ရယူရာတွင် ပြဿနာရှိနေသည်။",
"publish": "ထုတ်ဝေသည်။",
"publishChanges": "အပြောင်းအလဲများကို တင်ခဲ့သည်။",
@@ -378,4 +381,4 @@
"viewingVersions": "{{entityLabel}} {{documentTitle}} အတွက် ဗားရှင်းများကို ကြည့်ရှုခြင်း",
"viewingVersionsGlobal": "`ဂလိုဘယ်ဆိုင်ရာ {{entityLabel}} အတွက် ဗားရှင်းများကို ကြည့်ရှုနေသည်"
}
}
}

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "Bekreft avpublisering",
"confirmVersionRestoration": "Bekreft versjon-gjenoppretting",
"currentDocumentStatus": "Nåværende {{docStatus}} dokument",
"currentDraft": "Nåværende utkast",
"currentPublishedVersion": "Nåværende publiserte versjon",
"draft": "Utkast",
"draftSavedSuccessfully": "Utkast lagret.",
"lastSavedAgo": "Sist lagret {{distance}} siden",
"noFurtherVersionsFound": "Ingen flere versjoner funnet",
"noRowsFound": "Ingen {{label}} funnet",
"preview": "Forhåndsvisning",
"previouslyPublished": "Tidligere Publisert",
"problemRestoringVersion": "Det oppstod et problem med gjenoppretting av denne versjonen",
"publish": "Publisere",
"publishChanges": "Publiser endringer",
@@ -378,4 +381,4 @@
"viewingVersions": "Viser versjoner for {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "Viser versjoner for den globale variabelen {{entityLabel}}"
}
}
}

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "Bevestig depubliceren",
"confirmVersionRestoration": "Bevestig te herstellen versie",
"currentDocumentStatus": "Huidig {{docStatus}} document",
"currentDraft": "Huidige Concept",
"currentPublishedVersion": "Huidige Gepubliceerde Versie",
"draft": "Concept",
"draftSavedSuccessfully": "Concept succesvol bewaard.",
"lastSavedAgo": "Laatst opgeslagen {{distance}} geleden",
"noFurtherVersionsFound": "Geen verdere versies gevonden",
"noRowsFound": "Geen {{label}} gevonden",
"preview": "Voorbeeld",
"previouslyPublished": "Eerder gepubliceerd",
"problemRestoringVersion": "Er was een probleem bij het herstellen van deze versie",
"publish": "Publiceren",
"publishChanges": "Publiceer wijzigingen",
@@ -378,4 +381,4 @@
"viewingVersions": "Bekijk versies voor {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "`Bekijk versies voor global {{entityLabel}}"
}
}
}

View File

@@ -342,12 +342,15 @@
"confirmUnpublish": "Potwierdź cofnięcie publikacji",
"confirmVersionRestoration": "Potwierdź przywrócenie wersji",
"currentDocumentStatus": "Bieżący status {{docStatus}} dokumentu",
"currentDraft": "Aktualny projekt",
"currentPublishedVersion": "Aktualna opublikowana wersja",
"draft": "Szkic",
"draftSavedSuccessfully": "Wersja robocza została pomyślnie zapisana.",
"lastSavedAgo": "Ostatnio zapisane {{distance}} temu",
"noFurtherVersionsFound": "Nie znaleziono dalszych wersji",
"noRowsFound": "Nie znaleziono {{label}}",
"preview": "Podgląd",
"previouslyPublished": "Wcześniej opublikowane",
"problemRestoringVersion": "Wystąpił problem podczas przywracania tej wersji",
"publish": "Publikuj",
"publishChanges": "Opublikuj zmiany",
@@ -379,4 +382,4 @@
"viewingVersions": "Przeglądanie wersji {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "Przeglądanie wersji dla globalnej kolekcji {{entityLabel}}"
}
}
}

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "Confirmar despublicação",
"confirmVersionRestoration": "Confirmar Restauração de versão",
"currentDocumentStatus": "Documento {{docStatus}} atual",
"currentDraft": "Rascunho Atual",
"currentPublishedVersion": "Versão Publicada Atual",
"draft": "Rascunho",
"draftSavedSuccessfully": "Rascunho salvo com sucesso.",
"lastSavedAgo": "Última gravação há {{distance}}",
"noFurtherVersionsFound": "Nenhuma outra versão encontrada",
"noRowsFound": "Nenhum(a) {{label}} encontrado(a)",
"preview": "Pré-visualização",
"previouslyPublished": "Publicado Anteriormente",
"problemRestoringVersion": "Ocorreu um problema ao restaurar essa versão",
"publish": "Publicar",
"publishChanges": "Publicar alterações",
@@ -378,4 +381,4 @@
"viewingVersions": "Visualizando versões para o/a {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "`Visualizando versões para o global {{entityLabel}}"
}
}
}

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "Confirmați nepublicarea",
"confirmVersionRestoration": "Confirmați restaurarea versiunii",
"currentDocumentStatus": "Documentul {{docStatus}} curent",
"currentDraft": "Proiect curent",
"currentPublishedVersion": "Versiunea Publicată Curentă",
"draft": "Proiect",
"draftSavedSuccessfully": "Proiect salvat cu succes.",
"lastSavedAgo": "Ultima salvare acum {{distance}}",
"noFurtherVersionsFound": "Nu s-au găsit alte versiuni",
"noRowsFound": "Nu s-a găsit niciun {{label}}",
"preview": "Previzualizare",
"previouslyPublished": "Publicat anterior",
"problemRestoringVersion": "A existat o problemă la restaurarea acestei versiuni",
"publish": "Publicați",
"publishChanges": "Publicați modificările",
@@ -378,4 +381,4 @@
"viewingVersions": "Vizualizarea versiunilor pentru {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "Vizualizarea versiunilor pentru globala {{entityLabel}}"
}
}
}

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "Potvrdite poništavanje objave",
"confirmVersionRestoration": "Potvrdite vraćanje verzije",
"currentDocumentStatus": "Trenutni {{docStatus}} dokumenta",
"currentDraft": "Trenutni Nacrt",
"currentPublishedVersion": "Trenutna Objavljena Verzija",
"draft": "Nacrt",
"draftSavedSuccessfully": "Nacrt uspešno sačuvan.",
"lastSavedAgo": "Zadnji put sačuvano pre {{distance}",
"noFurtherVersionsFound": "Nisu pronađene naredne verzije",
"noRowsFound": "{{label}} nije pronađeno",
"preview": "Pregled",
"previouslyPublished": "Prethodno objavljeno",
"problemRestoringVersion": "Nastao je problem pri vraćanju ove verzije",
"publish": "Objaviti",
"publishChanges": "Objavi promene",
@@ -378,4 +381,4 @@
"viewingVersions": "Pregled verzija za {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "Pregled verzije za globalni {{entityLabel}}"
}
}
}

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "Потврдите поништавање објаве",
"confirmVersionRestoration": "Потврдите враћање верзије",
"currentDocumentStatus": "Тренутни {{docStatus}} документа",
"currentDraft": "Trenutni nacrt",
"currentPublishedVersion": "Trenutno objavljena verzija",
"draft": "Нацрт",
"draftSavedSuccessfully": "Нацрт успешно сачуван.",
"lastSavedAgo": "Задњи пут сачувано пре {{distance}",
"noFurtherVersionsFound": "Нису пронађене наредне верзије",
"noRowsFound": "{{label}} није пронађено",
"preview": "Преглед",
"previouslyPublished": "Prethodno objavljeno",
"problemRestoringVersion": "Настао је проблем при враћању ове верзије",
"publish": "Објавити",
"publishChanges": "Објави промене",
@@ -378,4 +381,4 @@
"viewingVersions": "Преглед верзија за {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "Преглед верзије за глобални {{entityLabel}}"
}
}
}

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "Подтвердить отмену публикации",
"confirmVersionRestoration": "Подтвердить восстановление версии",
"currentDocumentStatus": "Текущий статус {{docStatus}} документа",
"currentDraft": "Текущий проект",
"currentPublishedVersion": "Текущая опубликованная версия",
"draft": "Черновик",
"draftSavedSuccessfully": "Черновик успешно сохранен.",
"lastSavedAgo": "Последний раз сохранено {{distance}} назад",
"noFurtherVersionsFound": "Другие версии не найдены",
"noRowsFound": "Не найдено {{label}}",
"preview": "Предпросмотр",
"previouslyPublished": "Ранее опубликовано",
"problemRestoringVersion": "Возникла проблема с восстановлением этой версии",
"publish": "Публиковать",
"publishChanges": "Опубликовать изменения",
@@ -378,4 +381,4 @@
"viewingVersions": "Просмотр версий для {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "`Просмотр версии для глобальной Коллекции {{entityLabel}}"
}
}
}

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "Bekräfta avpublicering",
"confirmVersionRestoration": "Bekräfta Versionsåterställning",
"currentDocumentStatus": "Nuvarande {{docStatus}} dokument",
"currentDraft": "Nuvarande utkast",
"currentPublishedVersion": "Aktuell publicerad version",
"draft": "Utkast",
"draftSavedSuccessfully": "Utkastet sparades framgångsrikt.",
"lastSavedAgo": "Senast sparad för {{distance}} sedan",
"noFurtherVersionsFound": "Inga fler versioner hittades",
"noRowsFound": "Inga {{label}} hittades",
"preview": "Förhandsvisa",
"previouslyPublished": "Tidigare Publicerad",
"problemRestoringVersion": "Det uppstod ett problem när den här versionen skulle återställas",
"publish": "Publicera",
"publishChanges": "Publicera ändringar",
@@ -378,4 +381,4 @@
"viewingVersions": "Visar versioner för {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "Visa versioner för den globala {{entityLabel}}"
}
}
}

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "ยืนยันการยกเลิกการเผยแพร่",
"confirmVersionRestoration": "ยืนยันการกู้คืนเวอร์ชัน",
"currentDocumentStatus": "เอกสารปัจจุบัน",
"currentDraft": "ร่างปัจจุบัน",
"currentPublishedVersion": "เวอร์ชันที่เผยแพร่ในปัจจุบัน",
"draft": "ฉบับร่าง",
"draftSavedSuccessfully": "บันทึกร่างสำเร็จ",
"lastSavedAgo": "บันทึกครั้งล่าสุด {{distance}} ที่ผ่านมา",
"noFurtherVersionsFound": "ไม่พบเวอร์ชันอื่น ๆ",
"noRowsFound": "ไม่พบ {{label}}",
"preview": "ตัวอย่าง",
"previouslyPublished": "เผยแพร่ก่อนหน้านี้",
"problemRestoringVersion": "เกิดปัญหาระหว่างการกู้คืนเวอร์ชันนี้",
"publish": "เผยแพร่",
"publishChanges": "เผยแพร่การแก้ไข",
@@ -378,4 +381,4 @@
"viewingVersions": "กำลังดูเวอร์ชันของ {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "กำลังดูเวอร์ชันของ global {{entityLabel}}"
}
}
}

View File

@@ -169,10 +169,10 @@
"create": "Oluştur",
"createNew": "Yeni Oluştur",
"createNewLabel": "Yeni {{label}} oluştur",
"creatingNewLabel": "Yeni {{label}} oluşturuluyor",
"created": "Oluşturuldu",
"createdAt": "Oluşturulma Tarihi",
"creating": "Oluşturuluyor",
"creatingNewLabel": "Yeni {{label}} oluşturuluyor",
"dark": "Koyu",
"dashboard": "Kontrol Paneli",
"delete": "Sil",
@@ -225,8 +225,8 @@
"notFound": "Bulunamadı",
"nothingFound": "Hiçbir şey bulunamadı",
"of": "",
"or": "Veya",
"open": "Aç",
"or": "Veya",
"order": "Sıralama",
"pageNotFound": "Sayfa bulunamadı",
"password": "Parola",
@@ -248,8 +248,8 @@
"sort": "Sırala",
"sortByLabelDirection": "{{label}} {{direction}} göre sırala",
"stayOnThisPage": "Bu sayfada kal",
"submit": "Gönder",
"submissionSuccessful": "Gönderim Başarılı.",
"submit": "Gönder",
"successfullyCreated": "{{label}} başarıyla oluşturuldu.",
"successfullyDuplicated": "{{label}} başarıyla çoğaltıldı.",
"thisLanguage": "Türkçe",
@@ -288,10 +288,10 @@
"dragAndDrop": "Bir dosyayı sürükleyip bırakın",
"dragAndDropHere": "veya bir dosyayı buraya sürükleyip bırakın",
"editImage": "Görüntüyü Düzenle",
"focalPoint": "Odak Noktası",
"focalPointDescription": "Odak noktasını doğrudan önizleme üzerinde sürükleyin veya aşağıdaki değerleri ayarlayın.",
"fileName": "Dosya Adı",
"fileSize": "Dosya Boyutu",
"focalPoint": "Odak Noktası",
"focalPointDescription": "Odak noktasını doğrudan önizleme üzerinde sürükleyin veya aşağıdaki değerleri ayarlayın.",
"height": "Yükseklik",
"lessInfo": "Daha az bilgi",
"moreInfo": "Daha fazla bilgi",
@@ -341,12 +341,15 @@
"confirmUnpublish": "Yayından kaldırmayı onayla",
"confirmVersionRestoration": "Sürüm Geri Yüklemesini Onayla",
"currentDocumentStatus": "Geçerli {{docStatus}} belge",
"currentDraft": "Mevcut Taslak",
"currentPublishedVersion": "Mevcut Yayınlanan Sürüm",
"draft": "Taslak",
"draftSavedSuccessfully": "Taslak başarıyla kaydedildi.",
"lastSavedAgo": "Son kaydedilme {{distance}} önce",
"noFurtherVersionsFound": "Başka sürüm bulunamadı",
"noRowsFound": "Hiçbir {{label}} bulunamadı",
"preview": "Önizleme",
"previouslyPublished": "Daha Önce Yayınlanmış",
"problemRestoringVersion": "Bu sürümü geri yüklerken bir sorun oluştu",
"publish": "Yayınla",
"publishChanges": "Değişiklikleri yayınla",

View File

@@ -1314,6 +1314,12 @@
"currentDocumentStatus": {
"type": "string"
},
"currentDraft": {
"type": "string"
},
"currentPublishedVersion": {
"type": "string"
},
"draft": {
"type": "string"
},
@@ -1332,6 +1338,9 @@
"preview": {
"type": "string"
},
"previouslyPublished": {
"type": "string"
},
"problemRestoringVersion": {
"type": "string"
},

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "Підвтердити відміну публікації",
"confirmVersionRestoration": "Підтвердити відновлення версії",
"currentDocumentStatus": "Поточний статус {{docStatus}} документа",
"currentDraft": "Поточний проект",
"currentPublishedVersion": "Поточна опублікована версія",
"draft": "Чернетка",
"draftSavedSuccessfully": "Чернетка успішно збережена.",
"lastSavedAgo": "Останній раз збережено {{distance}} тому",
"noFurtherVersionsFound": "Інших версій не знайдено",
"noRowsFound": "Не знайдено {{label}}",
"preview": "Попередній перегляд",
"previouslyPublished": "Раніше опубліковано",
"problemRestoringVersion": "Виникла проблема з відновленням цієї версії",
"publish": "Опублікувати",
"publishChanges": "Опублікувати зміни",
@@ -378,4 +381,4 @@
"viewingVersions": "Огляд версій для {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "Огляд версій для глобальної колекції {{entityLabel}}"
}
}
}

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "Xác nhận, ngưng xuất bản",
"confirmVersionRestoration": "Xác nhận, khôi phục về phiên bản trước",
"currentDocumentStatus": "Trạng thái tài liệu hiện tại: {{docStatus}}",
"currentDraft": "Bản thảo hiện tại",
"currentPublishedVersion": "Phiên bản được xuất bản hiện tại",
"draft": "Bản nháp",
"draftSavedSuccessfully": "Bản nháp đã được lưu thành công.",
"lastSavedAgo": "Lần lưu cuối cùng {{distance}} trước đây",
"noFurtherVersionsFound": "Không tìm thấy phiên bản cũ hơn",
"noRowsFound": "Không tìm thấy: {{label}}",
"preview": "Bản xem trước",
"previouslyPublished": "Đã xuất bản trước đây",
"problemRestoringVersion": "Đã xảy ra vấn đề khi khôi phục phiên bản này",
"publish": "Công bố",
"publishChanges": "Xuất bản tài liệu",
@@ -378,4 +381,4 @@
"viewingVersions": "Xem những phiên bản của {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "`Xem những phiên bản toàn thể (global) của {{entityLabel}}"
}
}
}

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "確認取消發佈",
"confirmVersionRestoration": "確認版本回復",
"currentDocumentStatus": "目前{{docStatus}}文件",
"currentDraft": "目前的草案",
"currentPublishedVersion": "目前已發布的版本",
"draft": "草稿",
"draftSavedSuccessfully": "草稿儲存成功。",
"lastSavedAgo": "上次儲存在{{distance}}之前",
"noFurtherVersionsFound": "沒有發現其他版本",
"noRowsFound": "沒有發現{{label}}",
"preview": "預覽",
"previouslyPublished": "先前發表過的",
"problemRestoringVersion": "回復這個版本時發生了問題",
"publish": "發佈",
"publishChanges": "發佈修改",
@@ -378,4 +381,4 @@
"viewingVersions": "正在查看{{entityLabel}} {{documentTitle}}的版本",
"viewingVersionsGlobal": "正在查看全域{{entityLabel}}的版本"
}
}
}

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "确认取消发布",
"confirmVersionRestoration": "确认版本恢复",
"currentDocumentStatus": "当前{{docStatus}}文件",
"currentDraft": "当前草案",
"currentPublishedVersion": "当前发布的版本",
"draft": "草稿",
"draftSavedSuccessfully": "草稿成功保存。",
"lastSavedAgo": "上次保存{{distance}}之前",
"noFurtherVersionsFound": "没有发现其他版本",
"noRowsFound": "没有发现{{label}}",
"preview": "预览",
"previouslyPublished": "先前发表过的",
"problemRestoringVersion": "恢复这个版本时发生了问题",
"publish": "发布",
"publishChanges": "发布修改",
@@ -378,4 +381,4 @@
"viewingVersions": "正在查看{{entityLabel}} {{documentTitle}}的版本",
"viewingVersionsGlobal": "正在查看全局{{entityLabel}}的版本"
}
}
}

View File

@@ -9,13 +9,13 @@ export const percentToPixel = (value: string, dimension: number): number => {
export default async function cropImage({ cropData, dimensions, file }) {
try {
const fileIsAnimated = ['image/avif', 'image/gif', 'image/webp'].includes(file.mimetype)
const fileIsAnimatedType = ['image/avif', 'image/gif', 'image/webp'].includes(file.mimetype)
const { height, width, x, y } = cropData
const sharpOptions: SharpOptions = {}
if (fileIsAnimated) sharpOptions.animated = true
if (fileIsAnimatedType) sharpOptions.animated = true
const formattedCropData: sharp.Region = {
height: percentToPixel(height, dimensions.height),

View File

@@ -121,7 +121,7 @@ export const generateFileData = async <T>({
let newData = data
const filesToSave: FileToSave[] = []
const fileData: Partial<FileData> = {}
const fileIsAnimated = ['image/avif', 'image/gif', 'image/webp'].includes(file.mimetype)
const fileIsAnimatedType = ['image/avif', 'image/gif', 'image/webp'].includes(file.mimetype)
const cropData =
typeof uploadEdits === 'object' && 'crop' in uploadEdits ? uploadEdits.crop : undefined
@@ -139,23 +139,25 @@ export const generateFileData = async <T>({
const sharpOptions: SharpOptions = {}
if (fileIsAnimated) sharpOptions.animated = true
if (fileIsAnimatedType) sharpOptions.animated = true
if (fileHasAdjustments) {
if (sharp && (fileIsAnimatedType || fileHasAdjustments)) {
if (file.tempFilePath) {
sharpFile = sharp(file.tempFilePath, sharpOptions).rotate() // pass rotate() to auto-rotate based on EXIF data. https://github.com/payloadcms/payload/pull/3081
} else {
sharpFile = sharp(file.data, sharpOptions).rotate() // pass rotate() to auto-rotate based on EXIF data. https://github.com/payloadcms/payload/pull/3081
}
if (resizeOptions) {
sharpFile = sharpFile.resize(resizeOptions)
}
if (formatOptions) {
sharpFile = sharpFile.toFormat(formatOptions.format, formatOptions.options)
}
if (trimOptions) {
sharpFile = sharpFile.trim(trimOptions)
if (fileHasAdjustments) {
if (resizeOptions) {
sharpFile = sharpFile.resize(resizeOptions)
}
if (formatOptions) {
sharpFile = sharpFile.toFormat(formatOptions.format, formatOptions.options)
}
if (trimOptions) {
sharpFile = sharpFile.trim(trimOptions)
}
}
}
@@ -209,7 +211,6 @@ export const generateFileData = async <T>({
let fileForResize = file
if (cropData) {
const metadata = await sharpFile.metadata()
const { data: croppedImage, info } = await cropImage({ cropData, dimensions, file })
filesToSave.push({
@@ -223,7 +224,11 @@ export const generateFileData = async <T>({
size: info.size,
}
fileData.width = info.width
fileData.height = fileIsAnimated ? info.height / metadata.pages : info.height
fileData.height = info.height
if (fileIsAnimatedType) {
const metadata = await sharpFile.metadata()
fileData.height = metadata.pages ? info.height / metadata.pages : info.height
}
fileData.filesize = info.size
if (file.tempFilePath) {

View File

@@ -253,10 +253,10 @@ export default async function resizeAndTransformImageSizes({
if (!imageSizes) return defaultResult
// Determine if the file is animated
const fileIsAnimated = ['image/avif', 'image/gif', 'image/webp'].includes(file.mimetype)
const fileIsAnimatedType = ['image/avif', 'image/gif', 'image/webp'].includes(file.mimetype)
const sharpOptions: SharpOptions = {}
if (fileIsAnimated) sharpOptions.animated = true
if (fileIsAnimatedType) sharpOptions.animated = true
const sharpBase: Sharp | undefined = sharp(file.tempFilePath || file.data, sharpOptions).rotate() // pass rotate() to auto-rotate based on EXIF data. https://github.com/payloadcms/payload/pull/3081
@@ -277,16 +277,26 @@ export default async function resizeAndTransformImageSizes({
const metadata = await sharpBase.metadata()
if (incomingFocalPoint && applyPayloadAdjustments(imageResizeConfig, dimensions)) {
const { height: resizeHeight, width: resizeWidth } = imageResizeConfig
const resizeAspectRatio = resizeWidth / resizeHeight
let { height: resizeHeight, width: resizeWidth } = imageResizeConfig
const originalAspectRatio = dimensions.width / dimensions.height
const prioritizeHeight = resizeAspectRatio < originalAspectRatio
// Calculate resizeWidth based on original aspect ratio if it's undefined
if (resizeHeight && !resizeWidth) {
resizeWidth = Math.round(resizeHeight * originalAspectRatio)
}
// Calculate resizeHeight based on original aspect ratio if it's undefined
if (resizeWidth && !resizeHeight) {
resizeHeight = Math.round(resizeWidth / originalAspectRatio)
}
// Scale the image up or down to fit the resize dimensions
const scaledImage = imageToResize.resize({
height: prioritizeHeight ? resizeHeight : null,
width: prioritizeHeight ? null : resizeWidth,
height: resizeHeight,
width: resizeWidth,
})
const { info: scaledImageInfo } = await scaledImage.toBuffer({ resolveWithObject: true })
const safeResizeWidth = resizeWidth ?? scaledImageInfo.width
@@ -296,10 +306,16 @@ export default async function resizeAndTransformImageSizes({
)
const safeOffsetX = Math.min(Math.max(0, leftFocalEdge), maxOffsetX)
const safeResizeHeight = resizeHeight ?? scaledImageInfo.height
const isAnimated = fileIsAnimatedType && metadata.pages
const maxOffsetY = fileIsAnimated
? resizeHeight - safeResizeHeight
let safeResizeHeight = resizeHeight ?? scaledImageInfo.height
if (isAnimated && resizeHeight === undefined) {
safeResizeHeight = scaledImageInfo.height / metadata.pages
}
const maxOffsetY = isAnimated
? safeResizeHeight - (resizeHeight ?? safeResizeHeight)
: scaledImageInfo.height - safeResizeHeight
const topFocalEdge = Math.round(
@@ -308,7 +324,7 @@ export default async function resizeAndTransformImageSizes({
const safeOffsetY = Math.min(Math.max(0, topFocalEdge), maxOffsetY)
// extract the focal area from the scaled image
resized = (fileIsAnimated ? imageToResize : scaledImage).extract({
resized = (fileIsAnimatedType ? imageToResize : scaledImage).extract({
height: safeResizeHeight,
left: safeOffsetX,
top: safeOffsetY,
@@ -362,7 +378,7 @@ export default async function resizeAndTransformImageSizes({
name: imageResizeConfig.name,
filename: imageNameWithDimensions,
filesize: size,
height: fileIsAnimated ? height / metadata.pages : height,
height: fileIsAnimatedType && metadata.pages ? height / metadata.pages : height,
mimeType: mimeInfo?.mime || mimeType,
sizesToSave: [{ buffer: bufferData, path: imagePath }],
width,

View File

@@ -4,10 +4,12 @@ export type Autosave = {
export type IncomingDrafts = {
autosave?: Autosave | boolean
validate?: boolean
}
export type SanitizedDrafts = {
autosave: Autosave | false
validate: boolean
}
export type IncomingCollectionVersions = {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -5,11 +5,16 @@ import { mediaSlug } from '../Media'
export const postsSlug = 'posts'
export const PostsCollection: CollectionConfig = {
defaultSort: 'title',
fields: [
{
name: 'text',
type: 'text',
},
{
name: 'title',
type: 'text',
},
{
name: 'associatedMedia',
access: {

View File

@@ -32,6 +32,23 @@ export default buildConfigWithDefaults({
collection: postsSlug,
data: {
text: 'example post',
title: 'title1',
},
})
await payload.create({
collection: postsSlug,
data: {
text: 'example post',
title: 'title3',
},
})
await payload.create({
collection: postsSlug,
data: {
text: 'example post',
title: 'title2',
},
})
},

View File

@@ -511,10 +511,39 @@ describe('Auth', () => {
await tryLogin()
await tryLogin()
await payload.update({
const loginAfterLimit = await fetch(`${apiUrl}/${slug}/login`, {
body: JSON.stringify({
email: userEmail,
password,
}),
headers: {
Authorization: `JWT ${token}`,
'Content-Type': 'application/json',
},
method: 'post',
}).then((res) => res.json())
expect(loginAfterLimit.errors.length).toBeGreaterThan(0)
const lockedUser = await payload.find({
showHiddenFields: true,
collection: slug,
where: {
email: {
equals: userEmail,
},
},
})
expect(lockedUser.docs[0].loginAttempts).toBe(2)
expect(lockedUser.docs[0].lockUntil).toBeDefined()
const manuallyReleaseLock = new Date(Date.now() - 605 * 1000)
const userLockElapsed = await payload.update({
showHiddenFields: true,
collection: slug,
data: {
lockUntil: Date.now() - 605 * 1000,
lockUntil: manuallyReleaseLock,
},
where: {
email: {
@@ -523,6 +552,8 @@ describe('Auth', () => {
},
})
expect(userLockElapsed.docs[0].lockUntil).toEqual(manuallyReleaseLock.toISOString())
// login
await fetch(`${apiUrl}/${slug}/login`, {
body: JSON.stringify({

View File

@@ -0,0 +1,12 @@
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
import { slugs } from '../../shared'
import { ValidateDraftsOn } from '../ValidateDraftsOn'
export const ValidateDraftsOff: CollectionConfig = {
...ValidateDraftsOn,
slug: slugs.validateDraftsOff,
versions: {
drafts: true,
},
}

View File

@@ -0,0 +1,19 @@
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
import { slugs } from '../../shared'
export const ValidateDraftsOn: CollectionConfig = {
slug: slugs.validateDraftsOn,
fields: [
{
name: 'title',
type: 'text',
required: true,
},
],
versions: {
drafts: {
validate: true,
},
},
}

View File

@@ -0,0 +1,15 @@
import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types'
import { slugs } from '../../shared'
import { ValidateDraftsOn } from '../ValidateDraftsOn'
export const ValidateDraftsOnAndAutosave: CollectionConfig = {
...ValidateDraftsOn,
slug: slugs.validateDraftsOnAutosave,
versions: {
drafts: {
autosave: true,
validate: true,
},
},
}

View File

@@ -2,9 +2,18 @@ import { buildConfigWithDefaults } from '../buildConfigWithDefaults'
import { devUser } from '../credentials'
import { ErrorFieldsCollection } from './collections/ErrorFields'
import Uploads from './collections/Upload'
import { ValidateDraftsOff } from './collections/ValidateDraftsOff'
import { ValidateDraftsOn } from './collections/ValidateDraftsOn'
import { ValidateDraftsOnAndAutosave } from './collections/ValidateDraftsOnAutosave'
export default buildConfigWithDefaults({
collections: [ErrorFieldsCollection, Uploads],
collections: [
ErrorFieldsCollection,
Uploads,
ValidateDraftsOn,
ValidateDraftsOff,
ValidateDraftsOnAndAutosave,
],
graphQL: {
schemaOutputFile: './test/field-error-states/schema.graphql',
},

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