Compare commits
20 Commits
payload/2.
...
payload/2.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62fa22cb24 | ||
|
|
3b4bb3065a | ||
|
|
4e0725f7c6 | ||
|
|
ff70fd9813 | ||
|
|
e40570bd0d | ||
|
|
b7e852993b | ||
|
|
ab97590879 | ||
|
|
ed86b15242 | ||
|
|
d58631c12c | ||
|
|
37c8386a51 | ||
|
|
2f9ed34d13 | ||
|
|
921a5c065d | ||
|
|
e3003b443f | ||
|
|
8a622984e7 | ||
|
|
507e0954b2 | ||
|
|
63bc6ae52f | ||
|
|
d016fbd2a5 | ||
|
|
9525511e8b | ||
|
|
1e834e58a4 | ||
|
|
373cb00139 |
2
.github/ISSUE_TEMPLATE/2.bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/2.bug_report.yml
vendored
@@ -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
7
.vscode/launch.json
vendored
@@ -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}",
|
||||
|
||||
40
CHANGELOG.md
40
CHANGELOG.md
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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 ||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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[]
|
||||
}
|
||||
|
||||
@@ -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 || ''}
|
||||
/>
|
||||
|
||||
@@ -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[]
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@ const Condition: React.FC<Props> = (props) => {
|
||||
orIndex,
|
||||
})
|
||||
setInternalOperatorField(operator.value)
|
||||
setInternalValue('') // Reset value when operator changes
|
||||
}}
|
||||
options={activeField.operators}
|
||||
value={
|
||||
|
||||
@@ -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 },
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
@@ -0,0 +1,8 @@
|
||||
.script-language {
|
||||
height: 100%;
|
||||
|
||||
& > *,
|
||||
& > * > * {
|
||||
letter-spacing: 0 !important;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -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' : ''
|
||||
}`,
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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)}
|
||||
|
||||
{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}
|
||||
/>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
export const mostRecentVersionOption = {
|
||||
label: 'Most recent',
|
||||
value: 'mostRecent',
|
||||
}
|
||||
|
||||
export const publishedVersionOption = {
|
||||
label: 'Most recently published',
|
||||
value: 'published',
|
||||
}
|
||||
@@ -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`}>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
</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>
|
||||
|
||||
</React.Fragment>
|
||||
)}
|
||||
{status && renderPill(rowData, latestVersion, currentLabel, previousLabel, pillStyle)}
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
</React.Fragment>
|
||||
)}
|
||||
{row?.version._status === 'published' && (
|
||||
<React.Fragment>
|
||||
<Pill pillStyle="success">{t('published')}</Pill>
|
||||
|
||||
</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: '',
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
}}
|
||||
|
||||
@@ -17,5 +17,7 @@ export type Props = IndexProps & {
|
||||
id: string
|
||||
isLoading: boolean
|
||||
isLoadingVersions: boolean
|
||||
latestDraftVersion?: string
|
||||
latestPublishedVersion?: string
|
||||
versionsData: PaginatedDocs<Version>
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@ const DefaultEditView: React.FC<DefaultEditViewProps> = (props) => {
|
||||
} = props
|
||||
|
||||
const { setViewActions } = useActions()
|
||||
|
||||
const { reportUpdate } = useDocumentEvents()
|
||||
|
||||
const { auth } = collection
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -200,11 +200,4 @@ dialog {
|
||||
z-index: var(--z-modal);
|
||||
}
|
||||
|
||||
.script-language {
|
||||
& > *,
|
||||
& > * > * {
|
||||
letter-spacing: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@import '~payload-user-css';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,6 +228,7 @@ const collectionSchema = joi.object().keys({
|
||||
interval: joi.number(),
|
||||
}),
|
||||
),
|
||||
validate: joi.boolean(),
|
||||
}),
|
||||
joi.boolean(),
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -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',
|
||||
})
|
||||
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -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',
|
||||
})
|
||||
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -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',
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ const globalSchema = joi
|
||||
interval: joi.number(),
|
||||
}),
|
||||
),
|
||||
validate: joi.boolean(),
|
||||
}),
|
||||
joi.boolean(),
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -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}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -341,12 +341,15 @@
|
||||
"confirmUnpublish": "Confirmer l’annulation",
|
||||
"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}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}}에 대한 버전 보기"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}} အတွက် ဗားရှင်းများကို ကြည့်ရှုနေသည်"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}}的版本"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}}的版本"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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: {
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user