perf: remove duplicative deep loops during field sanitization (#12402)
Optimizes the field sanitization process by removing duplicative deep loops over the config. We were previously iterating over all fields of each collection potentially multiple times in order validate field configs, check reserved field names, etc. Now, we perform all necessary sanitization within a single loop.
This commit is contained in:
@@ -1,151 +0,0 @@
|
|||||||
// @ts-strict-ignore
|
|
||||||
import type { Field } from '../../fields/config/types.js'
|
|
||||||
import type { CollectionConfig } from '../../index.js'
|
|
||||||
|
|
||||||
import { ReservedFieldName } from '../../errors/ReservedFieldName.js'
|
|
||||||
import { fieldAffectsData } from '../../fields/config/types.js'
|
|
||||||
|
|
||||||
// Note for future reference: We've slimmed down the reserved field names but left them in here for reference in case it's needed in the future.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reserved field names for collections with auth config enabled
|
|
||||||
*/
|
|
||||||
const reservedBaseAuthFieldNames = [
|
|
||||||
/* 'email',
|
|
||||||
'resetPasswordToken',
|
|
||||||
'resetPasswordExpiration', */
|
|
||||||
'salt',
|
|
||||||
'hash',
|
|
||||||
]
|
|
||||||
/**
|
|
||||||
* Reserved field names for auth collections with verify: true
|
|
||||||
*/
|
|
||||||
const reservedVerifyFieldNames = [
|
|
||||||
/* '_verified', '_verificationToken' */
|
|
||||||
]
|
|
||||||
/**
|
|
||||||
* Reserved field names for auth collections with useApiKey: true
|
|
||||||
*/
|
|
||||||
const reservedAPIKeyFieldNames = [
|
|
||||||
/* 'enableAPIKey', 'apiKeyIndex', 'apiKey' */
|
|
||||||
]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reserved field names for collections with upload config enabled
|
|
||||||
*/
|
|
||||||
const reservedBaseUploadFieldNames = [
|
|
||||||
'file',
|
|
||||||
/* 'mimeType',
|
|
||||||
'thumbnailURL',
|
|
||||||
'width',
|
|
||||||
'height',
|
|
||||||
'filesize',
|
|
||||||
'filename',
|
|
||||||
'url',
|
|
||||||
'focalX',
|
|
||||||
'focalY',
|
|
||||||
'sizes', */
|
|
||||||
]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reserved field names for collections with versions enabled
|
|
||||||
*/
|
|
||||||
const reservedVersionsFieldNames = [
|
|
||||||
/* '__v', '_status' */
|
|
||||||
]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sanitize fields for collections with auth config enabled.
|
|
||||||
*
|
|
||||||
* Should run on top level fields only.
|
|
||||||
*/
|
|
||||||
export const sanitizeAuthFields = (fields: Field[], config: CollectionConfig) => {
|
|
||||||
for (let i = 0; i < fields.length; i++) {
|
|
||||||
const field = fields[i]
|
|
||||||
|
|
||||||
if (fieldAffectsData(field) && field.name) {
|
|
||||||
if (config.auth && typeof config.auth === 'object' && !config.auth.disableLocalStrategy) {
|
|
||||||
const auth = config.auth
|
|
||||||
|
|
||||||
if (reservedBaseAuthFieldNames.includes(field.name)) {
|
|
||||||
throw new ReservedFieldName(field, field.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auth.verify) {
|
|
||||||
if (reservedAPIKeyFieldNames.includes(field.name)) {
|
|
||||||
throw new ReservedFieldName(field, field.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* if (auth.maxLoginAttempts) {
|
|
||||||
if (field.name === 'loginAttempts' || field.name === 'lockUntil') {
|
|
||||||
throw new ReservedFieldName(field, field.name)
|
|
||||||
}
|
|
||||||
} */
|
|
||||||
|
|
||||||
/* if (auth.loginWithUsername) {
|
|
||||||
if (field.name === 'username') {
|
|
||||||
throw new ReservedFieldName(field, field.name)
|
|
||||||
}
|
|
||||||
} */
|
|
||||||
|
|
||||||
if (auth.verify) {
|
|
||||||
if (reservedVerifyFieldNames.includes(field.name)) {
|
|
||||||
throw new ReservedFieldName(field, field.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle tabs without a name
|
|
||||||
if (field.type === 'tabs') {
|
|
||||||
for (let j = 0; j < field.tabs.length; j++) {
|
|
||||||
const tab = field.tabs[j]
|
|
||||||
|
|
||||||
if (!('name' in tab)) {
|
|
||||||
sanitizeAuthFields(tab.fields, config)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle presentational fields like rows and collapsibles
|
|
||||||
if (!fieldAffectsData(field) && 'fields' in field && field.fields) {
|
|
||||||
sanitizeAuthFields(field.fields, config)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sanitize fields for collections with upload config enabled.
|
|
||||||
*
|
|
||||||
* Should run on top level fields only.
|
|
||||||
*/
|
|
||||||
export const sanitizeUploadFields = (fields: Field[], config: CollectionConfig) => {
|
|
||||||
if (config.upload && typeof config.upload === 'object') {
|
|
||||||
for (let i = 0; i < fields.length; i++) {
|
|
||||||
const field = fields[i]
|
|
||||||
|
|
||||||
if (fieldAffectsData(field) && field.name) {
|
|
||||||
if (reservedBaseUploadFieldNames.includes(field.name)) {
|
|
||||||
throw new ReservedFieldName(field, field.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle tabs without a name
|
|
||||||
if (field.type === 'tabs') {
|
|
||||||
for (let j = 0; j < field.tabs.length; j++) {
|
|
||||||
const tab = field.tabs[j]
|
|
||||||
|
|
||||||
if (!('name' in tab)) {
|
|
||||||
sanitizeUploadFields(tab.fields, config)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle presentational fields like rows and collapsibles
|
|
||||||
if (!fieldAffectsData(field) && 'fields' in field && field.fields) {
|
|
||||||
sanitizeUploadFields(field.fields, config)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -26,7 +26,6 @@ import {
|
|||||||
addDefaultsToCollectionConfig,
|
addDefaultsToCollectionConfig,
|
||||||
addDefaultsToLoginWithUsernameConfig,
|
addDefaultsToLoginWithUsernameConfig,
|
||||||
} from './defaults.js'
|
} from './defaults.js'
|
||||||
import { sanitizeAuthFields, sanitizeUploadFields } from './reservedFieldNames.js'
|
|
||||||
import { sanitizeCompoundIndexes } from './sanitizeCompoundIndexes.js'
|
import { sanitizeCompoundIndexes } from './sanitizeCompoundIndexes.js'
|
||||||
import { validateUseAsTitle } from './useAsTitle.js'
|
import { validateUseAsTitle } from './useAsTitle.js'
|
||||||
|
|
||||||
@@ -43,7 +42,9 @@ export const sanitizeCollection = async (
|
|||||||
if (collection._sanitized) {
|
if (collection._sanitized) {
|
||||||
return collection as SanitizedCollectionConfig
|
return collection as SanitizedCollectionConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
collection._sanitized = true
|
collection._sanitized = true
|
||||||
|
|
||||||
// /////////////////////////////////
|
// /////////////////////////////////
|
||||||
// Make copy of collection config
|
// Make copy of collection config
|
||||||
// /////////////////////////////////
|
// /////////////////////////////////
|
||||||
@@ -57,7 +58,9 @@ export const sanitizeCollection = async (
|
|||||||
const validRelationships = _validRelationships ?? config.collections.map((c) => c.slug) ?? []
|
const validRelationships = _validRelationships ?? config.collections.map((c) => c.slug) ?? []
|
||||||
|
|
||||||
const joins: SanitizedJoins = {}
|
const joins: SanitizedJoins = {}
|
||||||
|
|
||||||
const polymorphicJoins: SanitizedJoin[] = []
|
const polymorphicJoins: SanitizedJoin[] = []
|
||||||
|
|
||||||
sanitized.fields = await sanitizeFields({
|
sanitized.fields = await sanitizeFields({
|
||||||
collectionConfig: sanitized,
|
collectionConfig: sanitized,
|
||||||
config,
|
config,
|
||||||
@@ -96,17 +99,21 @@ export const sanitizeCollection = async (
|
|||||||
// add default timestamps fields only as needed
|
// add default timestamps fields only as needed
|
||||||
let hasUpdatedAt: boolean | null = null
|
let hasUpdatedAt: boolean | null = null
|
||||||
let hasCreatedAt: boolean | null = null
|
let hasCreatedAt: boolean | null = null
|
||||||
|
|
||||||
sanitized.fields.some((field) => {
|
sanitized.fields.some((field) => {
|
||||||
if (fieldAffectsData(field)) {
|
if (fieldAffectsData(field)) {
|
||||||
if (field.name === 'updatedAt') {
|
if (field.name === 'updatedAt') {
|
||||||
hasUpdatedAt = true
|
hasUpdatedAt = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.name === 'createdAt') {
|
if (field.name === 'createdAt') {
|
||||||
hasCreatedAt = true
|
hasCreatedAt = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return hasCreatedAt && hasUpdatedAt
|
return hasCreatedAt && hasUpdatedAt
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!hasUpdatedAt) {
|
if (!hasUpdatedAt) {
|
||||||
sanitized.fields.push({
|
sanitized.fields.push({
|
||||||
name: 'updatedAt',
|
name: 'updatedAt',
|
||||||
@@ -119,6 +126,7 @@ export const sanitizeCollection = async (
|
|||||||
label: ({ t }) => t('general:updatedAt'),
|
label: ({ t }) => t('general:updatedAt'),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasCreatedAt) {
|
if (!hasCreatedAt) {
|
||||||
sanitized.fields.push({
|
sanitized.fields.push({
|
||||||
name: 'createdAt',
|
name: 'createdAt',
|
||||||
@@ -175,9 +183,6 @@ export const sanitizeCollection = async (
|
|||||||
sanitized.upload = {}
|
sanitized.upload = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sanitize fields for reserved names
|
|
||||||
sanitizeUploadFields(sanitized.fields, sanitized)
|
|
||||||
|
|
||||||
sanitized.upload.cacheTags = sanitized.upload?.cacheTags ?? true
|
sanitized.upload.cacheTags = sanitized.upload?.cacheTags ?? true
|
||||||
sanitized.upload.bulkUpload = sanitized.upload?.bulkUpload ?? true
|
sanitized.upload.bulkUpload = sanitized.upload?.bulkUpload ?? true
|
||||||
sanitized.upload.staticDir = sanitized.upload.staticDir || sanitized.slug
|
sanitized.upload.staticDir = sanitized.upload.staticDir || sanitized.slug
|
||||||
@@ -195,9 +200,6 @@ export const sanitizeCollection = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sanitized.auth) {
|
if (sanitized.auth) {
|
||||||
// sanitize fields for reserved names
|
|
||||||
sanitizeAuthFields(sanitized.fields, sanitized)
|
|
||||||
|
|
||||||
sanitized.auth = addDefaultsToAuthConfig(
|
sanitized.auth = addDefaultsToAuthConfig(
|
||||||
typeof sanitized.auth === 'boolean' ? {} : sanitized.auth,
|
typeof sanitized.auth === 'boolean' ? {} : sanitized.auth,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { CollectionConfig } from '../../index.js'
|
import type { CollectionConfig } from '../../index.js'
|
||||||
|
|
||||||
import { InvalidConfiguration } from '../../errors/InvalidConfiguration.js'
|
import { InvalidConfiguration } from '../../errors/InvalidConfiguration.js'
|
||||||
import { fieldAffectsData, fieldIsVirtual } from '../../fields/config/types.js'
|
import { fieldAffectsData } from '../../fields/config/types.js'
|
||||||
import flattenFields from '../../utilities/flattenTopLevelFields.js'
|
import flattenFields from '../../utilities/flattenTopLevelFields.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ const sanitizeAdminConfig = (configToSanitize: Config): Partial<SanitizedConfig>
|
|||||||
// add default user collection if none provided
|
// add default user collection if none provided
|
||||||
if (!sanitizedConfig?.admin?.user) {
|
if (!sanitizedConfig?.admin?.user) {
|
||||||
const firstCollectionWithAuth = sanitizedConfig.collections.find(({ auth }) => Boolean(auth))
|
const firstCollectionWithAuth = sanitizedConfig.collections.find(({ auth }) => Boolean(auth))
|
||||||
|
|
||||||
if (firstCollectionWithAuth) {
|
if (firstCollectionWithAuth) {
|
||||||
sanitizedConfig.admin.user = firstCollectionWithAuth.slug
|
sanitizedConfig.admin.user = firstCollectionWithAuth.slug
|
||||||
} else {
|
} else {
|
||||||
@@ -69,6 +70,7 @@ const sanitizeAdminConfig = (configToSanitize: Config): Partial<SanitizedConfig>
|
|||||||
const userCollection = sanitizedConfig.collections.find(
|
const userCollection = sanitizedConfig.collections.find(
|
||||||
({ slug }) => slug === sanitizedConfig.admin.user,
|
({ slug }) => slug === sanitizedConfig.admin.user,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!userCollection || !userCollection.auth) {
|
if (!userCollection || !userCollection.auth) {
|
||||||
throw new InvalidConfiguration(
|
throw new InvalidConfiguration(
|
||||||
`${sanitizedConfig.admin.user} is not a valid admin user collection`,
|
`${sanitizedConfig.admin.user} is not a valid admin user collection`,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Config } from '../../config/types.js'
|
|||||||
import type { CollectionConfig, Field } from '../../index.js'
|
import type { CollectionConfig, Field } from '../../index.js'
|
||||||
|
|
||||||
import { ReservedFieldName } from '../../errors/index.js'
|
import { ReservedFieldName } from '../../errors/index.js'
|
||||||
import { sanitizeCollection } from './sanitize.js'
|
import { sanitizeCollection } from '../../collections/config/sanitize.js'
|
||||||
|
|
||||||
describe('reservedFieldNames - collections -', () => {
|
describe('reservedFieldNames - collections -', () => {
|
||||||
const config = {
|
const config = {
|
||||||
@@ -25,6 +25,7 @@ describe('reservedFieldNames - collections -', () => {
|
|||||||
label: 'some-collection',
|
label: 'some-collection',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
await expect(async () => {
|
await expect(async () => {
|
||||||
await sanitizeCollection(
|
await sanitizeCollection(
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
@@ -53,6 +54,7 @@ describe('reservedFieldNames - collections -', () => {
|
|||||||
label: 'some-collection',
|
label: 'some-collection',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
await expect(async () => {
|
await expect(async () => {
|
||||||
await sanitizeCollection(
|
await sanitizeCollection(
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
@@ -93,6 +95,7 @@ describe('reservedFieldNames - collections -', () => {
|
|||||||
label: 'some-collection',
|
label: 'some-collection',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
await expect(async () => {
|
await expect(async () => {
|
||||||
await sanitizeCollection(
|
await sanitizeCollection(
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
@@ -121,6 +124,7 @@ describe('reservedFieldNames - collections -', () => {
|
|||||||
label: 'some-collection',
|
label: 'some-collection',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
await expect(async () => {
|
await expect(async () => {
|
||||||
await sanitizeCollection(
|
await sanitizeCollection(
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
@@ -149,6 +153,7 @@ describe('reservedFieldNames - collections -', () => {
|
|||||||
label: 'some-collection',
|
label: 'some-collection',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
await expect(async () => {
|
await expect(async () => {
|
||||||
await sanitizeCollection(
|
await sanitizeCollection(
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
48
packages/payload/src/fields/config/reservedFieldNames.ts
Normal file
48
packages/payload/src/fields/config/reservedFieldNames.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* Reserved field names for collections with auth config enabled
|
||||||
|
*/
|
||||||
|
export const reservedBaseAuthFieldNames = [
|
||||||
|
/* 'email',
|
||||||
|
'resetPasswordToken',
|
||||||
|
'resetPasswordExpiration', */
|
||||||
|
'salt',
|
||||||
|
'hash',
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reserved field names for auth collections with verify: true
|
||||||
|
*/
|
||||||
|
export const reservedVerifyFieldNames = [
|
||||||
|
/* '_verified', '_verificationToken' */
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reserved field names for auth collections with useApiKey: true
|
||||||
|
*/
|
||||||
|
export const reservedAPIKeyFieldNames = [
|
||||||
|
/* 'enableAPIKey', 'apiKeyIndex', 'apiKey' */
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reserved field names for collections with upload config enabled
|
||||||
|
*/
|
||||||
|
export const reservedBaseUploadFieldNames = [
|
||||||
|
'file',
|
||||||
|
/* 'mimeType',
|
||||||
|
'thumbnailURL',
|
||||||
|
'width',
|
||||||
|
'height',
|
||||||
|
'filesize',
|
||||||
|
'filename',
|
||||||
|
'url',
|
||||||
|
'focalX',
|
||||||
|
'focalY',
|
||||||
|
'sizes', */
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reserved field names for collections with versions enabled
|
||||||
|
*/
|
||||||
|
export const reservedVersionsFieldNames = [
|
||||||
|
/* '__v', '_status' */
|
||||||
|
]
|
||||||
@@ -11,9 +11,12 @@ import type {
|
|||||||
|
|
||||||
import { InvalidFieldName, InvalidFieldRelationship, MissingFieldType } from '../../errors/index.js'
|
import { InvalidFieldName, InvalidFieldRelationship, MissingFieldType } from '../../errors/index.js'
|
||||||
import { sanitizeFields } from './sanitize.js'
|
import { sanitizeFields } from './sanitize.js'
|
||||||
|
import { CollectionConfig } from '../../index.js'
|
||||||
|
|
||||||
describe('sanitizeFields', () => {
|
describe('sanitizeFields', () => {
|
||||||
const config = {} as Config
|
const config = {} as Config
|
||||||
|
const collectionConfig = {} as CollectionConfig
|
||||||
|
|
||||||
it('should throw on missing type field', async () => {
|
it('should throw on missing type field', async () => {
|
||||||
const fields: Field[] = [
|
const fields: Field[] = [
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
@@ -22,14 +25,17 @@ describe('sanitizeFields', () => {
|
|||||||
label: 'some-collection',
|
label: 'some-collection',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
await expect(async () => {
|
await expect(async () => {
|
||||||
await sanitizeFields({
|
await sanitizeFields({
|
||||||
config,
|
config,
|
||||||
|
collectionConfig,
|
||||||
fields,
|
fields,
|
||||||
validRelationships: [],
|
validRelationships: [],
|
||||||
})
|
})
|
||||||
}).rejects.toThrow(MissingFieldType)
|
}).rejects.toThrow(MissingFieldType)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should throw on invalid field name', async () => {
|
it('should throw on invalid field name', async () => {
|
||||||
const fields: Field[] = [
|
const fields: Field[] = [
|
||||||
{
|
{
|
||||||
@@ -38,9 +44,11 @@ describe('sanitizeFields', () => {
|
|||||||
label: 'some.collection',
|
label: 'some.collection',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
await expect(async () => {
|
await expect(async () => {
|
||||||
await sanitizeFields({
|
await sanitizeFields({
|
||||||
config,
|
config,
|
||||||
|
collectionConfig,
|
||||||
fields,
|
fields,
|
||||||
validRelationships: [],
|
validRelationships: [],
|
||||||
})
|
})
|
||||||
@@ -55,17 +63,21 @@ describe('sanitizeFields', () => {
|
|||||||
type: 'text',
|
type: 'text',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const sanitizedField = (
|
const sanitizedField = (
|
||||||
await sanitizeFields({
|
await sanitizeFields({
|
||||||
config,
|
config,
|
||||||
|
collectionConfig,
|
||||||
fields,
|
fields,
|
||||||
validRelationships: [],
|
validRelationships: [],
|
||||||
})
|
})
|
||||||
)[0] as TextField
|
)[0] as TextField
|
||||||
|
|
||||||
expect(sanitizedField.name).toStrictEqual('someField')
|
expect(sanitizedField.name).toStrictEqual('someField')
|
||||||
expect(sanitizedField.label).toStrictEqual('Some Field')
|
expect(sanitizedField.label).toStrictEqual('Some Field')
|
||||||
expect(sanitizedField.type).toStrictEqual('text')
|
expect(sanitizedField.type).toStrictEqual('text')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should allow auto-label override', async () => {
|
it('should allow auto-label override', async () => {
|
||||||
const fields: Field[] = [
|
const fields: Field[] = [
|
||||||
{
|
{
|
||||||
@@ -74,13 +86,16 @@ describe('sanitizeFields', () => {
|
|||||||
label: 'Do not label',
|
label: 'Do not label',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const sanitizedField = (
|
const sanitizedField = (
|
||||||
await sanitizeFields({
|
await sanitizeFields({
|
||||||
config,
|
config,
|
||||||
|
collectionConfig,
|
||||||
fields,
|
fields,
|
||||||
validRelationships: [],
|
validRelationships: [],
|
||||||
})
|
})
|
||||||
)[0] as TextField
|
)[0] as TextField
|
||||||
|
|
||||||
expect(sanitizedField.name).toStrictEqual('someField')
|
expect(sanitizedField.name).toStrictEqual('someField')
|
||||||
expect(sanitizedField.label).toStrictEqual('Do not label')
|
expect(sanitizedField.label).toStrictEqual('Do not label')
|
||||||
expect(sanitizedField.type).toStrictEqual('text')
|
expect(sanitizedField.type).toStrictEqual('text')
|
||||||
@@ -95,13 +110,16 @@ describe('sanitizeFields', () => {
|
|||||||
label: false,
|
label: false,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const sanitizedField = (
|
const sanitizedField = (
|
||||||
await sanitizeFields({
|
await sanitizeFields({
|
||||||
config,
|
config,
|
||||||
|
collectionConfig,
|
||||||
fields,
|
fields,
|
||||||
validRelationships: [],
|
validRelationships: [],
|
||||||
})
|
})
|
||||||
)[0] as TextField
|
)[0] as TextField
|
||||||
|
|
||||||
expect(sanitizedField.name).toStrictEqual('someField')
|
expect(sanitizedField.name).toStrictEqual('someField')
|
||||||
expect(sanitizedField.label).toStrictEqual(false)
|
expect(sanitizedField.label).toStrictEqual(false)
|
||||||
expect(sanitizedField.type).toStrictEqual('text')
|
expect(sanitizedField.type).toStrictEqual('text')
|
||||||
@@ -119,18 +137,22 @@ describe('sanitizeFields', () => {
|
|||||||
],
|
],
|
||||||
label: false,
|
label: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
const sanitizedField = (
|
const sanitizedField = (
|
||||||
await sanitizeFields({
|
await sanitizeFields({
|
||||||
config,
|
config,
|
||||||
|
collectionConfig,
|
||||||
fields: [arrayField],
|
fields: [arrayField],
|
||||||
validRelationships: [],
|
validRelationships: [],
|
||||||
})
|
})
|
||||||
)[0] as ArrayField
|
)[0] as ArrayField
|
||||||
|
|
||||||
expect(sanitizedField.name).toStrictEqual('items')
|
expect(sanitizedField.name).toStrictEqual('items')
|
||||||
expect(sanitizedField.label).toStrictEqual(false)
|
expect(sanitizedField.label).toStrictEqual(false)
|
||||||
expect(sanitizedField.type).toStrictEqual('array')
|
expect(sanitizedField.type).toStrictEqual('array')
|
||||||
expect(sanitizedField.labels).toBeUndefined()
|
expect(sanitizedField.labels).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should allow label opt-out for blocks', async () => {
|
it('should allow label opt-out for blocks', async () => {
|
||||||
const fields: Field[] = [
|
const fields: Field[] = [
|
||||||
{
|
{
|
||||||
@@ -150,13 +172,16 @@ describe('sanitizeFields', () => {
|
|||||||
label: false,
|
label: false,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const sanitizedField = (
|
const sanitizedField = (
|
||||||
await sanitizeFields({
|
await sanitizeFields({
|
||||||
config,
|
config,
|
||||||
|
collectionConfig,
|
||||||
fields,
|
fields,
|
||||||
validRelationships: [],
|
validRelationships: [],
|
||||||
})
|
})
|
||||||
)[0] as BlocksField
|
)[0] as BlocksField
|
||||||
|
|
||||||
expect(sanitizedField.name).toStrictEqual('noLabelBlock')
|
expect(sanitizedField.name).toStrictEqual('noLabelBlock')
|
||||||
expect(sanitizedField.label).toStrictEqual(false)
|
expect(sanitizedField.label).toStrictEqual(false)
|
||||||
expect(sanitizedField.type).toStrictEqual('blocks')
|
expect(sanitizedField.type).toStrictEqual('blocks')
|
||||||
@@ -177,13 +202,16 @@ describe('sanitizeFields', () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const sanitizedField = (
|
const sanitizedField = (
|
||||||
await sanitizeFields({
|
await sanitizeFields({
|
||||||
config,
|
config,
|
||||||
|
collectionConfig,
|
||||||
fields,
|
fields,
|
||||||
validRelationships: [],
|
validRelationships: [],
|
||||||
})
|
})
|
||||||
)[0] as ArrayField
|
)[0] as ArrayField
|
||||||
|
|
||||||
expect(sanitizedField.name).toStrictEqual('items')
|
expect(sanitizedField.name).toStrictEqual('items')
|
||||||
expect(sanitizedField.label).toStrictEqual('Items')
|
expect(sanitizedField.label).toStrictEqual('Items')
|
||||||
expect(sanitizedField.type).toStrictEqual('array')
|
expect(sanitizedField.type).toStrictEqual('array')
|
||||||
@@ -203,13 +231,16 @@ describe('sanitizeFields', () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const sanitizedField = (
|
const sanitizedField = (
|
||||||
await sanitizeFields({
|
await sanitizeFields({
|
||||||
config,
|
config,
|
||||||
|
collectionConfig,
|
||||||
fields,
|
fields,
|
||||||
validRelationships: [],
|
validRelationships: [],
|
||||||
})
|
})
|
||||||
)[0] as BlocksField
|
)[0] as BlocksField
|
||||||
|
|
||||||
expect(sanitizedField.name).toStrictEqual('specialBlock')
|
expect(sanitizedField.name).toStrictEqual('specialBlock')
|
||||||
expect(sanitizedField.label).toStrictEqual('Special Block')
|
expect(sanitizedField.label).toStrictEqual('Special Block')
|
||||||
expect(sanitizedField.type).toStrictEqual('blocks')
|
expect(sanitizedField.type).toStrictEqual('blocks')
|
||||||
@@ -217,6 +248,7 @@ describe('sanitizeFields', () => {
|
|||||||
plural: 'Special Blocks',
|
plural: 'Special Blocks',
|
||||||
singular: 'Special Block',
|
singular: 'Special Block',
|
||||||
})
|
})
|
||||||
|
|
||||||
expect((sanitizedField.blocks[0].fields[0] as NumberField).label).toStrictEqual('Test Number')
|
expect((sanitizedField.blocks[0].fields[0] as NumberField).label).toStrictEqual('Test Number')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -232,8 +264,9 @@ describe('sanitizeFields', () => {
|
|||||||
relationTo: 'some-collection',
|
relationTo: 'some-collection',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
await expect(async () => {
|
await expect(async () => {
|
||||||
await sanitizeFields({ config, fields, validRelationships })
|
await sanitizeFields({ config, collectionConfig, fields, validRelationships })
|
||||||
}).not.toThrow()
|
}).not.toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -247,8 +280,9 @@ describe('sanitizeFields', () => {
|
|||||||
relationTo: ['some-collection', 'another-collection'],
|
relationTo: ['some-collection', 'another-collection'],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
await expect(async () => {
|
await expect(async () => {
|
||||||
await sanitizeFields({ config, fields, validRelationships })
|
await sanitizeFields({ config, collectionConfig, fields, validRelationships })
|
||||||
}).not.toThrow()
|
}).not.toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -265,6 +299,7 @@ describe('sanitizeFields', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
const fields: Field[] = [
|
const fields: Field[] = [
|
||||||
{
|
{
|
||||||
name: 'layout',
|
name: 'layout',
|
||||||
@@ -273,8 +308,9 @@ describe('sanitizeFields', () => {
|
|||||||
label: 'Layout Blocks',
|
label: 'Layout Blocks',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
await expect(async () => {
|
await expect(async () => {
|
||||||
await sanitizeFields({ config, fields, validRelationships })
|
await sanitizeFields({ config, collectionConfig, fields, validRelationships })
|
||||||
}).not.toThrow()
|
}).not.toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -288,8 +324,9 @@ describe('sanitizeFields', () => {
|
|||||||
relationTo: 'not-valid',
|
relationTo: 'not-valid',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
await expect(async () => {
|
await expect(async () => {
|
||||||
await sanitizeFields({ config, fields, validRelationships })
|
await sanitizeFields({ config, collectionConfig, fields, validRelationships })
|
||||||
}).rejects.toThrow(InvalidFieldRelationship)
|
}).rejects.toThrow(InvalidFieldRelationship)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -303,8 +340,9 @@ describe('sanitizeFields', () => {
|
|||||||
relationTo: ['some-collection', 'not-valid'],
|
relationTo: ['some-collection', 'not-valid'],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
await expect(async () => {
|
await expect(async () => {
|
||||||
await sanitizeFields({ config, fields, validRelationships })
|
await sanitizeFields({ config, collectionConfig, fields, validRelationships })
|
||||||
}).rejects.toThrow(InvalidFieldRelationship)
|
}).rejects.toThrow(InvalidFieldRelationship)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -321,6 +359,7 @@ describe('sanitizeFields', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
const fields: Field[] = [
|
const fields: Field[] = [
|
||||||
{
|
{
|
||||||
name: 'layout',
|
name: 'layout',
|
||||||
@@ -329,8 +368,9 @@ describe('sanitizeFields', () => {
|
|||||||
label: 'Layout Blocks',
|
label: 'Layout Blocks',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
await expect(async () => {
|
await expect(async () => {
|
||||||
await sanitizeFields({ config, fields, validRelationships })
|
await sanitizeFields({ config, collectionConfig, fields, validRelationships })
|
||||||
}).rejects.toThrow(InvalidFieldRelationship)
|
}).rejects.toThrow(InvalidFieldRelationship)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -346,19 +386,23 @@ describe('sanitizeFields', () => {
|
|||||||
const sanitizedField = (
|
const sanitizedField = (
|
||||||
await sanitizeFields({
|
await sanitizeFields({
|
||||||
config,
|
config,
|
||||||
|
collectionConfig,
|
||||||
fields,
|
fields,
|
||||||
validRelationships: [],
|
validRelationships: [],
|
||||||
})
|
})
|
||||||
)[0] as CheckboxField
|
)[0] as CheckboxField
|
||||||
|
|
||||||
expect(sanitizedField.defaultValue).toStrictEqual(false)
|
expect(sanitizedField.defaultValue).toStrictEqual(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return empty field array if no fields', async () => {
|
it('should return empty field array if no fields', async () => {
|
||||||
const sanitizedFields = await sanitizeFields({
|
const sanitizedFields = await sanitizeFields({
|
||||||
config,
|
config,
|
||||||
|
collectionConfig,
|
||||||
fields: [],
|
fields: [],
|
||||||
validRelationships: [],
|
validRelationships: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(sanitizedFields).toStrictEqual([])
|
expect(sanitizedFields).toStrictEqual([])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -385,9 +429,11 @@ describe('sanitizeFields', () => {
|
|||||||
label: false,
|
label: false,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const sanitizedField = (
|
const sanitizedField = (
|
||||||
await sanitizeFields({
|
await sanitizeFields({
|
||||||
config,
|
config,
|
||||||
|
collectionConfig,
|
||||||
fields,
|
fields,
|
||||||
validRelationships: [],
|
validRelationships: [],
|
||||||
})
|
})
|
||||||
@@ -416,9 +462,11 @@ describe('sanitizeFields', () => {
|
|||||||
label: false,
|
label: false,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const sanitizedField = (
|
const sanitizedField = (
|
||||||
await sanitizeFields({
|
await sanitizeFields({
|
||||||
config,
|
config,
|
||||||
|
collectionConfig,
|
||||||
fields,
|
fields,
|
||||||
validRelationships: [],
|
validRelationships: [],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
MissingEditorProp,
|
MissingEditorProp,
|
||||||
MissingFieldType,
|
MissingFieldType,
|
||||||
} from '../../errors/index.js'
|
} from '../../errors/index.js'
|
||||||
|
import { ReservedFieldName } from '../../errors/ReservedFieldName.js'
|
||||||
import { formatLabels, toWords } from '../../utilities/formatLabels.js'
|
import { formatLabels, toWords } from '../../utilities/formatLabels.js'
|
||||||
import { baseBlockFields } from '../baseFields/baseBlockFields.js'
|
import { baseBlockFields } from '../baseFields/baseBlockFields.js'
|
||||||
import { baseIDField } from '../baseFields/baseIDField.js'
|
import { baseIDField } from '../baseFields/baseIDField.js'
|
||||||
@@ -24,14 +25,24 @@ import { baseTimezoneField } from '../baseFields/timezone/baseField.js'
|
|||||||
import { defaultTimezones } from '../baseFields/timezone/defaultTimezones.js'
|
import { defaultTimezones } from '../baseFields/timezone/defaultTimezones.js'
|
||||||
import { setDefaultBeforeDuplicate } from '../setDefaultBeforeDuplicate.js'
|
import { setDefaultBeforeDuplicate } from '../setDefaultBeforeDuplicate.js'
|
||||||
import { validations } from '../validations.js'
|
import { validations } from '../validations.js'
|
||||||
|
import {
|
||||||
|
reservedAPIKeyFieldNames,
|
||||||
|
reservedBaseAuthFieldNames,
|
||||||
|
reservedBaseUploadFieldNames,
|
||||||
|
reservedVerifyFieldNames,
|
||||||
|
} from './reservedFieldNames.js'
|
||||||
import { sanitizeJoinField } from './sanitizeJoinField.js'
|
import { sanitizeJoinField } from './sanitizeJoinField.js'
|
||||||
import { fieldAffectsData, fieldIsLocalized, tabHasName } from './types.js'
|
import { fieldAffectsData as _fieldAffectsData, fieldIsLocalized, tabHasName } from './types.js'
|
||||||
|
|
||||||
type Args = {
|
type Args = {
|
||||||
collectionConfig?: CollectionConfig
|
collectionConfig?: CollectionConfig
|
||||||
config: Config
|
config: Config
|
||||||
existingFieldNames?: Set<string>
|
existingFieldNames?: Set<string>
|
||||||
fields: Field[]
|
fields: Field[]
|
||||||
|
/**
|
||||||
|
* Used to prevent unnecessary sanitization of fields that are not top-level.
|
||||||
|
*/
|
||||||
|
isTopLevelField?: boolean
|
||||||
joinPath?: string
|
joinPath?: string
|
||||||
/**
|
/**
|
||||||
* When not passed in, assume that join are not supported (globals, arrays, blocks)
|
* When not passed in, assume that join are not supported (globals, arrays, blocks)
|
||||||
@@ -39,7 +50,6 @@ type Args = {
|
|||||||
joins?: SanitizedJoins
|
joins?: SanitizedJoins
|
||||||
parentIsLocalized: boolean
|
parentIsLocalized: boolean
|
||||||
polymorphicJoins?: SanitizedJoin[]
|
polymorphicJoins?: SanitizedJoin[]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If true, a richText field will require an editor property to be set, as the sanitizeFields function will not add it from the payload config if not present.
|
* If true, a richText field will require an editor property to be set, as the sanitizeFields function will not add it from the payload config if not present.
|
||||||
*
|
*
|
||||||
@@ -59,9 +69,11 @@ type Args = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const sanitizeFields = async ({
|
export const sanitizeFields = async ({
|
||||||
|
collectionConfig,
|
||||||
config,
|
config,
|
||||||
existingFieldNames = new Set(),
|
existingFieldNames = new Set(),
|
||||||
fields,
|
fields,
|
||||||
|
isTopLevelField = true,
|
||||||
joinPath = '',
|
joinPath = '',
|
||||||
joins,
|
joins,
|
||||||
parentIsLocalized,
|
parentIsLocalized,
|
||||||
@@ -80,6 +92,7 @@ export const sanitizeFields = async ({
|
|||||||
if ('_sanitized' in field && field._sanitized === true) {
|
if ('_sanitized' in field && field._sanitized === true) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('_sanitized' in field) {
|
if ('_sanitized' in field) {
|
||||||
field._sanitized = true
|
field._sanitized = true
|
||||||
}
|
}
|
||||||
@@ -88,8 +101,39 @@ export const sanitizeFields = async ({
|
|||||||
throw new MissingFieldType(field)
|
throw new MissingFieldType(field)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fieldAffectsData = _fieldAffectsData(field)
|
||||||
|
|
||||||
|
if (isTopLevelField && fieldAffectsData && field.name) {
|
||||||
|
if (collectionConfig && collectionConfig.upload) {
|
||||||
|
if (reservedBaseUploadFieldNames.includes(field.name)) {
|
||||||
|
throw new ReservedFieldName(field, field.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
collectionConfig &&
|
||||||
|
collectionConfig.auth &&
|
||||||
|
typeof collectionConfig.auth === 'object' &&
|
||||||
|
!collectionConfig.auth.disableLocalStrategy
|
||||||
|
) {
|
||||||
|
if (reservedBaseAuthFieldNames.includes(field.name)) {
|
||||||
|
throw new ReservedFieldName(field, field.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (collectionConfig.auth.verify) {
|
||||||
|
if (reservedAPIKeyFieldNames.includes(field.name)) {
|
||||||
|
throw new ReservedFieldName(field, field.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reservedVerifyFieldNames.includes(field.name)) {
|
||||||
|
throw new ReservedFieldName(field, field.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// assert that field names do not contain forbidden characters
|
// assert that field names do not contain forbidden characters
|
||||||
if (fieldAffectsData(field) && field.name.includes('.')) {
|
if (fieldAffectsData && field.name.includes('.')) {
|
||||||
throw new InvalidFieldName(field, field.name)
|
throw new InvalidFieldName(field, field.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +166,7 @@ export const sanitizeFields = async ({
|
|||||||
const relationships = Array.isArray(field.relationTo)
|
const relationships = Array.isArray(field.relationTo)
|
||||||
? field.relationTo
|
? field.relationTo
|
||||||
: [field.relationTo]
|
: [field.relationTo]
|
||||||
|
|
||||||
relationships.forEach((relationship: string) => {
|
relationships.forEach((relationship: string) => {
|
||||||
if (!validRelationships.includes(relationship)) {
|
if (!validRelationships.includes(relationship)) {
|
||||||
throw new InvalidFieldRelationship(field, relationship)
|
throw new InvalidFieldRelationship(field, relationship)
|
||||||
@@ -135,6 +180,7 @@ export const sanitizeFields = async ({
|
|||||||
)
|
)
|
||||||
field.minRows = field.min
|
field.minRows = field.min
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.max && !field.maxRows) {
|
if (field.max && !field.maxRows) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`(payload): The "max" property is deprecated for the Relationship field "${field.name}" and will be removed in a future version. Please use "maxRows" instead.`,
|
`(payload): The "max" property is deprecated for the Relationship field "${field.name}" and will be removed in a future version. Please use "maxRows" instead.`,
|
||||||
@@ -160,7 +206,7 @@ export const sanitizeFields = async ({
|
|||||||
field.labels = field.labels || formatLabels(field.name)
|
field.labels = field.labels || formatLabels(field.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fieldAffectsData(field)) {
|
if (fieldAffectsData) {
|
||||||
if (existingFieldNames.has(field.name)) {
|
if (existingFieldNames.has(field.name)) {
|
||||||
throw new DuplicateFieldName(field.name)
|
throw new DuplicateFieldName(field.name)
|
||||||
} else if (!['blockName', 'id'].includes(field.name)) {
|
} else if (!['blockName', 'id'].includes(field.name)) {
|
||||||
@@ -254,9 +300,11 @@ export const sanitizeFields = async ({
|
|||||||
block.fields = block.fields.concat(baseBlockFields)
|
block.fields = block.fields.concat(baseBlockFields)
|
||||||
block.labels = !block.labels ? formatLabels(block.slug) : block.labels
|
block.labels = !block.labels ? formatLabels(block.slug) : block.labels
|
||||||
block.fields = await sanitizeFields({
|
block.fields = await sanitizeFields({
|
||||||
|
collectionConfig,
|
||||||
config,
|
config,
|
||||||
existingFieldNames: new Set(),
|
existingFieldNames: new Set(),
|
||||||
fields: block.fields,
|
fields: block.fields,
|
||||||
|
isTopLevelField: false,
|
||||||
parentIsLocalized: parentIsLocalized || field.localized,
|
parentIsLocalized: parentIsLocalized || field.localized,
|
||||||
requireFieldLevelRichTextEditor,
|
requireFieldLevelRichTextEditor,
|
||||||
richTextSanitizationPromises,
|
richTextSanitizationPromises,
|
||||||
@@ -267,12 +315,12 @@ export const sanitizeFields = async ({
|
|||||||
|
|
||||||
if ('fields' in field && field.fields) {
|
if ('fields' in field && field.fields) {
|
||||||
field.fields = await sanitizeFields({
|
field.fields = await sanitizeFields({
|
||||||
|
collectionConfig,
|
||||||
config,
|
config,
|
||||||
existingFieldNames: fieldAffectsData(field) ? new Set() : existingFieldNames,
|
existingFieldNames: fieldAffectsData ? new Set() : existingFieldNames,
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
joinPath: fieldAffectsData(field)
|
isTopLevelField: isTopLevelField && !fieldAffectsData,
|
||||||
? `${joinPath ? joinPath + '.' : ''}${field.name}`
|
joinPath: fieldAffectsData ? `${joinPath ? joinPath + '.' : ''}${field.name}` : joinPath,
|
||||||
: joinPath,
|
|
||||||
joins,
|
joins,
|
||||||
parentIsLocalized: parentIsLocalized || fieldIsLocalized(field),
|
parentIsLocalized: parentIsLocalized || fieldIsLocalized(field),
|
||||||
polymorphicJoins,
|
polymorphicJoins,
|
||||||
@@ -285,7 +333,10 @@ export const sanitizeFields = async ({
|
|||||||
if (field.type === 'tabs') {
|
if (field.type === 'tabs') {
|
||||||
for (let j = 0; j < field.tabs.length; j++) {
|
for (let j = 0; j < field.tabs.length; j++) {
|
||||||
const tab = field.tabs[j]
|
const tab = field.tabs[j]
|
||||||
if (tabHasName(tab) && typeof tab.label === 'undefined') {
|
|
||||||
|
const isNamedTab = tabHasName(tab)
|
||||||
|
|
||||||
|
if (isNamedTab && typeof tab.label === 'undefined') {
|
||||||
tab.label = toWords(tab.name)
|
tab.label = toWords(tab.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,21 +347,24 @@ export const sanitizeFields = async ({
|
|||||||
!tab.id
|
!tab.id
|
||||||
) {
|
) {
|
||||||
// Always attach a UUID to tabs with a condition so there's no conflicts even if there are duplicate nested names
|
// Always attach a UUID to tabs with a condition so there's no conflicts even if there are duplicate nested names
|
||||||
tab.id = tabHasName(tab) ? `${tab.name}_${uuid()}` : uuid()
|
tab.id = isNamedTab ? `${tab.name}_${uuid()}` : uuid()
|
||||||
}
|
}
|
||||||
|
|
||||||
tab.fields = await sanitizeFields({
|
tab.fields = await sanitizeFields({
|
||||||
|
collectionConfig,
|
||||||
config,
|
config,
|
||||||
existingFieldNames: tabHasName(tab) ? new Set() : existingFieldNames,
|
existingFieldNames: isNamedTab ? new Set() : existingFieldNames,
|
||||||
fields: tab.fields,
|
fields: tab.fields,
|
||||||
joinPath: tabHasName(tab) ? `${joinPath ? joinPath + '.' : ''}${tab.name}` : joinPath,
|
isTopLevelField: isTopLevelField && !isNamedTab,
|
||||||
|
joinPath: isNamedTab ? `${joinPath ? joinPath + '.' : ''}${tab.name}` : joinPath,
|
||||||
joins,
|
joins,
|
||||||
parentIsLocalized: parentIsLocalized || (tabHasName(tab) && tab.localized),
|
parentIsLocalized: parentIsLocalized || (isNamedTab && tab.localized),
|
||||||
polymorphicJoins,
|
polymorphicJoins,
|
||||||
requireFieldLevelRichTextEditor,
|
requireFieldLevelRichTextEditor,
|
||||||
richTextSanitizationPromises,
|
richTextSanitizationPromises,
|
||||||
validRelationships,
|
validRelationships,
|
||||||
})
|
})
|
||||||
|
|
||||||
field.tabs[j] = tab
|
field.tabs[j] = tab
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user