Compare commits

..

24 Commits

Author SHA1 Message Date
Elliot DeNolf
ab97590879 chore(release): payload/2.20.0 [skip ci] 2024-06-11 19:00:22 -04:00
Jessica Chowdhury
ed86b15242 feat(ui): updates draft/published version pills (#6732) 2024-06-11 18:18:32 -04:00
Elliot DeNolf
d58631c12c chore: add v2 tag to issue template 2024-06-11 16:08:11 -04:00
Jessica Chowdhury
37c8386a51 fix: withinCollapsible should be undefined by default (#6666)
## Description

Closes #6658

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

### Issue: 

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

### Fix: 

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

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

## Type of change

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

## Checklist:

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

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

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

## Type of change

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

## Checklist:

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

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

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

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

### Issue: 

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

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

### Fix:

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

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

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

## Type of change

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

## Checklist:

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

Fixes #6469 

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

## Type of change

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

## Checklist:

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

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


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

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

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

- Removes formQueryParams in favor of an upload edit provider.
- Hoists the document `action` up to the doc provider
2024-06-07 09:12:19 -04:00
Elliot DeNolf
558b298bf0 chore(release): payload/2.19.2 [skip ci] 2024-06-06 12:08:04 -04:00
Jacob Fletcher
cd24e2bb3c docs: adds live preview csp troubleshooting tips (#6656) 2024-06-06 11:04:53 -04:00
Jarrod Flesch
ac8c2096af fix: cascade draft arg when querying globals with graphql (#6651) 2024-06-06 10:36:33 -04:00
Patrik
626be15578 fix: filtered out disableListColumn fields reappeared after toggling other fields (#6636)
## Description

There was an issue with fields w/ the `admin.disableListColumn` prop
reappearing in the column selector after toggling other fields in the
column selector.

This PR makes sure fields with `admin.disableListColumn` set to `true`
do not reappear under any circumstance.

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

## Type of change

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

## Checklist:

- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] Existing test suite passes locally with my changes
2024-06-05 11:17:34 -04:00
Patrik
67c0b0e6e0 fix: resizing animated images (#6621)
Fixes #2181 #6146
2024-06-04 13:56:21 -04:00
Elliot DeNolf
11239103a6 chore(release): bundler-webpack/1.0.7 [skip ci] 2024-06-04 11:14:40 -04:00
Elliot DeNolf
4998ef8c9b chore(release): payload/2.19.1 [skip ci] 2024-06-04 11:13:38 -04:00
Alessio Gravili
e44ce819ce fix: override ajv dependency version to 8.14.0 wherever possible (#6618)
ajv issue: https://github.com/ajv-validator/ajv/issues/2446

This also removes css-minimizer-webpack-plugin which is not used within
bundler-webpack
2024-06-04 10:54:42 -04:00
Elliot DeNolf
b700208b98 chore(release): payload/2.19.0 [skip ci] 2024-06-03 22:49:24 -04:00
Alessio Gravili
4a54aa7776 fix: pin ajv to 8.14.0, as 8.15.0 is broken (#6607) 2024-06-03 20:22:24 -04:00
95 changed files with 8339 additions and 7696 deletions

View File

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

View File

@@ -1,3 +1,64 @@
## [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)
### Bug Fixes
* cascade draft arg when querying globals with graphql ([#6651](https://github.com/payloadcms/payload/issues/6651)) ([ac8c209](https://github.com/payloadcms/payload/commit/ac8c2096af641a6886e4543ee65c9790e45f080f))
* filtered out `disableListColumn` fields reappeared after toggling other fields ([#6636](https://github.com/payloadcms/payload/issues/6636)) ([626be15](https://github.com/payloadcms/payload/commit/626be155784dda181276bb87617433822a0accf3))
* resizing animated images ([#6621](https://github.com/payloadcms/payload/issues/6621)) ([67c0b0e](https://github.com/payloadcms/payload/commit/67c0b0e6e0b5b190f6a916b59ba02f8c18479e18)), closes [#2181](https://github.com/payloadcms/payload/issues/2181) [#6146](https://github.com/payloadcms/payload/issues/6146)
## [2.19.1](https://github.com/payloadcms/payload/compare/v2.19.0...v2.19.1) (2024-06-04)
### Bug Fixes
* override ajv dependency version to 8.14.0 wherever possible ([#6618](https://github.com/payloadcms/payload/issues/6618)) ([e44ce81](https://github.com/payloadcms/payload/commit/e44ce819cefddeaaf20b2b7ce804e94a9272baf1))
## [2.19.0](https://github.com/payloadcms/payload/compare/v2.18.3...v2.19.0) (2024-06-04)
### Features
* **translations:** update Turkish translations ([#5738](https://github.com/payloadcms/payload/issues/5738)) ([4fddea8](https://github.com/payloadcms/payload/commit/4fddea86ebd5f21705be2310f8b7053d31109189))
### Bug Fixes
* adds new `userEmailAlreadyRegistered` translations ([#6549](https://github.com/payloadcms/payload/issues/6549)) ([56c6700](https://github.com/payloadcms/payload/commit/56c6700cf25570cc217e28dc69459a3b81adbced)), closes [#6535](https://github.com/payloadcms/payload/issues/6535)
* adjusts sizing of remove/add buttons to be same size ([#6527](https://github.com/payloadcms/payload/issues/6527)) ([a352ebc](https://github.com/payloadcms/payload/commit/a352ebc5520bbd0f6a9caef068825976dba05ded)), closes [#6098](https://github.com/payloadcms/payload/issues/6098)
* focalPoint undefined handling ([#6552](https://github.com/payloadcms/payload/issues/6552)) ([fcfc3c5](https://github.com/payloadcms/payload/commit/fcfc3c593f69f63c51f8aa09973fcacbfbe04952))
* pagination on polymorphic relationship field requesting entries with page parameter set to NaN ([#5366](https://github.com/payloadcms/payload/issues/5366)) ([547acfe](https://github.com/payloadcms/payload/commit/547acfe876bdf0df2ce808941f72b690c9dbcae3))
* safely evaluates `field.admin` in WhereBuilder ([#6534](https://github.com/payloadcms/payload/issues/6534)) ([4f9d78d](https://github.com/payloadcms/payload/commit/4f9d78df5e38f3f70852bb6de47cff619f57c648))
* separate sort and search fields when looking up relationship. ([#5964](https://github.com/payloadcms/payload/issues/5964)) ([c009219](https://github.com/payloadcms/payload/commit/c0092191a6ded1098a94d9f48918ab79171e5e32)), closes [#4815](https://github.com/payloadcms/payload/issues/4815) [#5222](https://github.com/payloadcms/payload/issues/5222)
* ui field validation error with `admin.disableListColumn` property ([#6530](https://github.com/payloadcms/payload/issues/6530)) ([eeddece](https://github.com/payloadcms/payload/commit/eeddeceda988d7a4ce8ad31d3036a4ee84aceec3)), closes [#6521](https://github.com/payloadcms/payload/issues/6521)
* **ui:** blocks browser save dialog from opening when hotkey used with no changes ([#6365](https://github.com/payloadcms/payload/issues/6365)) ([8f03cd7](https://github.com/payloadcms/payload/commit/8f03cd7c789eda7613ddced0d45a32afe49b1e01)), closes [#214](https://github.com/payloadcms/payload/issues/214)
## [2.18.3](https://github.com/payloadcms/payload/compare/v2.18.2...v2.18.3) (2024-05-17)

View File

@@ -1,6 +1,6 @@
---
title: Implementing Live Preview in your app
label: Frontend Implementation
label: Frontend
order: 20
desc: Learn how to implement Live Preview in your front-end application.
keywords: live preview, frontend, react, next.js, vue, nuxt.js, svelte, hook, useLivePreview
@@ -274,3 +274,11 @@ const { data } = useLivePreview<PageType>({
depth: 1, // Ensure this matches the depth of your initial request
})
```
#### 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:
```plaintext
frame-ancestors: "self" localhost:* https://your-site.com;
```

View File

@@ -129,6 +129,6 @@
},
"dependencies": {
"@sentry/react": "^7.77.0",
"ajv": "^8.12.0"
"ajv": "8.14.0"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/bundler-webpack",
"version": "1.0.6",
"version": "1.0.7",
"description": "The officially supported Webpack bundler adapter for Payload",
"repository": {
"type": "git",
@@ -27,7 +27,6 @@
"compression": "1.7.4",
"connect-history-api-fallback": "1.6.0",
"css-loader": "5.2.7",
"css-minimizer-webpack-plugin": "^5.0.0",
"file-loader": "6.2.0",
"find-node-modules": "^2.1.3",
"html-webpack-plugin": "^5.5.0",
@@ -48,7 +47,8 @@
"webpack-bundle-analyzer": "^4.8.0",
"webpack-cli": "^4.10.0",
"webpack-dev-middleware": "6.1.2",
"webpack-hot-middleware": "^2.25.3"
"webpack-hot-middleware": "^2.25.3",
"ajv": "8.14.0"
},
"devDependencies": {
"@payloadcms/eslint-config": "workspace:*",
@@ -62,8 +62,44 @@
"@types/webpack-hot-middleware": "2.25.6",
"payload": "workspace:*"
},
"resolutions": {
"ajv": "8.14.0",
"webpack-dev-middleware/**/ajv": "8.14.0",
"css-minimizer-webpack-plugin/**/ajv": "8.14.0"
},
"peerDependencies": {
"payload": "^2.0.0"
"payload": "^2.0.0",
"ajv": "8.14.0"
},
"overrides": {
"ajv": "8.14.0",
"webpack-dev-middleware": {
"ajv": "8.14.0",
"schema-utils": {
"ajv": "8.14.0"
}
},
"css-minimizer-webpack-plugin": {
"schema-utils": {
"ajv": "8.14.0"
}
}
},
"pnpm": {
"overrides": {
"ajv": "8.14.0",
"webpack-dev-middleware": {
"ajv": "8.14.0",
"schema-utils": {
"ajv": "8.14.0"
}
},
"css-minimizer-webpack-plugin": {
"schema-utils": {
"ajv": "8.14.0"
}
}
}
},
"publishConfig": {
"main": "./dist/index.js",

View File

@@ -1,6 +1,6 @@
{
"name": "payload",
"version": "2.18.3",
"version": "2.20.0",
"description": "Node, React and MongoDB Headless CMS and Application Framework",
"license": "MIT",
"main": "./dist/index.js",
@@ -209,6 +209,29 @@
"vite": "^4.4.9",
"webpack": "^5.78.0"
},
"resolutions": {
"ajv": "8.14.0",
"webpack-dev-middleware/**/ajv": "8.14.0",
"css-minimizer-webpack-plugin/**/ajv": "8.14.0"
},
"overrides": {
"ajv": "8.14.0",
"css-minimizer-webpack-plugin": {
"schema-utils": {
"ajv": "8.14.0"
}
}
},
"pnpm": {
"overrides": {
"ajv": "8.14.0",
"css-minimizer-webpack-plugin": {
"schema-utils": {
"ajv": "8.14.0"
}
}
}
},
"engines": {
"node": ">=14"
},

View File

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

View File

@@ -1,6 +1,7 @@
import React, { useId } from 'react'
import { useTranslation } from 'react-i18next'
import type { Column } from '../Table/types'
import type { Props } from './types'
import { getTranslation } from '../../../../utilities/getTranslation'
@@ -14,6 +15,12 @@ import './index.scss'
const baseClass = 'column-selector'
const filterColumnFields = (fields: Column[]): Column[] => {
return fields.filter((field) => {
return !field.admin?.disableListColumn
})
}
const ColumnSelector: React.FC<Props> = (props) => {
const { slug } = props
@@ -27,10 +34,12 @@ const ColumnSelector: React.FC<Props> = (props) => {
return null
}
const filteredColumns = filterColumnFields(columns)
return (
<DraggableSortable
className={baseClass}
ids={columns.map((col) => col.accessor)}
ids={filteredColumns.map((col) => col.accessor)}
onDragEnd={({ moveFromIndex, moveToIndex }) => {
moveColumn({
fromIndex: moveFromIndex,
@@ -38,7 +47,7 @@ const ColumnSelector: React.FC<Props> = (props) => {
})
}}
>
{columns.map((col, i) => {
{filteredColumns.map((col, i) => {
const { name, accessor, active, label } = col
if (col.accessor === '_select') return null

View File

@@ -1,5 +1,4 @@
import { useModal } from '@faceless-ui/modal'
import queryString from 'qs'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { toast } from 'react-toastify'
@@ -18,7 +17,6 @@ import X from '../../icons/X'
import { useAuth } from '../../utilities/Auth'
import { useConfig } from '../../utilities/Config'
import { DocumentInfoProvider, useDocumentInfo } from '../../utilities/DocumentInfo'
import { useFormQueryParams } from '../../utilities/FormQueryParams'
import { useLocale } from '../../utilities/Locale'
import RenderCustomComponent from '../../utilities/RenderCustomComponent'
import DefaultEdit from '../../views/collections/Edit/Default'
@@ -45,12 +43,10 @@ const Content: React.FC<DocumentDrawerProps> = ({
const [isOpen, setIsOpen] = useState(false)
const [collectionConfig] = useRelatedCollections(collectionSlug)
const config = useConfig()
const { formQueryParams } = useFormQueryParams()
const formattedQueryParams = queryString.stringify(formQueryParams)
const { admin: { components: { views: { Edit } = {} } = {} } = {} } = collectionConfig
const { id, docPermissions, getDocPreferences } = useDocumentInfo()
const { id, action, docPermissions, getDocPreferences } = useDocumentInfo()
// If they are replacing the entire edit view, use that.
// Else let the DefaultEdit determine what to render.
@@ -90,7 +86,7 @@ const Content: React.FC<DocumentDrawerProps> = ({
setInternalState(state)
}
awaitInitialState()
void awaitInitialState()
hasInitializedState.current = true
}, [data, fields, id, user, locale, isLoadingDocument, t, getDocPreferences, config])
@@ -111,10 +107,6 @@ const Content: React.FC<DocumentDrawerProps> = ({
const apiURL = id ? `${serverURL}${api}/${collectionSlug}/${id}?locale=${locale}` : null
const action = `${serverURL}${api}/${collectionSlug}${
isEditing ? `/${id}` : ''
}?${formattedQueryParams}`
const hasSavePermission =
(isEditing && docPermissions?.update?.permission) ||
(!isEditing && (docPermissions as CollectionPermission)?.create?.permission)

View File

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

View File

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

View File

@@ -8,7 +8,6 @@ import { useConfig } from '../../utilities/Config'
import { useDocumentInfo } from '../../utilities/DocumentInfo'
import { useEditDepth } from '../../utilities/EditDepth'
import { useLocale } from '../../utilities/Locale'
import { useOperation } from '../../utilities/OperationProvider'
import RenderCustomComponent from '../../utilities/RenderCustomComponent'
const baseClass = 'save-draft'
@@ -70,13 +69,12 @@ 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 saveDraft = useCallback(async () => {
const search = `?locale=${locale}&depth=0&fallback-locale=null&draft=true`

View File

@@ -5,6 +5,7 @@ import type { FieldBase } from '../../../../fields/config/types'
export type Column = {
accessor: string
active: boolean
admin?: FieldBase['admin']
components: {
Heading: React.ReactNode
renderCell: (row: any, data: any) => React.ReactNode

View File

@@ -42,18 +42,27 @@ const buildColumns = ({
colIndex += 1
}
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
return {
name: field.name,
accessor: field.name,
active: isActive,
admin: {
disableListColumn: field.admin?.disableListColumn,
disableListFilter,
},
components: {
Heading: (
<SortColumn
disable={
('disableSort' in field && Boolean(field.disableSort)) ||
fieldIsPresentationalOnly(field) ||
undefined
}
disable={fieldAffectsDataSubFields || fieldIsPresentationalOnly(field) || undefined}
label={field.label || field.name}
name={field.name}
/>

View File

@@ -3,31 +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, operator, value }) => {
const { t } = useTranslation('general')
const { t } = useTranslation()
const isMulti = ['in', 'not_in'].includes(operator)
let valueToRender
const [valueToRender, setValueToRender] = React.useState<
{ id: string; label: string; value: { value: number } }[]
>([])
if (isMulti && Array.isArray(value)) {
valueToRender = value.map((val) => ({ label: val, value: val }))
} else if (value) {
valueToRender = { label: value, value }
}
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)]
}
}
return (
onChange(newValue)
},
[isMulti, onChange],
)
React.useEffect(() => {
if (Array.isArray(value)) {
setValueToRender(
value.map((val, index) => {
return {
id: `${val}${index}`, // append index to avoid duplicate keys but allow duplicate numbers
label: `${val}`,
value: {
toString: () => `${val}${index}`,
value: (val as any)?.value || val,
},
}
}),
)
} else {
setValueToRender([])
}
}, [value])
return isMulti ? (
<ReactSelect
disabled={disabled}
isClearable
isCreatable
isMulti={isMulti}
isSortable
numberOnly
onChange={onSelect}
options={[]}
placeholder={t('general:enterAValue')}
value={valueToRender || []}
/>
) : (
<input
className={baseClass}
disabled={disabled}
onChange={(e) => onChange(e.target.value)}
placeholder={t('enterAValue')}
placeholder={t('general:enterAValue')}
type="number"
value={value}
value={value as number}
/>
)
}

View File

@@ -4,5 +4,5 @@ export type Props = {
disabled?: boolean
onChange: (e: string) => void
operator: Operator
value: string
value: number | number[]
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,63 @@
'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) => {
console.log(data.id === latestVersion)
return (
<React.Fragment>
{data?.id === latestVersion ? (
<Pill pillStyle={pillStyle}>{currentLabel}</Pill>
) : (
<Pill>{previousLabel}</Pill>
)}
&nbsp;&nbsp;
</React.Fragment>
)
}
export const AutosaveCell: React.FC<AutosaveCellProps> = ({
latestDraftVersion,
latestPublishedVersion,
rowData,
}) => {
const { t } = useTranslation()
const status = rowData?.version._status
const versionInfo = {
draft: {
currentLabel: t('version:currentDraft'),
latestVersion: latestDraftVersion,
pillStyle: undefined,
previousLabel: t('version:draft'),
},
published: {
currentLabel: t('version:currentPublishedVersion'),
latestVersion: latestPublishedVersion,
pillStyle: 'success',
previousLabel: t('version:previouslyPublished'),
},
}
const { currentLabel, latestVersion, pillStyle, previousLabel } = versionInfo[status] || {}
return (
<Fragment>
{rowData?.autosave && (
<React.Fragment>
<Pill>{t('version:autosave')}</Pill>
&nbsp;&nbsp;
</React.Fragment>
)}
{status && renderPill(rowData, latestVersion, currentLabel, previousLabel, pillStyle)}
</Fragment>
)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,17 +8,32 @@ import findOne from '../../operations/findOne'
export default function findOneResolver(globalConfig: SanitizedGlobalConfig): Document {
return async function resolver(_, args, context) {
if (args.locale) context.req.locale = args.locale
if (args.fallbackLocale) context.req.fallbackLocale = args.fallbackLocale
let { req } = context
const locale = req.locale
const fallbackLocale = req.fallbackLocale
req = isolateObjectProperty(req, 'locale')
req = isolateObjectProperty(req, 'fallbackLocale')
req.locale = args.locale || locale
req.fallbackLocale = args.fallbackLocale || fallbackLocale
const { slug } = globalConfig
if (!req.query) req.query = {}
const draft: boolean =
args.draft ?? req.query?.draft === 'false'
? false
: req.query?.draft === 'true'
? true
: undefined
if (typeof draft === 'boolean') req.query.draft = String(draft)
context.req = req
const options = {
slug: globalConfig.slug,
depth: 0,
draft: args.draft,
globalConfig,
req: isolateObjectProperty(context.req, 'transactionID'),
slug,
req: isolateObjectProperty(req, 'transactionID'),
}
const result = await findOne(options)

View File

@@ -24,15 +24,21 @@ export type Resolver = (
export default function findVersionByIDResolver(globalConfig: SanitizedGlobalConfig): Resolver {
return async function resolver(_, args, context) {
if (args.locale) context.req.locale = args.locale
if (args.fallbackLocale) context.req.fallbackLocale = args.fallbackLocale
let { req } = context
const locale = req.locale
const fallbackLocale = req.fallbackLocale
req = isolateObjectProperty(req, 'locale')
req = isolateObjectProperty(req, 'fallbackLocale')
req.locale = args.locale || locale
req.fallbackLocale = args.fallbackLocale || fallbackLocale
context.req = req
const options = {
id: args.id,
depth: 0,
draft: args.draft,
globalConfig,
req: isolateObjectProperty<PayloadRequest>(context.req, 'transactionID'),
req: isolateObjectProperty<PayloadRequest>(req, 'transactionID'),
}
const result = await findVersionByID(options)

View File

@@ -26,18 +26,32 @@ export default function updateResolver<TSlug extends keyof GeneratedTypes['globa
globalConfig: SanitizedGlobalConfig,
): Resolver<TSlug> {
return async function resolver(_, args, context) {
if (args.locale) context.req.locale = args.locale
if (args.fallbackLocale) context.req.fallbackLocale = args.fallbackLocale
let { req } = context
const locale = req.locale
const fallbackLocale = req.fallbackLocale
req = isolateObjectProperty<PayloadRequest>(req, 'locale')
req = isolateObjectProperty<PayloadRequest>(req, 'fallbackLocale')
req.locale = args.locale || locale
req.fallbackLocale = args.fallbackLocale || fallbackLocale
if (!req.query) req.query = {}
const { slug } = globalConfig
const draft: boolean =
args.draft ?? req.query?.draft === 'false'
? false
: req.query?.draft === 'true'
? true
: undefined
if (typeof draft === 'boolean') req.query.draft = String(draft)
context.req = req
const options = {
slug: globalConfig.slug,
data: args.data,
depth: 0,
draft: args.draft,
globalConfig,
req: isolateObjectProperty<PayloadRequest>(context.req, 'transactionID'),
slug,
req: isolateObjectProperty<PayloadRequest>(req, 'transactionID'),
}
const result = await update<TSlug>(options)

View File

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

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "Dərcdən çıxartmağı təsdiq edin",
"confirmVersionRestoration": "Versiyanın bərpasını təsdiq edin",
"currentDocumentStatus": "Cari {{docStatus}} sənədi",
"currentDraft": "Cari Müsvedde",
"currentPublishedVersion": "Hazırki Nəşr Versiyası",
"draft": "Qaralama",
"draftSavedSuccessfully": "Qaralama uğurla yadda saxlandı.",
"lastSavedAgo": "{{distance}} əvvəl son yadda saxlanıldı",
"noFurtherVersionsFound": "Başqa versiyalar tapılmadı",
"noRowsFound": "Heç bir {{label}} tapılmadı",
"preview": "Öncədən baxış",
"previouslyPublished": "Əvvəlki nəşr olunmuş",
"problemRestoringVersion": "Bu versiyanın bərpasında problem yaşandı",
"publish": "Dərc et",
"publishChanges": "Dəyişiklikləri dərc et",
@@ -378,4 +381,4 @@
"viewingVersions": "{{entityLabel}} {{documentTitle}} üçün versiyaları göstərir",
"viewingVersionsGlobal": "Qlobal {{entityLabel}} üçün versiyaları göstərir"
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -341,12 +341,15 @@
"confirmUnpublish": "A közzététel visszavonásának megerősítése",
"confirmVersionRestoration": "Verzió-visszaállítás megerősítése",
"currentDocumentStatus": "Jelenlegi {{docStatus}} dokumentum",
"currentDraft": "Jelenlegi vázlat",
"currentPublishedVersion": "Aktuálisan Kiadott Verzió",
"draft": "Piszkozat",
"draftSavedSuccessfully": "A piszkozat sikeresen mentve.",
"lastSavedAgo": "Utoljára mentve {{distance}} órája",
"noFurtherVersionsFound": "További verziók nem találhatók",
"noRowsFound": "Nem található {{label}}",
"preview": "Előnézet",
"previouslyPublished": "Korábban Megjelent",
"problemRestoringVersion": "Hiba történt a verzió visszaállításakor",
"publish": "Közzététel",
"publishChanges": "Módosítások közzététele",
@@ -378,4 +381,4 @@
"viewingVersions": "A {{entityLabel}} {{documentTitle}} verzióinak megtekintése",
"viewingVersionsGlobal": "A globális {{entityLabel}} verzióinak megtekintése"
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,5 @@
import type { SharpOptions } from 'sharp'
import sharp from 'sharp'
export const percentToPixel = (value: string, dimension: number): number => {
@@ -7,8 +9,14 @@ 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 { height, width, x, y } = cropData
const sharpOptions: SharpOptions = {}
if (fileIsAnimated) sharpOptions.animated = true
const formattedCropData: sharp.Region = {
height: percentToPixel(height, dimensions.height),
left: percentToPixel(x, dimensions.width),
@@ -16,7 +24,7 @@ export default async function cropImage({ cropData, dimensions, file }) {
width: percentToPixel(width, dimensions.width),
}
const cropped = sharp(file.tempFilePath || file.data).extract(formattedCropData)
const cropped = sharp(file.tempFilePath || file.data, sharpOptions).extract(formattedCropData)
return await cropped.toBuffer({
resolveWithObject: true,

View File

@@ -121,7 +121,7 @@ export const generateFileData = async <T>({
let newData = data
const filesToSave: FileToSave[] = []
const fileData: Partial<FileData> = {}
const fileIsAnimated = file.mimetype === 'image/gif' || file.mimetype === 'image/webp'
const fileIsAnimated = ['image/avif', 'image/gif', 'image/webp'].includes(file.mimetype)
const cropData =
typeof uploadEdits === 'object' && 'crop' in uploadEdits ? uploadEdits.crop : undefined
@@ -135,27 +135,29 @@ export const generateFileData = async <T>({
let mime: string
const fileHasAdjustments =
fileSupportsResize &&
Boolean(resizeOptions || formatOptions || trimOptions || file.tempFilePath)
Boolean(resizeOptions || formatOptions || imageSizes || trimOptions || file.tempFilePath)
const sharpOptions: SharpOptions = {}
if (fileIsAnimated) sharpOptions.animated = true
if (fileHasAdjustments) {
if (sharp && (fileIsAnimated || 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)
}
}
}
@@ -223,6 +225,10 @@ export const generateFileData = async <T>({
}
fileData.width = info.width
fileData.height = info.height
if (fileIsAnimated) {
const metadata = await sharpFile.metadata()
fileData.height = metadata.pages ? info.height / metadata.pages : info.height
}
fileData.filesize = info.size
if (file.tempFilePath) {

View File

@@ -1,5 +1,5 @@
import type { UploadedFile } from 'express-fileupload'
import type { OutputInfo } from 'sharp'
import type { OutputInfo, Sharp, SharpOptions } from 'sharp'
import { fromBuffer } from 'file-type'
import fs from 'fs'
@@ -217,7 +217,7 @@ const sanitizeResizeConfig = (resizeConfig: ImageSize): ImageSize => {
*
* The image will be resized according to the provided
* resize config. If no image sizes are requested, the resolved data will be empty.
* For every image that dos not need to be resized, an result object with `null`
* For every image that does not need to be resized, a result object with `null`
* parameters will be returned.
*
* @param resizeConfig - the resize config
@@ -252,7 +252,13 @@ export default async function resizeAndTransformImageSizes({
// Nothing to resize here so return as early as possible
if (!imageSizes) return defaultResult
const sharpBase = sharp(file.tempFilePath || file.data).rotate() // pass rotate() to auto-rotate based on EXIF data. https://github.com/payloadcms/payload/pull/3081
// Determine if the file is animated
const fileIsAnimated = ['image/avif', 'image/gif', 'image/webp'].includes(file.mimetype)
const sharpOptions: SharpOptions = {}
if (fileIsAnimated) 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
const results: ImageSizesResult[] = await Promise.all(
imageSizes.map(async (imageResizeConfig): Promise<ImageSizesResult> => {
@@ -268,6 +274,8 @@ export default async function resizeAndTransformImageSizes({
const imageToResize = sharpBase.clone()
let resized = imageToResize
const metadata = await sharpBase.metadata()
if (incomingFocalPoint && applyPayloadAdjustments(imageResizeConfig, dimensions)) {
const { height: resizeHeight, width: resizeWidth } = imageResizeConfig
const resizeAspectRatio = resizeWidth / resizeHeight
@@ -289,14 +297,18 @@ export default async function resizeAndTransformImageSizes({
const safeOffsetX = Math.min(Math.max(0, leftFocalEdge), maxOffsetX)
const safeResizeHeight = resizeHeight ?? scaledImageInfo.height
const maxOffsetY = scaledImageInfo.height - safeResizeHeight
const maxOffsetY = fileIsAnimated
? resizeHeight - safeResizeHeight
: scaledImageInfo.height - safeResizeHeight
const topFocalEdge = Math.round(
scaledImageInfo.height * (incomingFocalPoint.y / 100) - safeResizeHeight / 2,
)
const safeOffsetY = Math.min(Math.max(0, topFocalEdge), maxOffsetY)
// extract the focal area from the scaled image
resized = scaledImage.extract({
resized = (fileIsAnimated ? imageToResize : scaledImage).extract({
height: safeResizeHeight,
left: safeOffsetX,
top: safeOffsetY,
@@ -350,7 +362,7 @@ export default async function resizeAndTransformImageSizes({
name: imageResizeConfig.name,
filename: imageNameWithDimensions,
filesize: size,
height,
height: fileIsAnimated && metadata.pages ? height / metadata.pages : height,
mimeType: mimeInfo?.mime || mimeType,
sizesToSave: [{ buffer: bufferData, path: imagePath }],
width,

View File

@@ -1,7 +1,7 @@
/**
* Creates a proxy for the given object that has its own property
*/
export default function isolateObjectProperty<T>(object: T, key): T {
export default function isolateObjectProperty<T>(object: T, key: keyof T): T {
const delegate = {}
const handler = {
deleteProperty(target, p): boolean {

128
pnpm-lock.yaml generated
View File

@@ -20,8 +20,8 @@ importers:
specifier: ^7.77.0
version: 7.112.2(react@18.2.0)
ajv:
specifier: ^8.12.0
version: 8.12.0
specifier: 8.14.0
version: 8.14.0
react:
specifier: 18.2.0
version: 18.2.0
@@ -270,6 +270,9 @@ importers:
packages/bundler-webpack:
dependencies:
ajv:
specifier: 8.14.0
version: 8.14.0
compression:
specifier: 1.7.4
version: 1.7.4
@@ -279,9 +282,6 @@ importers:
css-loader:
specifier: 5.2.7
version: 5.2.7(webpack@5.91.0)
css-minimizer-webpack-plugin:
specifier: ^5.0.0
version: 5.0.1(webpack@5.91.0)
file-loader:
specifier: 6.2.0
version: 6.2.0(webpack@5.91.0)
@@ -438,52 +438,6 @@ importers:
specifier: ^29.1.0
version: 29.1.2(@babel/core@7.24.4)(jest@29.7.0)(typescript@5.2.2)
packages/db-example:
dependencies:
bson-objectid:
specifier: 2.0.4
version: 2.0.4
deepmerge:
specifier: 4.3.1
version: 4.3.1
get-port:
specifier: 5.1.1
version: 5.1.1
http-status:
specifier: 1.6.2
version: 1.6.2
mongoose:
specifier: 6.12.3
version: 6.12.3
mongoose-aggregate-paginate-v2:
specifier: 1.0.6
version: 1.0.6
mongoose-paginate-v2:
specifier: 1.7.22
version: 1.7.22
prompts:
specifier: 2.4.2
version: 2.4.2
uuid:
specifier: 9.0.0
version: 9.0.0
devDependencies:
'@payloadcms/eslint-config':
specifier: workspace:*
version: link:../eslint-config-payload
'@types/mongoose-aggregate-paginate-v2':
specifier: 1.0.9
version: 1.0.9
mongodb:
specifier: 4.17.1
version: 4.17.1
mongodb-memory-server:
specifier: ^9
version: 9.2.0
payload:
specifier: workspace:*
version: link:../payload
packages/db-mongodb:
dependencies:
bson-objectid:
@@ -5484,6 +5438,7 @@ packages:
/@trysound/sax@0.2.0:
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
engines: {node: '>=10.13.0'}
dev: true
/@tsconfig/node10@1.0.11:
resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==}
@@ -6827,7 +6782,7 @@ packages:
- supports-color
dev: true
/ajv-formats@2.1.1(ajv@8.12.0):
/ajv-formats@2.1.1(ajv@8.14.0):
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
peerDependencies:
ajv: ^8.0.0
@@ -6835,7 +6790,7 @@ packages:
ajv:
optional: true
dependencies:
ajv: 8.12.0
ajv: 8.14.0
/ajv-keywords@3.5.2(ajv@6.12.6):
resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==}
@@ -6844,12 +6799,12 @@ packages:
dependencies:
ajv: 6.12.6
/ajv-keywords@5.1.0(ajv@8.12.0):
/ajv-keywords@5.1.0(ajv@8.14.0):
resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==}
peerDependencies:
ajv: ^8.8.2
dependencies:
ajv: 8.12.0
ajv: 8.14.0
fast-deep-equal: 3.1.3
/ajv@6.12.6:
@@ -6860,8 +6815,8 @@ packages:
json-schema-traverse: 0.4.1
uri-js: 4.4.1
/ajv@8.12.0:
resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==}
/ajv@8.14.0:
resolution: {integrity: sha512-oYs1UUtO97ZO2lJ4bwnWeQW8/zvOIQLGKcvPTsWmvc2SYgBb+upuNS5NxoLaMU4h8Ju3Nbj6Cq8mD2LQoqVKFA==}
dependencies:
fast-deep-equal: 3.1.3
json-schema-traverse: 1.0.0
@@ -7624,6 +7579,7 @@ packages:
caniuse-lite: 1.0.30001612
lodash.memoize: 4.1.2
lodash.uniq: 4.5.0
dev: true
/caniuse-lite@1.0.30001612:
resolution: {integrity: sha512-lFgnZ07UhaCcsSZgWW0K5j4e69dK1u/ltrL9lTUiFOwNHs12S3UMIEYgBV0Z6C6hRDev7iRnMzzYmKabYdXF9g==}
@@ -7840,6 +7796,7 @@ packages:
/colord@2.9.3:
resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==}
dev: true
/colorette@2.0.20:
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
@@ -7934,8 +7891,8 @@ packages:
resolution: {integrity: sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==}
engines: {node: '>=12'}
dependencies:
ajv: 8.12.0
ajv-formats: 2.1.1(ajv@8.12.0)
ajv: 8.14.0
ajv-formats: 2.1.1(ajv@8.14.0)
atomically: 1.7.0
debounce-fn: 4.0.0
dot-prop: 6.0.1
@@ -8420,6 +8377,7 @@ packages:
postcss: ^8.0.9
dependencies:
postcss: 8.4.31
dev: true
/css-has-pseudo@6.0.3(postcss@8.4.31):
resolution: {integrity: sha512-qIsDxK/z0byH/mpNsv5hzQ5NOl8m1FRmOLgZpx4bG5uYHnOlO2XafeMI4mFIgNSViHwoUWcxSJZyyijaAmbs+A==}
@@ -8482,6 +8440,7 @@ packages:
schema-utils: 4.2.0
serialize-javascript: 6.0.2
webpack: 5.91.0(@swc/core@1.3.107)(webpack-cli@4.10.0)
dev: true
/css-prefers-color-scheme@9.0.1(postcss@8.4.31):
resolution: {integrity: sha512-iFit06ochwCKPRiWagbTa1OAWCvWWVdEnIFd8BaRrgO8YrrNh4RAWUQTFcYX5tdFZgFl1DJ3iiULchZyEbnF4g==}
@@ -8509,6 +8468,7 @@ packages:
domhandler: 5.0.3
domutils: 3.1.0
nth-check: 2.1.1
dev: true
/css-tree@2.2.1:
resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==}
@@ -8516,6 +8476,7 @@ packages:
dependencies:
mdn-data: 2.0.28
source-map-js: 1.2.0
dev: true
/css-tree@2.3.1:
resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==}
@@ -8523,6 +8484,7 @@ packages:
dependencies:
mdn-data: 2.0.30
source-map-js: 1.2.0
dev: true
/css-what@6.1.0:
resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
@@ -8581,6 +8543,7 @@ packages:
postcss-reduce-transforms: 6.0.2(postcss@8.4.31)
postcss-svgo: 6.0.3(postcss@8.4.31)
postcss-unique-selectors: 6.0.4(postcss@8.4.31)
dev: true
/cssnano-utils@4.0.2(postcss@8.4.31):
resolution: {integrity: sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==}
@@ -8589,6 +8552,7 @@ packages:
postcss: ^8.4.31
dependencies:
postcss: 8.4.31
dev: true
/cssnano@6.1.2(postcss@8.4.31):
resolution: {integrity: sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==}
@@ -8599,12 +8563,14 @@ packages:
cssnano-preset-default: 6.1.2(postcss@8.4.31)
lilconfig: 3.1.1
postcss: 8.4.31
dev: true
/csso@5.0.5:
resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==}
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'}
dependencies:
css-tree: 2.2.1
dev: true
/cssom@0.3.8:
resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==}
@@ -9031,6 +8997,7 @@ packages:
domelementtype: 2.3.0
domhandler: 5.0.3
entities: 4.5.0
dev: true
/domelementtype@2.3.0:
resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
@@ -9055,6 +9022,7 @@ packages:
engines: {node: '>= 4'}
dependencies:
domelementtype: 2.3.0
dev: true
/domutils@2.8.0:
resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
@@ -9070,6 +9038,7 @@ packages:
dom-serializer: 2.0.0
domelementtype: 2.3.0
domhandler: 5.0.3
dev: true
/dot-case@3.0.4:
resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
@@ -9269,6 +9238,7 @@ packages:
/entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
dev: true
/env-paths@2.2.1:
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
@@ -12915,6 +12885,7 @@ packages:
/lilconfig@3.1.1:
resolution: {integrity: sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==}
engines: {node: '>=14'}
dev: true
/lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
@@ -13058,6 +13029,7 @@ packages:
/lodash.memoize@4.1.2:
resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
dev: true
/lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
@@ -13069,6 +13041,7 @@ packages:
/lodash.uniq@4.5.0:
resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==}
dev: true
/lodash.uniqby@4.7.0:
resolution: {integrity: sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==}
@@ -13216,9 +13189,11 @@ packages:
/mdn-data@2.0.28:
resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==}
dev: true
/mdn-data@2.0.30:
resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
dev: true
/media-typer@0.3.0:
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
@@ -14584,6 +14559,7 @@ packages:
postcss: 8.4.31
postcss-selector-parser: 6.0.16
postcss-value-parser: 4.2.0
dev: true
/postcss-clamp@4.1.0(postcss@8.4.31):
resolution: {integrity: sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==}
@@ -14638,6 +14614,7 @@ packages:
colord: 2.9.3
postcss: 8.4.31
postcss-value-parser: 4.2.0
dev: true
/postcss-convert-values@6.1.0(postcss@8.4.31):
resolution: {integrity: sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==}
@@ -14648,6 +14625,7 @@ packages:
browserslist: 4.23.0
postcss: 8.4.31
postcss-value-parser: 4.2.0
dev: true
/postcss-custom-media@10.0.4(postcss@8.4.31):
resolution: {integrity: sha512-Ubs7O3wj2prghaKRa68VHBvuy3KnTQ0zbGwqDYY1mntxJD0QL2AeiAy+AMfl3HBedTCVr2IcFNktwty9YpSskA==}
@@ -14702,6 +14680,7 @@ packages:
postcss: ^8.4.31
dependencies:
postcss: 8.4.31
dev: true
/postcss-discard-duplicates@6.0.3(postcss@8.4.31):
resolution: {integrity: sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==}
@@ -14710,6 +14689,7 @@ packages:
postcss: ^8.4.31
dependencies:
postcss: 8.4.31
dev: true
/postcss-discard-empty@6.0.3(postcss@8.4.31):
resolution: {integrity: sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==}
@@ -14718,6 +14698,7 @@ packages:
postcss: ^8.4.31
dependencies:
postcss: 8.4.31
dev: true
/postcss-discard-overridden@6.0.2(postcss@8.4.31):
resolution: {integrity: sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==}
@@ -14726,6 +14707,7 @@ packages:
postcss: ^8.4.31
dependencies:
postcss: 8.4.31
dev: true
/postcss-double-position-gradients@5.0.6(postcss@8.4.31):
resolution: {integrity: sha512-QJ+089FKMaqDxOhhIHsJrh4IP7h4PIHNC5jZP5PMmnfUScNu8Hji2lskqpFWCvu+5sj+2EJFyzKd13sLEWOZmQ==}
@@ -14832,6 +14814,7 @@ packages:
postcss: 8.4.31
postcss-value-parser: 4.2.0
stylehacks: 6.1.1(postcss@8.4.31)
dev: true
/postcss-merge-rules@6.1.1(postcss@8.4.31):
resolution: {integrity: sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==}
@@ -14844,6 +14827,7 @@ packages:
cssnano-utils: 4.0.2(postcss@8.4.31)
postcss: 8.4.31
postcss-selector-parser: 6.0.16
dev: true
/postcss-minify-font-values@6.1.0(postcss@8.4.31):
resolution: {integrity: sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==}
@@ -14853,6 +14837,7 @@ packages:
dependencies:
postcss: 8.4.31
postcss-value-parser: 4.2.0
dev: true
/postcss-minify-gradients@6.0.3(postcss@8.4.31):
resolution: {integrity: sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==}
@@ -14864,6 +14849,7 @@ packages:
cssnano-utils: 4.0.2(postcss@8.4.31)
postcss: 8.4.31
postcss-value-parser: 4.2.0
dev: true
/postcss-minify-params@6.1.0(postcss@8.4.31):
resolution: {integrity: sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==}
@@ -14875,6 +14861,7 @@ packages:
cssnano-utils: 4.0.2(postcss@8.4.31)
postcss: 8.4.31
postcss-value-parser: 4.2.0
dev: true
/postcss-minify-selectors@6.0.4(postcss@8.4.31):
resolution: {integrity: sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==}
@@ -14884,6 +14871,7 @@ packages:
dependencies:
postcss: 8.4.31
postcss-selector-parser: 6.0.16
dev: true
/postcss-modules-extract-imports@3.1.0(postcss@8.4.31):
resolution: {integrity: sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==}
@@ -14940,6 +14928,7 @@ packages:
postcss: ^8.4.31
dependencies:
postcss: 8.4.31
dev: true
/postcss-normalize-display-values@6.0.2(postcss@8.4.31):
resolution: {integrity: sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==}
@@ -14949,6 +14938,7 @@ packages:
dependencies:
postcss: 8.4.31
postcss-value-parser: 4.2.0
dev: true
/postcss-normalize-positions@6.0.2(postcss@8.4.31):
resolution: {integrity: sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==}
@@ -14958,6 +14948,7 @@ packages:
dependencies:
postcss: 8.4.31
postcss-value-parser: 4.2.0
dev: true
/postcss-normalize-repeat-style@6.0.2(postcss@8.4.31):
resolution: {integrity: sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==}
@@ -14967,6 +14958,7 @@ packages:
dependencies:
postcss: 8.4.31
postcss-value-parser: 4.2.0
dev: true
/postcss-normalize-string@6.0.2(postcss@8.4.31):
resolution: {integrity: sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==}
@@ -14976,6 +14968,7 @@ packages:
dependencies:
postcss: 8.4.31
postcss-value-parser: 4.2.0
dev: true
/postcss-normalize-timing-functions@6.0.2(postcss@8.4.31):
resolution: {integrity: sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==}
@@ -14985,6 +14978,7 @@ packages:
dependencies:
postcss: 8.4.31
postcss-value-parser: 4.2.0
dev: true
/postcss-normalize-unicode@6.1.0(postcss@8.4.31):
resolution: {integrity: sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==}
@@ -14995,6 +14989,7 @@ packages:
browserslist: 4.23.0
postcss: 8.4.31
postcss-value-parser: 4.2.0
dev: true
/postcss-normalize-url@6.0.2(postcss@8.4.31):
resolution: {integrity: sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==}
@@ -15004,6 +14999,7 @@ packages:
dependencies:
postcss: 8.4.31
postcss-value-parser: 4.2.0
dev: true
/postcss-normalize-whitespace@6.0.2(postcss@8.4.31):
resolution: {integrity: sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==}
@@ -15013,6 +15009,7 @@ packages:
dependencies:
postcss: 8.4.31
postcss-value-parser: 4.2.0
dev: true
/postcss-opacity-percentage@2.0.0(postcss@8.4.31):
resolution: {integrity: sha512-lyDrCOtntq5Y1JZpBFzIWm2wG9kbEdujpNt4NLannF+J9c8CgFIzPa80YQfdza+Y+yFfzbYj/rfoOsYsooUWTQ==}
@@ -15031,6 +15028,7 @@ packages:
cssnano-utils: 4.0.2(postcss@8.4.31)
postcss: 8.4.31
postcss-value-parser: 4.2.0
dev: true
/postcss-overflow-shorthand@5.0.1(postcss@8.4.31):
resolution: {integrity: sha512-XzjBYKLd1t6vHsaokMV9URBt2EwC9a7nDhpQpjoPk2HRTSQfokPfyAS/Q7AOrzUu6q+vp/GnrDBGuj/FCaRqrQ==}
@@ -15139,6 +15137,7 @@ packages:
browserslist: 4.23.0
caniuse-api: 3.0.0
postcss: 8.4.31
dev: true
/postcss-reduce-transforms@6.0.2(postcss@8.4.31):
resolution: {integrity: sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==}
@@ -15148,6 +15147,7 @@ packages:
dependencies:
postcss: 8.4.31
postcss-value-parser: 4.2.0
dev: true
/postcss-replace-overflow-wrap@4.0.0(postcss@8.4.31):
resolution: {integrity: sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==}
@@ -15181,6 +15181,7 @@ packages:
postcss: 8.4.31
postcss-value-parser: 4.2.0
svgo: 3.2.0
dev: true
/postcss-unique-selectors@6.0.4(postcss@8.4.31):
resolution: {integrity: sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==}
@@ -15190,6 +15191,7 @@ packages:
dependencies:
postcss: 8.4.31
postcss-selector-parser: 6.0.16
dev: true
/postcss-value-parser@4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
@@ -16293,9 +16295,9 @@ packages:
engines: {node: '>= 12.13.0'}
dependencies:
'@types/json-schema': 7.0.15
ajv: 8.12.0
ajv-formats: 2.1.1(ajv@8.12.0)
ajv-keywords: 5.1.0(ajv@8.12.0)
ajv: 8.14.0
ajv-formats: 2.1.1(ajv@8.14.0)
ajv-keywords: 5.1.0(ajv@8.14.0)
/scmp@2.1.0:
resolution: {integrity: sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==}
@@ -16992,6 +16994,7 @@ packages:
browserslist: 4.23.0
postcss: 8.4.31
postcss-selector-parser: 6.0.16
dev: true
/stylis@4.2.0:
resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==}
@@ -17046,6 +17049,7 @@ packages:
css-what: 6.1.0
csso: 5.0.5
picocolors: 1.0.0
dev: true
/swc-loader@0.2.3(@swc/core@1.3.107)(webpack@5.91.0):
resolution: {integrity: sha512-D1p6XXURfSPleZZA/Lipb3A8pZ17fP4NObZvFCDjK/OKljroqDpPmsBdTraWhVBqUNpcWBQY1imWdoPScRlQ7A==}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -380,6 +380,29 @@ export default buildConfigWithDefaults({
],
},
],
globals: [
{
slug: 'global-1',
access: {
read: openAccess.read,
update: openAccess.update,
},
fields: [
{
type: 'text',
name: 'title',
},
{
name: 'relationship',
type: 'relationship',
relationTo: 'cyclical-relationship',
},
],
versions: {
drafts: true,
},
},
],
graphQL: {
queries: (GraphQL) => {
return {

View File

@@ -984,6 +984,59 @@ describe('collections-graphql', () => {
expect(queriedDoc.media.title).toEqual('example')
})
})
it('should cascade draft arg with globals', async () => {
// publish relationship doc
const newDoc = await payload.create({
collection: 'cyclical-relationship',
draft: false,
data: {
title: 'published relationship',
},
})
// save draft version relationship doc
await payload.update({
collection: 'cyclical-relationship',
id: newDoc.id,
draft: true,
data: {
title: 'draft relationship',
},
})
// update global (published data)
await payload.updateGlobal({
slug: 'global-1',
data: {
title: 'published title',
relationship: newDoc.id,
},
})
// update global (draft data)
await payload.updateGlobal({
slug: 'global-1',
draft: true,
data: {
title: 'draft title',
},
})
const query = `{
Global1(draft: true) {
title
relationship {
title
}
}
}`
const response = (await client.request(query)) as any
const queriedGlobal = response.Global1
expect(queriedGlobal.title).toEqual('draft title')
expect(queriedGlobal.relationship.title).toEqual('draft relationship')
})
})
describe('Error Handler', () => {

View File

@@ -78,6 +78,26 @@ describe('fields', () => {
).toBeHidden()
})
test('should not display admin.disableListColumn true field in list view column selector if toggling other fields', async () => {
await page.goto(url.list)
await page.locator('.list-controls__toggle-columns').click()
await expect(page.locator('.column-selector')).toBeVisible()
// Click another field in column selector
const updatedAtButton = page.locator(`.column-selector .column-selector__column`, {
hasText: exactText('Updated At'),
})
await updatedAtButton.click()
// Check if "Disable List Column Text" is not present in the column options
await expect(
page.locator(`.column-selector .column-selector__column`, {
hasText: exactText('Disable List Column Text'),
}),
).toBeHidden()
})
test('should display field in list view filter selector if admin.disableListColumn is true and admin.disableListFilter is false', async () => {
await page.goto(url.list)
await page.locator('.list-controls__toggle-where').click()

View File

@@ -19,6 +19,13 @@ const access = {
}
export default buildConfigWithDefaults({
collections: [
{
slug: 'media',
upload: true,
fields: [],
},
],
globals: [
{
access,
@@ -31,6 +38,11 @@ export default buildConfigWithDefaults({
name: 'title',
type: 'text',
},
{
name: 'media',
type: 'upload',
relationTo: 'media',
},
],
slug,
},

29
test/globals/e2e.spec.ts Normal file
View File

@@ -0,0 +1,29 @@
import type { Page } from '@playwright/test'
import { expect, test } from '@playwright/test'
import { initPageConsoleErrorCatch } from '../helpers'
import { AdminUrlUtil } from '../helpers/adminUrlUtil'
import { initPayloadE2E } from '../helpers/configHelpers'
const { beforeAll, describe } = test
describe('Globals', () => {
let page: Page
let url: AdminUrlUtil
beforeAll(async ({ browser }) => {
const { serverURL } = await initPayloadE2E(__dirname)
url = new AdminUrlUtil(serverURL, 'media')
const context = await browser.newContext()
page = await context.newPage()
initPageConsoleErrorCatch(page)
})
test('can edit media from field', async () => {
await page.goto(url.create)
// const textCell = page.locator('.row-1 .cell-text')
})
})

View File

@@ -101,6 +101,13 @@ export const selectTableRow = async (page: Page, title: string): Promise<void> =
expect(await page.locator(selector).isChecked()).toBe(true)
}
export async function navigateToListCellLink(page: Page, selector = '.cell-id') {
const cellLink = page.locator(`${selector} a`).first()
const linkURL = await cellLink.getAttribute('href')
await cellLink.click()
await page.waitForURL(`**${linkURL}`)
}
export const findTableCell = async (
page: Page,
fieldName: string,

View File

@@ -10,7 +10,7 @@ import { Users } from './collections/Users'
import { Footer } from './globals/Footer'
import { Header } from './globals/Header'
import { seed } from './seed'
import { mobileBreakpoint } from './shared'
import { desktopBreakpoint, mobileBreakpoint } from './shared'
import { formatLivePreviewURL } from './utilities/formatLivePreviewURL'
const mockModulePath = path.resolve(__dirname, './mocks/mockFSModule.js')
@@ -21,7 +21,7 @@ export default buildConfigWithDefaults({
// You can define any of these properties on a per collection or global basis
// The Live Preview config cascades from the top down, properties are inherited from here
url: formatLivePreviewURL,
breakpoints: [mobileBreakpoint],
breakpoints: [mobileBreakpoint, desktopBreakpoint],
collections: ['pages', 'posts'],
globals: ['header', 'footer'],
},

View File

@@ -5,7 +5,14 @@ import { expect, test } from '@playwright/test'
import { exactText, initPageConsoleErrorCatch, saveDocAndAssert } from '../helpers'
import { AdminUrlUtil } from '../helpers/adminUrlUtil'
import { initPayloadE2E } from '../helpers/configHelpers'
import { mobileBreakpoint } from './shared'
import {
ensureDeviceIsCentered,
ensureDeviceIsLeftAligned,
goToCollectionLivePreview,
selectLivePreviewBreakpoint,
selectLivePreviewZoom,
} from './helpers'
import { desktopBreakpoint, mobileBreakpoint } from './shared'
import { startLivePreviewDemo } from './startLivePreviewDemo'
const { beforeAll, describe } = test
@@ -216,4 +223,34 @@ describe('Live Preview', () => {
const height = parseInt(heightInputValue)
expect(height).toBe(mobileBreakpoint.height)
})
test('device — centers device when smaller than frame despite zoom', async () => {
await goToCollectionLivePreview(page, url)
await selectLivePreviewBreakpoint(page, mobileBreakpoint.label)
await ensureDeviceIsCentered(page)
await selectLivePreviewZoom(page, '75%')
await ensureDeviceIsCentered(page)
await selectLivePreviewZoom(page, '50%')
await ensureDeviceIsCentered(page)
await selectLivePreviewZoom(page, '125%')
await ensureDeviceIsCentered(page)
await selectLivePreviewZoom(page, '200%')
await ensureDeviceIsCentered(page)
expect(true).toBeTruthy()
})
test('device — left-aligns device when larger than frame despite zoom', async () => {
await goToCollectionLivePreview(page, url)
await selectLivePreviewBreakpoint(page, desktopBreakpoint.label)
await ensureDeviceIsLeftAligned(page)
await selectLivePreviewZoom(page, '75%')
await ensureDeviceIsLeftAligned(page)
await selectLivePreviewZoom(page, '50%')
await ensureDeviceIsLeftAligned(page)
await selectLivePreviewZoom(page, '125%')
await ensureDeviceIsLeftAligned(page)
await selectLivePreviewZoom(page, '200%')
await ensureDeviceIsLeftAligned(page)
expect(true).toBeTruthy()
})
})

View File

@@ -0,0 +1,124 @@
import type { Page } from '@playwright/test'
import { expect } from '@playwright/test'
import { exactText, navigateToListCellLink } from '../helpers.js'
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
export const EXPECT_TIMEOUT = 8000
export const POLL_TOPASS_TIMEOUT = EXPECT_TIMEOUT * 4
export const goToDoc = async (page: Page, urlUtil: AdminUrlUtil) => {
await page.goto(urlUtil.list)
await page.waitForURL(urlUtil.list)
await navigateToListCellLink(page)
}
export const goToCollectionLivePreview = async (
page: Page,
urlUtil: AdminUrlUtil,
): Promise<void> => {
await goToDoc(page, urlUtil)
await page.goto(`${page.url()}/preview`)
await page.waitForURL(`**/preview`)
}
export const goToGlobalLivePreview = async (
page: Page,
slug: string,
serverURL: string,
): Promise<void> => {
const global = new AdminUrlUtil(serverURL, slug)
const previewURL = `${global.global(slug)}/preview`
await page.goto(previewURL)
await page.waitForURL(previewURL)
}
export const selectLivePreviewBreakpoint = async (page: Page, breakpointLabel: string) => {
const breakpointSelector = page.locator(
'.live-preview-toolbar-controls__breakpoint button.popup-button',
)
await expect(() => expect(breakpointSelector).toBeTruthy()).toPass({
timeout: POLL_TOPASS_TIMEOUT,
})
await breakpointSelector.first().click()
await page
.locator(`.live-preview-toolbar-controls__breakpoint button.popup-button-list__button`)
.filter({ hasText: breakpointLabel })
.click()
await expect(breakpointSelector).toContainText(breakpointLabel)
const option = page.locator(
'.live-preview-toolbar-controls__breakpoint button.popup-button-list__button--selected',
)
await expect(option).toHaveText(breakpointLabel)
}
export const selectLivePreviewZoom = async (page: Page, zoomLabel: string) => {
const zoomSelector = page.locator('.live-preview-toolbar-controls__zoom button.popup-button')
await expect(() => expect(zoomSelector).toBeTruthy()).toPass({
timeout: POLL_TOPASS_TIMEOUT,
})
await zoomSelector.first().click()
const zoomOption = page.locator(
'.live-preview-toolbar-controls__zoom button.popup-button-list__button',
{
hasText: exactText(zoomLabel),
},
)
expect(zoomOption).toBeTruthy()
await zoomOption.click()
await expect(zoomSelector).toContainText(zoomLabel)
const option = page.locator(
'.live-preview-toolbar-controls__zoom button.popup-button-list__button--selected',
)
await expect(option).toHaveText(zoomLabel)
}
export const ensureDeviceIsCentered = async (page: Page) => {
const main = page.locator('.live-preview-window__main')
const iframe = page.locator('iframe.live-preview-iframe')
const mainBoxAfterZoom = await main.boundingBox()
const iframeBoxAfterZoom = await iframe.boundingBox()
const distanceFromIframeLeftToMainLeftAfterZoom = Math.abs(
mainBoxAfterZoom?.x - iframeBoxAfterZoom?.x,
)
const distanceFromIFrameRightToMainRightAfterZoom = Math.abs(
mainBoxAfterZoom?.x +
mainBoxAfterZoom?.width -
iframeBoxAfterZoom?.x -
iframeBoxAfterZoom?.width,
)
await expect(() =>
expect(distanceFromIframeLeftToMainLeftAfterZoom).toBe(
distanceFromIFrameRightToMainRightAfterZoom,
),
).toPass({
timeout: POLL_TOPASS_TIMEOUT,
})
}
export const ensureDeviceIsLeftAligned = async (page: Page) => {
const main = page.locator('.live-preview-window__main > div')
const iframe = page.locator('iframe.live-preview-iframe')
const mainBoxAfterZoom = await main.boundingBox()
const iframeBoxAfterZoom = await iframe.boundingBox()
const distanceFromIframeLeftToMainLeftAfterZoom = Math.abs(
mainBoxAfterZoom?.x - iframeBoxAfterZoom?.x,
)
await expect(() => expect(distanceFromIframeLeftToMainLeftAfterZoom).toBe(0)).toPass({
timeout: POLL_TOPASS_TIMEOUT,
})
}

View File

@@ -8,3 +8,10 @@ export const mobileBreakpoint = {
width: 375,
height: 667,
}
export const desktopBreakpoint = {
label: 'Desktop',
name: 'desktop',
width: 1920,
height: 1024,
}

View File

@@ -12,6 +12,7 @@ import {
cropOnlySlug,
enlargeSlug,
focalOnlySlug,
globalWithMedia,
mediaSlug,
reduceSlug,
relationSlug,
@@ -491,6 +492,18 @@ export default buildConfigWithDefaults({
},
},
],
globals: [
{
slug: globalWithMedia,
fields: [
{
type: 'upload',
name: 'media',
relationTo: cropOnlySlug,
},
],
},
],
onInit: async (payload) => {
const uploadsDir = path.resolve(__dirname, './media')
removeFiles(path.normalize(uploadsDir))

View File

@@ -12,7 +12,7 @@ import { AdminUrlUtil } from '../helpers/adminUrlUtil'
import { initPayloadE2E } from '../helpers/configHelpers'
import { RESTClient } from '../helpers/rest'
import { adminThumbnailSrc } from './collections/admin-thumbnail'
import { adminThumbnailSlug, audioSlug, mediaSlug, relationSlug } from './shared'
import { adminThumbnailSlug, audioSlug, globalWithMedia, mediaSlug, relationSlug } from './shared'
const { beforeAll, describe } = test
@@ -21,6 +21,7 @@ let mediaURL: AdminUrlUtil
let audioURL: AdminUrlUtil
let relationURL: AdminUrlUtil
let adminThumbnailURL: AdminUrlUtil
let globalURL: string
describe('uploads', () => {
let page: Page
@@ -36,6 +37,7 @@ describe('uploads', () => {
audioURL = new AdminUrlUtil(serverURL, audioSlug)
relationURL = new AdminUrlUtil(serverURL, relationSlug)
adminThumbnailURL = new AdminUrlUtil(serverURL, adminThumbnailSlug)
globalURL = new AdminUrlUtil(serverURL, globalWithMedia).global(globalWithMedia)
const context = await browser.newContext()
page = await context.newPage()
@@ -323,4 +325,17 @@ describe('uploads', () => {
expect(redDoc.filesize).toEqual(1207)
})
})
describe('globals', () => {
test('should be able to crop media from a global', async () => {
await page.goto(globalURL)
await page.click('.upload__toggler.doc-drawer__toggler')
await page.setInputFiles('input[type="file"]', path.resolve(__dirname, './image.png'))
await page.click('.file-field__edit')
await page.click('.btn.edit-upload__save')
await saveDocAndAssert(page, '.drawer__content #action-save')
await saveDocAndAssert(page)
await expect(page.locator('.thumbnail img')).toBeVisible()
})
})
})

View File

@@ -7,3 +7,4 @@ export const mediaSlug = 'media'
export const reduceSlug = 'reduce'
export const relationSlug = 'relation'
export const versionSlug = 'versions'
export const globalWithMedia = 'global-with-media'