diff --git a/CHANGELOG.md b/CHANGELOG.md index 36d22526ea..95326c9e3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ +## [1.5.4](https://github.com/payloadcms/payload/compare/v1.5.3...v1.5.4) (2023-01-06) + + +### Features + +* allows init to accept a pre-built config ([84e00bf](https://github.com/payloadcms/payload/commit/84e00bf7b3dfc1c23367765eec60bec45b81617b)) + +## [1.5.3](https://github.com/payloadcms/payload/compare/v1.5.2...v1.5.3) (2023-01-05) + + +### Bug Fixes + +* theme flicker on code editor ([6567454](https://github.com/payloadcms/payload/commit/6567454ae4e4808303da9b80d26633bc77e1445d)) + ## [1.5.2](https://github.com/payloadcms/payload/compare/v1.5.1...v1.5.2) (2023-01-04) diff --git a/docs/authentication/using-middleware.mdx b/docs/authentication/using-middleware.mdx index e26b924e04..607d493faf 100644 --- a/docs/authentication/using-middleware.mdx +++ b/docs/authentication/using-middleware.mdx @@ -25,24 +25,25 @@ payload.init({ secret: 'PAYLOAD_SECRET_KEY', mongoURL: 'mongodb://localhost/payload', express: app, -}); + onInit: async () => { + const router = express.Router(); -const router = express.Router(); + router.use(payload.authenticate); // highlight-line -router.use(payload.authenticate); // highlight-line + router.get('/', (req, res) => { + if (req.user) { + return res.send(`Authenticated successfully as ${req.user.email}.`); + } -router.get('/', (req, res) => { - if (req.user) { - return res.send(`Authenticated successfully as ${req.user.email}.`); - } + return res.send('Not authenticated'); + }); - return res.send('Not authenticated'); -}); + app.use('/some-route-here', router); -app.use('/some-route-here', router); - -app.listen(3000, async () => { - payload.logger.info(`listening on ${3000}...`); + app.listen(3000, async () => { + payload.logger.info(`listening on ${3000}...`); + }); + }, }); ``` diff --git a/docs/getting-started/installation.mdx b/docs/getting-started/installation.mdx index cc2dcecac2..a0a8499fc3 100644 --- a/docs/getting-started/installation.mdx +++ b/docs/getting-started/installation.mdx @@ -66,7 +66,7 @@ app.listen(3000, async () => { }); ``` -This server doesn't do anything just yet. But, after you have this in place, we can initialize Payload via its `init()` method, which accepts a small set of arguments to tell it how to operate. For a full list of `init` arguments, please consult the [main configuration](/docs/configuration/overview#init) docs. +This server doesn't do anything just yet. But, after you have this in place, we can initialize Payload via its `init()` method, which accepts a small set of arguments to tell it how to operate. For a full list of `init` arguments, please consult the [main configuration](/docs/configuration/overview) docs. To initialize Payload, update your `server.js` file to reflect the following code: @@ -80,12 +80,13 @@ payload.init({ secret: 'SECRET_KEY', mongoURL: 'mongodb://localhost/payload', express: app, + onInit: () => { + app.listen(3000, async () => { + console.log('Express is now listening for incoming connections on port 3000.') + }); + } }) -app.listen(3000, async () => { - console.log('Express is now listening for incoming connections on port 3000.') -}); - ``` Here is a list of all properties available to pass through `payload.init`: diff --git a/package.json b/package.json index 576e98e170..b465fed993 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "payload", - "version": "1.5.2", + "version": "1.5.4", "description": "Node, React and MongoDB Headless CMS and Application Framework", "license": "MIT", "engines": { diff --git a/src/admin/components/elements/ListDrawer/DrawerContent.tsx b/src/admin/components/elements/ListDrawer/DrawerContent.tsx index 665a5fa1d8..9796e8dae2 100644 --- a/src/admin/components/elements/ListDrawer/DrawerContent.tsx +++ b/src/admin/components/elements/ListDrawer/DrawerContent.tsx @@ -58,13 +58,16 @@ const shouldIncludeCollection = ({ collectionSlugs, }) => (enableRichTextRelationship && ((uploads && Boolean(upload)) || collectionSlugs?.includes(slug))); -export const ListDrawerContent: React.FC = ({ +const DrawerContent: React.FC = ({ drawerSlug, onSelect, customHeader, collectionSlugs, uploads, selectedCollection, + enabledCollectionConfigs }) => { const { t, i18n } = useTranslation(['upload', 'general']); const { permissions } = useAuth(); @@ -75,7 +78,7 @@ export const ListDrawerContent: React.FC = ({ const [page, setPage] = useState(1); const [where, setWhere] = useState(null); const { serverURL, routes: { api }, collections } = useConfig(); - const [enabledCollectionConfigs] = useState(() => collections.filter((coll) => shouldIncludeCollection({ coll, uploads, collectionSlugs }))); + const [selectedCollectionConfig, setSelectedCollectionConfig] = useState(() => { let initialSelection: SanitizedCollectionConfig; if (selectedCollection) { @@ -92,7 +95,9 @@ export const ListDrawerContent: React.FC = ({ }); const [selectedOption, setSelectedOption] = useState<{ label: string, value: string }>(() => (selectedCollectionConfig ? { label: getTranslation(selectedCollectionConfig.labels.singular, i18n), value: selectedCollectionConfig.slug } : undefined)); + const [fields, setFields] = useState(() => formatFields(selectedCollectionConfig, t)); + const [tableColumns, setTableColumns] = useState(() => { const initialColumns = getInitialColumnState(fields, selectedCollectionConfig.admin.useAsTitle, selectedCollectionConfig.admin.defaultColumns); return buildColumns({ @@ -308,3 +313,25 @@ export const ListDrawerContent: React.FC = ({ ); }; + +export const ListDrawerContent: React.FC = (props) => { + const { + collectionSlugs, + uploads, + } = props; + + const { collections } = useConfig(); + + const [enabledCollectionConfigs] = useState(() => collections.filter((coll) => shouldIncludeCollection({ coll, uploads, collectionSlugs }))); + + if (enabledCollectionConfigs.length === 0){ + return null; + } + + return ( + + ) +} diff --git a/src/admin/components/forms/field-types/Group/index.scss b/src/admin/components/forms/field-types/Group/index.scss index b5d738be26..5e975b31b7 100644 --- a/src/admin/components/forms/field-types/Group/index.scss +++ b/src/admin/components/forms/field-types/Group/index.scss @@ -29,6 +29,12 @@ border-bottom: 0; } + &--within-row { + margin: 0; + border-top: 0; + border-bottom: 0; + } + &--within-tab:first-child { margin-top: 0; border-top: 0; @@ -80,4 +86,8 @@ .group-field--within-collapsible+.group-field--within-collapsible { margin-top: base(-1); -} \ No newline at end of file +} + +.group-field--within-row+.group-field--within-row { + margin-top: 0; +} diff --git a/src/admin/components/forms/field-types/Group/index.tsx b/src/admin/components/forms/field-types/Group/index.tsx index fa601a65fc..6a7eb80eed 100644 --- a/src/admin/components/forms/field-types/Group/index.tsx +++ b/src/admin/components/forms/field-types/Group/index.tsx @@ -6,6 +6,7 @@ import FieldDescription from '../../FieldDescription'; import { Props } from './types'; import { useCollapsible } from '../../../elements/Collapsible/provider'; import { GroupProvider, useGroup } from './provider'; +import { useRow } from '../Row/provider'; import { useTabs } from '../Tabs/provider'; import { getTranslation } from '../../../../../utilities/getTranslation'; import { createNestedFieldPath } from '../../Form/createNestedFieldPath'; @@ -35,6 +36,7 @@ const Group: React.FC = (props) => { const isWithinCollapsible = useCollapsible(); const isWithinGroup = useGroup(); + const isWithinRow = useRow(); const isWithinTab = useTabs(); const { i18n } = useTranslation(); @@ -48,6 +50,7 @@ const Group: React.FC = (props) => { baseClass, isWithinCollapsible && `${baseClass}--within-collapsible`, isWithinGroup && `${baseClass}--within-group`, + isWithinRow && `${baseClass}--within-row`, isWithinTab && `${baseClass}--within-tab`, (!hideGutter && isWithinGroup) && `${baseClass}--gutter`, className, diff --git a/src/admin/components/forms/field-types/Row/index.tsx b/src/admin/components/forms/field-types/Row/index.tsx index c03e4bc953..0baf167187 100644 --- a/src/admin/components/forms/field-types/Row/index.tsx +++ b/src/admin/components/forms/field-types/Row/index.tsx @@ -3,6 +3,7 @@ import RenderFields from '../../RenderFields'; import withCondition from '../../withCondition'; import { Props } from './types'; import { createNestedFieldPath } from '../../Form/createNestedFieldPath'; +import { RowProvider } from './provider'; import './index.scss'; @@ -26,17 +27,19 @@ const Row: React.FC = (props) => { ].filter(Boolean).join(' '); return ( - ({ - ...field, - path: createNestedFieldPath(path, field), - }))} - /> + + ({ + ...field, + path: createNestedFieldPath(path, field), + }))} + /> + ); }; export default withCondition(Row); diff --git a/src/admin/components/forms/field-types/Row/provider.tsx b/src/admin/components/forms/field-types/Row/provider.tsx new file mode 100644 index 0000000000..65bb3f6314 --- /dev/null +++ b/src/admin/components/forms/field-types/Row/provider.tsx @@ -0,0 +1,17 @@ +import React, { + createContext, useContext, +} from 'react'; + +const Context = createContext(false); + +export const RowProvider: React.FC<{ children?: React.ReactNode, withinRow?: boolean }> = ({ children, withinRow = true }) => { + return ( + + {children} + + ); +}; + +export const useRow = (): boolean => useContext(Context); + +export default Context; diff --git a/src/bin/generateGraphQLSchema.ts b/src/bin/generateGraphQLSchema.ts index 1f58ed3eff..5c4ae8a5f0 100644 --- a/src/bin/generateGraphQLSchema.ts +++ b/src/bin/generateGraphQLSchema.ts @@ -5,11 +5,11 @@ import Logger from '../utilities/logger'; import loadConfig from '../config/load'; import payload from '..'; -export function generateGraphQLSchema(): void { +export async function generateGraphQLSchema(): Promise { const logger = Logger(); const config = loadConfig(); - payload.init({ + await payload.init({ secret: '--unused--', mongoURL: false, local: true, diff --git a/src/config/types.ts b/src/config/types.ts index 2f0c24701e..0b81ad5cfe 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -108,6 +108,7 @@ export type InitOptions = { * See Pino Docs for options: https://getpino.io/#/docs/api?id=options */ loggerOptions?: LoggerOptions; + config?: SanitizedConfig }; /** diff --git a/src/fields/config/types.ts b/src/fields/config/types.ts index 0e7114b68e..6bdc16d905 100644 --- a/src/fields/config/types.ts +++ b/src/fields/config/types.ts @@ -297,7 +297,7 @@ export type ValueWithRelation = { } export function valueIsValueWithRelation(value: unknown): value is ValueWithRelation { - return typeof value === 'object' && 'relationTo' in value && 'value' in value; + return value !== null && typeof value === 'object' && 'relationTo' in value && 'value' in value; } export type RelationshipValue = (string | number) diff --git a/src/fields/hooks/beforeChange/getExistingRowDoc.ts b/src/fields/hooks/beforeChange/getExistingRowDoc.ts index 84e237d304..20f229ab09 100644 --- a/src/fields/hooks/beforeChange/getExistingRowDoc.ts +++ b/src/fields/hooks/beforeChange/getExistingRowDoc.ts @@ -5,9 +5,17 @@ * Otherwise, return an empty object. */ -export const getExistingRowDoc = (incomingRow: Record, existingRow?: Record): Record => { - if (incomingRow.id && incomingRow.id === existingRow?.id) { - return existingRow; +export const getExistingRowDoc = (incomingRow: Record, existingRows?: unknown): Record => { + if (incomingRow.id && Array.isArray(existingRows)) { + const matchedExistingRow = existingRows.find((existingRow) => { + if (typeof existingRow === 'object' && 'id' in existingRow) { + if (existingRow.id === incomingRow.id) return existingRow; + } + + return false; + }); + + if (matchedExistingRow) return matchedExistingRow; } return {}; diff --git a/src/fields/hooks/beforeChange/promise.ts b/src/fields/hooks/beforeChange/promise.ts index 5600393d1a..9451293ae8 100644 --- a/src/fields/hooks/beforeChange/promise.ts +++ b/src/fields/hooks/beforeChange/promise.ts @@ -230,8 +230,8 @@ export const promise = async ({ path: `${path}${field.name}.${i}.`, req, siblingData: row, - siblingDoc: getExistingRowDoc(row, siblingDoc[field.name]?.[i]), - siblingDocWithLocales: getExistingRowDoc(row, siblingDocWithLocales[field.name]?.[i]), + siblingDoc: getExistingRowDoc(row, siblingDoc[field.name]), + siblingDocWithLocales: getExistingRowDoc(row, siblingDocWithLocales[field.name]), skipValidation: skipValidationFromHere, })); }); @@ -263,8 +263,8 @@ export const promise = async ({ path: `${path}${field.name}.${i}.`, req, siblingData: row, - siblingDoc: getExistingRowDoc(row, siblingDoc[field.name]?.[i]), - siblingDocWithLocales: getExistingRowDoc(row, siblingDocWithLocales[field.name]?.[i]), + siblingDoc: getExistingRowDoc(row, siblingDoc[field.name]), + siblingDocWithLocales: getExistingRowDoc(row, siblingDocWithLocales[field.name]), skipValidation: skipValidationFromHere, })); } diff --git a/src/globals/config/types.ts b/src/globals/config/types.ts index e65cd30729..3a90637f75 100644 --- a/src/globals/config/types.ts +++ b/src/globals/config/types.ts @@ -89,18 +89,19 @@ export type GlobalConfig = { } } -export interface SanitizedGlobalConfig extends Omit, 'fields' | 'versions' | 'graphQL'> { +export interface SanitizedGlobalConfig extends Omit, 'fields' | 'versions'> { fields: Field[] versions: SanitizedGlobalVersions - graphQL?: { - name?: string - type: GraphQLObjectType - mutationInputType: GraphQLNonNull - versionType?: GraphQLObjectType - } } export type Globals = { Model: GlobalModel config: SanitizedGlobalConfig[] + graphQL?: { + [slug: string]: { + type: GraphQLObjectType + mutationInputType: GraphQLNonNull + versionType?: GraphQLObjectType + } + } } diff --git a/src/globals/graphql/init.ts b/src/globals/graphql/init.ts index e07eec698c..05b931ad3f 100644 --- a/src/globals/graphql/init.ts +++ b/src/globals/graphql/init.ts @@ -30,27 +30,28 @@ function initGlobalsGraphQL(payload: Payload): void { const formattedName = global.graphQL?.name ? global.graphQL.name : singular(toWords(global.slug, true)); - global.graphQL = {} as SanitizedGlobalConfig['graphQL']; - const forceNullableObjectType = Boolean(versions?.drafts); - global.graphQL.type = buildObjectType({ - payload, - name: formattedName, - parentName: formattedName, - fields, - forceNullable: forceNullableObjectType, - }); + if (!payload.globals.graphQL) payload.globals.graphQL = {}; - global.graphQL.mutationInputType = new GraphQLNonNull(buildMutationInputType( - payload, - formattedName, - fields, - formattedName, - )); + payload.globals.graphQL[slug] = { + type: buildObjectType({ + payload, + name: formattedName, + parentName: formattedName, + fields, + forceNullable: forceNullableObjectType, + }), + mutationInputType: new GraphQLNonNull(buildMutationInputType( + payload, + formattedName, + fields, + formattedName, + )), + }; payload.Query.fields[formattedName] = { - type: global.graphQL.type, + type: payload.globals.graphQL[slug].type, args: { draft: { type: GraphQLBoolean }, ...(payload.config.localization ? { @@ -62,9 +63,9 @@ function initGlobalsGraphQL(payload: Payload): void { }; payload.Mutation.fields[`update${formattedName}`] = { - type: global.graphQL.type, + type: payload.globals.graphQL[slug].type, args: { - data: { type: global.graphQL.mutationInputType }, + data: { type: payload.globals.graphQL[slug].mutationInputType }, draft: { type: GraphQLBoolean }, ...(payload.config.localization ? { locale: { type: payload.types.localeInputType }, @@ -102,7 +103,7 @@ function initGlobalsGraphQL(payload: Payload): void { }, ]; - global.graphQL.versionType = buildObjectType({ + payload.globals.graphQL[slug].versionType = buildObjectType({ payload, name: `${formattedName}Version`, parentName: `${formattedName}Version`, @@ -111,7 +112,7 @@ function initGlobalsGraphQL(payload: Payload): void { }); payload.Query.fields[`version${formatName(formattedName)}`] = { - type: global.graphQL.versionType, + type: payload.globals.graphQL[slug].versionType, args: { id: { type: GraphQLString }, ...(payload.config.localization ? { @@ -122,7 +123,7 @@ function initGlobalsGraphQL(payload: Payload): void { resolve: findVersionByIDResolver(global), }; payload.Query.fields[`versions${formattedName}`] = { - type: buildPaginatedListType(`versions${formatName(formattedName)}`, global.graphQL.versionType), + type: buildPaginatedListType(`versions${formatName(formattedName)}`, payload.globals.graphQL[slug].versionType), args: { where: { type: buildWhereInputType( @@ -142,7 +143,7 @@ function initGlobalsGraphQL(payload: Payload): void { resolve: findVersionsResolver(global), }; payload.Mutation.fields[`restoreVersion${formatName(formattedName)}`] = { - type: global.graphQL.type, + type: payload.globals.graphQL[slug].type, args: { id: { type: GraphQLString }, }, diff --git a/src/index.ts b/src/index.ts index 319a5e9fc5..1d31742b03 100644 --- a/src/index.ts +++ b/src/index.ts @@ -44,7 +44,7 @@ import { Result as ResetPasswordResult } from './auth/operations/resetPassword'; import { Result as LoginResult } from './auth/operations/login'; import { Options as FindGlobalOptions } from './globals/operations/local/findOne'; import { Options as UpdateGlobalOptions } from './globals/operations/local/update'; -import { initSync, initAsync } from './init'; +import { initPayload } from './init'; require('isomorphic-fetch'); @@ -121,12 +121,8 @@ export class Payload { * @description Initializes Payload * @param options */ - init(options: InitOptions): void { - initSync(this, options); - } - - async initAsync(options: InitOptions): Promise { - await initAsync(this, options); + async init(options: InitOptions): Promise { + await initPayload(this, options); } getAdminURL = (): string => `${this.config.serverURL}${this.config.routes.admin}`; diff --git a/src/init.ts b/src/init.ts index f2ea9020c0..5f87c76d2d 100644 --- a/src/init.ts +++ b/src/init.ts @@ -1,6 +1,7 @@ /* eslint-disable no-param-reassign */ import express, { NextFunction, Response } from 'express'; import crypto from 'crypto'; +import path from 'path'; import mongoose from 'mongoose'; import { InitOptions } from './config/types'; @@ -30,8 +31,17 @@ import Logger from './utilities/logger'; import { getDataLoader } from './collections/dataloader'; import mountEndpoints from './express/mountEndpoints'; import PreferencesModel from './preferences/model'; +import findConfig from './config/find'; + +export const initPayload = async (payload: Payload, options: InitOptions): Promise => { + payload.logger = Logger('payload', options.loggerOptions); + payload.mongoURL = options.mongoURL; + + if (payload.mongoURL) { + mongoose.set('strictQuery', false); + payload.mongoMemoryServer = await connectMongoose(payload.mongoURL, options.mongoOptions, payload.logger); + } -export const init = (payload: Payload, options: InitOptions): void => { payload.logger.info('Starting Payload...'); if (!options.secret) { throw new Error( @@ -52,7 +62,21 @@ export const init = (payload: Payload, options: InitOptions): void => { payload.local = options.local; - payload.config = loadConfig(payload.logger); + if (options.config) { + payload.config = options.config; + const configPath = findConfig(); + + payload.config = { + ...options.config, + paths: { + configDir: path.dirname(configPath), + config: configPath, + rawConfig: configPath, + }, + }; + } else { + payload.config = loadConfig(payload.logger); + } // If not initializing locally, scaffold router if (!payload.local) { @@ -129,34 +153,7 @@ export const init = (payload: Payload, options: InitOptions): void => { } serverInitTelemetry(payload); -}; - -export const initAsync = async (payload: Payload, options: InitOptions): Promise => { - payload.logger = Logger('payload', options.loggerOptions); - payload.mongoURL = options.mongoURL; - - if (payload.mongoURL) { - mongoose.set('strictQuery', false); - payload.mongoMemoryServer = await connectMongoose(payload.mongoURL, options.mongoOptions, payload.logger); - } - - init(payload, options); if (typeof options.onInit === 'function') await options.onInit(payload); if (typeof payload.config.onInit === 'function') await payload.config.onInit(payload); }; - -export const initSync = (payload: Payload, options: InitOptions): void => { - payload.logger = Logger('payload', options.loggerOptions); - payload.mongoURL = options.mongoURL; - - if (payload.mongoURL) { - mongoose.set('strictQuery', false); - connectMongoose(payload.mongoURL, options.mongoOptions, payload.logger); - } - - init(payload, options); - - if (typeof options.onInit === 'function') options.onInit(payload); - if (typeof payload.config.onInit === 'function') payload.config.onInit(payload); -}; diff --git a/src/translations/fr.json b/src/translations/fr.json index 5134ab9133..734e3ee7fe 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -20,10 +20,10 @@ "forceUnlock": "Forcer le déverrouillage", "forgotPassword": "Mot de passe oublié", "forgotPasswordEmailInstructions": "Veuillez saisir votre e-mail ci-dessous. Vous recevrez un e-mail avec des instructions concernant comment réinitialiser votre mot de passe.", - "forgotPasswordQuestion": "Mot de passe oublié?", + "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 la clé précédente. Êtes-vous sûr de vouloir continuer?", + "generatingNewAPIKeyWillInvalidate": "La génération d'une nouvelle clé API <1>invalidera la clé précédente. Êtes-vous sûr de vouloir continuer ?", "lockUntil": "Verrouiller jusqu'à", "logBackIn": "Se reconnecter", "logOut": "Se déconnecter", @@ -52,8 +52,8 @@ "verify": "Vérifier", "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:", + "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": { @@ -63,11 +63,11 @@ "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_many": "Les champs suivants ne sont pas valides:", - "followingFieldsInvalid_one": "Le champ suivant n'est pas valide:", + "followingFieldsInvalid_many": "Les champs suivants ne sont pas valides :", + "followingFieldsInvalid_one": "Le champ suivant n'est pas valide :", "incorrectCollection": "Collection incorrecte", "invalidFileType": "Type de fichier invalide", - "invalidFileTypeValue": "Type de fichier invalide: {{value}}", + "invalidFileTypeValue": "Type de fichier invalide : {{value}}", "loadingDocument": "Un problème est survenu lors du chargement du document qui a pour identifiant {{id}}.", "missingEmail": "E-mail manquant.", "missingIDOfDocument": "Il manque l'identifiant du document à mettre à jour.", @@ -118,13 +118,13 @@ "searchForBlock": "Rechercher un bloc", "selectExistingLabel": "Sélectionnez {{label}} existant", "showAll": "Afficher tout", - "swapRelationship": "Relation D'échange", + "swapRelationship": "Changer de relation", "swapUpload": "Changer de Fichier", "toggleBlock": "Bloc bascule", "uploadNewLabel": "Téléverser un(e) nouveau ou nouvelle {{label}}" }, "general": { - "aboutToDelete": "Vous êtes sur le point de supprimer ce ou cette {{label}} <1>{{title}}. Êtes-vous sûr?", + "aboutToDelete": "Vous êtes sur le point de supprimer ce ou cette {{label}} <1>{{title}}. Êtes-vous sûr ?", "addBelow": "Ajoutez ci-dessous", "addFilter": "Ajouter un filtre", "adminTheme": "Thème d'administration", @@ -208,7 +208,7 @@ "thisLanguage": "Français", "titleDeleted": "{{label}} \"{{title}}\" supprimé(e) avec succès.", "unauthorized": "Non autorisé", - "unsavedChangesDuplicate": "Vous avez des changements non enregistrés. Souhaitez-vous continuer la duplication?", + "unsavedChangesDuplicate": "Vous avez des changements non enregistrés. Souhaitez-vous continuer la duplication ?", "untitled": "Sans titre", "updatedAt": "Modifié le", "updatedSuccessfully": "Mis à jour avec succés.", @@ -236,7 +236,7 @@ "greaterThanMax": "\"{{value}}\" est supérieur à la valeur maximale autorisée 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 des sélections invalides suivantes :", "lessThanMin": "\"{{value}}\" est inférieur à la valeur minimale autorisée de {{min}}.", "longerThanMin": "Cette valeur doit être supérieure à la longueur minimale de {{minLength}} caractères.", "notValidDate": "\"{{value}}\" n'est pas une date valide.", @@ -251,13 +251,13 @@ "version": { "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?", + "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 ?", "autosave": "Enregistrement automatique", "autosavedSuccessfully": "Enregistrement automatique réussi.", "autosavedVersion": "Version enregistrée automatiquement", "changed": "Modifié", - "compareVersion": "Comparez cette version à:", + "compareVersion": "Comparez cette version à :", "confirmRevertToSaved": "Confirmer la restauration", "confirmUnpublish": "Confirmer l'annulation", "confirmVersionRestoration": "Confirmer la restauration de la version", @@ -279,7 +279,7 @@ "saveDraft": "Enregistrer le brouillon", "selectLocals": "Sélectionnez les paramètres régionaux à afficher", "selectVersionToCompare": "Sélectionnez une version à comparer", - "showLocales": "Afficher les paramètres régionaux:", + "showLocales": "Afficher les paramètres régionaux :", "status": "Statut", "type": "Type", "unpublish": "Annuler la publication", @@ -288,7 +288,7 @@ "versionCount_many": "{{count}} versions trouvées", "versionCount_none": "Aucune version trouvée", "versionCount_one": "{{count}} version trouvée", - "versionCreatedOn": "{{version}} créé(e) le:", + "versionCreatedOn": "{{version}} créé(e) le :", "versionID": "Identifiant de la version", "versions": "Versions", "viewingVersion": "Affichage de la version de ou du {{entityLabel}} {{documentTitle}}", diff --git a/src/translations/th.json b/src/translations/th.json index 2d38689447..4f634c6197 100644 --- a/src/translations/th.json +++ b/src/translations/th.json @@ -189,7 +189,7 @@ "newPassword": "รหัสผ่านใหม่", "noFiltersSet": "ไม่มีการกรอง", "noLabel": "<ไม่มี {{label}}>", - "noResults": "ไม่พบ {{label}} อาจเป็นเพราะยังไม่มี {{label}} หรือไม่มี {{label}} ใดตรงกับการกร้องด้านบน", + "noResults": "ไม่พบ {{label}} เนื่องจากยังไม่มี {{label}} หรือไม่มี {{label}} ใดตรงกับการกรองด้านบน", "noValue": "ไม่มีค่า", "none": "ไม่มี", "notFound": "ไม่พบ", diff --git a/src/uploads/getBaseFields.ts b/src/uploads/getBaseFields.ts index 4693551b48..035db45730 100644 --- a/src/uploads/getBaseFields.ts +++ b/src/uploads/getBaseFields.ts @@ -21,7 +21,7 @@ const getBaseUploadFields = ({ config, collection }: Options): Field[] => { type: 'text', admin: { readOnly: true, - disabled: true, + hidden: true, }, }; @@ -31,7 +31,7 @@ const getBaseUploadFields = ({ config, collection }: Options): Field[] => { type: 'text', admin: { readOnly: true, - disabled: true, + hidden: true, }, }; @@ -41,7 +41,7 @@ const getBaseUploadFields = ({ config, collection }: Options): Field[] => { type: 'number', admin: { readOnly: true, - disabled: true, + hidden: true, }, }; @@ -51,7 +51,7 @@ const getBaseUploadFields = ({ config, collection }: Options): Field[] => { type: 'number', admin: { readOnly: true, - disabled: true, + hidden: true, }, }; @@ -61,7 +61,7 @@ const getBaseUploadFields = ({ config, collection }: Options): Field[] => { type: 'number', admin: { readOnly: true, - disabled: true, + hidden: true, }, }; @@ -73,7 +73,7 @@ const getBaseUploadFields = ({ config, collection }: Options): Field[] => { unique: true, admin: { readOnly: true, - disabled: true, + hidden: true, }, }; @@ -110,14 +110,14 @@ const getBaseUploadFields = ({ config, collection }: Options): Field[] => { label: labels['upload:Sizes'], type: 'group', admin: { - disabled: true, + hidden: true, }, fields: uploadOptions.imageSizes.map((size) => ({ label: size.name, name: size.name, type: 'group', admin: { - disabled: true, + hidden: true, }, fields: [ { diff --git a/test/devServer.ts b/test/devServer.ts index 9d452d9a1a..49138309ba 100644 --- a/test/devServer.ts +++ b/test/devServer.ts @@ -5,7 +5,7 @@ import payload from '../src'; const expressApp = express(); const init = async () => { - await payload.initAsync({ + await payload.init({ secret: uuid(), mongoURL: process.env.MONGO_URL || 'mongodb://localhost/payload', express: expressApp, diff --git a/test/fields/collections/Group/index.ts b/test/fields/collections/Group/index.ts index 2b7c78c407..617838f93a 100644 --- a/test/fields/collections/Group/index.ts +++ b/test/fields/collections/Group/index.ts @@ -67,6 +67,49 @@ const GroupFields: CollectionConfig = { }, ], }, + { + type: 'row', + fields: [ + { + name: 'groupInRow', + type: 'group', + fields: [ + { + name: 'field', + type: 'text', + }, + { + name: 'secondField', + type: 'text', + }, + { + name: 'thirdField', + type: 'text', + }, + ], + }, + { + name: 'secondGroupInRow', + type: 'group', + fields: [ + { + name: 'field', + type: 'text', + }, + { + name: 'nestedGroup', + type: 'group', + fields: [ + { + name: 'nestedField', + type: 'text', + }, + ], + }, + ], + }, + ], + }, ], }; diff --git a/test/fields/payload-types.ts b/test/fields/payload-types.ts index 9179ed9425..622484aa1a 100644 --- a/test/fields/payload-types.ts +++ b/test/fields/payload-types.ts @@ -282,6 +282,17 @@ export interface GroupField { potentiallyEmptyGroup: { text?: string; }; + groupInRow: { + field?: string; + secondField?: string; + thirdField?: string; + }; + secondGroupInRow: { + field?: string; + nestedGroup: { + nestedField?: string; + }; + }; createdAt: string; updatedAt: string; } diff --git a/test/helpers/configHelpers.ts b/test/helpers/configHelpers.ts index 3144a8921e..bd1d30e5cc 100644 --- a/test/helpers/configHelpers.ts +++ b/test/helpers/configHelpers.ts @@ -40,7 +40,7 @@ export async function initPayloadTest(options: Options): Promise<{ serverURL: st initOptions.express = express(); } - await payload.initAsync(initOptions); + await payload.init(initOptions); if (initOptions.express) { initOptions.express.listen(port); diff --git a/test/localization/config.ts b/test/localization/config.ts index 4fe4f9f8aa..11d9190e79 100644 --- a/test/localization/config.ts +++ b/test/localization/config.ts @@ -1,5 +1,6 @@ import { buildConfig } from '../buildConfig'; import { devUser } from '../credentials'; +import { GlobalArray } from './Array'; import { LocalizedPost, RelationshipLocalized } from './payload-types'; import { defaultLocale, @@ -196,6 +197,24 @@ export default buildConfig({ ], }, ], + globals: [ + { + slug: 'global-array', + fields: [ + { + name: 'array', + type: 'array', + fields: [ + { + name: 'text', + type: 'text', + localized: true, + }, + ], + }, + ], + }, + ], onInit: async (payload) => { const collection = slug; @@ -287,5 +306,30 @@ export default buildConfig({ ], }, }); + + const globalArray = await payload.updateGlobal({ + slug: 'global-array', + data: { + array: [ + { + text: 'test en 1', + }, + { + text: 'test en 2', + }, + ], + }, + }); + + await payload.updateGlobal({ + slug: 'global-array', + locale: 'es', + data: { + array: globalArray.array.map((row, i) => ({ + ...row, + text: `test es ${i + 1}`, + })), + }, + }); }, }); diff --git a/test/localization/int.spec.ts b/test/localization/int.spec.ts index c44808e52d..c017201089 100644 --- a/test/localization/int.spec.ts +++ b/test/localization/int.spec.ts @@ -7,6 +7,7 @@ import type { WithLocalizedRelationship, LocalizedRequired, RelationshipLocalized, + GlobalArray, } from './payload-types'; import type { LocalizedPostAllLocale } from './config'; import config, { relationshipLocalizedSlug, slug, withLocalizedRelSlug, withRequiredLocalizedFields } from './config'; @@ -477,6 +478,27 @@ describe('Localization', () => { }); }); + describe('Localized - arrays with nested localized fields', () => { + it('should allow moving rows and retain existing row locale data', async () => { + const globalArray = await payload.findGlobal({ + slug: 'global-array', + }); + + const reversedArrayRows = [...globalArray.array].reverse(); + + const updatedGlobal = await payload.updateGlobal({ + slug: 'global-array', + locale: 'all', + data: { + array: reversedArrayRows, + }, + }); + + expect(updatedGlobal.array[0].text.en).toStrictEqual('test en 2'); + expect(updatedGlobal.array[0].text.es).toStrictEqual('test es 2'); + }); + }); + describe('Localized - required', () => { it('should update without passing all required fields', async () => { const newDoc = await payload.create({ diff --git a/test/localization/payload-types.ts b/test/localization/payload-types.ts index d0ddb398dc..c7aa42b862 100644 --- a/test/localization/payload-types.ts +++ b/test/localization/payload-types.ts @@ -6,6 +6,17 @@ */ export interface Config {} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "global-array". + */ +export interface GlobalArray { + id: string; + array: { + text?: string; + id?: string; + }[]; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "users". diff --git a/tsconfig.json b/tsconfig.json index d7e93c627d..9772585039 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "target": "es2019", + "sourceMap": true, "module": "commonjs", "allowJs": true, /* Allow javascript files to be compiled. */ "checkJs": false, /* Report errors in .js files. */