Compare commits
18 Commits
3.0.0-beta
...
payload/2.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
576ee14976 | ||
|
|
bf77cec7e9 | ||
|
|
ab8b2f3fb8 | ||
|
|
db5f3f3ccd | ||
|
|
cece39957f | ||
|
|
157fff0417 | ||
|
|
88e113a545 | ||
|
|
d33afe48fe | ||
|
|
e76df32f09 | ||
|
|
b068f30f51 | ||
|
|
82bd5c656f | ||
|
|
afe8992ca6 | ||
|
|
48a410e294 | ||
|
|
82b88a315f | ||
|
|
cc94078607 | ||
|
|
1c30ad73b6 | ||
|
|
d9442dcce3 | ||
|
|
de92c50847 |
56
CHANGELOG.md
56
CHANGELOG.md
@@ -1,3 +1,59 @@
|
||||
## [2.18.3](https://github.com/payloadcms/payload/compare/v2.18.2...v2.18.3) (2024-05-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **db-postgres:** query with like on id columns ([#6416](https://github.com/payloadcms/payload/issues/6416)) ([bf77cec](https://github.com/payloadcms/payload/commit/bf77cec7e9e7db4988e481d464178636203fca32))
|
||||
* **db-postgres:** uuid custom db name ([#6409](https://github.com/payloadcms/payload/issues/6409)) ([db5f3f3](https://github.com/payloadcms/payload/commit/db5f3f3ccdaedd9e8036c3e39fc20650a309a151))
|
||||
* nested `disableListColumn` in rows ([#6412](https://github.com/payloadcms/payload/issues/6412)) ([ab8b2f3](https://github.com/payloadcms/payload/commit/ab8b2f3fb87864484582b7d819ca307888a9449b)), closes [#6407](https://github.com/payloadcms/payload/issues/6407)
|
||||
|
||||
## [2.18.2](https://github.com/payloadcms/payload/compare/v2.18.1...v2.18.2) (2024-05-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow focal point when no sizes defined ([#6397](https://github.com/payloadcms/payload/issues/6397)) ([88e113a](https://github.com/payloadcms/payload/commit/88e113a5452300434f690186d10ea02ab159ffc3))
|
||||
|
||||
## [2.18.1](https://github.com/payloadcms/payload/compare/v2.18.0...v2.18.1) (2024-05-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add back explicit crop x and y values ([#6391](https://github.com/payloadcms/payload/issues/6391)) ([e76df32](https://github.com/payloadcms/payload/commit/e76df32f0987cc92dc8d9c693950e650c52576bf))
|
||||
|
||||
## [2.18.0](https://github.com/payloadcms/payload/compare/v2.17.0...v2.18.0) (2024-05-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* store focal point on uploads ([#6364](https://github.com/payloadcms/payload/issues/6364)) ([82b88a3](https://github.com/payloadcms/payload/commit/82b88a315ff1d52f0b19a70224d5c600a3a97eb5))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **db-postgres:** filter with ID not_in AND queries - postgres ([#6358](https://github.com/payloadcms/payload/issues/6358)) ([cc94078](https://github.com/payloadcms/payload/commit/cc940786072c0065f10fdd2893050bddc4595a21)), closes [#5151](https://github.com/payloadcms/payload/issues/5151)
|
||||
* **richtext-lexical:** upload, relationship and block node insertion fails sometimes ([#6390](https://github.com/payloadcms/payload/issues/6390)) ([48a410e](https://github.com/payloadcms/payload/commit/48a410e294598af9c73577a04f86466248f93da0))
|
||||
|
||||
## [2.17.0](https://github.com/payloadcms/payload/compare/v2.16.1...v2.17.0) (2024-05-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* adds misc translations to API view and react select ([#6138](https://github.com/payloadcms/payload/issues/6138)) ([30e535b](https://github.com/payloadcms/payload/commit/30e535b5b929dddead007d8a9adca62808595e2c))
|
||||
|
||||
* **richtext-lexical:** remove LexicalBlock, RichTextFieldRequiredEditor and FieldWithRichTextRequiredEditor types ([#6279](https://github.com/payloadcms/payload/issues/6279)) ([9df5ab8](https://github.com/payloadcms/payload/commit/9df5ab8a10a35ad34615d7e4da024f59ff037e0e))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* appends `editDepth` value to `radio` & `checkbox` IDs when inside drawer ([#6181](https://github.com/payloadcms/payload/issues/6181)) ([69c93d3](https://github.com/payloadcms/payload/commit/69c93d3c62394a5cf995a2eaec9a3ab30e0f77af))
|
||||
* collection labels with locales not working when creating new doc ([#5995](https://github.com/payloadcms/payload/issues/5995)) ([51efe4f](https://github.com/payloadcms/payload/commit/51efe4f39bcaadccb109a2a02a690ca65041ee57))
|
||||
* safely access cookie header for uploads ([#6367](https://github.com/payloadcms/payload/issues/6367)) ([de92c50](https://github.com/payloadcms/payload/commit/de92c50847640661f915455f8db0029873ddc7ab))
|
||||
* step-nav breadcrumbs ellipsis ([#6345](https://github.com/payloadcms/payload/issues/6345)) ([d02b1fb](https://github.com/payloadcms/payload/commit/d02b1fb084e636e49122ad55b25b9c49eb761f1c))
|
||||
*
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* **richtext-lexical:** remove LexicalBlock, RichTextFieldRequiredEditor and FieldWithRichTextRequiredEditor types (#6279)
|
||||
|
||||
## [2.16.1](https://github.com/payloadcms/payload/compare/v2.16.0...v2.16.1) (2024-05-07)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "0.8.2",
|
||||
"version": "0.8.3",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -45,8 +45,7 @@ export async function createVersion<T extends TypeWithID>(
|
||||
|
||||
const table = this.tables[tableName]
|
||||
|
||||
const relationshipsTable =
|
||||
this.tables[`_${defaultTableName}${this.versionsSuffix}${this.relationshipsSuffix}`]
|
||||
const relationshipsTable = this.tables[`${tableName}${this.relationshipsSuffix}`]
|
||||
|
||||
if (collection.versions.drafts) {
|
||||
await db.execute(sql`
|
||||
|
||||
@@ -10,8 +10,8 @@ import type { GenericColumn, PostgresAdapter } from '../types'
|
||||
import type { BuildQueryJoinAliases, BuildQueryJoins } from './buildQuery'
|
||||
|
||||
import { buildAndOrConditions } from './buildAndOrConditions'
|
||||
import { convertPathToJSONTraversal } from './createJSONQuery/convertPathToJSONTraversal'
|
||||
import { createJSONQuery } from './createJSONQuery'
|
||||
import { convertPathToJSONTraversal } from './createJSONQuery/convertPathToJSONTraversal'
|
||||
import { getTableColumnFromPath } from './getTableColumnFromPath'
|
||||
import { operatorMap } from './operatorMap'
|
||||
import { sanitizeQueryValue } from './sanitizeQueryValue'
|
||||
@@ -71,7 +71,7 @@ export async function parseParams({
|
||||
// So we need to loop on keys again here to handle each operator independently
|
||||
const pathOperators = where[relationOrPath]
|
||||
if (typeof pathOperators === 'object') {
|
||||
for (const operator of Object.keys(pathOperators)) {
|
||||
for (let operator of Object.keys(pathOperators)) {
|
||||
if (validOperators.includes(operator as Operator)) {
|
||||
const val = where[relationOrPath][operator]
|
||||
const {
|
||||
@@ -157,6 +157,13 @@ export async function parseParams({
|
||||
break
|
||||
}
|
||||
|
||||
if (
|
||||
operator === 'like' &&
|
||||
(field.type === 'number' || table[columnName].columnType === 'PgUUID')
|
||||
) {
|
||||
operator = 'equals'
|
||||
}
|
||||
|
||||
if (operator === 'like') {
|
||||
constraints.push(
|
||||
and(...val.split(' ').map((word) => ilike(table[columnName], `%${word}%`))),
|
||||
@@ -195,10 +202,10 @@ export async function parseParams({
|
||||
operator === 'not_in'
|
||||
) {
|
||||
constraints.push(
|
||||
sql`${notInArray(table[columnName], queryValue)} OR
|
||||
sql`(${notInArray(table[columnName], queryValue)} OR
|
||||
${table[columnName]}
|
||||
IS
|
||||
NULL`,
|
||||
NULL)`,
|
||||
)
|
||||
|
||||
break
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload",
|
||||
"version": "2.16.1",
|
||||
"version": "2.18.3",
|
||||
"description": "Node, React and MongoDB Headless CMS and Application Framework",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@@ -32,7 +32,7 @@ export const EditUpload: React.FC<{
|
||||
imageCacheTag?: string
|
||||
showCrop?: boolean
|
||||
showFocalPoint?: boolean
|
||||
}> = ({ fileName, fileSrc, imageCacheTag, showCrop, showFocalPoint }) => {
|
||||
}> = ({ doc, fileName, fileSrc, imageCacheTag, showCrop, showFocalPoint }) => {
|
||||
const { closeModal } = useModal()
|
||||
const { t } = useTranslation(['general', 'upload'])
|
||||
const { formQueryParams, setFormQueryParams } = useFormQueryParams()
|
||||
@@ -45,10 +45,11 @@ export const EditUpload: React.FC<{
|
||||
y: uploadEdits?.crop?.y || 0,
|
||||
})
|
||||
|
||||
const [pointPosition, setPointPosition] = useState<{ x: number; y: number }>({
|
||||
x: uploadEdits?.focalPoint?.x || 50,
|
||||
y: uploadEdits?.focalPoint?.y || 50,
|
||||
const [focalPosition, setFocalPosition] = useState<{ x: number; y: number }>({
|
||||
x: uploadEdits?.focalPoint?.x || doc.focalX || 50,
|
||||
y: uploadEdits?.focalPoint?.y || doc.focalY || 50,
|
||||
})
|
||||
|
||||
const [checkBounds, setCheckBounds] = useState<boolean>(false)
|
||||
const [originalHeight, setOriginalHeight] = useState<number>(0)
|
||||
const [originalWidth, setOriginalWidth] = useState<number>(0)
|
||||
@@ -72,10 +73,16 @@ export const EditUpload: React.FC<{
|
||||
})
|
||||
}
|
||||
|
||||
const fineTuneFocalPoint = ({ coordinate, value }: { coordinate: 'x' | 'y'; value: string }) => {
|
||||
const fineTuneFocalPosition = ({
|
||||
coordinate,
|
||||
value,
|
||||
}: {
|
||||
coordinate: 'x' | 'y'
|
||||
value: string
|
||||
}) => {
|
||||
const intValue = parseInt(value)
|
||||
if (intValue >= 0 && intValue <= 100) {
|
||||
setPointPosition((prevPosition) => ({ ...prevPosition, [coordinate]: intValue }))
|
||||
setFocalPosition((prevPosition) => ({ ...prevPosition, [coordinate]: intValue }))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,14 +91,14 @@ export const EditUpload: React.FC<{
|
||||
...formQueryParams,
|
||||
uploadEdits: {
|
||||
crop: crop || undefined,
|
||||
focalPoint: pointPosition ? pointPosition : undefined,
|
||||
focalPoint: focalPosition ? focalPosition : undefined,
|
||||
},
|
||||
})
|
||||
closeModal(editDrawerSlug)
|
||||
}
|
||||
|
||||
const onDragEnd = React.useCallback(({ x, y }) => {
|
||||
setPointPosition({ x, y })
|
||||
setFocalPosition({ x, y })
|
||||
setCheckBounds(false)
|
||||
}, [])
|
||||
|
||||
@@ -104,7 +111,7 @@ export const EditUpload: React.FC<{
|
||||
((boundsRect.left - containerRect.left + boundsRect.width / 2) / containerRect.width) * 100
|
||||
const yCenter =
|
||||
((boundsRect.top - containerRect.top + boundsRect.height / 2) / containerRect.height) * 100
|
||||
setPointPosition({ x: xCenter, y: yCenter })
|
||||
setFocalPosition({ x: xCenter, y: yCenter })
|
||||
}
|
||||
|
||||
const fileSrcToUse = imageCacheTag ? `${fileSrc}?${imageCacheTag}` : fileSrc
|
||||
@@ -180,7 +187,7 @@ export const EditUpload: React.FC<{
|
||||
checkBounds={showCrop ? checkBounds : false}
|
||||
className={`${baseClass}__focalPoint`}
|
||||
containerRef={focalWrapRef}
|
||||
initialPosition={pointPosition}
|
||||
initialPosition={focalPosition}
|
||||
onDragEnd={onDragEnd}
|
||||
setCheckBounds={showCrop ? setCheckBounds : false}
|
||||
>
|
||||
@@ -251,13 +258,13 @@ export const EditUpload: React.FC<{
|
||||
<div className={`${baseClass}__inputsWrap`}>
|
||||
<Input
|
||||
name="X %"
|
||||
onChange={(value) => fineTuneFocalPoint({ coordinate: 'x', value })}
|
||||
value={pointPosition.x.toFixed(0)}
|
||||
onChange={(value) => fineTuneFocalPosition({ coordinate: 'x', value })}
|
||||
value={focalPosition.x.toFixed(0)}
|
||||
/>
|
||||
<Input
|
||||
name="Y %"
|
||||
onChange={(value) => fineTuneFocalPoint({ coordinate: 'y', value })}
|
||||
value={pointPosition.y.toFixed(0)}
|
||||
onChange={(value) => fineTuneFocalPosition({ coordinate: 'y', value })}
|
||||
value={focalPosition.y.toFixed(0)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,12 +9,12 @@ import React, {
|
||||
} from 'react'
|
||||
|
||||
import type { SanitizedCollectionConfig } from '../../../../collections/config/types'
|
||||
import type { Field } from '../../../../fields/config/types'
|
||||
import type { Props as CellProps } from '../../views/collections/List/Cell/types'
|
||||
import type { ListPreferences } from '../../views/collections/List/types'
|
||||
import type { Column } from '../Table/types'
|
||||
import type { Action } from './columnReducer'
|
||||
|
||||
import { type Field, fieldHasSubFields } from '../../../../fields/config/types'
|
||||
import { usePreferences } from '../../utilities/Preferences'
|
||||
import formatFields from '../../views/collections/List/formatFields'
|
||||
import buildColumns from './buildColumns'
|
||||
@@ -35,6 +35,12 @@ export const useTableColumns = (): ITableColumns => useContext(TableColumnContex
|
||||
|
||||
const filterTableFields = (fields: Field[]): Field[] => {
|
||||
return fields.reduce((acc, field) => {
|
||||
if (fieldHasSubFields(field)) {
|
||||
field = {
|
||||
...field,
|
||||
fields: filterTableFields(field.fields),
|
||||
}
|
||||
}
|
||||
if (!field.admin?.disableListColumn) acc.push(field)
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
@@ -116,10 +116,12 @@ export const Upload: React.FC<Props> = (props) => {
|
||||
|
||||
const hasImageSizes = collection?.upload?.imageSizes?.length > 0
|
||||
const hasResizeOptions = Boolean(collection?.upload?.resizeOptions)
|
||||
// Explicitly check if set to true, default is undefined
|
||||
const focalPointEnabled = collection?.upload?.focalPoint === true
|
||||
|
||||
const { collection: { upload: { crop: showCrop = true, focalPoint = true } } = {} } = props
|
||||
|
||||
const showFocalPoint = focalPoint && (hasImageSizes || hasResizeOptions)
|
||||
const showFocalPoint = focalPoint && (hasImageSizes || hasResizeOptions || focalPointEnabled)
|
||||
|
||||
const lastSubmittedTime = submitted ? new Date().toISOString() : null
|
||||
|
||||
|
||||
@@ -130,6 +130,7 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
collection,
|
||||
config,
|
||||
data,
|
||||
operation: 'create',
|
||||
overwriteExistingFiles,
|
||||
req,
|
||||
throwOnMissingFile:
|
||||
|
||||
@@ -157,6 +157,7 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
collection,
|
||||
config,
|
||||
data: bulkUpdateData,
|
||||
operation: 'update',
|
||||
overwriteExistingFiles,
|
||||
req,
|
||||
throwOnMissingFile: false,
|
||||
|
||||
@@ -148,6 +148,8 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
collection,
|
||||
config,
|
||||
data,
|
||||
operation: 'update',
|
||||
originalDoc,
|
||||
overwriteExistingFiles,
|
||||
req,
|
||||
throwOnMissingFile: false,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "./translation-schema.json",
|
||||
"authentication": {
|
||||
"account": "Compte",
|
||||
"accountOfCurrentUser": "Compte de l'utilisateur actuel",
|
||||
"accountOfCurrentUser": "Compte de l’utilisateur actuel",
|
||||
"alreadyActivated": "Déjà activé",
|
||||
"alreadyLoggedIn": "Déjà connecté",
|
||||
"apiKey": "Clé API",
|
||||
@@ -14,7 +14,7 @@
|
||||
"confirmGeneration": "Confirmer la génération",
|
||||
"confirmPassword": "Confirmez le mot de passe",
|
||||
"createFirstUser": "Créer le premier utilisateur",
|
||||
"emailNotValid": "L'adresse e-mail fourni n'est pas valide",
|
||||
"emailNotValid": "L’adresse e-mail fournie n’est pas valide",
|
||||
"emailSent": "E-mail envoyé",
|
||||
"enableAPIKey": "Activer la clé API",
|
||||
"failedToUnlock": "Déverrouillage échoué",
|
||||
@@ -24,76 +24,76 @@
|
||||
"forgotPasswordQuestion": "Mot de passe oublié ?",
|
||||
"generate": "Générer",
|
||||
"generateNewAPIKey": "Générer une nouvelle clé API",
|
||||
"generatingNewAPIKeyWillInvalidate": "La génération d'une nouvelle clé API <1>invalidera</1> la clé précédente. Êtes-vous sûr de vouloir continuer ?",
|
||||
"lockUntil": "Verrouiller jusqu'à",
|
||||
"generatingNewAPIKeyWillInvalidate": "La génération d’une nouvelle clé API <1>invalidera</1> la clé précédente. Êtes-vous sûr de vouloir continuer ?",
|
||||
"lockUntil": "Verrouiller jusqu’à",
|
||||
"logBackIn": "Se reconnecter",
|
||||
"logOut": "Se déconnecter",
|
||||
"loggedIn": "Pour vous connecter en tant qu'un autre utilisateur, vous devez d'abord vous <0>déconnecter</0>.",
|
||||
"loggedIn": "Pour vous connecter en tant qu’un autre utilisateur, vous devez d’abord vous <0>déconnecter</0>.",
|
||||
"loggedInChangePassword": "Pour changer votre mot de passe, rendez-vous sur votre <0>compte</0> puis modifiez-y votre mot de passe.",
|
||||
"loggedOutInactivity": "Vous avez été déconnecté pour cause d'inactivité.",
|
||||
"loggedOutInactivity": "Vous avez été déconnecté pour cause d’inactivité.",
|
||||
"loggedOutSuccessfully": "Vous avez été déconnecté avec succès.",
|
||||
"login": "Se connecter",
|
||||
"loginAttempts": "Tentatives de connexion",
|
||||
"loginUser": "Connecter l'utilisateur",
|
||||
"loginWithAnotherUser": "Pour vous connecter en tant qu'un autre utilisateur, vous devez d'abord vous <0>déconnecter</0>.",
|
||||
"loginUser": "Connecter l’utilisateur",
|
||||
"loginWithAnotherUser": "Pour vous connecter en tant qu’un autre utilisateur, vous devez d’abord vous <0>déconnecter</0>.",
|
||||
"logout": "Se déconnecter",
|
||||
"logoutUser": "Déconnecter l'utilisateur",
|
||||
"logoutUser": "Déconnecter l’utilisateur",
|
||||
"newAPIKeyGenerated": "Nouvelle clé API générée.",
|
||||
"newAccountCreated": "Un nouveau compte vient d'être créé pour vous permettre d'accéder <a href=\"{{serverURL}}\">{{serverURL}}</a>. Veuillez cliquer sur le lien suivant ou collez l'URL ci-dessous dans votre navigateur pour vérifier votre adresse e-mail: <a href=\"{{verificationURL}}\">{{verificationURL}}</a><br>. Après avoir vérifié votre adresse e-mail, vous pourrez vous connecter avec succès.",
|
||||
"newAccountCreated": "Un nouveau compte vient d’être créé pour vous permettre d’accéder <a href=\"{{serverURL}}\">{{serverURL}}</a>. Veuillez cliquer sur le lien suivant ou collez l’URL ci-dessous dans votre navigateur pour vérifier votre adresse e-mail: <a href=\"{{verificationURL}}\">{{verificationURL}}</a><br>. Après avoir vérifié votre adresse e-mail, vous pourrez vous connecter avec succès.",
|
||||
"newPassword": "Nouveau mot de passe",
|
||||
"resetPassword": "Réinitialiser le mot de passe",
|
||||
"resetPasswordExpiration": "Réinitialiser l'expiration du mot de passe",
|
||||
"resetPasswordExpiration": "Réinitialiser l’expiration du mot de passe",
|
||||
"resetPasswordToken": "Réinitialiser le jeton de mot de passe",
|
||||
"resetYourPassword": "Réinitialisez votre mot de passe",
|
||||
"stayLoggedIn": "Rester connecté",
|
||||
"successfullyUnlocked": "Déverrouillé avec succès",
|
||||
"unableToVerify": "Vérification échoué",
|
||||
"unableToVerify": "Vérification échouée",
|
||||
"verified": "Vérifié",
|
||||
"verifiedSuccessfully": "Vérifié avec succès",
|
||||
"verify": "Vérifier",
|
||||
"verifyUser": "Vérifier l'utilisateur",
|
||||
"verifyUser": "Vérifier l’utilisateur",
|
||||
"verifyYourEmail": "Vérifiez votre e-mail",
|
||||
"youAreInactive": "Vous n'avez pas été actif depuis un moment alors vous serez bientôt automatiquement déconnecté pour votre propre sécurité. Souhaitez-vous rester connecté ?",
|
||||
"youAreReceivingResetPassword": "Vous recevez ceci parce que vous (ou quelqu'un d'autre) avez demandé la réinitialisation du mot de passe de votre compte. Veuillez cliquer sur le lien suivant ou le coller dans votre navigateur pour terminer le processus :",
|
||||
"youDidNotRequestPassword": "Si vous ne l'avez pas demandé, veuillez ignorer cet e-mail et votre mot de passe restera inchangé."
|
||||
"youAreInactive": "Vous n’avez pas été actif depuis un moment alors vous serez bientôt automatiquement déconnecté pour votre propre sécurité. Souhaitez-vous rester connecté ?",
|
||||
"youAreReceivingResetPassword": "Vous recevez ceci parce que vous (ou quelqu’un d’autre) avez demandé la réinitialisation du mot de passe de votre compte. Veuillez cliquer sur le lien suivant ou le coller dans votre navigateur pour terminer le processus :",
|
||||
"youDidNotRequestPassword": "Si vous ne l’avez pas demandé, veuillez ignorer cet e-mail et votre mot de passe restera inchangé."
|
||||
},
|
||||
"error": {
|
||||
"accountAlreadyActivated": "Ce compte a déjà été activé.",
|
||||
"autosaving": "Un problème est survenu lors de l'enregistrement automatique de ce document.",
|
||||
"autosaving": "Un problème est survenu lors de l’enregistrement automatique de ce document.",
|
||||
"correctInvalidFields": "Veuillez corriger les champs invalides.",
|
||||
"deletingFile": "Une erreur s'est produite lors de la suppression du fichier.",
|
||||
"deletingTitle": "Une erreur s'est produite lors de la suppression de {{title}}. Veuillez vérifier votre connexion puis réessayer.",
|
||||
"emailOrPasswordIncorrect": "L'adresse e-mail ou le mot de passe fourni est incorrect.",
|
||||
"followingFieldsInvalid_one": "Le champ suivant n'est pas valide :",
|
||||
"deletingFile": "Une erreur s’est produite lors de la suppression du fichier.",
|
||||
"deletingTitle": "Une erreur s’est produite lors de la suppression de {{title}}. Veuillez vérifier votre connexion puis réessayer.",
|
||||
"emailOrPasswordIncorrect": "L’adresse e-mail ou le mot de passe fourni est incorrect.",
|
||||
"followingFieldsInvalid_one": "Le champ suivant n’est pas valide :",
|
||||
"followingFieldsInvalid_other": "Les champs suivants ne sont pas valides :",
|
||||
"incorrectCollection": "Collection incorrecte",
|
||||
"invalidFileType": "Type de fichier invalide",
|
||||
"invalidFileTypeValue": "Type de fichier invalide : {{value}}",
|
||||
"loadingDocument": "Un problème est survenu lors du chargement du document qui a pour identifiant {{id}}.",
|
||||
"localesNotSaved_one": "Le paramètre régional suivant n'a pas pu être enregistré :",
|
||||
"localesNotSaved_other": "Les paramètres régionaux suivants n'ont pas pu être enregistrés :",
|
||||
"localesNotSaved_one": "Le paramètre régional suivant n’a pas pu être enregistré :",
|
||||
"localesNotSaved_other": "Les paramètres régionaux suivants n’ont pas pu être enregistrés :",
|
||||
"missingEmail": "E-mail manquant.",
|
||||
"missingIDOfDocument": "Il manque l'identifiant du document à mettre à jour.",
|
||||
"missingIDOfVersion": "Il manque l'identifiant de la version.",
|
||||
"missingIDOfDocument": "Il manque l’identifiant du document à mettre à jour.",
|
||||
"missingIDOfVersion": "Il manque l’identifiant de la version.",
|
||||
"missingRequiredData": "Données requises manquantes.",
|
||||
"noFilesUploaded": "Aucun fichier n'a été téléversé.",
|
||||
"noMatchedField": "Aucun champ correspondant n'a été trouvé pour \"{{label}}\"",
|
||||
"noFilesUploaded": "Aucun fichier n’a été téléversé.",
|
||||
"noMatchedField": "Aucun champ correspondant n’a été trouvé pour \"{{label}}\"",
|
||||
"noUser": "Aucun utilisateur",
|
||||
"notAllowedToAccessPage": "Vous n'êtes pas autorisé à accéder à cette page.",
|
||||
"notAllowedToPerformAction": "Vous n'êtes pas autorisé à effectuer cette action.",
|
||||
"notFound": "La ressource demandée n'a pas été trouvée.",
|
||||
"previewing": "Un problème est survenu lors de l'aperçu de ce document.",
|
||||
"notAllowedToAccessPage": "Vous n’êtes pas autorisé à accéder à cette page.",
|
||||
"notAllowedToPerformAction": "Vous n’êtes pas autorisé à effectuer cette action.",
|
||||
"notFound": "La ressource demandée n’a pas été trouvée.",
|
||||
"previewing": "Un problème est survenu lors de l’aperçu de ce document.",
|
||||
"problemUploadingFile": "Il y a eu un problème lors du téléversement du fichier.",
|
||||
"tokenInvalidOrExpired": "Le jeton n'est soit pas valide ou a expiré.",
|
||||
"unPublishingDocument": "Un problème est survenu lors de l'annulation de la publication de ce document.",
|
||||
"tokenInvalidOrExpired": "Le jeton n’est soit pas valide ou a expiré.",
|
||||
"unPublishingDocument": "Un problème est survenu lors de l’annulation de la publication de ce document.",
|
||||
"unableToDeleteCount": "Impossible de supprimer {{count}} sur {{total}} {{label}}.",
|
||||
"unableToUpdateCount": "Impossible de mettre à jour {{count}} sur {{total}} {{label}}.",
|
||||
"unauthorized": "Non autorisé, vous devez être connecté pour effectuer cette demande.",
|
||||
"unknown": "Une erreur inconnue s'est produite.",
|
||||
"unknown": "Une erreur inconnue s’est produite.",
|
||||
"unspecific": "Une erreur est survenue.",
|
||||
"userLocked": "Cet utilisateur est verrouillé en raison d'un trop grand nombre de tentatives de connexion infructueuses.",
|
||||
"userLocked": "Cet utilisateur est verrouillé en raison d’un trop grand nombre de tentatives de connexion infructueuses.",
|
||||
"valueMustBeUnique": "La valeur doit être unique",
|
||||
"verificationTokenInvalid": "Le jeton de vérification n'est pas valide."
|
||||
"verificationTokenInvalid": "Le jeton de vérification n’est pas valide."
|
||||
},
|
||||
"fields": {
|
||||
"addLabel": "Ajouter {{label}}",
|
||||
@@ -128,7 +128,7 @@
|
||||
"relatedDocument": "Document connexe",
|
||||
"relationTo": "Lié à",
|
||||
"removeRelationship": "Supprimer la relation",
|
||||
"removeUpload": "Supprimer le Téléversement",
|
||||
"removeUpload": "Supprimer le téléversement",
|
||||
"saveChanges": "Sauvegarder les modifications",
|
||||
"searchForBlock": "Rechercher un bloc",
|
||||
"selectExistingLabel": "Sélectionnez {{label}} existant",
|
||||
@@ -147,14 +147,14 @@
|
||||
"aboutToDeleteCount_other": "Vous êtes sur le point de supprimer {{count}} {{label}}",
|
||||
"addBelow": "Ajoutez ci-dessous",
|
||||
"addFilter": "Ajouter un filtre",
|
||||
"adminTheme": "Thème d'administration",
|
||||
"adminTheme": "Thème d’administration",
|
||||
"and": "Et",
|
||||
"applyChanges": "Appliquer les modifications",
|
||||
"ascending": "Ascendant",
|
||||
"automatic": "Automatique",
|
||||
"backToDashboard": "Retour au tableau de bord",
|
||||
"cancel": "Annuler",
|
||||
"changesNotSaved": "Vos modifications n'ont pas été enregistrées. Vous perdrez vos modifications si vous quittez maintenant.",
|
||||
"changesNotSaved": "Vos modifications n’ont pas été enregistrées. Vous perdrez vos modifications si vous quittez maintenant.",
|
||||
"close": "Fermer",
|
||||
"collapse": "Réduire",
|
||||
"collections": "Collections",
|
||||
@@ -171,8 +171,8 @@
|
||||
"created": "Créé(e)",
|
||||
"createdAt": "Créé(e) à",
|
||||
"creating": "création en cours",
|
||||
"creatingNewLabel": "Création d'un(e) nouveau ou nouvelle {{label}}",
|
||||
"dark": "Nuit",
|
||||
"creatingNewLabel": "Création d’un(e) nouveau ou nouvelle {{label}}",
|
||||
"dark": "Sombre",
|
||||
"dashboard": "Tableau de bord",
|
||||
"delete": "Supprimer",
|
||||
"deletedCountSuccessfully": "{{count}} {{label}} supprimé avec succès.",
|
||||
@@ -206,7 +206,7 @@
|
||||
"lastModified": "Dernière modification",
|
||||
"leaveAnyway": "Quitter quand même",
|
||||
"leaveWithoutSaving": "Quitter sans sauvegarder",
|
||||
"light": "Lumière ou Jour",
|
||||
"light": "Clair",
|
||||
"livePreview": "Aperçu",
|
||||
"loading": "Chargement en cours",
|
||||
"locale": "Paramètres régionaux",
|
||||
@@ -218,11 +218,11 @@
|
||||
"noFiltersSet": "Aucun filtre défini",
|
||||
"noLabel": "<Pas de {{label}}>",
|
||||
"noOptions": "Aucune option",
|
||||
"noResults": "Aucun(e) {{label}} trouvé(e). Soit aucun(e) {{label}} n'existe encore, soit aucun(e) ne correspond aux filtres que vous avez spécifiés ci-dessus",
|
||||
"noResults": "Aucun(e) {{label}} trouvé(e). Soit aucun(e) {{label}} n’existe encore, soit aucun(e) ne correspond aux filtres que vous avez spécifiés ci-dessus",
|
||||
"noValue": "Aucune valeur",
|
||||
"none": "Aucun(e)",
|
||||
"notFound": "Pas trouvé",
|
||||
"nothingFound": "Rien n'a été trouvé",
|
||||
"nothingFound": "Rien n’a été trouvé",
|
||||
"of": "de",
|
||||
"open": "Ouvrir",
|
||||
"or": "ou",
|
||||
@@ -259,13 +259,13 @@
|
||||
"untitled": "Sans titre",
|
||||
"updatedAt": "Modifié le",
|
||||
"updatedCountSuccessfully": "{{count}} {{label}} mis à jour avec succès.",
|
||||
"updatedSuccessfully": "Mis à jour avec succés.",
|
||||
"updatedSuccessfully": "Mis à jour avec succès.",
|
||||
"updating": "Mise à jour",
|
||||
"uploading": "Téléchargement",
|
||||
"user": "Utilisateur",
|
||||
"users": "Utilisateurs",
|
||||
"value": "Valeur",
|
||||
"welcome": "Bienvenu(e)"
|
||||
"welcome": "Bienvenue"
|
||||
},
|
||||
"operators": {
|
||||
"contains": "contient",
|
||||
@@ -277,24 +277,24 @@
|
||||
"isLessThan": "est inférieur à",
|
||||
"isLessThanOrEqualTo": "est inférieur ou égal à",
|
||||
"isLike": "est comme",
|
||||
"isNotEqualTo": "n'est pas égal à",
|
||||
"isNotIn": "n'est pas dans",
|
||||
"isNotEqualTo": "n’est pas égal à",
|
||||
"isNotIn": "n’est pas dans",
|
||||
"near": "proche"
|
||||
},
|
||||
"upload": {
|
||||
"crop": "Récolte",
|
||||
"crop": "Recadrer",
|
||||
"cropToolDescription": "Faites glisser les coins de la zone sélectionnée, dessinez une nouvelle zone ou ajustez les valeurs ci-dessous.",
|
||||
"dragAndDrop": "Glisser-déposer un fichier",
|
||||
"dragAndDropHere": "ou glissez-déposez un fichier ici",
|
||||
"editImage": "Modifier l'image",
|
||||
"editImage": "Modifier l’image",
|
||||
"fileName": "Nom du fichier",
|
||||
"fileSize": "Taille du fichier",
|
||||
"focalPoint": "Point focal",
|
||||
"focalPointDescription": "Faites glisser le point focal directement sur l'aperçu ou ajustez les valeurs ci-dessous.",
|
||||
"focalPointDescription": "Faites glisser le point focal directement sur l’aperçu ou ajustez les valeurs ci-dessous.",
|
||||
"height": "Hauteur",
|
||||
"lessInfo": "Moins d'infos",
|
||||
"moreInfo": "Plus d'infos",
|
||||
"previewSizes": "Tailles d'aperçu",
|
||||
"lessInfo": "Moins d’infos",
|
||||
"moreInfo": "Plus d’infos",
|
||||
"previewSizes": "Tailles d’aperçu",
|
||||
"selectCollectionToBrowse": "Sélectionnez une collection à parcourir",
|
||||
"selectFile": "Sélectionnez un fichier",
|
||||
"setCropArea": "Définir la zone de recadrage",
|
||||
@@ -304,32 +304,32 @@
|
||||
"width": "Largeur"
|
||||
},
|
||||
"validation": {
|
||||
"emailAddress": "S'il vous plaît, veuillez entrer une adresse e-mail valide.",
|
||||
"enterNumber": "S'il vous plait, veuillez entrer un nombre valide.",
|
||||
"fieldHasNo": "Ce champ n'a pas de {{label}}",
|
||||
"emailAddress": "S’il vous plaît, veuillez entrer une adresse e-mail valide.",
|
||||
"enterNumber": "S’il vous plait, veuillez entrer un nombre valide.",
|
||||
"fieldHasNo": "Ce champ n’a pas de {{label}}",
|
||||
"greaterThanMax": "{{value}} est supérieur au max autorisé {{label}} de {{max}}.",
|
||||
"invalidInput": "Ce champ a une entrée invalide.",
|
||||
"invalidSelection": "Ce champ a une sélection invalide.",
|
||||
"invalidSelections": "Ce champ contient des sélections invalides suivantes :",
|
||||
"invalidSelections": "Ce champ contient les sélections invalides suivantes :",
|
||||
"lessThanMin": "{{value}} est inférieur au min autorisé {{label}} de {{min}}.",
|
||||
"limitReached": "Limite atteinte, seulement {{max}} éléments peuvent être ajoutés.",
|
||||
"longerThanMin": "Cette valeur doit être supérieure à la longueur minimale de {{minLength}} caractères.",
|
||||
"notValidDate": "\"{{value}}\" n'est pas une date valide.",
|
||||
"notValidDate": "\"{{value}}\" n’est pas une date valide.",
|
||||
"required": "Ce champ est requis.",
|
||||
"requiresAtLeast": "Ce champ doit avoir au moins {{count}} {{label}}.",
|
||||
"requiresNoMoreThan": "Ce champ ne doit pas avoir plus de {{count}} {{label}}.",
|
||||
"requiresTwoNumbers": "Ce champ doit avoir deux chiffres.",
|
||||
"shorterThanMax": "Cette valeur doit être inférieure à la longueur maximale de {{maxLength}} caractères.",
|
||||
"trueOrFalse": "Ce champ ne peut être égal qu'à vrai ou faux.",
|
||||
"validUploadID": "Ce champ n'est pas un valide identifiant de fichier."
|
||||
"trueOrFalse": "Ce champ ne peut être égal qu’à vrai ou faux.",
|
||||
"validUploadID": "Ce champ n’est pas un valide identifiant de fichier."
|
||||
},
|
||||
"version": {
|
||||
"aboutToPublishSelection": "Vous êtes sur le point de publier tous les {{label}} de la sélection. Es-tu sûr?",
|
||||
"aboutToRestore": "Vous êtes sur le point de restaurer le document {{label}} à l'état où il se trouvait le {{versionDate}}.",
|
||||
"aboutToRestoreGlobal": "Vous êtes sur le point de restaurer le ou la {{label}} global(e) à l'état où il ou elle se trouvait le {{versionDate}}.",
|
||||
"aboutToPublishSelection": "Vous êtes sur le point de publier tous les {{label}} de la sélection. Êtes-vous sûr ?",
|
||||
"aboutToRestore": "Vous êtes sur le point de restaurer le document {{label}} à l’état où il se trouvait le {{versionDate}}.",
|
||||
"aboutToRestoreGlobal": "Vous êtes sur le point de restaurer le ou la {{label}} global(e) à l’état où il ou elle se trouvait le {{versionDate}}.",
|
||||
"aboutToRevertToPublished": "Vous êtes sur le point de rétablir les modifications apportées à ce document à la version publiée. Êtes-vous sûr ?",
|
||||
"aboutToUnpublish": "Vous êtes sur le point d'annuler la publication de ce document. Êtes-vous sûr ?",
|
||||
"aboutToUnpublishSelection": "Vous êtes sur le point de dépublier tous les {{label}} de la sélection. Es-tu sûr?",
|
||||
"aboutToUnpublish": "Vous êtes sur le point d’annuler la publication de ce document. Êtes-vous sûr ?",
|
||||
"aboutToUnpublishSelection": "Vous êtes sur le point de dépublier tous les {{label}} de la sélection. Êtes-vous sûr ?",
|
||||
"autosave": "Enregistrement automatique",
|
||||
"autosavedSuccessfully": "Enregistrement automatique réussi.",
|
||||
"autosavedVersion": "Version enregistrée automatiquement",
|
||||
@@ -337,7 +337,7 @@
|
||||
"compareVersion": "Comparez cette version à :",
|
||||
"confirmPublish": "Confirmer la publication",
|
||||
"confirmRevertToSaved": "Confirmer la restauration",
|
||||
"confirmUnpublish": "Confirmer l'annulation",
|
||||
"confirmUnpublish": "Confirmer l’annulation",
|
||||
"confirmVersionRestoration": "Confirmer la restauration de la version",
|
||||
"currentDocumentStatus": "Document {{docStatus}} actuel",
|
||||
"draft": "Brouillon",
|
||||
@@ -377,4 +377,4 @@
|
||||
"viewingVersions": "Affichage des versions de ou du {{entityLabel}} {{documentTitle}}",
|
||||
"viewingVersionsGlobal": "Affichage des versions globales de ou du {{entityLabel}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import sharp from 'sharp'
|
||||
|
||||
export const percentToPixel = (value, dimension) => {
|
||||
export const percentToPixel = (value: string, dimension: number): number => {
|
||||
if (!value) return 0
|
||||
return Math.floor((parseFloat(value) / 100) * dimension)
|
||||
}
|
||||
|
||||
@@ -8,7 +9,7 @@ export default async function cropImage({ cropData, dimensions, file }) {
|
||||
try {
|
||||
const { height, width, x, y } = cropData
|
||||
|
||||
const formattedCropData = {
|
||||
const formattedCropData: sharp.Region = {
|
||||
height: percentToPixel(height, dimensions.height),
|
||||
left: percentToPixel(x, dimensions.width),
|
||||
top: percentToPixel(y, dimensions.height),
|
||||
|
||||
@@ -11,7 +11,7 @@ import sharp from 'sharp'
|
||||
import type { Collection } from '../collections/config/types'
|
||||
import type { SanitizedConfig } from '../config/types'
|
||||
import type { PayloadRequest } from '../express/types'
|
||||
import type { FileData, FileToSave, ProbedImageSize } from './types'
|
||||
import type { FileData, FileToSave, ProbedImageSize, UploadEdits } from './types'
|
||||
|
||||
import { FileUploadError, MissingFile } from '../errors'
|
||||
import FileRetrievalError from '../errors/FileRetrievalError'
|
||||
@@ -28,6 +28,8 @@ type Args<T> = {
|
||||
collection: Collection
|
||||
config: SanitizedConfig
|
||||
data: T
|
||||
operation: 'create' | 'update'
|
||||
originalDoc?: T
|
||||
overwriteExistingFiles?: boolean
|
||||
req: PayloadRequest
|
||||
throwOnMissingFile?: boolean
|
||||
@@ -42,6 +44,8 @@ export const generateFileData = async <T>({
|
||||
collection: { config: collectionConfig },
|
||||
config,
|
||||
data,
|
||||
operation,
|
||||
originalDoc,
|
||||
overwriteExistingFiles,
|
||||
req,
|
||||
throwOnMissingFile,
|
||||
@@ -54,10 +58,23 @@ export const generateFileData = async <T>({
|
||||
}
|
||||
|
||||
let file = req.files?.file || undefined
|
||||
const { uploadEdits } = req.query || {}
|
||||
|
||||
const { disableLocalStorage, formatOptions, imageSizes, resizeOptions, staticDir, trimOptions } =
|
||||
collectionConfig.upload
|
||||
const uploadEdits = parseUploadEditsFromReqOrIncomingData({
|
||||
data,
|
||||
operation,
|
||||
originalDoc,
|
||||
req,
|
||||
})
|
||||
|
||||
const {
|
||||
disableLocalStorage,
|
||||
focalPoint: focalPointEnabled,
|
||||
formatOptions,
|
||||
imageSizes,
|
||||
resizeOptions,
|
||||
staticDir,
|
||||
trimOptions,
|
||||
} = collectionConfig.upload
|
||||
|
||||
let staticPath = staticDir
|
||||
if (staticDir.indexOf('/') !== 0) {
|
||||
@@ -234,9 +251,9 @@ export const generateFileData = async <T>({
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(imageSizes) && fileSupportsResize) {
|
||||
if (fileSupportsResize && (Array.isArray(imageSizes) || focalPointEnabled !== false)) {
|
||||
req.payloadUploadSizes = {}
|
||||
const { sizeData, sizesToSave } = await resizeAndTransformImageSizes({
|
||||
const { focalPoint, sizeData, sizesToSave } = await resizeAndTransformImageSizes({
|
||||
config: collectionConfig,
|
||||
dimensions: !cropData
|
||||
? dimensions
|
||||
@@ -250,13 +267,16 @@ export const generateFileData = async <T>({
|
||||
req,
|
||||
savedFilename: fsSafeName || file.name,
|
||||
staticPath,
|
||||
uploadEdits,
|
||||
})
|
||||
|
||||
fileData.sizes = sizeData
|
||||
fileData.focalX = focalPoint?.x
|
||||
fileData.focalY = focalPoint?.y
|
||||
filesToSave.push(...sizesToSave)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
req.payload.logger.error({ err, msg: 'Error uploading file' })
|
||||
throw new FileUploadError(req.t)
|
||||
}
|
||||
|
||||
@@ -270,3 +290,50 @@ export const generateFileData = async <T>({
|
||||
files: filesToSave,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse upload edits from req or incoming data
|
||||
*/
|
||||
function parseUploadEditsFromReqOrIncomingData(args: {
|
||||
data: unknown
|
||||
operation: 'create' | 'update'
|
||||
originalDoc: unknown
|
||||
req: PayloadRequest
|
||||
}): UploadEdits {
|
||||
const { data, operation, originalDoc, req } = args
|
||||
|
||||
// Get intended focal point change from query string or incoming data
|
||||
const {
|
||||
uploadEdits = {},
|
||||
}: {
|
||||
uploadEdits?: UploadEdits
|
||||
} = req.query || {}
|
||||
|
||||
if (uploadEdits.focalPoint) return uploadEdits
|
||||
|
||||
const incomingData = data as FileData
|
||||
const origDoc = originalDoc as FileData
|
||||
|
||||
// If no change in focal point, return undefined.
|
||||
// This prevents a refocal operation triggered from admin, because it always sends the focal point.
|
||||
if (origDoc && incomingData.focalX === origDoc.focalX && incomingData.focalY === origDoc.focalY) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (incomingData.focalX && incomingData.focalY) {
|
||||
uploadEdits.focalPoint = {
|
||||
x: incomingData.focalX,
|
||||
y: incomingData.focalY,
|
||||
}
|
||||
return uploadEdits
|
||||
}
|
||||
|
||||
// If no focal point is set, default to center
|
||||
if (operation === 'create') {
|
||||
uploadEdits.focalPoint = {
|
||||
x: 50,
|
||||
y: 50,
|
||||
}
|
||||
}
|
||||
return uploadEdits
|
||||
}
|
||||
|
||||
@@ -25,56 +25,57 @@ const getBaseUploadFields = ({ collection, config }: Options): Field[] => {
|
||||
|
||||
const mimeType: Field = {
|
||||
name: 'mimeType',
|
||||
type: 'text',
|
||||
admin: {
|
||||
hidden: true,
|
||||
readOnly: true,
|
||||
},
|
||||
label: 'MIME Type',
|
||||
type: 'text',
|
||||
}
|
||||
|
||||
const url: Field = {
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
admin: {
|
||||
hidden: true,
|
||||
readOnly: true,
|
||||
},
|
||||
label: 'URL',
|
||||
type: 'text',
|
||||
}
|
||||
|
||||
const width: Field = {
|
||||
name: 'width',
|
||||
type: 'number',
|
||||
admin: {
|
||||
hidden: true,
|
||||
readOnly: true,
|
||||
},
|
||||
label: labels['upload:width'],
|
||||
type: 'number',
|
||||
}
|
||||
|
||||
const height: Field = {
|
||||
name: 'height',
|
||||
type: 'number',
|
||||
admin: {
|
||||
hidden: true,
|
||||
readOnly: true,
|
||||
},
|
||||
label: labels['upload:height'],
|
||||
type: 'number',
|
||||
}
|
||||
|
||||
const filesize: Field = {
|
||||
name: 'filesize',
|
||||
type: 'number',
|
||||
admin: {
|
||||
hidden: true,
|
||||
readOnly: true,
|
||||
},
|
||||
label: labels['upload:fileSize'],
|
||||
type: 'number',
|
||||
}
|
||||
|
||||
const filename: Field = {
|
||||
name: 'filename',
|
||||
type: 'text',
|
||||
admin: {
|
||||
disableBulkEdit: true,
|
||||
hidden: true,
|
||||
@@ -82,7 +83,6 @@ const getBaseUploadFields = ({ collection, config }: Options): Field[] => {
|
||||
},
|
||||
index: true,
|
||||
label: labels['upload:fileName'],
|
||||
type: 'text',
|
||||
unique: true,
|
||||
}
|
||||
|
||||
@@ -115,15 +115,36 @@ const getBaseUploadFields = ({ collection, config }: Options): Field[] => {
|
||||
mimeType.validate = mimeTypeValidator(uploadOptions.mimeTypes)
|
||||
}
|
||||
|
||||
// Add focal point fields if not disabled
|
||||
if (
|
||||
uploadOptions.focalPoint !== false ||
|
||||
uploadOptions.imageSizes ||
|
||||
uploadOptions.resizeOptions
|
||||
) {
|
||||
uploadFields = uploadFields.concat(
|
||||
['focalX', 'focalY'].map((name) => {
|
||||
return {
|
||||
name,
|
||||
type: 'number',
|
||||
admin: {
|
||||
hidden: true,
|
||||
},
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
if (uploadOptions.imageSizes) {
|
||||
uploadFields = uploadFields.concat([
|
||||
{
|
||||
name: 'sizes',
|
||||
type: 'group',
|
||||
admin: {
|
||||
hidden: true,
|
||||
},
|
||||
fields: uploadOptions.imageSizes.map((size) => ({
|
||||
name: size.name,
|
||||
type: 'group',
|
||||
admin: {
|
||||
hidden: true,
|
||||
},
|
||||
@@ -157,13 +178,12 @@ const getBaseUploadFields = ({ collection, config }: Options): Field[] => {
|
||||
},
|
||||
],
|
||||
label: size.name,
|
||||
type: 'group',
|
||||
})),
|
||||
label: labels['upload:Sizes'],
|
||||
type: 'group',
|
||||
label: labels['upload:sizes'],
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
return uploadFields
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export const getExternalFile = async ({ data, req, uploadConfig }: Args): Promis
|
||||
const headers = uploadConfig.externalFileHeaderFilter
|
||||
? uploadConfig.externalFileHeaderFilter(headersToObject(req.headers))
|
||||
: {
|
||||
cookie: req.headers['cookie'],
|
||||
cookie: req.headers?.['cookie'],
|
||||
}
|
||||
|
||||
const res = await fetch(fileURL, {
|
||||
|
||||
@@ -6,10 +6,16 @@ import fs from 'fs'
|
||||
import sanitize from 'sanitize-filename'
|
||||
import sharp from 'sharp'
|
||||
|
||||
import type { UploadEdits } from '../admin/components/views/collections/Edit/types'
|
||||
import type { SanitizedCollectionConfig } from '../collections/config/types'
|
||||
import type { PayloadRequest } from '../express/types'
|
||||
import type { FileSize, FileSizes, FileToSave, ImageSize, ProbedImageSize } from './types'
|
||||
import type {
|
||||
FileSize,
|
||||
FileSizes,
|
||||
FileToSave,
|
||||
ImageSize,
|
||||
ProbedImageSize,
|
||||
UploadEdits,
|
||||
} from './types'
|
||||
|
||||
import { isNumber } from '../utilities/isNumber'
|
||||
import fileExists from './fileExists'
|
||||
@@ -19,17 +25,15 @@ type ResizeArgs = {
|
||||
dimensions: ProbedImageSize
|
||||
file: UploadedFile
|
||||
mimeType: string
|
||||
req: PayloadRequest & {
|
||||
query?: {
|
||||
uploadEdits?: UploadEdits
|
||||
}
|
||||
}
|
||||
req: PayloadRequest
|
||||
savedFilename: string
|
||||
staticPath: string
|
||||
uploadEdits?: UploadEdits
|
||||
}
|
||||
|
||||
/** Result from resizing and transforming the requested image sizes */
|
||||
type ImageSizesResult = {
|
||||
focalPoint?: UploadEdits['focalPoint']
|
||||
sizeData: FileSizes
|
||||
sizesToSave: FileToSave[]
|
||||
}
|
||||
@@ -70,6 +74,16 @@ const createImageName = (
|
||||
extension: string,
|
||||
) => `${outputImageName}-${width}x${height}.${extension}`
|
||||
|
||||
type CreateResultArgs = {
|
||||
filename?: FileSize['filename']
|
||||
filesize?: FileSize['filesize']
|
||||
height?: FileSize['height']
|
||||
mimeType?: FileSize['mimeType']
|
||||
name: string
|
||||
sizesToSave?: FileToSave[]
|
||||
width?: FileSize['width']
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the result object for the image resize operation based on the
|
||||
* provided parameters. If the name is not provided, an empty result object
|
||||
@@ -84,26 +98,28 @@ const createImageName = (
|
||||
* @param sizesToSave - the sizes to save
|
||||
* @returns the result object
|
||||
*/
|
||||
const createResult = (
|
||||
name: string,
|
||||
filename: FileSize['filename'] = null,
|
||||
width: FileSize['width'] = null,
|
||||
height: FileSize['height'] = null,
|
||||
filesize: FileSize['filesize'] = null,
|
||||
mimeType: FileSize['mimeType'] = null,
|
||||
sizesToSave: FileToSave[] = [],
|
||||
): ImageSizesResult => ({
|
||||
sizeData: {
|
||||
[name]: {
|
||||
filename,
|
||||
filesize,
|
||||
height,
|
||||
mimeType,
|
||||
width,
|
||||
const createResult = ({
|
||||
name,
|
||||
filename = null,
|
||||
filesize = null,
|
||||
height = null,
|
||||
mimeType = null,
|
||||
sizesToSave = [],
|
||||
width = null,
|
||||
}: CreateResultArgs): ImageSizesResult => {
|
||||
return {
|
||||
sizeData: {
|
||||
[name]: {
|
||||
filename,
|
||||
filesize,
|
||||
height,
|
||||
mimeType,
|
||||
width,
|
||||
},
|
||||
},
|
||||
},
|
||||
sizesToSave,
|
||||
})
|
||||
sizesToSave,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the image needs to be resized according to the requested dimensions
|
||||
@@ -215,10 +231,26 @@ export default async function resizeAndTransformImageSizes({
|
||||
req,
|
||||
savedFilename,
|
||||
staticPath,
|
||||
uploadEdits,
|
||||
}: ResizeArgs): Promise<ImageSizesResult> {
|
||||
const { imageSizes } = config.upload
|
||||
// Noting to resize here so return as early as possible
|
||||
if (!imageSizes) return { sizeData: {}, sizesToSave: [] }
|
||||
const { focalPoint: focalPointEnabled = true, imageSizes } = config.upload
|
||||
|
||||
// Focal point adjustments
|
||||
const incomingFocalPoint = uploadEdits.focalPoint
|
||||
? {
|
||||
x: isNumber(uploadEdits.focalPoint.x) ? Math.round(uploadEdits.focalPoint.x) : 50,
|
||||
y: isNumber(uploadEdits.focalPoint.y) ? Math.round(uploadEdits.focalPoint.y) : 50,
|
||||
}
|
||||
: undefined
|
||||
|
||||
const defaultResult: ImageSizesResult = {
|
||||
...(focalPointEnabled && incomingFocalPoint && { focalPoint: incomingFocalPoint }),
|
||||
sizeData: {},
|
||||
sizesToSave: [],
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
@@ -230,16 +262,13 @@ export default async function resizeAndTransformImageSizes({
|
||||
// skipped COMPLETELY and thus will not be included in the resulting images.
|
||||
// All further format/trim options will thus be skipped as well.
|
||||
if (preventResize(imageResizeConfig, dimensions)) {
|
||||
return createResult(imageResizeConfig.name)
|
||||
return createResult({ name: imageResizeConfig.name })
|
||||
}
|
||||
|
||||
const imageToResize = sharpBase.clone()
|
||||
let resized = imageToResize
|
||||
|
||||
if (
|
||||
req.query?.uploadEdits?.focalPoint &&
|
||||
applyPayloadAdjustments(imageResizeConfig, dimensions)
|
||||
) {
|
||||
if (incomingFocalPoint && applyPayloadAdjustments(imageResizeConfig, dimensions)) {
|
||||
const { height: resizeHeight, width: resizeWidth } = imageResizeConfig
|
||||
const resizeAspectRatio = resizeWidth / resizeHeight
|
||||
const originalAspectRatio = dimensions.width / dimensions.height
|
||||
@@ -252,27 +281,17 @@ export default async function resizeAndTransformImageSizes({
|
||||
})
|
||||
const { info: scaledImageInfo } = await scaledImage.toBuffer({ resolveWithObject: true })
|
||||
|
||||
// Focal point adjustments
|
||||
const focalPoint = {
|
||||
x: isNumber(req.query.uploadEdits.focalPoint?.x)
|
||||
? req.query.uploadEdits.focalPoint.x
|
||||
: 50,
|
||||
y: isNumber(req.query.uploadEdits.focalPoint?.y)
|
||||
? req.query.uploadEdits.focalPoint.y
|
||||
: 50,
|
||||
}
|
||||
|
||||
const safeResizeWidth = resizeWidth ?? scaledImageInfo.width
|
||||
const maxOffsetX = scaledImageInfo.width - safeResizeWidth
|
||||
const leftFocalEdge = Math.round(
|
||||
scaledImageInfo.width * (focalPoint.x / 100) - safeResizeWidth / 2,
|
||||
scaledImageInfo.width * (incomingFocalPoint.x / 100) - safeResizeWidth / 2,
|
||||
)
|
||||
const safeOffsetX = Math.min(Math.max(0, leftFocalEdge), maxOffsetX)
|
||||
|
||||
const safeResizeHeight = resizeHeight ?? scaledImageInfo.height
|
||||
const maxOffsetY = scaledImageInfo.height - safeResizeHeight
|
||||
const topFocalEdge = Math.round(
|
||||
scaledImageInfo.height * (focalPoint.y / 100) - safeResizeHeight / 2,
|
||||
scaledImageInfo.height * (incomingFocalPoint.y / 100) - safeResizeHeight / 2,
|
||||
)
|
||||
const safeOffsetY = Math.min(Math.max(0, topFocalEdge), maxOffsetY)
|
||||
|
||||
@@ -327,24 +346,21 @@ export default async function resizeAndTransformImageSizes({
|
||||
}
|
||||
|
||||
const { height, size, width } = bufferInfo
|
||||
return createResult(
|
||||
imageResizeConfig.name,
|
||||
imageNameWithDimensions,
|
||||
width,
|
||||
return createResult({
|
||||
name: imageResizeConfig.name,
|
||||
filename: imageNameWithDimensions,
|
||||
filesize: size,
|
||||
height,
|
||||
size,
|
||||
mimeInfo?.mime || mimeType,
|
||||
[{ buffer: bufferData, path: imagePath }],
|
||||
)
|
||||
mimeType: mimeInfo?.mime || mimeType,
|
||||
sizesToSave: [{ buffer: bufferData, path: imagePath }],
|
||||
width,
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
return results.reduce(
|
||||
(acc, result) => {
|
||||
Object.assign(acc.sizeData, result.sizeData)
|
||||
acc.sizesToSave.push(...result.sizesToSave)
|
||||
return acc
|
||||
},
|
||||
{ sizeData: {}, sizesToSave: [] },
|
||||
)
|
||||
return results.reduce((acc, result) => {
|
||||
Object.assign(acc.sizeData, result.sizeData)
|
||||
acc.sizesToSave.push(...result.sizesToSave)
|
||||
return acc
|
||||
}, defaultResult)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import type express from 'express'
|
||||
import type serveStatic from 'serve-static'
|
||||
import type { ResizeOptions, Sharp } from 'sharp'
|
||||
@@ -18,6 +17,8 @@ export type FileSizes = {
|
||||
export type FileData = {
|
||||
filename: string
|
||||
filesize: number
|
||||
focalX?: number
|
||||
focalY?: number
|
||||
height: number
|
||||
mimeType: string
|
||||
sizes: FileSizes
|
||||
@@ -122,3 +123,16 @@ export type FileToSave = {
|
||||
buffer: Buffer
|
||||
path: string
|
||||
}
|
||||
|
||||
export type UploadEdits = {
|
||||
crop?: {
|
||||
height?: number
|
||||
width?: number
|
||||
x?: number
|
||||
y?: number
|
||||
}
|
||||
focalPoint?: {
|
||||
x?: number
|
||||
y?: number
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/richtext-lexical",
|
||||
"version": "0.10.0",
|
||||
"version": "0.11.1",
|
||||
"description": "The officially supported Lexical richtext adapter for Payload",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -31,11 +31,12 @@ export function BlocksPlugin(): JSX.Element | null {
|
||||
INSERT_BLOCK_COMMAND,
|
||||
(payload: InsertBlockPayload) => {
|
||||
editor.update(() => {
|
||||
const blockNode = $createBlockNode(payload)
|
||||
|
||||
const selection = $getSelection() || $getPreviousSelection()
|
||||
|
||||
if ($isRangeSelection(selection)) {
|
||||
const blockNode = $createBlockNode(payload)
|
||||
$insertNodeToNearestRoot(blockNode)
|
||||
|
||||
const { focus } = selection
|
||||
const focusNode = focus.getNode()
|
||||
|
||||
@@ -51,8 +52,6 @@ export function BlocksPlugin(): JSX.Element | null {
|
||||
) {
|
||||
focusNode.remove()
|
||||
}
|
||||
|
||||
$insertNodeToNearestRoot(blockNode)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -46,11 +46,12 @@ export function RelationshipPlugin(props?: RelationshipFeatureProps): JSX.Elemen
|
||||
return editor.registerCommand<RelationshipData>(
|
||||
INSERT_RELATIONSHIP_COMMAND,
|
||||
(payload) => {
|
||||
const relationshipNode = $createRelationshipNode(payload)
|
||||
|
||||
const selection = $getSelection() || $getPreviousSelection()
|
||||
|
||||
if ($isRangeSelection(selection)) {
|
||||
const relationshipNode = $createRelationshipNode(payload)
|
||||
$insertNodeToNearestRoot(relationshipNode)
|
||||
|
||||
const { focus } = selection
|
||||
const focusNode = focus.getNode()
|
||||
|
||||
@@ -66,8 +67,6 @@ export function RelationshipPlugin(props?: RelationshipFeatureProps): JSX.Elemen
|
||||
) {
|
||||
focusNode.remove()
|
||||
}
|
||||
|
||||
$insertNodeToNearestRoot(relationshipNode)
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
@@ -57,7 +57,7 @@ export const ExtraFieldsUploadDrawer: React.FC<
|
||||
const fieldSchema = useMemo(() => {
|
||||
const fieldSchemaUnSanitized = (
|
||||
editorConfig?.resolvedFeatureMap.get('upload')?.props as UploadFeatureProps
|
||||
)?.collections?.[relatedCollection.slug].fields
|
||||
)?.collections?.[relatedCollection.slug]?.fields
|
||||
|
||||
const validRelationships = config.collections.map((c) => c.slug) || []
|
||||
// Sanitize custom fields here
|
||||
|
||||
@@ -37,19 +37,20 @@ export function UploadPlugin(): JSX.Element | null {
|
||||
INSERT_UPLOAD_COMMAND,
|
||||
(payload: InsertUploadPayload) => {
|
||||
editor.update(() => {
|
||||
const uploadNode = $createUploadNode({
|
||||
data: {
|
||||
fields: payload.fields,
|
||||
relationTo: payload.relationTo,
|
||||
value: {
|
||||
id: payload.id,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const selection = $getSelection() || $getPreviousSelection()
|
||||
|
||||
if ($isRangeSelection(selection)) {
|
||||
const uploadNode = $createUploadNode({
|
||||
data: {
|
||||
fields: payload.fields,
|
||||
relationTo: payload.relationTo,
|
||||
value: {
|
||||
id: payload.id,
|
||||
},
|
||||
},
|
||||
})
|
||||
$insertNodeToNearestRoot(uploadNode)
|
||||
|
||||
const { focus } = selection
|
||||
const focusNode = focus.getNode()
|
||||
|
||||
@@ -65,8 +66,6 @@ export function UploadPlugin(): JSX.Element | null {
|
||||
) {
|
||||
focusNode.remove()
|
||||
}
|
||||
|
||||
$insertNodeToNearestRoot(uploadNode)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -513,6 +513,26 @@ describe('collections-rest', () => {
|
||||
expect(result.docs).toEqual([post])
|
||||
expect(result.totalDocs).toEqual(1)
|
||||
})
|
||||
|
||||
it('should query LIKE by ID', async () => {
|
||||
const post = await payload.create({
|
||||
collection: slug,
|
||||
data: {
|
||||
title: 'find me buddy',
|
||||
},
|
||||
})
|
||||
|
||||
const { result, status } = await client.find<Post>({
|
||||
query: {
|
||||
id: {
|
||||
like: post.id,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(status).toStrictEqual(200)
|
||||
expect(result.totalDocs).toStrictEqual(1)
|
||||
})
|
||||
})
|
||||
|
||||
it('should query nested relationship - hasMany', async () => {
|
||||
|
||||
@@ -134,7 +134,9 @@ export default buildConfigWithDefaults({
|
||||
],
|
||||
},
|
||||
],
|
||||
versions: true,
|
||||
versions: {
|
||||
drafts: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
globals: [
|
||||
|
||||
@@ -24,6 +24,14 @@ const RowFields: CollectionConfig = {
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'disableListColumnText',
|
||||
type: 'text',
|
||||
admin: {
|
||||
disableListColumn: true,
|
||||
disableListFilter: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2061,6 +2061,31 @@ describe('fields', () => {
|
||||
url = new AdminUrlUtil(serverURL, 'row-fields')
|
||||
})
|
||||
|
||||
test('should not display field in list view column selector if admin.disableListColumn is true', async () => {
|
||||
await page.goto(url.create)
|
||||
const idInput = page.locator('input#field-id')
|
||||
await idInput.fill('000')
|
||||
const titleInput = page.locator('input#field-title')
|
||||
await titleInput.fill('Row 000')
|
||||
const disableListColumnText = page.locator('input#field-disableListColumnText')
|
||||
await disableListColumnText.fill('Disable List Column Text')
|
||||
await page.locator('#action-save').click()
|
||||
await wait(200)
|
||||
await expect(page.locator('.Toastify')).toContainText('successfully')
|
||||
|
||||
await page.goto(url.list)
|
||||
await page.locator('.list-controls__toggle-columns').click()
|
||||
|
||||
await expect(page.locator('.column-selector')).toBeVisible()
|
||||
|
||||
// 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 show row fields as table columns', async () => {
|
||||
await page.goto(url.create)
|
||||
|
||||
|
||||
@@ -7,7 +7,16 @@ import removeFiles from '../helpers/removeFiles'
|
||||
import { Uploads1 } from './collections/Upload1'
|
||||
import Uploads2 from './collections/Upload2'
|
||||
import AdminThumbnailCol from './collections/admin-thumbnail'
|
||||
import { audioSlug, enlargeSlug, mediaSlug, reduceSlug, relationSlug, versionSlug } from './shared'
|
||||
import {
|
||||
audioSlug,
|
||||
cropOnlySlug,
|
||||
enlargeSlug,
|
||||
focalOnlySlug,
|
||||
mediaSlug,
|
||||
reduceSlug,
|
||||
relationSlug,
|
||||
versionSlug,
|
||||
} from './shared'
|
||||
|
||||
const mockModulePath = path.resolve(__dirname, './mocks/mockFSModule.js')
|
||||
|
||||
@@ -136,7 +145,7 @@ export default buildConfigWithDefaults({
|
||||
},
|
||||
},
|
||||
{
|
||||
slug: 'crop-only',
|
||||
slug: cropOnlySlug,
|
||||
fields: [],
|
||||
upload: {
|
||||
focalPoint: false,
|
||||
@@ -163,7 +172,7 @@ export default buildConfigWithDefaults({
|
||||
},
|
||||
},
|
||||
{
|
||||
slug: 'focal-only',
|
||||
slug: focalOnlySlug,
|
||||
fields: [],
|
||||
upload: {
|
||||
crop: false,
|
||||
@@ -189,6 +198,17 @@ export default buildConfigWithDefaults({
|
||||
staticURL: '/focal-only',
|
||||
},
|
||||
},
|
||||
{
|
||||
slug: 'focal-no-sizes',
|
||||
fields: [],
|
||||
upload: {
|
||||
crop: false,
|
||||
focalPoint: true,
|
||||
mimeTypes: ['image/png', 'image/jpg', 'image/jpeg'],
|
||||
staticDir: './focal-no-sizes',
|
||||
staticURL: '/focal-no-sizes',
|
||||
},
|
||||
},
|
||||
{
|
||||
slug: mediaSlug,
|
||||
fields: [],
|
||||
|
||||
@@ -10,7 +10,7 @@ import getFileByPath from '../../packages/payload/src/uploads/getFileByPath'
|
||||
import { initPayloadTest } from '../helpers/configHelpers'
|
||||
import { RESTClient } from '../helpers/rest'
|
||||
import configPromise from './config'
|
||||
import { enlargeSlug, mediaSlug, reduceSlug, relationSlug } from './shared'
|
||||
import { enlargeSlug, focalOnlySlug, mediaSlug, reduceSlug, relationSlug } from './shared'
|
||||
|
||||
const stat = promisify(fs.stat)
|
||||
|
||||
@@ -53,6 +53,8 @@ describe('Collections - Uploads', () => {
|
||||
|
||||
// Check api response
|
||||
expect(doc.mimeType).toEqual('image/png')
|
||||
expect(doc.focalX).toEqual(50)
|
||||
expect(doc.focalY).toEqual(50)
|
||||
expect(sizes.maintainedAspectRatio.url).toContain('/media/image')
|
||||
expect(sizes.maintainedAspectRatio.url).toContain('.png')
|
||||
expect(sizes.maintainedAspectRatio.width).toEqual(1024)
|
||||
@@ -283,6 +285,75 @@ describe('Collections - Uploads', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('focal point', () => {
|
||||
let file
|
||||
|
||||
beforeAll(async () => {
|
||||
// Create image
|
||||
const filePath = path.resolve(__dirname, './image.png')
|
||||
file = await getFileByPath(filePath)
|
||||
file.name = 'focal.png'
|
||||
})
|
||||
|
||||
it('should be able to set focal point through local API', async () => {
|
||||
const doc = await payload.create({
|
||||
collection: focalOnlySlug,
|
||||
data: {
|
||||
focalX: 5,
|
||||
focalY: 5,
|
||||
},
|
||||
file,
|
||||
})
|
||||
|
||||
expect(doc.focalX).toEqual(5)
|
||||
expect(doc.focalY).toEqual(5)
|
||||
|
||||
const updatedFocal = await payload.update({
|
||||
collection: focalOnlySlug,
|
||||
id: doc.id,
|
||||
data: {
|
||||
focalX: 10,
|
||||
focalY: 10,
|
||||
},
|
||||
})
|
||||
|
||||
expect(updatedFocal.focalX).toEqual(10)
|
||||
expect(updatedFocal.focalY).toEqual(10)
|
||||
|
||||
const updateWithoutFocal = await payload.update({
|
||||
collection: focalOnlySlug,
|
||||
id: doc.id,
|
||||
data: {},
|
||||
})
|
||||
|
||||
// Expect focal point to be the same
|
||||
expect(updateWithoutFocal.focalX).toEqual(10)
|
||||
expect(updateWithoutFocal.focalY).toEqual(10)
|
||||
})
|
||||
|
||||
it('should default focal point to 50, 50', async () => {
|
||||
const doc = await payload.create({
|
||||
collection: focalOnlySlug,
|
||||
data: {
|
||||
// No focal point
|
||||
},
|
||||
file,
|
||||
})
|
||||
|
||||
expect(doc.focalX).toEqual(50)
|
||||
expect(doc.focalY).toEqual(50)
|
||||
|
||||
const updateWithoutFocal = await payload.update({
|
||||
collection: focalOnlySlug,
|
||||
id: doc.id,
|
||||
data: {},
|
||||
})
|
||||
|
||||
expect(updateWithoutFocal.focalX).toEqual(50)
|
||||
expect(updateWithoutFocal.focalY).toEqual(50)
|
||||
})
|
||||
})
|
||||
|
||||
it('update', async () => {
|
||||
// Create image
|
||||
const filePath = path.resolve(__dirname, './image.png')
|
||||
|
||||
@@ -47,6 +47,8 @@ export interface Media {
|
||||
filesize?: number
|
||||
width?: number
|
||||
height?: number
|
||||
focalX?: number
|
||||
focalY?: number
|
||||
sizes?: {
|
||||
maintainedAspectRatio?: {
|
||||
url?: string
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
export const mediaSlug = 'media'
|
||||
|
||||
export const relationSlug = 'relation'
|
||||
|
||||
export const audioSlug = 'audio'
|
||||
|
||||
export const enlargeSlug = 'enlarge'
|
||||
|
||||
export const reduceSlug = 'reduce'
|
||||
|
||||
export const adminThumbnailSlug = 'admin-thumbnail'
|
||||
|
||||
export const audioSlug = 'audio'
|
||||
export const cropOnlySlug = 'crop-only'
|
||||
export const enlargeSlug = 'enlarge'
|
||||
export const focalOnlySlug = 'focal-only'
|
||||
export const mediaSlug = 'media'
|
||||
export const reduceSlug = 'reduce'
|
||||
export const relationSlug = 'relation'
|
||||
export const versionSlug = 'versions'
|
||||
|
||||
@@ -908,6 +908,36 @@ describe('Versions', () => {
|
||||
|
||||
expect(byID.docs).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('should be able to query by id AND any other field with draft=true', async () => {
|
||||
const allDocs = await payload.find({
|
||||
collection: 'draft-posts',
|
||||
draft: true,
|
||||
})
|
||||
|
||||
expect(allDocs.docs.length).toBeGreaterThan(1)
|
||||
|
||||
const results = await payload.find({
|
||||
collection: 'draft-posts',
|
||||
draft: true,
|
||||
where: {
|
||||
and: [
|
||||
{
|
||||
id: {
|
||||
not_in: allDocs.docs[0].id,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: {
|
||||
like: 'Published',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
expect(results.docs).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Collections - GraphQL', () => {
|
||||
|
||||
Reference in New Issue
Block a user