fix: localized fields within block references were not handled properly if any parent is localized (#11207)
The `localized` properly was not stripped out of referenced block fields, if any parent was localized. For normal fields, this is done in sanitizeConfig. As the same referenced block config can be used in both a localized and non-localized config, we are not able to strip it out inside sanitizeConfig by modifying the block config. Instead, this PR had to bring back tedious logic to handle it everywhere the `field.localized` property is accessed. For backwards-compatibility, we need to keep the existing sanitizeConfig logic. In 4.0, we should remove it to benefit from better test coverage of runtime field.localized handling - for now, this is done for our test suite using the `PAYLOAD_DO_NOT_SANITIZE_LOCALIZED_PROPERTY` flag.
This commit is contained in:
@@ -23,6 +23,7 @@ export const defaultESLintIgnores = [
|
||||
'next-env.d.ts',
|
||||
'**/app',
|
||||
'src/**/*.spec.ts',
|
||||
'**/jest.setup.js',
|
||||
]
|
||||
|
||||
/** @typedef {import('eslint').Linter.Config} Config */
|
||||
|
||||
@@ -27,6 +27,8 @@ const config = withBundleAnalyzer(
|
||||
env: {
|
||||
PAYLOAD_CORE_DEV: 'true',
|
||||
ROOT_DIR: path.resolve(dirname),
|
||||
// @todo remove in 4.0 - will behave like this by default in 4.0
|
||||
PAYLOAD_DO_NOT_SANITIZE_LOCALIZED_PROPERTY: 'true',
|
||||
},
|
||||
async redirects() {
|
||||
return [
|
||||
|
||||
@@ -26,15 +26,19 @@ export const init: Init = function init(this: MongooseAdapter) {
|
||||
|
||||
const versionCollectionFields = buildVersionCollectionFields(this.payload.config, collection)
|
||||
|
||||
const versionSchema = buildSchema(this.payload, versionCollectionFields, {
|
||||
disableUnique: true,
|
||||
draftsEnabled: true,
|
||||
indexSortableFields: this.payload.config.indexSortableFields,
|
||||
options: {
|
||||
minimize: false,
|
||||
timestamps: false,
|
||||
const versionSchema = buildSchema({
|
||||
buildSchemaOptions: {
|
||||
disableUnique: true,
|
||||
draftsEnabled: true,
|
||||
indexSortableFields: this.payload.config.indexSortableFields,
|
||||
options: {
|
||||
minimize: false,
|
||||
timestamps: false,
|
||||
},
|
||||
...schemaOptions,
|
||||
},
|
||||
...schemaOptions,
|
||||
configFields: versionCollectionFields,
|
||||
payload: this.payload,
|
||||
})
|
||||
|
||||
versionSchema.plugin<any, PaginateOptions>(paginate, { useEstimatedCount: true }).plugin(
|
||||
@@ -77,14 +81,18 @@ export const init: Init = function init(this: MongooseAdapter) {
|
||||
|
||||
const versionGlobalFields = buildVersionGlobalFields(this.payload.config, global)
|
||||
|
||||
const versionSchema = buildSchema(this.payload, versionGlobalFields, {
|
||||
disableUnique: true,
|
||||
draftsEnabled: true,
|
||||
indexSortableFields: this.payload.config.indexSortableFields,
|
||||
options: {
|
||||
minimize: false,
|
||||
timestamps: false,
|
||||
const versionSchema = buildSchema({
|
||||
buildSchemaOptions: {
|
||||
disableUnique: true,
|
||||
draftsEnabled: true,
|
||||
indexSortableFields: this.payload.config.indexSortableFields,
|
||||
options: {
|
||||
minimize: false,
|
||||
timestamps: false,
|
||||
},
|
||||
},
|
||||
configFields: versionGlobalFields,
|
||||
payload: this.payload,
|
||||
})
|
||||
|
||||
versionSchema.plugin<any, PaginateOptions>(paginate, { useEstimatedCount: true }).plugin(
|
||||
|
||||
@@ -12,14 +12,20 @@ export const buildCollectionSchema = (
|
||||
payload: Payload,
|
||||
schemaOptions = {},
|
||||
): Schema => {
|
||||
const schema = buildSchema(payload, collection.fields, {
|
||||
draftsEnabled: Boolean(typeof collection?.versions === 'object' && collection.versions.drafts),
|
||||
indexSortableFields: payload.config.indexSortableFields,
|
||||
options: {
|
||||
minimize: false,
|
||||
timestamps: collection.timestamps !== false,
|
||||
...schemaOptions,
|
||||
const schema = buildSchema({
|
||||
buildSchemaOptions: {
|
||||
draftsEnabled: Boolean(
|
||||
typeof collection?.versions === 'object' && collection.versions.drafts,
|
||||
),
|
||||
indexSortableFields: payload.config.indexSortableFields,
|
||||
options: {
|
||||
minimize: false,
|
||||
timestamps: collection.timestamps !== false,
|
||||
...schemaOptions,
|
||||
},
|
||||
},
|
||||
configFields: collection.fields,
|
||||
payload,
|
||||
})
|
||||
|
||||
if (Array.isArray(collection.upload.filenameCompoundIndex)) {
|
||||
|
||||
@@ -19,10 +19,14 @@ export const buildGlobalModel = (payload: Payload): GlobalModel | null => {
|
||||
const Globals = mongoose.model('globals', globalsSchema, 'globals') as unknown as GlobalModel
|
||||
|
||||
Object.values(payload.config.globals).forEach((globalConfig) => {
|
||||
const globalSchema = buildSchema(payload, globalConfig.fields, {
|
||||
options: {
|
||||
minimize: false,
|
||||
const globalSchema = buildSchema({
|
||||
buildSchemaOptions: {
|
||||
options: {
|
||||
minimize: false,
|
||||
},
|
||||
},
|
||||
configFields: globalConfig.fields,
|
||||
payload,
|
||||
})
|
||||
Globals.discriminator(globalConfig.slug, globalSchema)
|
||||
})
|
||||
|
||||
@@ -31,9 +31,9 @@ import {
|
||||
} from 'payload'
|
||||
import {
|
||||
fieldAffectsData,
|
||||
fieldIsLocalized,
|
||||
fieldIsPresentationalOnly,
|
||||
fieldIsVirtual,
|
||||
fieldShouldBeLocalized,
|
||||
tabHasName,
|
||||
} from 'payload/shared'
|
||||
|
||||
@@ -50,6 +50,7 @@ type FieldSchemaGenerator = (
|
||||
schema: Schema,
|
||||
config: Payload,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
parentIsLocalized: boolean,
|
||||
) => void
|
||||
|
||||
/**
|
||||
@@ -61,7 +62,15 @@ const formatDefaultValue = (field: FieldAffectingData) =>
|
||||
? field.defaultValue
|
||||
: undefined
|
||||
|
||||
const formatBaseSchema = (field: FieldAffectingData, buildSchemaOptions: BuildSchemaOptions) => {
|
||||
const formatBaseSchema = ({
|
||||
buildSchemaOptions,
|
||||
field,
|
||||
parentIsLocalized,
|
||||
}: {
|
||||
buildSchemaOptions: BuildSchemaOptions
|
||||
field: FieldAffectingData
|
||||
parentIsLocalized: boolean
|
||||
}) => {
|
||||
const { disableUnique, draftsEnabled, indexSortableFields } = buildSchemaOptions
|
||||
const schema: SchemaTypeOptions<unknown> = {
|
||||
default: formatDefaultValue(field),
|
||||
@@ -72,7 +81,7 @@ const formatBaseSchema = (field: FieldAffectingData, buildSchemaOptions: BuildSc
|
||||
|
||||
if (
|
||||
schema.unique &&
|
||||
(field.localized ||
|
||||
(fieldShouldBeLocalized({ field, parentIsLocalized }) ||
|
||||
draftsEnabled ||
|
||||
(fieldAffectsData(field) &&
|
||||
field.type !== 'group' &&
|
||||
@@ -93,8 +102,13 @@ const localizeSchema = (
|
||||
entity: NonPresentationalField | Tab,
|
||||
schema,
|
||||
localization: false | SanitizedLocalizationConfig,
|
||||
parentIsLocalized: boolean,
|
||||
) => {
|
||||
if (fieldIsLocalized(entity) && localization && Array.isArray(localization.locales)) {
|
||||
if (
|
||||
fieldShouldBeLocalized({ field: entity, parentIsLocalized }) &&
|
||||
localization &&
|
||||
Array.isArray(localization.locales)
|
||||
) {
|
||||
return {
|
||||
type: localization.localeCodes.reduce(
|
||||
(localeSchema, locale) => ({
|
||||
@@ -111,11 +125,13 @@ const localizeSchema = (
|
||||
return schema
|
||||
}
|
||||
|
||||
export const buildSchema = (
|
||||
payload: Payload,
|
||||
configFields: Field[],
|
||||
buildSchemaOptions: BuildSchemaOptions = {},
|
||||
): Schema => {
|
||||
export const buildSchema = (args: {
|
||||
buildSchemaOptions: BuildSchemaOptions
|
||||
configFields: Field[]
|
||||
parentIsLocalized?: boolean
|
||||
payload: Payload
|
||||
}): Schema => {
|
||||
const { buildSchemaOptions = {}, configFields, parentIsLocalized, payload } = args
|
||||
const { allowIDField, options } = buildSchemaOptions
|
||||
let fields = {}
|
||||
|
||||
@@ -144,7 +160,7 @@ export const buildSchema = (
|
||||
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[field.type]
|
||||
|
||||
if (addFieldSchema) {
|
||||
addFieldSchema(field, schema, payload, buildSchemaOptions)
|
||||
addFieldSchema(field, schema, payload, buildSchemaOptions, parentIsLocalized)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -153,44 +169,49 @@ export const buildSchema = (
|
||||
}
|
||||
|
||||
const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
array: (
|
||||
field: ArrayField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
) => {
|
||||
array: (field: ArrayField, schema, payload, buildSchemaOptions, parentIsLocalized) => {
|
||||
const baseSchema = {
|
||||
...formatBaseSchema(field, buildSchemaOptions),
|
||||
...formatBaseSchema({ buildSchemaOptions, field, parentIsLocalized }),
|
||||
type: [
|
||||
buildSchema(payload, field.fields, {
|
||||
allowIDField: true,
|
||||
disableUnique: buildSchemaOptions.disableUnique,
|
||||
draftsEnabled: buildSchemaOptions.draftsEnabled,
|
||||
options: {
|
||||
_id: false,
|
||||
id: false,
|
||||
minimize: false,
|
||||
buildSchema({
|
||||
buildSchemaOptions: {
|
||||
allowIDField: true,
|
||||
disableUnique: buildSchemaOptions.disableUnique,
|
||||
draftsEnabled: buildSchemaOptions.draftsEnabled,
|
||||
options: {
|
||||
_id: false,
|
||||
id: false,
|
||||
minimize: false,
|
||||
},
|
||||
},
|
||||
configFields: field.fields,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
payload,
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
schema.add({
|
||||
[field.name]: localizeSchema(field, baseSchema, payload.config.localization),
|
||||
[field.name]: localizeSchema(
|
||||
field,
|
||||
baseSchema,
|
||||
payload.config.localization,
|
||||
parentIsLocalized,
|
||||
),
|
||||
})
|
||||
},
|
||||
blocks: (
|
||||
field: BlocksField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
blocks: (field: BlocksField, schema, payload, buildSchemaOptions, parentIsLocalized): void => {
|
||||
const fieldSchema = {
|
||||
type: [new mongoose.Schema({}, { _id: false, discriminatorKey: 'blockType' })],
|
||||
}
|
||||
|
||||
schema.add({
|
||||
[field.name]: localizeSchema(field, fieldSchema, payload.config.localization),
|
||||
[field.name]: localizeSchema(
|
||||
field,
|
||||
fieldSchema,
|
||||
payload.config.localization,
|
||||
parentIsLocalized,
|
||||
),
|
||||
})
|
||||
;(field.blockReferences ?? field.blocks).forEach((blockItem) => {
|
||||
const blockSchema = new mongoose.Schema({}, { _id: false, id: false })
|
||||
@@ -200,11 +221,17 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
block.fields.forEach((blockField) => {
|
||||
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[blockField.type]
|
||||
if (addFieldSchema) {
|
||||
addFieldSchema(blockField, blockSchema, payload, buildSchemaOptions)
|
||||
addFieldSchema(
|
||||
blockField,
|
||||
blockSchema,
|
||||
payload,
|
||||
buildSchemaOptions,
|
||||
parentIsLocalized || field.localized,
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
if (field.localized && payload.config.localization) {
|
||||
if (fieldShouldBeLocalized({ field, parentIsLocalized }) && payload.config.localization) {
|
||||
payload.config.localization.localeCodes.forEach((localeCode) => {
|
||||
// @ts-expect-error Possible incorrect typing in mongoose types, this works
|
||||
schema.path(`${field.name}.${localeCode}`).discriminator(block.slug, blockSchema)
|
||||
@@ -217,33 +244,46 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
},
|
||||
checkbox: (
|
||||
field: CheckboxField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
schema,
|
||||
payload,
|
||||
buildSchemaOptions,
|
||||
parentIsLocalized,
|
||||
): void => {
|
||||
const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: Boolean }
|
||||
const baseSchema = {
|
||||
...formatBaseSchema({ buildSchemaOptions, field, parentIsLocalized }),
|
||||
type: Boolean,
|
||||
}
|
||||
|
||||
schema.add({
|
||||
[field.name]: localizeSchema(field, baseSchema, payload.config.localization),
|
||||
[field.name]: localizeSchema(
|
||||
field,
|
||||
baseSchema,
|
||||
payload.config.localization,
|
||||
parentIsLocalized,
|
||||
),
|
||||
})
|
||||
},
|
||||
code: (
|
||||
field: CodeField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: String }
|
||||
code: (field: CodeField, schema, payload, buildSchemaOptions, parentIsLocalized): void => {
|
||||
const baseSchema = {
|
||||
...formatBaseSchema({ buildSchemaOptions, field, parentIsLocalized }),
|
||||
type: String,
|
||||
}
|
||||
|
||||
schema.add({
|
||||
[field.name]: localizeSchema(field, baseSchema, payload.config.localization),
|
||||
[field.name]: localizeSchema(
|
||||
field,
|
||||
baseSchema,
|
||||
payload.config.localization,
|
||||
parentIsLocalized,
|
||||
),
|
||||
})
|
||||
},
|
||||
collapsible: (
|
||||
field: CollapsibleField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
schema,
|
||||
payload,
|
||||
buildSchemaOptions,
|
||||
parentIsLocalized,
|
||||
): void => {
|
||||
field.fields.forEach((subField: Field) => {
|
||||
if (fieldIsVirtual(subField)) {
|
||||
@@ -253,41 +293,42 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[subField.type]
|
||||
|
||||
if (addFieldSchema) {
|
||||
addFieldSchema(subField, schema, payload, buildSchemaOptions)
|
||||
addFieldSchema(subField, schema, payload, buildSchemaOptions, parentIsLocalized)
|
||||
}
|
||||
})
|
||||
},
|
||||
date: (
|
||||
field: DateField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: Date }
|
||||
date: (field: DateField, schema, payload, buildSchemaOptions, parentIsLocalized): void => {
|
||||
const baseSchema = {
|
||||
...formatBaseSchema({ buildSchemaOptions, field, parentIsLocalized }),
|
||||
type: Date,
|
||||
}
|
||||
|
||||
schema.add({
|
||||
[field.name]: localizeSchema(field, baseSchema, payload.config.localization),
|
||||
[field.name]: localizeSchema(
|
||||
field,
|
||||
baseSchema,
|
||||
payload.config.localization,
|
||||
parentIsLocalized,
|
||||
),
|
||||
})
|
||||
},
|
||||
email: (
|
||||
field: EmailField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: String }
|
||||
email: (field: EmailField, schema, payload, buildSchemaOptions, parentIsLocalized): void => {
|
||||
const baseSchema = {
|
||||
...formatBaseSchema({ buildSchemaOptions, field, parentIsLocalized }),
|
||||
type: String,
|
||||
}
|
||||
|
||||
schema.add({
|
||||
[field.name]: localizeSchema(field, baseSchema, payload.config.localization),
|
||||
[field.name]: localizeSchema(
|
||||
field,
|
||||
baseSchema,
|
||||
payload.config.localization,
|
||||
parentIsLocalized,
|
||||
),
|
||||
})
|
||||
},
|
||||
group: (
|
||||
field: GroupField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
const formattedBaseSchema = formatBaseSchema(field, buildSchemaOptions)
|
||||
group: (field: GroupField, schema, payload, buildSchemaOptions, parentIsLocalized): void => {
|
||||
const formattedBaseSchema = formatBaseSchema({ buildSchemaOptions, field, parentIsLocalized })
|
||||
|
||||
// carry indexSortableFields through to versions if drafts enabled
|
||||
const indexSortableFields =
|
||||
@@ -297,58 +338,63 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
|
||||
const baseSchema = {
|
||||
...formattedBaseSchema,
|
||||
type: buildSchema(payload, field.fields, {
|
||||
disableUnique: buildSchemaOptions.disableUnique,
|
||||
draftsEnabled: buildSchemaOptions.draftsEnabled,
|
||||
indexSortableFields,
|
||||
options: {
|
||||
_id: false,
|
||||
id: false,
|
||||
minimize: false,
|
||||
type: buildSchema({
|
||||
buildSchemaOptions: {
|
||||
disableUnique: buildSchemaOptions.disableUnique,
|
||||
draftsEnabled: buildSchemaOptions.draftsEnabled,
|
||||
indexSortableFields,
|
||||
options: {
|
||||
_id: false,
|
||||
id: false,
|
||||
minimize: false,
|
||||
},
|
||||
},
|
||||
configFields: field.fields,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
payload,
|
||||
}),
|
||||
}
|
||||
|
||||
schema.add({
|
||||
[field.name]: localizeSchema(field, baseSchema, payload.config.localization),
|
||||
[field.name]: localizeSchema(
|
||||
field,
|
||||
baseSchema,
|
||||
payload.config.localization,
|
||||
parentIsLocalized,
|
||||
),
|
||||
})
|
||||
},
|
||||
json: (
|
||||
field: JSONField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
json: (field: JSONField, schema, payload, buildSchemaOptions, parentIsLocalized): void => {
|
||||
const baseSchema = {
|
||||
...formatBaseSchema(field, buildSchemaOptions),
|
||||
...formatBaseSchema({ buildSchemaOptions, field, parentIsLocalized }),
|
||||
type: mongoose.Schema.Types.Mixed,
|
||||
}
|
||||
|
||||
schema.add({
|
||||
[field.name]: localizeSchema(field, baseSchema, payload.config.localization),
|
||||
[field.name]: localizeSchema(
|
||||
field,
|
||||
baseSchema,
|
||||
payload.config.localization,
|
||||
parentIsLocalized,
|
||||
),
|
||||
})
|
||||
},
|
||||
number: (
|
||||
field: NumberField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
number: (field: NumberField, schema, payload, buildSchemaOptions, parentIsLocalized): void => {
|
||||
const baseSchema = {
|
||||
...formatBaseSchema(field, buildSchemaOptions),
|
||||
...formatBaseSchema({ buildSchemaOptions, field, parentIsLocalized }),
|
||||
type: field.hasMany ? [Number] : Number,
|
||||
}
|
||||
|
||||
schema.add({
|
||||
[field.name]: localizeSchema(field, baseSchema, payload.config.localization),
|
||||
[field.name]: localizeSchema(
|
||||
field,
|
||||
baseSchema,
|
||||
payload.config.localization,
|
||||
parentIsLocalized,
|
||||
),
|
||||
})
|
||||
},
|
||||
point: (
|
||||
field: PointField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
point: (field: PointField, schema, payload, buildSchemaOptions, parentIsLocalized): void => {
|
||||
const baseSchema: SchemaTypeOptions<unknown> = {
|
||||
type: {
|
||||
type: String,
|
||||
@@ -363,12 +409,21 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
required: false,
|
||||
},
|
||||
}
|
||||
if (buildSchemaOptions.disableUnique && field.unique && field.localized) {
|
||||
if (
|
||||
buildSchemaOptions.disableUnique &&
|
||||
field.unique &&
|
||||
fieldShouldBeLocalized({ field, parentIsLocalized })
|
||||
) {
|
||||
baseSchema.coordinates.sparse = true
|
||||
}
|
||||
|
||||
schema.add({
|
||||
[field.name]: localizeSchema(field, baseSchema, payload.config.localization),
|
||||
[field.name]: localizeSchema(
|
||||
field,
|
||||
baseSchema,
|
||||
payload.config.localization,
|
||||
parentIsLocalized,
|
||||
),
|
||||
})
|
||||
|
||||
if (field.index === true || field.index === undefined) {
|
||||
@@ -377,7 +432,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
indexOptions.sparse = true
|
||||
indexOptions.unique = true
|
||||
}
|
||||
if (field.localized && payload.config.localization) {
|
||||
if (fieldShouldBeLocalized({ field, parentIsLocalized }) && payload.config.localization) {
|
||||
payload.config.localization.locales.forEach((locale) => {
|
||||
schema.index({ [`${field.name}.${locale.code}`]: '2dsphere' }, indexOptions)
|
||||
})
|
||||
@@ -386,14 +441,9 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
}
|
||||
}
|
||||
},
|
||||
radio: (
|
||||
field: RadioField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
radio: (field: RadioField, schema, payload, buildSchemaOptions, parentIsLocalized): void => {
|
||||
const baseSchema = {
|
||||
...formatBaseSchema(field, buildSchemaOptions),
|
||||
...formatBaseSchema({ buildSchemaOptions, field, parentIsLocalized }),
|
||||
type: String,
|
||||
enum: field.options.map((option) => {
|
||||
if (typeof option === 'object') {
|
||||
@@ -404,28 +454,34 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
}
|
||||
|
||||
schema.add({
|
||||
[field.name]: localizeSchema(field, baseSchema, payload.config.localization),
|
||||
[field.name]: localizeSchema(
|
||||
field,
|
||||
baseSchema,
|
||||
payload.config.localization,
|
||||
parentIsLocalized,
|
||||
),
|
||||
})
|
||||
},
|
||||
relationship: (
|
||||
field: RelationshipField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
schema,
|
||||
payload,
|
||||
buildSchemaOptions,
|
||||
parentIsLocalized,
|
||||
) => {
|
||||
const hasManyRelations = Array.isArray(field.relationTo)
|
||||
let schemaToReturn: { [key: string]: any } = {}
|
||||
|
||||
const valueType = getRelationshipValueType(field, payload)
|
||||
|
||||
if (field.localized && payload.config.localization) {
|
||||
if (fieldShouldBeLocalized({ field, parentIsLocalized }) && payload.config.localization) {
|
||||
schemaToReturn = {
|
||||
type: payload.config.localization.localeCodes.reduce((locales, locale) => {
|
||||
let localeSchema: { [key: string]: any } = {}
|
||||
|
||||
if (hasManyRelations) {
|
||||
localeSchema = {
|
||||
...formatBaseSchema(field, buildSchemaOptions),
|
||||
...formatBaseSchema({ buildSchemaOptions, field, parentIsLocalized }),
|
||||
_id: false,
|
||||
type: mongoose.Schema.Types.Mixed,
|
||||
relationTo: { type: String, enum: field.relationTo },
|
||||
@@ -436,7 +492,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
}
|
||||
} else {
|
||||
localeSchema = {
|
||||
...formatBaseSchema(field, buildSchemaOptions),
|
||||
...formatBaseSchema({ buildSchemaOptions, field, parentIsLocalized }),
|
||||
type: valueType,
|
||||
ref: field.relationTo,
|
||||
}
|
||||
@@ -453,7 +509,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
}
|
||||
} else if (hasManyRelations) {
|
||||
schemaToReturn = {
|
||||
...formatBaseSchema(field, buildSchemaOptions),
|
||||
...formatBaseSchema({ buildSchemaOptions, field, parentIsLocalized }),
|
||||
_id: false,
|
||||
type: mongoose.Schema.Types.Mixed,
|
||||
relationTo: { type: String, enum: field.relationTo },
|
||||
@@ -471,7 +527,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
}
|
||||
} else {
|
||||
schemaToReturn = {
|
||||
...formatBaseSchema(field, buildSchemaOptions),
|
||||
...formatBaseSchema({ buildSchemaOptions, field, parentIsLocalized }),
|
||||
type: valueType,
|
||||
ref: field.relationTo,
|
||||
}
|
||||
@@ -490,25 +546,26 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
},
|
||||
richText: (
|
||||
field: RichTextField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
schema,
|
||||
payload,
|
||||
buildSchemaOptions,
|
||||
parentIsLocalized,
|
||||
): void => {
|
||||
const baseSchema = {
|
||||
...formatBaseSchema(field, buildSchemaOptions),
|
||||
...formatBaseSchema({ buildSchemaOptions, field, parentIsLocalized }),
|
||||
type: mongoose.Schema.Types.Mixed,
|
||||
}
|
||||
|
||||
schema.add({
|
||||
[field.name]: localizeSchema(field, baseSchema, payload.config.localization),
|
||||
[field.name]: localizeSchema(
|
||||
field,
|
||||
baseSchema,
|
||||
payload.config.localization,
|
||||
parentIsLocalized,
|
||||
),
|
||||
})
|
||||
},
|
||||
row: (
|
||||
field: RowField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
row: (field: RowField, schema, payload, buildSchemaOptions, parentIsLocalized): void => {
|
||||
field.fields.forEach((subField: Field) => {
|
||||
if (fieldIsVirtual(subField)) {
|
||||
return
|
||||
@@ -517,18 +574,13 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[subField.type]
|
||||
|
||||
if (addFieldSchema) {
|
||||
addFieldSchema(subField, schema, payload, buildSchemaOptions)
|
||||
addFieldSchema(subField, schema, payload, buildSchemaOptions, parentIsLocalized)
|
||||
}
|
||||
})
|
||||
},
|
||||
select: (
|
||||
field: SelectField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
select: (field: SelectField, schema, payload, buildSchemaOptions, parentIsLocalized): void => {
|
||||
const baseSchema = {
|
||||
...formatBaseSchema(field, buildSchemaOptions),
|
||||
...formatBaseSchema({ buildSchemaOptions, field, parentIsLocalized }),
|
||||
type: String,
|
||||
enum: field.options.map((option) => {
|
||||
if (typeof option === 'object') {
|
||||
@@ -547,34 +599,40 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
field,
|
||||
field.hasMany ? [baseSchema] : baseSchema,
|
||||
payload.config.localization,
|
||||
parentIsLocalized,
|
||||
),
|
||||
})
|
||||
},
|
||||
tabs: (
|
||||
field: TabsField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
tabs: (field: TabsField, schema, payload, buildSchemaOptions, parentIsLocalized): void => {
|
||||
field.tabs.forEach((tab) => {
|
||||
if (tabHasName(tab)) {
|
||||
if (fieldIsVirtual(tab)) {
|
||||
return
|
||||
}
|
||||
const baseSchema = {
|
||||
type: buildSchema(payload, tab.fields, {
|
||||
disableUnique: buildSchemaOptions.disableUnique,
|
||||
draftsEnabled: buildSchemaOptions.draftsEnabled,
|
||||
options: {
|
||||
_id: false,
|
||||
id: false,
|
||||
minimize: false,
|
||||
type: buildSchema({
|
||||
buildSchemaOptions: {
|
||||
disableUnique: buildSchemaOptions.disableUnique,
|
||||
draftsEnabled: buildSchemaOptions.draftsEnabled,
|
||||
options: {
|
||||
_id: false,
|
||||
id: false,
|
||||
minimize: false,
|
||||
},
|
||||
},
|
||||
configFields: tab.fields,
|
||||
parentIsLocalized: parentIsLocalized || tab.localized,
|
||||
payload,
|
||||
}),
|
||||
}
|
||||
|
||||
schema.add({
|
||||
[tab.name]: localizeSchema(tab, baseSchema, payload.config.localization),
|
||||
[tab.name]: localizeSchema(
|
||||
tab,
|
||||
baseSchema,
|
||||
payload.config.localization,
|
||||
parentIsLocalized,
|
||||
),
|
||||
})
|
||||
} else {
|
||||
tab.fields.forEach((subField: Field) => {
|
||||
@@ -584,58 +642,68 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[subField.type]
|
||||
|
||||
if (addFieldSchema) {
|
||||
addFieldSchema(subField, schema, payload, buildSchemaOptions)
|
||||
addFieldSchema(
|
||||
subField,
|
||||
schema,
|
||||
payload,
|
||||
buildSchemaOptions,
|
||||
parentIsLocalized || tab.localized,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
text: (
|
||||
field: TextField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
text: (field: TextField, schema, payload, buildSchemaOptions, parentIsLocalized): void => {
|
||||
const baseSchema = {
|
||||
...formatBaseSchema(field, buildSchemaOptions),
|
||||
...formatBaseSchema({ buildSchemaOptions, field, parentIsLocalized }),
|
||||
type: field.hasMany ? [String] : String,
|
||||
}
|
||||
|
||||
schema.add({
|
||||
[field.name]: localizeSchema(field, baseSchema, payload.config.localization),
|
||||
[field.name]: localizeSchema(
|
||||
field,
|
||||
baseSchema,
|
||||
payload.config.localization,
|
||||
parentIsLocalized,
|
||||
),
|
||||
})
|
||||
},
|
||||
textarea: (
|
||||
field: TextareaField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
schema,
|
||||
payload,
|
||||
buildSchemaOptions,
|
||||
parentIsLocalized,
|
||||
): void => {
|
||||
const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: String }
|
||||
const baseSchema = {
|
||||
...formatBaseSchema({ buildSchemaOptions, field, parentIsLocalized }),
|
||||
type: String,
|
||||
}
|
||||
|
||||
schema.add({
|
||||
[field.name]: localizeSchema(field, baseSchema, payload.config.localization),
|
||||
[field.name]: localizeSchema(
|
||||
field,
|
||||
baseSchema,
|
||||
payload.config.localization,
|
||||
parentIsLocalized,
|
||||
),
|
||||
})
|
||||
},
|
||||
upload: (
|
||||
field: UploadField,
|
||||
schema: Schema,
|
||||
payload: Payload,
|
||||
buildSchemaOptions: BuildSchemaOptions,
|
||||
): void => {
|
||||
upload: (field: UploadField, schema, payload, buildSchemaOptions, parentIsLocalized): void => {
|
||||
const hasManyRelations = Array.isArray(field.relationTo)
|
||||
let schemaToReturn: { [key: string]: any } = {}
|
||||
|
||||
const valueType = getRelationshipValueType(field, payload)
|
||||
|
||||
if (field.localized && payload.config.localization) {
|
||||
if (fieldShouldBeLocalized({ field, parentIsLocalized }) && payload.config.localization) {
|
||||
schemaToReturn = {
|
||||
type: payload.config.localization.localeCodes.reduce((locales, locale) => {
|
||||
let localeSchema: { [key: string]: any } = {}
|
||||
|
||||
if (hasManyRelations) {
|
||||
localeSchema = {
|
||||
...formatBaseSchema(field, buildSchemaOptions),
|
||||
...formatBaseSchema({ buildSchemaOptions, field, parentIsLocalized }),
|
||||
_id: false,
|
||||
type: mongoose.Schema.Types.Mixed,
|
||||
relationTo: { type: String, enum: field.relationTo },
|
||||
@@ -646,7 +714,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
}
|
||||
} else {
|
||||
localeSchema = {
|
||||
...formatBaseSchema(field, buildSchemaOptions),
|
||||
...formatBaseSchema({ buildSchemaOptions, field, parentIsLocalized }),
|
||||
type: valueType,
|
||||
ref: field.relationTo,
|
||||
}
|
||||
@@ -663,7 +731,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
}
|
||||
} else if (hasManyRelations) {
|
||||
schemaToReturn = {
|
||||
...formatBaseSchema(field, buildSchemaOptions),
|
||||
...formatBaseSchema({ buildSchemaOptions, field, parentIsLocalized }),
|
||||
_id: false,
|
||||
type: mongoose.Schema.Types.Mixed,
|
||||
relationTo: { type: String, enum: field.relationTo },
|
||||
@@ -681,7 +749,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
}
|
||||
} else {
|
||||
schemaToReturn = {
|
||||
...formatBaseSchema(field, buildSchemaOptions),
|
||||
...formatBaseSchema({ buildSchemaOptions, field, parentIsLocalized }),
|
||||
type: valueType,
|
||||
ref: field.relationTo,
|
||||
}
|
||||
|
||||
@@ -13,12 +13,14 @@ const migrateModelWithBatching = async ({
|
||||
config,
|
||||
fields,
|
||||
Model,
|
||||
parentIsLocalized,
|
||||
session,
|
||||
}: {
|
||||
batchSize: number
|
||||
config: SanitizedConfig
|
||||
fields: Field[]
|
||||
Model: Model<any>
|
||||
parentIsLocalized: boolean
|
||||
session: ClientSession
|
||||
}): Promise<void> => {
|
||||
let hasNext = true
|
||||
@@ -47,7 +49,7 @@ const migrateModelWithBatching = async ({
|
||||
}
|
||||
|
||||
for (const doc of docs) {
|
||||
sanitizeRelationshipIDs({ config, data: doc, fields })
|
||||
sanitizeRelationshipIDs({ config, data: doc, fields, parentIsLocalized })
|
||||
}
|
||||
|
||||
await Model.collection.bulkWrite(
|
||||
@@ -124,6 +126,7 @@ export async function migrateRelationshipsV2_V3({
|
||||
config,
|
||||
fields: collection.fields,
|
||||
Model: db.collections[collection.slug],
|
||||
parentIsLocalized: false,
|
||||
session,
|
||||
})
|
||||
|
||||
@@ -138,6 +141,7 @@ export async function migrateRelationshipsV2_V3({
|
||||
config,
|
||||
fields: buildVersionCollectionFields(config, collection),
|
||||
Model: db.versions[collection.slug],
|
||||
parentIsLocalized: false,
|
||||
session,
|
||||
})
|
||||
|
||||
@@ -163,7 +167,11 @@ export async function migrateRelationshipsV2_V3({
|
||||
|
||||
// in case if the global doesn't exist in the database yet (not saved)
|
||||
if (doc) {
|
||||
sanitizeRelationshipIDs({ config, data: doc, fields: global.fields })
|
||||
sanitizeRelationshipIDs({
|
||||
config,
|
||||
data: doc,
|
||||
fields: global.fields,
|
||||
})
|
||||
|
||||
await GlobalsModel.collection.updateOne(
|
||||
{
|
||||
@@ -185,6 +193,7 @@ export async function migrateRelationshipsV2_V3({
|
||||
config,
|
||||
fields: buildVersionGlobalFields(config, global),
|
||||
Model: db.versions[global.slug],
|
||||
parentIsLocalized: false,
|
||||
session,
|
||||
})
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ export async function buildAndOrConditions({
|
||||
fields,
|
||||
globalSlug,
|
||||
locale,
|
||||
parentIsLocalized,
|
||||
payload,
|
||||
where,
|
||||
}: {
|
||||
@@ -14,6 +15,7 @@ export async function buildAndOrConditions({
|
||||
fields: FlattenedField[]
|
||||
globalSlug?: string
|
||||
locale?: string
|
||||
parentIsLocalized: boolean
|
||||
payload: Payload
|
||||
where: Where[]
|
||||
}): Promise<Record<string, unknown>[]> {
|
||||
@@ -29,6 +31,7 @@ export async function buildAndOrConditions({
|
||||
fields,
|
||||
globalSlug,
|
||||
locale,
|
||||
parentIsLocalized,
|
||||
payload,
|
||||
where: condition,
|
||||
})
|
||||
|
||||
@@ -47,6 +47,7 @@ export const getBuildQueryPlugin = ({
|
||||
fields,
|
||||
globalSlug,
|
||||
locale,
|
||||
parentIsLocalized: false,
|
||||
payload,
|
||||
where,
|
||||
})
|
||||
|
||||
@@ -30,6 +30,7 @@ export async function buildSearchParam({
|
||||
incomingPath,
|
||||
locale,
|
||||
operator,
|
||||
parentIsLocalized,
|
||||
payload,
|
||||
val,
|
||||
}: {
|
||||
@@ -39,6 +40,7 @@ export async function buildSearchParam({
|
||||
incomingPath: string
|
||||
locale?: string
|
||||
operator: string
|
||||
parentIsLocalized: boolean
|
||||
payload: Payload
|
||||
val: unknown
|
||||
}): Promise<SearchParam> {
|
||||
@@ -69,6 +71,7 @@ export async function buildSearchParam({
|
||||
name: 'id',
|
||||
type: idFieldType,
|
||||
} as FlattenedField,
|
||||
parentIsLocalized,
|
||||
path: '_id',
|
||||
})
|
||||
} else {
|
||||
@@ -78,6 +81,7 @@ export async function buildSearchParam({
|
||||
globalSlug,
|
||||
incomingPath: sanitizedPath,
|
||||
locale,
|
||||
parentIsLocalized,
|
||||
payload,
|
||||
})
|
||||
}
|
||||
@@ -89,6 +93,7 @@ export async function buildSearchParam({
|
||||
hasCustomID,
|
||||
locale,
|
||||
operator,
|
||||
parentIsLocalized,
|
||||
path,
|
||||
payload,
|
||||
val,
|
||||
|
||||
@@ -7,6 +7,7 @@ type Args = {
|
||||
config: SanitizedConfig
|
||||
fields: FlattenedField[]
|
||||
locale: string
|
||||
parentIsLocalized?: boolean
|
||||
sort: Sort
|
||||
timestamps: boolean
|
||||
}
|
||||
@@ -22,6 +23,7 @@ export const buildSortParam = ({
|
||||
config,
|
||||
fields,
|
||||
locale,
|
||||
parentIsLocalized,
|
||||
sort,
|
||||
timestamps,
|
||||
}: Args): PaginateOptions['sort'] => {
|
||||
@@ -55,6 +57,7 @@ export const buildSortParam = ({
|
||||
config,
|
||||
fields,
|
||||
locale,
|
||||
parentIsLocalized,
|
||||
segments: sortProperty.split('.'),
|
||||
})
|
||||
acc[localizedProperty] = sortDirection
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import type { FlattenedField, SanitizedConfig } from 'payload'
|
||||
|
||||
import { fieldAffectsData, fieldIsPresentationalOnly } from 'payload/shared'
|
||||
import { fieldAffectsData, fieldIsPresentationalOnly, fieldShouldBeLocalized } from 'payload/shared'
|
||||
|
||||
type Args = {
|
||||
config: SanitizedConfig
|
||||
fields: FlattenedField[]
|
||||
locale: string
|
||||
parentIsLocalized: boolean
|
||||
result?: string
|
||||
segments: string[]
|
||||
}
|
||||
@@ -14,6 +15,7 @@ export const getLocalizedSortProperty = ({
|
||||
config,
|
||||
fields,
|
||||
locale,
|
||||
parentIsLocalized,
|
||||
result: incomingResult,
|
||||
segments: incomingSegments,
|
||||
}: Args): string => {
|
||||
@@ -35,10 +37,11 @@ export const getLocalizedSortProperty = ({
|
||||
|
||||
if (matchedField && !fieldIsPresentationalOnly(matchedField)) {
|
||||
let nextFields: FlattenedField[]
|
||||
let nextParentIsLocalized = parentIsLocalized
|
||||
const remainingSegments = [...segments]
|
||||
let localizedSegment = matchedField.name
|
||||
|
||||
if (matchedField.localized) {
|
||||
if (fieldShouldBeLocalized({ field: matchedField, parentIsLocalized })) {
|
||||
// Check to see if next segment is a locale
|
||||
if (segments.length > 0) {
|
||||
const nextSegmentIsLocale = config.localization.localeCodes.includes(remainingSegments[0])
|
||||
@@ -62,6 +65,9 @@ export const getLocalizedSortProperty = ({
|
||||
matchedField.type === 'array'
|
||||
) {
|
||||
nextFields = matchedField.flattenedFields
|
||||
if (!nextParentIsLocalized) {
|
||||
nextParentIsLocalized = matchedField.localized
|
||||
}
|
||||
}
|
||||
|
||||
if (matchedField.type === 'blocks') {
|
||||
@@ -92,6 +98,7 @@ export const getLocalizedSortProperty = ({
|
||||
config,
|
||||
fields: nextFields,
|
||||
locale,
|
||||
parentIsLocalized: nextParentIsLocalized,
|
||||
result,
|
||||
segments: remainingSegments,
|
||||
})
|
||||
|
||||
@@ -12,6 +12,7 @@ export async function parseParams({
|
||||
fields,
|
||||
globalSlug,
|
||||
locale,
|
||||
parentIsLocalized,
|
||||
payload,
|
||||
where,
|
||||
}: {
|
||||
@@ -19,6 +20,7 @@ export async function parseParams({
|
||||
fields: FlattenedField[]
|
||||
globalSlug?: string
|
||||
locale: string
|
||||
parentIsLocalized: boolean
|
||||
payload: Payload
|
||||
where: Where
|
||||
}): Promise<Record<string, unknown>> {
|
||||
@@ -40,6 +42,7 @@ export async function parseParams({
|
||||
fields,
|
||||
globalSlug,
|
||||
locale,
|
||||
parentIsLocalized,
|
||||
payload,
|
||||
where: condition,
|
||||
})
|
||||
@@ -63,6 +66,7 @@ export async function parseParams({
|
||||
incomingPath: relationOrPath,
|
||||
locale,
|
||||
operator,
|
||||
parentIsLocalized,
|
||||
payload,
|
||||
val: pathOperators[operator],
|
||||
})
|
||||
|
||||
@@ -8,12 +8,14 @@ import type {
|
||||
|
||||
import { Types } from 'mongoose'
|
||||
import { createArrayFromCommaDelineated } from 'payload'
|
||||
import { fieldShouldBeLocalized } from 'payload/shared'
|
||||
|
||||
type SanitizeQueryValueArgs = {
|
||||
field: FlattenedField
|
||||
hasCustomID: boolean
|
||||
locale?: string
|
||||
operator: string
|
||||
parentIsLocalized: boolean
|
||||
path: string
|
||||
payload: Payload
|
||||
val: any
|
||||
@@ -87,6 +89,7 @@ export const sanitizeQueryValue = ({
|
||||
hasCustomID,
|
||||
locale,
|
||||
operator,
|
||||
parentIsLocalized,
|
||||
path,
|
||||
payload,
|
||||
val,
|
||||
@@ -219,7 +222,11 @@ export const sanitizeQueryValue = ({
|
||||
|
||||
let localizedPath = path
|
||||
|
||||
if (field.localized && payload.config.localization && locale) {
|
||||
if (
|
||||
fieldShouldBeLocalized({ field, parentIsLocalized }) &&
|
||||
payload.config.localization &&
|
||||
locale
|
||||
) {
|
||||
localizedPath = `${path}.${locale}`
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { PipelineStage } from 'mongoose'
|
||||
import type { CollectionSlug, JoinQuery, SanitizedCollectionConfig, Where } from 'payload'
|
||||
|
||||
import { fieldShouldBeLocalized } from 'payload/shared'
|
||||
|
||||
import type { MongooseAdapter } from '../index.js'
|
||||
|
||||
import { buildSortParam } from '../queries/buildSortParam.js'
|
||||
@@ -148,7 +150,14 @@ export const buildJoinAggregation = async ({
|
||||
})
|
||||
} else {
|
||||
const localeSuffix =
|
||||
join.field.localized && adapter.payload.config.localization && locale ? `.${locale}` : ''
|
||||
fieldShouldBeLocalized({
|
||||
field: join.field,
|
||||
parentIsLocalized: join.parentIsLocalized,
|
||||
}) &&
|
||||
adapter.payload.config.localization &&
|
||||
locale
|
||||
? `.${locale}`
|
||||
: ''
|
||||
const as = `${versions ? `version.${join.joinPath}` : join.joinPath}${localeSuffix}`
|
||||
|
||||
let foreignField: string
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import type { FieldAffectingData, FlattenedField, SelectMode, SelectType } from 'payload'
|
||||
|
||||
import { deepCopyObjectSimple, fieldAffectsData, getSelectMode } from 'payload/shared'
|
||||
import {
|
||||
deepCopyObjectSimple,
|
||||
fieldAffectsData,
|
||||
fieldShouldBeLocalized,
|
||||
getSelectMode,
|
||||
} from 'payload/shared'
|
||||
|
||||
import type { MongooseAdapter } from '../index.js'
|
||||
|
||||
@@ -8,18 +13,18 @@ const addFieldToProjection = ({
|
||||
adapter,
|
||||
databaseSchemaPath,
|
||||
field,
|
||||
parentIsLocalized,
|
||||
projection,
|
||||
withinLocalizedField,
|
||||
}: {
|
||||
adapter: MongooseAdapter
|
||||
databaseSchemaPath: string
|
||||
field: FieldAffectingData
|
||||
parentIsLocalized: boolean
|
||||
projection: Record<string, true>
|
||||
withinLocalizedField: boolean
|
||||
}) => {
|
||||
const { config } = adapter.payload
|
||||
|
||||
if (withinLocalizedField && config.localization) {
|
||||
if (parentIsLocalized && config.localization) {
|
||||
for (const locale of config.localization.localeCodes) {
|
||||
const localeDatabaseSchemaPath = databaseSchemaPath.replace('<locale>', locale)
|
||||
projection[`${localeDatabaseSchemaPath}${field.name}`] = true
|
||||
@@ -33,20 +38,20 @@ const traverseFields = ({
|
||||
adapter,
|
||||
databaseSchemaPath = '',
|
||||
fields,
|
||||
parentIsLocalized = false,
|
||||
projection,
|
||||
select,
|
||||
selectAllOnCurrentLevel = false,
|
||||
selectMode,
|
||||
withinLocalizedField = false,
|
||||
}: {
|
||||
adapter: MongooseAdapter
|
||||
databaseSchemaPath?: string
|
||||
fields: FlattenedField[]
|
||||
parentIsLocalized?: boolean
|
||||
projection: Record<string, true>
|
||||
select: SelectType
|
||||
selectAllOnCurrentLevel?: boolean
|
||||
selectMode: SelectMode
|
||||
withinLocalizedField?: boolean
|
||||
}) => {
|
||||
for (const field of fields) {
|
||||
if (fieldAffectsData(field)) {
|
||||
@@ -56,8 +61,8 @@ const traverseFields = ({
|
||||
adapter,
|
||||
databaseSchemaPath,
|
||||
field,
|
||||
parentIsLocalized,
|
||||
projection,
|
||||
withinLocalizedField,
|
||||
})
|
||||
continue
|
||||
}
|
||||
@@ -73,8 +78,8 @@ const traverseFields = ({
|
||||
adapter,
|
||||
databaseSchemaPath,
|
||||
field,
|
||||
parentIsLocalized,
|
||||
projection,
|
||||
withinLocalizedField,
|
||||
})
|
||||
continue
|
||||
}
|
||||
@@ -86,14 +91,12 @@ const traverseFields = ({
|
||||
}
|
||||
|
||||
let fieldDatabaseSchemaPath = databaseSchemaPath
|
||||
let fieldWithinLocalizedField = withinLocalizedField
|
||||
|
||||
if (fieldAffectsData(field)) {
|
||||
fieldDatabaseSchemaPath = `${databaseSchemaPath}${field.name}.`
|
||||
|
||||
if (field.localized) {
|
||||
if (fieldShouldBeLocalized({ field, parentIsLocalized })) {
|
||||
fieldDatabaseSchemaPath = `${fieldDatabaseSchemaPath}<locale>.`
|
||||
fieldWithinLocalizedField = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,10 +114,10 @@ const traverseFields = ({
|
||||
adapter,
|
||||
databaseSchemaPath: fieldDatabaseSchemaPath,
|
||||
fields: field.flattenedFields,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
projection,
|
||||
select: fieldSelect,
|
||||
selectMode,
|
||||
withinLocalizedField: fieldWithinLocalizedField,
|
||||
})
|
||||
|
||||
break
|
||||
@@ -133,11 +136,11 @@ const traverseFields = ({
|
||||
adapter,
|
||||
databaseSchemaPath: fieldDatabaseSchemaPath,
|
||||
fields: block.flattenedFields,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
projection,
|
||||
select: {},
|
||||
selectAllOnCurrentLevel: true,
|
||||
selectMode: 'include',
|
||||
withinLocalizedField: fieldWithinLocalizedField,
|
||||
})
|
||||
continue
|
||||
}
|
||||
@@ -161,10 +164,10 @@ const traverseFields = ({
|
||||
adapter,
|
||||
databaseSchemaPath: fieldDatabaseSchemaPath,
|
||||
fields: block.flattenedFields,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
projection,
|
||||
select: blocksSelect[block.slug] as SelectType,
|
||||
selectMode: blockSelectMode,
|
||||
withinLocalizedField: fieldWithinLocalizedField,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,13 @@ import type { CollectionConfig, Field, SanitizedConfig, TraverseFieldsCallback }
|
||||
|
||||
import { Types } from 'mongoose'
|
||||
import { traverseFields } from 'payload'
|
||||
import { fieldAffectsData } from 'payload/shared'
|
||||
import { fieldAffectsData, fieldShouldBeLocalized } from 'payload/shared'
|
||||
|
||||
type Args = {
|
||||
config: SanitizedConfig
|
||||
data: Record<string, unknown>
|
||||
fields: Field[]
|
||||
parentIsLocalized?: boolean
|
||||
}
|
||||
|
||||
interface RelationObject {
|
||||
@@ -112,6 +113,7 @@ export const sanitizeRelationshipIDs = ({
|
||||
config,
|
||||
data,
|
||||
fields,
|
||||
parentIsLocalized,
|
||||
}: Args): Record<string, unknown> => {
|
||||
const sanitize: TraverseFieldsCallback = ({ field, ref }) => {
|
||||
if (!ref || typeof ref !== 'object') {
|
||||
@@ -124,7 +126,7 @@ export const sanitizeRelationshipIDs = ({
|
||||
}
|
||||
|
||||
// handle localized relationships
|
||||
if (config.localization && field.localized) {
|
||||
if (config.localization && fieldShouldBeLocalized({ field, parentIsLocalized })) {
|
||||
const locales = config.localization.locales
|
||||
const fieldRef = ref[field.name]
|
||||
if (typeof fieldRef !== 'object') {
|
||||
@@ -150,7 +152,14 @@ export const sanitizeRelationshipIDs = ({
|
||||
}
|
||||
}
|
||||
|
||||
traverseFields({ callback: sanitize, config, fields, fillEmpty: false, ref: data })
|
||||
traverseFields({
|
||||
callback: sanitize,
|
||||
config,
|
||||
fields,
|
||||
fillEmpty: false,
|
||||
parentIsLocalized,
|
||||
ref: data,
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { LibSQLDatabase } from 'drizzle-orm/libsql'
|
||||
import type { FlattenedField, JoinQuery, SelectMode, SelectType, Where } from 'payload'
|
||||
|
||||
import { sql } from 'drizzle-orm'
|
||||
import { fieldIsVirtual } from 'payload/shared'
|
||||
import { fieldIsVirtual, fieldShouldBeLocalized } from 'payload/shared'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { BuildQueryJoinAliases, ChainedMethods, DrizzleAdapter } from '../types.js'
|
||||
@@ -26,6 +26,7 @@ type TraverseFieldArgs = {
|
||||
joinQuery: JoinQuery
|
||||
joins?: BuildQueryJoinAliases
|
||||
locale?: string
|
||||
parentIsLocalized?: boolean
|
||||
path: string
|
||||
select?: SelectType
|
||||
selectAllOnCurrentLevel?: boolean
|
||||
@@ -34,7 +35,6 @@ type TraverseFieldArgs = {
|
||||
topLevelArgs: Record<string, unknown>
|
||||
topLevelTableName: string
|
||||
versions?: boolean
|
||||
withinLocalizedField?: boolean
|
||||
withTabledFields: {
|
||||
numbers?: boolean
|
||||
rels?: boolean
|
||||
@@ -53,6 +53,7 @@ export const traverseFields = ({
|
||||
joinQuery = {},
|
||||
joins,
|
||||
locale,
|
||||
parentIsLocalized = false,
|
||||
path,
|
||||
select,
|
||||
selectAllOnCurrentLevel = false,
|
||||
@@ -61,7 +62,6 @@ export const traverseFields = ({
|
||||
topLevelArgs,
|
||||
topLevelTableName,
|
||||
versions,
|
||||
withinLocalizedField = false,
|
||||
withTabledFields,
|
||||
}: TraverseFieldArgs) => {
|
||||
fields.forEach((field) => {
|
||||
@@ -69,6 +69,11 @@ export const traverseFields = ({
|
||||
return
|
||||
}
|
||||
|
||||
const isFieldLocalized = fieldShouldBeLocalized({
|
||||
field,
|
||||
parentIsLocalized,
|
||||
})
|
||||
|
||||
// handle simple relationship
|
||||
if (
|
||||
depth > 0 &&
|
||||
@@ -76,7 +81,7 @@ export const traverseFields = ({
|
||||
!field.hasMany &&
|
||||
typeof field.relationTo === 'string'
|
||||
) {
|
||||
if (field.localized) {
|
||||
if (isFieldLocalized) {
|
||||
_locales.with[`${path}${field.name}`] = true
|
||||
} else {
|
||||
currentArgs.with[`${path}${field.name}`] = true
|
||||
@@ -152,13 +157,13 @@ export const traverseFields = ({
|
||||
fields: field.flattenedFields,
|
||||
joinQuery,
|
||||
locale,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
path: '',
|
||||
select: typeof arraySelect === 'object' ? arraySelect : undefined,
|
||||
selectMode,
|
||||
tablePath: '',
|
||||
topLevelArgs,
|
||||
topLevelTableName,
|
||||
withinLocalizedField: withinLocalizedField || field.localized,
|
||||
withTabledFields,
|
||||
})
|
||||
|
||||
@@ -263,13 +268,13 @@ export const traverseFields = ({
|
||||
fields: block.flattenedFields,
|
||||
joinQuery,
|
||||
locale,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
path: '',
|
||||
select: typeof blockSelect === 'object' ? blockSelect : undefined,
|
||||
selectMode: blockSelectMode,
|
||||
tablePath: '',
|
||||
topLevelArgs,
|
||||
topLevelTableName,
|
||||
withinLocalizedField: withinLocalizedField || field.localized,
|
||||
withTabledFields,
|
||||
})
|
||||
|
||||
@@ -305,6 +310,7 @@ export const traverseFields = ({
|
||||
joinQuery,
|
||||
joins,
|
||||
locale,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
path: `${path}${field.name}_`,
|
||||
select: typeof fieldSelect === 'object' ? fieldSelect : undefined,
|
||||
selectAllOnCurrentLevel:
|
||||
@@ -316,7 +322,6 @@ export const traverseFields = ({
|
||||
topLevelArgs,
|
||||
topLevelTableName,
|
||||
versions,
|
||||
withinLocalizedField: withinLocalizedField || field.localized,
|
||||
withTabledFields,
|
||||
})
|
||||
|
||||
@@ -407,6 +412,9 @@ export const traverseFields = ({
|
||||
fields,
|
||||
joins,
|
||||
locale,
|
||||
// Parent is never localized, as we're passing the `fields` of a **different** collection here. This means that the
|
||||
// parent localization "boundary" is crossed, and we're now in the context of the joined collection.
|
||||
parentIsLocalized: false,
|
||||
selectLocale: true,
|
||||
sort,
|
||||
tableName: joinCollectionTableName,
|
||||
@@ -469,7 +477,7 @@ export const traverseFields = ({
|
||||
break
|
||||
}
|
||||
|
||||
const args = field.localized ? _locales : currentArgs
|
||||
const args = isFieldLocalized ? _locales : currentArgs
|
||||
if (!args.columns) {
|
||||
args.columns = {}
|
||||
}
|
||||
@@ -531,7 +539,7 @@ export const traverseFields = ({
|
||||
if (select || selectAllOnCurrentLevel) {
|
||||
const fieldPath = `${path}${field.name}`
|
||||
|
||||
if ((field.localized || withinLocalizedField) && _locales) {
|
||||
if ((isFieldLocalized || parentIsLocalized) && _locales) {
|
||||
_locales.columns[fieldPath] = true
|
||||
} else if (adapter.tables[currentTableName]?.[fieldPath]) {
|
||||
currentArgs.columns[fieldPath] = true
|
||||
@@ -553,7 +561,7 @@ export const traverseFields = ({
|
||||
) {
|
||||
const fieldPath = `${path}${field.name}`
|
||||
|
||||
if ((field.localized || withinLocalizedField) && _locales) {
|
||||
if ((isFieldLocalized || parentIsLocalized) && _locales) {
|
||||
_locales.columns[fieldPath] = true
|
||||
} else if (adapter.tables[currentTableName]?.[fieldPath]) {
|
||||
currentArgs.columns[fieldPath] = true
|
||||
|
||||
@@ -12,6 +12,7 @@ export function buildAndOrConditions({
|
||||
fields,
|
||||
joins,
|
||||
locale,
|
||||
parentIsLocalized,
|
||||
selectFields,
|
||||
selectLocale,
|
||||
tableName,
|
||||
@@ -24,6 +25,7 @@ export function buildAndOrConditions({
|
||||
globalSlug?: string
|
||||
joins: BuildQueryJoinAliases
|
||||
locale?: string
|
||||
parentIsLocalized: boolean
|
||||
selectFields: Record<string, GenericColumn>
|
||||
selectLocale?: boolean
|
||||
tableName: string
|
||||
@@ -42,6 +44,7 @@ export function buildAndOrConditions({
|
||||
fields,
|
||||
joins,
|
||||
locale,
|
||||
parentIsLocalized,
|
||||
selectFields,
|
||||
selectLocale,
|
||||
tableName,
|
||||
|
||||
@@ -15,6 +15,7 @@ type Args = {
|
||||
fields: FlattenedField[]
|
||||
joins: BuildQueryJoinAliases
|
||||
locale?: string
|
||||
parentIsLocalized: boolean
|
||||
selectFields: Record<string, GenericColumn>
|
||||
sort?: Sort
|
||||
tableName: string
|
||||
@@ -29,6 +30,7 @@ export const buildOrderBy = ({
|
||||
fields,
|
||||
joins,
|
||||
locale,
|
||||
parentIsLocalized,
|
||||
selectFields,
|
||||
sort,
|
||||
tableName,
|
||||
@@ -65,6 +67,7 @@ export const buildOrderBy = ({
|
||||
fields,
|
||||
joins,
|
||||
locale,
|
||||
parentIsLocalized,
|
||||
pathSegments: sortProperty.replace(/__/g, '.').split('.'),
|
||||
selectFields,
|
||||
tableName,
|
||||
|
||||
@@ -20,6 +20,7 @@ type BuildQueryArgs = {
|
||||
fields: FlattenedField[]
|
||||
joins?: BuildQueryJoinAliases
|
||||
locale?: string
|
||||
parentIsLocalized?: boolean
|
||||
selectLocale?: boolean
|
||||
sort?: Sort
|
||||
tableName: string
|
||||
@@ -41,6 +42,7 @@ const buildQuery = function buildQuery({
|
||||
fields,
|
||||
joins = [],
|
||||
locale,
|
||||
parentIsLocalized,
|
||||
selectLocale,
|
||||
sort,
|
||||
tableName,
|
||||
@@ -56,6 +58,7 @@ const buildQuery = function buildQuery({
|
||||
fields,
|
||||
joins,
|
||||
locale,
|
||||
parentIsLocalized,
|
||||
selectFields,
|
||||
sort,
|
||||
tableName,
|
||||
@@ -70,6 +73,7 @@ const buildQuery = function buildQuery({
|
||||
fields,
|
||||
joins,
|
||||
locale,
|
||||
parentIsLocalized,
|
||||
selectFields,
|
||||
selectLocale,
|
||||
tableName,
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { FlattenedBlock, FlattenedField, NumberField, TextField } from 'pay
|
||||
import { and, eq, like, sql } from 'drizzle-orm'
|
||||
import { type PgTableWithColumns } from 'drizzle-orm/pg-core'
|
||||
import { APIError } from 'payload'
|
||||
import { tabHasName } from 'payload/shared'
|
||||
import { fieldShouldBeLocalized, tabHasName } from 'payload/shared'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
import { validate as uuidValidate } from 'uuid'
|
||||
|
||||
@@ -46,6 +46,7 @@ type Args = {
|
||||
fields: FlattenedField[]
|
||||
joins: BuildQueryJoinAliases
|
||||
locale?: string
|
||||
parentIsLocalized: boolean
|
||||
pathSegments: string[]
|
||||
rootTableName?: string
|
||||
selectFields: Record<string, GenericColumn>
|
||||
@@ -75,6 +76,7 @@ export const getTableColumnFromPath = ({
|
||||
fields,
|
||||
joins,
|
||||
locale: incomingLocale,
|
||||
parentIsLocalized,
|
||||
pathSegments: incomingSegments,
|
||||
rootTableName: incomingRootTableName,
|
||||
selectFields,
|
||||
@@ -107,9 +109,11 @@ export const getTableColumnFromPath = ({
|
||||
if (field) {
|
||||
const pathSegments = [...incomingSegments]
|
||||
|
||||
const isFieldLocalized = fieldShouldBeLocalized({ field, parentIsLocalized })
|
||||
|
||||
// If next segment is a locale,
|
||||
// we need to take it out and use it as the locale from this point on
|
||||
if ('localized' in field && field.localized && adapter.payload.config.localization) {
|
||||
if (isFieldLocalized && adapter.payload.config.localization) {
|
||||
const matchedLocale = adapter.payload.config.localization.localeCodes.find(
|
||||
(locale) => locale === pathSegments[1],
|
||||
)
|
||||
@@ -129,7 +133,7 @@ export const getTableColumnFromPath = ({
|
||||
const arrayParentTable = aliasTable || adapter.tables[tableName]
|
||||
|
||||
constraintPath = `${constraintPath}${field.name}.%.`
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
if (locale && isFieldLocalized && adapter.payload.config.localization) {
|
||||
const conditions = [eq(arrayParentTable.id, adapter.tables[newTableName]._parentID)]
|
||||
|
||||
if (selectLocale) {
|
||||
@@ -159,6 +163,7 @@ export const getTableColumnFromPath = ({
|
||||
fields: field.flattenedFields,
|
||||
joins,
|
||||
locale,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
pathSegments: pathSegments.slice(1),
|
||||
rootTableName,
|
||||
selectFields,
|
||||
@@ -224,6 +229,7 @@ export const getTableColumnFromPath = ({
|
||||
fields: block.flattenedFields,
|
||||
joins,
|
||||
locale,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
pathSegments: pathSegments.slice(1),
|
||||
rootTableName,
|
||||
selectFields: blockSelectFields,
|
||||
@@ -240,7 +246,7 @@ export const getTableColumnFromPath = ({
|
||||
blockTableColumn = result
|
||||
constraints = constraints.concat(blockConstraints)
|
||||
selectFields = { ...selectFields, ...blockSelectFields }
|
||||
if (field.localized && adapter.payload.config.localization) {
|
||||
if (isFieldLocalized && adapter.payload.config.localization) {
|
||||
const conditions = [
|
||||
eq(
|
||||
(aliasTable || adapter.tables[tableName]).id,
|
||||
@@ -281,7 +287,7 @@ export const getTableColumnFromPath = ({
|
||||
}
|
||||
|
||||
case 'group': {
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
if (locale && isFieldLocalized && adapter.payload.config.localization) {
|
||||
newTableName = `${tableName}${adapter.localesSuffix}`
|
||||
|
||||
let condition = eq(adapter.tables[tableName].id, adapter.tables[newTableName]._parentID)
|
||||
@@ -306,6 +312,7 @@ export const getTableColumnFromPath = ({
|
||||
fields: field.flattenedFields,
|
||||
joins,
|
||||
locale,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
pathSegments: pathSegments.slice(1),
|
||||
rootTableName,
|
||||
selectFields,
|
||||
@@ -331,7 +338,7 @@ export const getTableColumnFromPath = ({
|
||||
like(adapter.tables[newTableName].path, `${constraintPath}${field.name}`),
|
||||
]
|
||||
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
if (locale && isFieldLocalized && adapter.payload.config.localization) {
|
||||
const conditions = [...joinConstraints]
|
||||
|
||||
if (locale !== 'all') {
|
||||
@@ -375,12 +382,12 @@ export const getTableColumnFromPath = ({
|
||||
tableName: relationTableName,
|
||||
})
|
||||
|
||||
if (selectLocale && field.localized && adapter.payload.config.localization) {
|
||||
if (selectLocale && isFieldLocalized && adapter.payload.config.localization) {
|
||||
selectFields._locale = aliasRelationshipTable.locale
|
||||
}
|
||||
|
||||
// Join in the relationships table
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
if (locale && isFieldLocalized && adapter.payload.config.localization) {
|
||||
const conditions = [
|
||||
eq((aliasTable || adapter.tables[rootTableName]).id, aliasRelationshipTable.parent),
|
||||
like(aliasRelationshipTable.path, `${constraintPath}${field.name}`),
|
||||
@@ -546,9 +553,11 @@ export const getTableColumnFromPath = ({
|
||||
aliasTable: newAliasTable,
|
||||
collectionPath: newCollectionPath,
|
||||
constraints,
|
||||
// relationshipFields are fields from a different collection => no parentIsLocalized
|
||||
fields: relationshipFields,
|
||||
joins,
|
||||
locale,
|
||||
parentIsLocalized: false,
|
||||
pathSegments: pathSegments.slice(1),
|
||||
rootTableName: newTableName,
|
||||
selectFields,
|
||||
@@ -567,7 +576,7 @@ export const getTableColumnFromPath = ({
|
||||
)
|
||||
const { newAliasTable } = getTableAlias({ adapter, tableName: newTableName })
|
||||
|
||||
if (field.localized && adapter.payload.config.localization) {
|
||||
if (isFieldLocalized && adapter.payload.config.localization) {
|
||||
const { newAliasTable: aliasLocaleTable } = getTableAlias({
|
||||
adapter,
|
||||
tableName: `${rootTableName}${adapter.localesSuffix}`,
|
||||
@@ -614,6 +623,7 @@ export const getTableColumnFromPath = ({
|
||||
fields: adapter.payload.collections[field.relationTo].config.flattenedFields,
|
||||
joins,
|
||||
locale,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
pathSegments: pathSegments.slice(1),
|
||||
selectFields,
|
||||
tableName: newTableName,
|
||||
@@ -629,7 +639,7 @@ export const getTableColumnFromPath = ({
|
||||
`${tableName}_${tableNameSuffix}${toSnakeCase(field.name)}`,
|
||||
)
|
||||
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
if (locale && isFieldLocalized && adapter.payload.config.localization) {
|
||||
const conditions = [
|
||||
eq(adapter.tables[tableName].id, adapter.tables[newTableName].parent),
|
||||
eq(adapter.tables[newTableName]._locale, locale),
|
||||
@@ -674,6 +684,7 @@ export const getTableColumnFromPath = ({
|
||||
fields: field.flattenedFields,
|
||||
joins,
|
||||
locale,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
pathSegments: pathSegments.slice(1),
|
||||
rootTableName,
|
||||
selectFields,
|
||||
@@ -693,6 +704,7 @@ export const getTableColumnFromPath = ({
|
||||
fields: field.flattenedFields,
|
||||
joins,
|
||||
locale,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
pathSegments: pathSegments.slice(1),
|
||||
rootTableName,
|
||||
selectFields,
|
||||
@@ -711,7 +723,7 @@ export const getTableColumnFromPath = ({
|
||||
|
||||
let newTable = adapter.tables[newTableName]
|
||||
|
||||
if (field.localized && adapter.payload.config.localization) {
|
||||
if (isFieldLocalized && adapter.payload.config.localization) {
|
||||
// If localized, we go to localized table and set aliasTable to undefined
|
||||
// so it is not picked up below to be used as targetTable
|
||||
const parentTable = aliasTable || adapter.tables[tableName]
|
||||
|
||||
@@ -20,6 +20,7 @@ type Args = {
|
||||
fields: FlattenedField[]
|
||||
joins: BuildQueryJoinAliases
|
||||
locale: string
|
||||
parentIsLocalized: boolean
|
||||
selectFields: Record<string, GenericColumn>
|
||||
selectLocale?: boolean
|
||||
tableName: string
|
||||
@@ -32,6 +33,7 @@ export function parseParams({
|
||||
fields,
|
||||
joins,
|
||||
locale,
|
||||
parentIsLocalized,
|
||||
selectFields,
|
||||
selectLocale,
|
||||
tableName,
|
||||
@@ -58,6 +60,7 @@ export function parseParams({
|
||||
fields,
|
||||
joins,
|
||||
locale,
|
||||
parentIsLocalized,
|
||||
selectFields,
|
||||
selectLocale,
|
||||
tableName,
|
||||
@@ -92,6 +95,7 @@ export function parseParams({
|
||||
fields,
|
||||
joins,
|
||||
locale,
|
||||
parentIsLocalized,
|
||||
pathSegments: relationOrPath.replace(/__/g, '.').split('.'),
|
||||
selectFields,
|
||||
selectLocale,
|
||||
|
||||
@@ -37,6 +37,7 @@ type Args = {
|
||||
disableRelsTableUnique?: boolean
|
||||
disableUnique: boolean
|
||||
fields: FlattenedField[]
|
||||
parentIsLocalized: boolean
|
||||
rootRelationships?: Set<string>
|
||||
rootRelationsToBuild?: RelationMap
|
||||
rootTableIDColType?: IDType
|
||||
@@ -71,6 +72,7 @@ export const buildTable = ({
|
||||
disableRelsTableUnique = false,
|
||||
disableUnique = false,
|
||||
fields,
|
||||
parentIsLocalized,
|
||||
rootRelationships,
|
||||
rootRelationsToBuild,
|
||||
rootTableIDColType,
|
||||
@@ -124,6 +126,7 @@ export const buildTable = ({
|
||||
localesColumns,
|
||||
localesIndexes,
|
||||
newTableName: tableName,
|
||||
parentIsLocalized,
|
||||
parentTableName: tableName,
|
||||
relationships,
|
||||
relationsToBuild,
|
||||
|
||||
@@ -55,6 +55,7 @@ export const buildRawSchema = ({
|
||||
disableNotNull: !!collection?.versions?.drafts,
|
||||
disableUnique: false,
|
||||
fields: collection.flattenedFields,
|
||||
parentIsLocalized: false,
|
||||
setColumnID,
|
||||
tableName,
|
||||
timestamps: collection.timestamps,
|
||||
@@ -72,6 +73,7 @@ export const buildRawSchema = ({
|
||||
disableNotNull: !!collection.versions?.drafts,
|
||||
disableUnique: true,
|
||||
fields: versionFields,
|
||||
parentIsLocalized: false,
|
||||
setColumnID,
|
||||
tableName: versionsTableName,
|
||||
timestamps: true,
|
||||
@@ -91,6 +93,7 @@ export const buildRawSchema = ({
|
||||
disableNotNull: !!global?.versions?.drafts,
|
||||
disableUnique: false,
|
||||
fields: global.flattenedFields,
|
||||
parentIsLocalized: false,
|
||||
setColumnID,
|
||||
tableName,
|
||||
timestamps: false,
|
||||
@@ -112,6 +115,7 @@ export const buildRawSchema = ({
|
||||
disableNotNull: !!global.versions?.drafts,
|
||||
disableUnique: true,
|
||||
fields: versionFields,
|
||||
parentIsLocalized: false,
|
||||
setColumnID,
|
||||
tableName: versionsTableName,
|
||||
timestamps: true,
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import type { FlattenedField } from 'payload'
|
||||
|
||||
import { InvalidConfiguration } from 'payload'
|
||||
import { fieldAffectsData, fieldIsVirtual, optionIsObject } from 'payload/shared'
|
||||
import {
|
||||
fieldAffectsData,
|
||||
fieldIsVirtual,
|
||||
fieldShouldBeLocalized,
|
||||
optionIsObject,
|
||||
} from 'payload/shared'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type {
|
||||
@@ -37,6 +42,7 @@ type Args = {
|
||||
localesColumns: Record<string, RawColumn>
|
||||
localesIndexes: Record<string, RawIndex>
|
||||
newTableName: string
|
||||
parentIsLocalized: boolean
|
||||
parentTableName: string
|
||||
relationships: Set<string>
|
||||
relationsToBuild: RelationMap
|
||||
@@ -76,6 +82,7 @@ export const traverseFields = ({
|
||||
localesColumns,
|
||||
localesIndexes,
|
||||
newTableName,
|
||||
parentIsLocalized,
|
||||
parentTableName,
|
||||
relationships,
|
||||
relationsToBuild,
|
||||
@@ -119,11 +126,13 @@ export const traverseFields = ({
|
||||
)}`
|
||||
const fieldName = `${fieldPrefix?.replace('.', '_') || ''}${field.name}`
|
||||
|
||||
const isFieldLocalized = fieldShouldBeLocalized({ field, parentIsLocalized })
|
||||
|
||||
// If field is localized,
|
||||
// add the column to the locale table instead of main table
|
||||
if (
|
||||
adapter.payload.config.localization &&
|
||||
(field.localized || forceLocalized) &&
|
||||
(isFieldLocalized || forceLocalized) &&
|
||||
field.type !== 'array' &&
|
||||
field.type !== 'blocks' &&
|
||||
(('hasMany' in field && field.hasMany !== true) || !('hasMany' in field))
|
||||
@@ -152,7 +161,7 @@ export const traverseFields = ({
|
||||
|
||||
targetIndexes[indexName] = {
|
||||
name: indexName,
|
||||
on: field.localized ? [fieldName, '_locale'] : fieldName,
|
||||
on: isFieldLocalized ? [fieldName, '_locale'] : fieldName,
|
||||
unique,
|
||||
}
|
||||
}
|
||||
@@ -209,7 +218,7 @@ export const traverseFields = ({
|
||||
}
|
||||
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
Boolean(isFieldLocalized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock ||
|
||||
forceLocalized
|
||||
|
||||
@@ -243,6 +252,7 @@ export const traverseFields = ({
|
||||
disableRelsTableUnique: true,
|
||||
disableUnique,
|
||||
fields: disableUnique ? idToUUID(field.flattenedFields) : field.flattenedFields,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
rootRelationships: relationships,
|
||||
rootRelationsToBuild,
|
||||
rootTableIDColType,
|
||||
@@ -299,7 +309,12 @@ export const traverseFields = ({
|
||||
},
|
||||
}
|
||||
|
||||
if (hasLocalesTable(field.fields)) {
|
||||
if (
|
||||
hasLocalesTable({
|
||||
fields: field.fields,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
})
|
||||
) {
|
||||
arrayRelations._locales = {
|
||||
type: 'many',
|
||||
relationName: '_locales',
|
||||
@@ -403,7 +418,7 @@ export const traverseFields = ({
|
||||
}
|
||||
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
Boolean(isFieldLocalized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock ||
|
||||
forceLocalized
|
||||
|
||||
@@ -437,6 +452,7 @@ export const traverseFields = ({
|
||||
disableRelsTableUnique: true,
|
||||
disableUnique,
|
||||
fields: disableUnique ? idToUUID(block.flattenedFields) : block.flattenedFields,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
rootRelationships: relationships,
|
||||
rootRelationsToBuild,
|
||||
rootTableIDColType,
|
||||
@@ -487,7 +503,12 @@ export const traverseFields = ({
|
||||
},
|
||||
}
|
||||
|
||||
if (hasLocalesTable(block.fields)) {
|
||||
if (
|
||||
hasLocalesTable({
|
||||
fields: block.fields,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
})
|
||||
) {
|
||||
blockRelations._locales = {
|
||||
type: 'many',
|
||||
relationName: '_locales',
|
||||
@@ -529,6 +550,7 @@ export const traverseFields = ({
|
||||
validateExistingBlockIsIdentical({
|
||||
block,
|
||||
localized: field.localized,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
rootTableName,
|
||||
table: adapter.rawTables[blockTableName],
|
||||
tableLocales: adapter.rawTables[`${blockTableName}${adapter.localesSuffix}`],
|
||||
@@ -605,11 +627,12 @@ export const traverseFields = ({
|
||||
disableUnique,
|
||||
fieldPrefix: `${fieldName}.`,
|
||||
fields: field.flattenedFields,
|
||||
forceLocalized: field.localized,
|
||||
forceLocalized: isFieldLocalized,
|
||||
indexes,
|
||||
localesColumns,
|
||||
localesIndexes,
|
||||
newTableName: `${parentTableName}_${columnName}`,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentTableName,
|
||||
relationships,
|
||||
relationsToBuild,
|
||||
@@ -619,7 +642,7 @@ export const traverseFields = ({
|
||||
setColumnID,
|
||||
uniqueRelationships,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock: withinLocalizedArrayOrBlock || field.localized,
|
||||
withinLocalizedArrayOrBlock: withinLocalizedArrayOrBlock || isFieldLocalized,
|
||||
})
|
||||
|
||||
if (groupHasLocalizedField) {
|
||||
@@ -659,7 +682,7 @@ export const traverseFields = ({
|
||||
case 'number': {
|
||||
if (field.hasMany) {
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
Boolean(isFieldLocalized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock ||
|
||||
forceLocalized
|
||||
|
||||
@@ -784,7 +807,7 @@ export const traverseFields = ({
|
||||
}
|
||||
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
Boolean(isFieldLocalized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock ||
|
||||
forceLocalized
|
||||
|
||||
@@ -817,6 +840,7 @@ export const traverseFields = ({
|
||||
disableNotNull,
|
||||
disableUnique,
|
||||
fields: [],
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
rootTableName,
|
||||
setColumnID,
|
||||
tableName: selectTableName,
|
||||
@@ -904,7 +928,7 @@ export const traverseFields = ({
|
||||
// add relationship to table
|
||||
relationsToBuild.set(fieldName, {
|
||||
type: 'one',
|
||||
localized: adapter.payload.config.localization && (field.localized || forceLocalized),
|
||||
localized: adapter.payload.config.localization && (isFieldLocalized || forceLocalized),
|
||||
target: tableName,
|
||||
})
|
||||
|
||||
@@ -916,7 +940,7 @@ export const traverseFields = ({
|
||||
}
|
||||
|
||||
if (
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
Boolean(isFieldLocalized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock
|
||||
) {
|
||||
hasLocalizedRelationshipField = true
|
||||
@@ -927,7 +951,7 @@ export const traverseFields = ({
|
||||
case 'text': {
|
||||
if (field.hasMany) {
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
Boolean(isFieldLocalized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock ||
|
||||
forceLocalized
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ type TransformArgs = {
|
||||
fields: FlattenedField[]
|
||||
joinQuery?: JoinQuery
|
||||
locale?: string
|
||||
parentIsLocalized?: boolean
|
||||
}
|
||||
|
||||
// This is the entry point to transform Drizzle output data
|
||||
@@ -24,6 +25,7 @@ export const transform = <T extends Record<string, unknown> | TypeWithID>({
|
||||
data,
|
||||
fields,
|
||||
joinQuery,
|
||||
parentIsLocalized,
|
||||
}: TransformArgs): T => {
|
||||
let relationships: Record<string, Record<string, unknown>[]> = {}
|
||||
let texts: Record<string, Record<string, unknown>[]> = {}
|
||||
@@ -59,6 +61,7 @@ export const transform = <T extends Record<string, unknown> | TypeWithID>({
|
||||
fields,
|
||||
joinQuery,
|
||||
numbers,
|
||||
parentIsLocalized,
|
||||
path: '',
|
||||
relationships,
|
||||
table: data,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { FlattenedBlock, FlattenedField, JoinQuery, SanitizedConfig } from 'payload'
|
||||
|
||||
import { fieldIsVirtual } from 'payload/shared'
|
||||
import { fieldIsVirtual, fieldShouldBeLocalized } from 'payload/shared'
|
||||
|
||||
import type { DrizzleAdapter } from '../../types.js'
|
||||
import type { BlocksMap } from '../../utilities/createBlocksMap.js'
|
||||
@@ -46,6 +46,7 @@ type TraverseFieldsArgs = {
|
||||
* All hasMany number fields, as returned by Drizzle, keyed on an object by field path
|
||||
*/
|
||||
numbers: Record<string, Record<string, unknown>[]>
|
||||
parentIsLocalized: boolean
|
||||
/**
|
||||
* The current field path (in dot notation), used to merge in relationships
|
||||
*/
|
||||
@@ -80,6 +81,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
fields,
|
||||
joinQuery,
|
||||
numbers,
|
||||
parentIsLocalized,
|
||||
path,
|
||||
relationships,
|
||||
table,
|
||||
@@ -105,9 +107,11 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
deletions.push(() => delete table[fieldName])
|
||||
}
|
||||
|
||||
const isLocalized = fieldShouldBeLocalized({ field, parentIsLocalized })
|
||||
|
||||
if (field.type === 'array') {
|
||||
if (Array.isArray(fieldData)) {
|
||||
if (field.localized) {
|
||||
if (isLocalized) {
|
||||
result[field.name] = fieldData.reduce((arrayResult, row) => {
|
||||
if (typeof row._locale === 'string') {
|
||||
if (!arrayResult[row._locale]) {
|
||||
@@ -130,6 +134,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
fieldPrefix: '',
|
||||
fields: field.flattenedFields,
|
||||
numbers,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
path: `${sanitizedPath}${field.name}.${row._order - 1}`,
|
||||
relationships,
|
||||
table: row,
|
||||
@@ -175,6 +180,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
fieldPrefix: '',
|
||||
fields: field.flattenedFields,
|
||||
numbers,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
path: `${sanitizedPath}${field.name}.${i}`,
|
||||
relationships,
|
||||
table: row,
|
||||
@@ -197,7 +203,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
const blocksByPath = blocks[blockFieldPath]
|
||||
|
||||
if (Array.isArray(blocksByPath)) {
|
||||
if (field.localized) {
|
||||
if (isLocalized) {
|
||||
result[field.name] = {}
|
||||
|
||||
blocksByPath.forEach((row) => {
|
||||
@@ -232,6 +238,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
fieldPrefix: '',
|
||||
fields: block.flattenedFields,
|
||||
numbers,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
path: `${blockFieldPath}.${row._order - 1}`,
|
||||
relationships,
|
||||
table: row,
|
||||
@@ -303,6 +310,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
fieldPrefix: '',
|
||||
fields: block.flattenedFields,
|
||||
numbers,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
path: `${blockFieldPath}.${i}`,
|
||||
relationships,
|
||||
table: row,
|
||||
@@ -328,7 +336,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
if (field.type === 'relationship' || field.type === 'upload') {
|
||||
if (typeof field.relationTo === 'string' && !('hasMany' in field && field.hasMany)) {
|
||||
if (
|
||||
field.localized &&
|
||||
isLocalized &&
|
||||
config.localization &&
|
||||
config.localization.locales &&
|
||||
Array.isArray(table?._locales)
|
||||
@@ -344,7 +352,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
|
||||
if (!relationPathMatch) {
|
||||
if ('hasMany' in field && field.hasMany) {
|
||||
if (field.localized && config.localization && config.localization.locales) {
|
||||
if (isLocalized && config.localization && config.localization.locales) {
|
||||
result[field.name] = {
|
||||
[config.localization.defaultLocale]: [],
|
||||
}
|
||||
@@ -356,7 +364,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
return result
|
||||
}
|
||||
|
||||
if (field.localized) {
|
||||
if (isLocalized) {
|
||||
result[field.name] = {}
|
||||
const relationsByLocale: Record<string, Record<string, unknown>[]> = {}
|
||||
|
||||
@@ -402,7 +410,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
| { docs: unknown[]; hasNextPage: boolean }
|
||||
| Record<string, { docs: unknown[]; hasNextPage: boolean }>
|
||||
if (Array.isArray(fieldData)) {
|
||||
if (field.localized && adapter.payload.config.localization) {
|
||||
if (isLocalized && adapter.payload.config.localization) {
|
||||
fieldResult = fieldData.reduce(
|
||||
(joinResult, row) => {
|
||||
if (typeof row.locale === 'string') {
|
||||
@@ -446,7 +454,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
return result
|
||||
}
|
||||
|
||||
if (field.localized) {
|
||||
if (isLocalized) {
|
||||
result[field.name] = {}
|
||||
const textsByLocale: Record<string, Record<string, unknown>[]> = {}
|
||||
|
||||
@@ -485,7 +493,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
return result
|
||||
}
|
||||
|
||||
if (field.localized) {
|
||||
if (isLocalized) {
|
||||
result[field.name] = {}
|
||||
const numbersByLocale: Record<string, Record<string, unknown>[]> = {}
|
||||
|
||||
@@ -520,7 +528,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
|
||||
if (field.type === 'select' && field.hasMany) {
|
||||
if (Array.isArray(fieldData)) {
|
||||
if (field.localized) {
|
||||
if (isLocalized) {
|
||||
result[field.name] = fieldData.reduce((selectResult, row) => {
|
||||
if (typeof row.locale === 'string') {
|
||||
if (!selectResult[row.locale]) {
|
||||
@@ -542,7 +550,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
return result
|
||||
}
|
||||
|
||||
if (field.localized && Array.isArray(table._locales)) {
|
||||
if (isLocalized && Array.isArray(table._locales)) {
|
||||
if (!table._locales.length && adapter.payload.config.localization) {
|
||||
adapter.payload.config.localization.localeCodes.forEach((_locale) =>
|
||||
(table._locales as unknown[]).push({ _locale }),
|
||||
@@ -581,9 +589,9 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
const groupFieldPrefix = `${fieldPrefix || ''}${field.name}_`
|
||||
const groupData = {}
|
||||
const locale = table._locale as string
|
||||
const refKey = field.localized && locale ? locale : field.name
|
||||
const refKey = isLocalized && locale ? locale : field.name
|
||||
|
||||
if (field.localized && locale) {
|
||||
if (isLocalized && locale) {
|
||||
delete table._locale
|
||||
}
|
||||
ref[refKey] = traverseFields<Record<string, unknown>>({
|
||||
@@ -595,6 +603,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
fieldPrefix: groupFieldPrefix,
|
||||
fields: field.flattenedFields,
|
||||
numbers,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
path: `${sanitizedPath}${field.name}`,
|
||||
relationships,
|
||||
table,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { FlattenedArrayField } from 'payload'
|
||||
|
||||
import { fieldShouldBeLocalized } from 'payload/shared'
|
||||
|
||||
import type { DrizzleAdapter } from '../../types.js'
|
||||
import type { ArrayRowToInsert, BlockRowToInsert, RelationshipToDelete } from './types.js'
|
||||
|
||||
@@ -18,6 +20,7 @@ type Args = {
|
||||
field: FlattenedArrayField
|
||||
locale?: string
|
||||
numbers: Record<string, unknown>[]
|
||||
parentIsLocalized: boolean
|
||||
path: string
|
||||
relationships: Record<string, unknown>[]
|
||||
relationshipsToDelete: RelationshipToDelete[]
|
||||
@@ -42,6 +45,7 @@ export const transformArray = ({
|
||||
field,
|
||||
locale,
|
||||
numbers,
|
||||
parentIsLocalized,
|
||||
path,
|
||||
relationships,
|
||||
relationshipsToDelete,
|
||||
@@ -79,7 +83,7 @@ export const transformArray = ({
|
||||
}
|
||||
}
|
||||
|
||||
if (field.localized) {
|
||||
if (fieldShouldBeLocalized({ field, parentIsLocalized }) && locale) {
|
||||
newRow.row._locale = locale
|
||||
}
|
||||
|
||||
@@ -100,6 +104,7 @@ export const transformArray = ({
|
||||
insideArrayOrBlock: true,
|
||||
locales: newRow.locales,
|
||||
numbers,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentTableName: arrayTableName,
|
||||
path: `${path || ''}${field.name}.${i}.`,
|
||||
relationships,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { FlattenedBlock, FlattenedBlocksField } from 'payload'
|
||||
|
||||
import { fieldShouldBeLocalized } from 'payload/shared'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { DrizzleAdapter } from '../../types.js'
|
||||
@@ -18,6 +19,7 @@ type Args = {
|
||||
field: FlattenedBlocksField
|
||||
locale?: string
|
||||
numbers: Record<string, unknown>[]
|
||||
parentIsLocalized: boolean
|
||||
path: string
|
||||
relationships: Record<string, unknown>[]
|
||||
relationshipsToDelete: RelationshipToDelete[]
|
||||
@@ -40,6 +42,7 @@ export const transformBlocks = ({
|
||||
field,
|
||||
locale,
|
||||
numbers,
|
||||
parentIsLocalized,
|
||||
path,
|
||||
relationships,
|
||||
relationshipsToDelete,
|
||||
@@ -76,7 +79,7 @@ export const transformBlocks = ({
|
||||
},
|
||||
}
|
||||
|
||||
if (field.localized && locale) {
|
||||
if (fieldShouldBeLocalized({ field, parentIsLocalized }) && locale) {
|
||||
newRow.row._locale = locale
|
||||
}
|
||||
if (withinArrayOrBlockLocale) {
|
||||
@@ -110,6 +113,7 @@ export const transformBlocks = ({
|
||||
insideArrayOrBlock: true,
|
||||
locales: newRow.locales,
|
||||
numbers,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentTableName: blockTableName,
|
||||
path: `${path || ''}${field.name}.${i}.`,
|
||||
relationships,
|
||||
|
||||
@@ -9,6 +9,7 @@ type Args = {
|
||||
adapter: DrizzleAdapter
|
||||
data: Record<string, unknown>
|
||||
fields: FlattenedField[]
|
||||
parentIsLocalized?: boolean
|
||||
path?: string
|
||||
tableName: string
|
||||
}
|
||||
@@ -17,6 +18,7 @@ export const transformForWrite = ({
|
||||
adapter,
|
||||
data,
|
||||
fields,
|
||||
parentIsLocalized,
|
||||
path = '',
|
||||
tableName,
|
||||
}: Args): RowToInsert => {
|
||||
@@ -48,6 +50,7 @@ export const transformForWrite = ({
|
||||
fields,
|
||||
locales: rowToInsert.locales,
|
||||
numbers: rowToInsert.numbers,
|
||||
parentIsLocalized,
|
||||
parentTableName: tableName,
|
||||
path,
|
||||
relationships: rowToInsert.relationships,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { FlattenedField } from 'payload'
|
||||
|
||||
import { sql } from 'drizzle-orm'
|
||||
import { fieldIsVirtual } from 'payload/shared'
|
||||
import { fieldIsVirtual, fieldShouldBeLocalized } from 'payload/shared'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { DrizzleAdapter } from '../../types.js'
|
||||
@@ -50,6 +50,7 @@ type Args = {
|
||||
[locale: string]: Record<string, unknown>
|
||||
}
|
||||
numbers: Record<string, unknown>[]
|
||||
parentIsLocalized: boolean
|
||||
/**
|
||||
* This is the name of the parent table
|
||||
*/
|
||||
@@ -84,6 +85,7 @@ export const traverseFields = ({
|
||||
insideArrayOrBlock = false,
|
||||
locales,
|
||||
numbers,
|
||||
parentIsLocalized,
|
||||
parentTableName,
|
||||
path,
|
||||
relationships,
|
||||
@@ -110,6 +112,8 @@ export const traverseFields = ({
|
||||
fieldName = `${fieldPrefix || ''}${field.name}`
|
||||
fieldData = data[field.name]
|
||||
|
||||
const isLocalized = fieldShouldBeLocalized({ field, parentIsLocalized })
|
||||
|
||||
if (field.type === 'array') {
|
||||
const arrayTableName = adapter.tableNameMap.get(`${parentTableName}_${columnName}`)
|
||||
|
||||
@@ -117,7 +121,7 @@ export const traverseFields = ({
|
||||
arrays[arrayTableName] = []
|
||||
}
|
||||
|
||||
if (field.localized) {
|
||||
if (isLocalized) {
|
||||
if (typeof data[field.name] === 'object' && data[field.name] !== null) {
|
||||
Object.entries(data[field.name]).forEach(([localeKey, localeData]) => {
|
||||
if (Array.isArray(localeData)) {
|
||||
@@ -131,6 +135,7 @@ export const traverseFields = ({
|
||||
field,
|
||||
locale: localeKey,
|
||||
numbers,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
path,
|
||||
relationships,
|
||||
relationshipsToDelete,
|
||||
@@ -153,6 +158,7 @@ export const traverseFields = ({
|
||||
data: data[field.name],
|
||||
field,
|
||||
numbers,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
path,
|
||||
relationships,
|
||||
relationshipsToDelete,
|
||||
@@ -172,7 +178,7 @@ export const traverseFields = ({
|
||||
blocksToDelete.add(toSnakeCase(typeof block === 'string' ? block : block.slug))
|
||||
})
|
||||
|
||||
if (field.localized) {
|
||||
if (isLocalized) {
|
||||
if (typeof data[field.name] === 'object' && data[field.name] !== null) {
|
||||
Object.entries(data[field.name]).forEach(([localeKey, localeData]) => {
|
||||
if (Array.isArray(localeData)) {
|
||||
@@ -185,6 +191,7 @@ export const traverseFields = ({
|
||||
field,
|
||||
locale: localeKey,
|
||||
numbers,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
path,
|
||||
relationships,
|
||||
relationshipsToDelete,
|
||||
@@ -204,6 +211,7 @@ export const traverseFields = ({
|
||||
data: fieldData,
|
||||
field,
|
||||
numbers,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
path,
|
||||
relationships,
|
||||
relationshipsToDelete,
|
||||
@@ -218,7 +226,7 @@ export const traverseFields = ({
|
||||
|
||||
if (field.type === 'group' || field.type === 'tab') {
|
||||
if (typeof data[field.name] === 'object' && data[field.name] !== null) {
|
||||
if (field.localized) {
|
||||
if (isLocalized) {
|
||||
Object.entries(data[field.name]).forEach(([localeKey, localeData]) => {
|
||||
// preserve array ID if there is
|
||||
localeData._uuid = data.id || data._uuid
|
||||
@@ -238,6 +246,7 @@ export const traverseFields = ({
|
||||
insideArrayOrBlock,
|
||||
locales,
|
||||
numbers,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentTableName,
|
||||
path: `${path || ''}${field.name}.`,
|
||||
relationships,
|
||||
@@ -267,6 +276,7 @@ export const traverseFields = ({
|
||||
insideArrayOrBlock,
|
||||
locales,
|
||||
numbers,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentTableName,
|
||||
path: `${path || ''}${field.name}.`,
|
||||
relationships,
|
||||
@@ -286,7 +296,7 @@ export const traverseFields = ({
|
||||
const relationshipPath = `${path || ''}${field.name}`
|
||||
|
||||
if (
|
||||
field.localized &&
|
||||
isLocalized &&
|
||||
(Array.isArray(field.relationTo) || ('hasMany' in field && field.hasMany))
|
||||
) {
|
||||
if (typeof fieldData === 'object') {
|
||||
@@ -329,14 +339,14 @@ export const traverseFields = ({
|
||||
return
|
||||
} else {
|
||||
if (
|
||||
!field.localized &&
|
||||
!isLocalized &&
|
||||
fieldData &&
|
||||
typeof fieldData === 'object' &&
|
||||
'id' in fieldData &&
|
||||
fieldData?.id
|
||||
) {
|
||||
fieldData = fieldData.id
|
||||
} else if (field.localized) {
|
||||
} else if (isLocalized) {
|
||||
if (typeof fieldData === 'object') {
|
||||
Object.entries(fieldData).forEach(([localeKey, localeData]) => {
|
||||
if (typeof localeData === 'object') {
|
||||
@@ -355,7 +365,7 @@ export const traverseFields = ({
|
||||
if (field.type === 'text' && field.hasMany) {
|
||||
const textPath = `${path || ''}${field.name}`
|
||||
|
||||
if (field.localized) {
|
||||
if (isLocalized) {
|
||||
if (typeof fieldData === 'object') {
|
||||
Object.entries(fieldData).forEach(([localeKey, localeData]) => {
|
||||
if (Array.isArray(localeData)) {
|
||||
@@ -387,7 +397,7 @@ export const traverseFields = ({
|
||||
if (field.type === 'number' && field.hasMany) {
|
||||
const numberPath = `${path || ''}${field.name}`
|
||||
|
||||
if (field.localized) {
|
||||
if (isLocalized) {
|
||||
if (typeof fieldData === 'object') {
|
||||
Object.entries(fieldData).forEach(([localeKey, localeData]) => {
|
||||
if (Array.isArray(localeData)) {
|
||||
@@ -422,7 +432,7 @@ export const traverseFields = ({
|
||||
selects[selectTableName] = []
|
||||
}
|
||||
|
||||
if (field.localized) {
|
||||
if (isLocalized) {
|
||||
if (typeof data[field.name] === 'object' && data[field.name] !== null) {
|
||||
Object.entries(data[field.name]).forEach(([localeKey, localeData]) => {
|
||||
if (Array.isArray(localeData)) {
|
||||
@@ -451,7 +461,7 @@ export const traverseFields = ({
|
||||
|
||||
const valuesToTransform: { localeKey?: string; ref: unknown; value: unknown }[] = []
|
||||
|
||||
if (field.localized) {
|
||||
if (isLocalized) {
|
||||
if (typeof fieldData === 'object' && fieldData !== null) {
|
||||
Object.entries(fieldData).forEach(([localeKey, localeData]) => {
|
||||
if (!locales[localeKey]) {
|
||||
|
||||
@@ -1,21 +1,38 @@
|
||||
import type { Field } from 'payload'
|
||||
|
||||
import { fieldAffectsData, fieldHasSubFields } from 'payload/shared'
|
||||
import { fieldAffectsData, fieldHasSubFields, fieldShouldBeLocalized } from 'payload/shared'
|
||||
|
||||
export const hasLocalesTable = (fields: Field[]): boolean => {
|
||||
export const hasLocalesTable = ({
|
||||
fields,
|
||||
parentIsLocalized,
|
||||
}: {
|
||||
fields: Field[]
|
||||
/**
|
||||
* @todo make required in v4.0. Usually you'd wanna pass this in
|
||||
*/
|
||||
parentIsLocalized?: boolean
|
||||
}): boolean => {
|
||||
return fields.some((field) => {
|
||||
// arrays always get a separate table
|
||||
if (field.type === 'array') {
|
||||
return false
|
||||
}
|
||||
if (fieldAffectsData(field) && field.localized) {
|
||||
if (fieldAffectsData(field) && fieldShouldBeLocalized({ field, parentIsLocalized })) {
|
||||
return true
|
||||
}
|
||||
if (fieldHasSubFields(field)) {
|
||||
return hasLocalesTable(field.fields)
|
||||
return hasLocalesTable({
|
||||
fields: field.fields,
|
||||
parentIsLocalized: parentIsLocalized || ('localized' in field && field.localized),
|
||||
})
|
||||
}
|
||||
if (field.type === 'tabs') {
|
||||
return field.tabs.some((tab) => hasLocalesTable(tab.fields))
|
||||
return field.tabs.some((tab) =>
|
||||
hasLocalesTable({
|
||||
fields: tab.fields,
|
||||
parentIsLocalized: parentIsLocalized || tab.localized,
|
||||
}),
|
||||
)
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
@@ -1,22 +1,33 @@
|
||||
import type { Block, Field } from 'payload'
|
||||
|
||||
import { InvalidConfiguration } from 'payload'
|
||||
import { fieldAffectsData, fieldHasSubFields, tabHasName } from 'payload/shared'
|
||||
import {
|
||||
fieldAffectsData,
|
||||
fieldHasSubFields,
|
||||
fieldShouldBeLocalized,
|
||||
tabHasName,
|
||||
} from 'payload/shared'
|
||||
|
||||
import type { RawTable } from '../types.js'
|
||||
|
||||
type Args = {
|
||||
block: Block
|
||||
localized: boolean
|
||||
/**
|
||||
* @todo make required in v4.0. Usually you'd wanna pass this in
|
||||
*/
|
||||
parentIsLocalized?: boolean
|
||||
rootTableName: string
|
||||
table: RawTable
|
||||
tableLocales?: RawTable
|
||||
}
|
||||
|
||||
const getFlattenedFieldNames = (
|
||||
fields: Field[],
|
||||
prefix: string = '',
|
||||
): { localized?: boolean; name: string }[] => {
|
||||
const getFlattenedFieldNames = (args: {
|
||||
fields: Field[]
|
||||
parentIsLocalized: boolean
|
||||
prefix?: string
|
||||
}): { localized?: boolean; name: string }[] => {
|
||||
const { fields, parentIsLocalized, prefix = '' } = args
|
||||
return fields.reduce((fieldsToUse, field) => {
|
||||
let fieldPrefix = prefix
|
||||
|
||||
@@ -29,7 +40,14 @@ const getFlattenedFieldNames = (
|
||||
|
||||
if (fieldHasSubFields(field)) {
|
||||
fieldPrefix = 'name' in field ? `${prefix}${field.name}_` : prefix
|
||||
return [...fieldsToUse, ...getFlattenedFieldNames(field.fields, fieldPrefix)]
|
||||
return [
|
||||
...fieldsToUse,
|
||||
...getFlattenedFieldNames({
|
||||
fields: field.fields,
|
||||
parentIsLocalized: parentIsLocalized || ('localized' in field && field.localized),
|
||||
prefix: fieldPrefix,
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
if (field.type === 'tabs') {
|
||||
@@ -41,7 +59,11 @@ const getFlattenedFieldNames = (
|
||||
...tabFields,
|
||||
...(tabHasName(tab)
|
||||
? [{ ...tab, type: 'tab' }]
|
||||
: getFlattenedFieldNames(tab.fields, fieldPrefix)),
|
||||
: getFlattenedFieldNames({
|
||||
fields: tab.fields,
|
||||
parentIsLocalized: parentIsLocalized || tab.localized,
|
||||
prefix: fieldPrefix,
|
||||
})),
|
||||
]
|
||||
}, []),
|
||||
]
|
||||
@@ -52,7 +74,7 @@ const getFlattenedFieldNames = (
|
||||
...fieldsToUse,
|
||||
{
|
||||
name: `${fieldPrefix}${field.name}`,
|
||||
localized: field.localized,
|
||||
localized: fieldShouldBeLocalized({ field, parentIsLocalized }),
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -64,11 +86,15 @@ const getFlattenedFieldNames = (
|
||||
export const validateExistingBlockIsIdentical = ({
|
||||
block,
|
||||
localized,
|
||||
parentIsLocalized,
|
||||
rootTableName,
|
||||
table,
|
||||
tableLocales,
|
||||
}: Args): void => {
|
||||
const fieldNames = getFlattenedFieldNames(block.fields)
|
||||
const fieldNames = getFlattenedFieldNames({
|
||||
fields: block.fields,
|
||||
parentIsLocalized: parentIsLocalized || localized,
|
||||
})
|
||||
|
||||
const missingField =
|
||||
// ensure every field from the config is in the matching table
|
||||
|
||||
@@ -75,6 +75,7 @@ type BuildMutationInputTypeArgs = {
|
||||
forceNullable?: boolean
|
||||
graphqlResult: GraphQLInfo
|
||||
name: string
|
||||
parentIsLocalized: boolean
|
||||
parentName: string
|
||||
}
|
||||
|
||||
@@ -84,6 +85,7 @@ export function buildMutationInputType({
|
||||
fields,
|
||||
forceNullable = false,
|
||||
graphqlResult,
|
||||
parentIsLocalized,
|
||||
parentName,
|
||||
}: BuildMutationInputTypeArgs): GraphQLInputObjectType | null {
|
||||
const fieldToSchemaMap = {
|
||||
@@ -94,6 +96,7 @@ export function buildMutationInputType({
|
||||
config,
|
||||
fields: field.fields,
|
||||
graphqlResult,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentName: fullName,
|
||||
})
|
||||
|
||||
@@ -101,7 +104,7 @@ export function buildMutationInputType({
|
||||
return inputObjectTypeConfig
|
||||
}
|
||||
|
||||
type = new GraphQLList(withNullableType(field, type, forceNullable))
|
||||
type = new GraphQLList(withNullableType({ type, field, forceNullable, parentIsLocalized }))
|
||||
return {
|
||||
...inputObjectTypeConfig,
|
||||
[field.name]: { type },
|
||||
@@ -117,7 +120,9 @@ export function buildMutationInputType({
|
||||
}),
|
||||
code: (inputObjectTypeConfig: InputObjectTypeConfig, field: CodeField) => ({
|
||||
...inputObjectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, GraphQLString, forceNullable) },
|
||||
[field.name]: {
|
||||
type: withNullableType({ type: GraphQLString, field, forceNullable, parentIsLocalized }),
|
||||
},
|
||||
}),
|
||||
collapsible: (inputObjectTypeConfig: InputObjectTypeConfig, field: CollapsibleField) =>
|
||||
field.fields.reduce((acc, subField: CollapsibleField) => {
|
||||
@@ -129,11 +134,15 @@ export function buildMutationInputType({
|
||||
}, inputObjectTypeConfig),
|
||||
date: (inputObjectTypeConfig: InputObjectTypeConfig, field: DateField) => ({
|
||||
...inputObjectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, GraphQLString, forceNullable) },
|
||||
[field.name]: {
|
||||
type: withNullableType({ type: GraphQLString, field, forceNullable, parentIsLocalized }),
|
||||
},
|
||||
}),
|
||||
email: (inputObjectTypeConfig: InputObjectTypeConfig, field: EmailField) => ({
|
||||
...inputObjectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, GraphQLString, forceNullable) },
|
||||
[field.name]: {
|
||||
type: withNullableType({ type: GraphQLString, field, forceNullable, parentIsLocalized }),
|
||||
},
|
||||
}),
|
||||
group: (inputObjectTypeConfig: InputObjectTypeConfig, field: GroupField) => {
|
||||
const requiresAtLeastOneField = groupOrTabHasRequiredSubfield(field)
|
||||
@@ -143,6 +152,7 @@ export function buildMutationInputType({
|
||||
config,
|
||||
fields: field.fields,
|
||||
graphqlResult,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentName: fullName,
|
||||
})
|
||||
|
||||
@@ -160,28 +170,40 @@ export function buildMutationInputType({
|
||||
},
|
||||
json: (inputObjectTypeConfig: InputObjectTypeConfig, field: JSONField) => ({
|
||||
...inputObjectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, GraphQLJSON, forceNullable) },
|
||||
[field.name]: {
|
||||
type: withNullableType({ type: GraphQLJSON, field, forceNullable, parentIsLocalized }),
|
||||
},
|
||||
}),
|
||||
number: (inputObjectTypeConfig: InputObjectTypeConfig, field: NumberField) => {
|
||||
const type = field.name === 'id' ? GraphQLInt : GraphQLFloat
|
||||
return {
|
||||
...inputObjectTypeConfig,
|
||||
[field.name]: {
|
||||
type: withNullableType(
|
||||
type: withNullableType({
|
||||
type: field.hasMany === true ? new GraphQLList(type) : type,
|
||||
field,
|
||||
field.hasMany === true ? new GraphQLList(type) : type,
|
||||
forceNullable,
|
||||
),
|
||||
parentIsLocalized,
|
||||
}),
|
||||
},
|
||||
}
|
||||
},
|
||||
point: (inputObjectTypeConfig: InputObjectTypeConfig, field: PointField) => ({
|
||||
...inputObjectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, new GraphQLList(GraphQLFloat), forceNullable) },
|
||||
[field.name]: {
|
||||
type: withNullableType({
|
||||
type: new GraphQLList(GraphQLFloat),
|
||||
field,
|
||||
forceNullable,
|
||||
parentIsLocalized,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
radio: (inputObjectTypeConfig: InputObjectTypeConfig, field: RadioField) => ({
|
||||
...inputObjectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, GraphQLString, forceNullable) },
|
||||
[field.name]: {
|
||||
type: withNullableType({ type: GraphQLString, field, forceNullable, parentIsLocalized }),
|
||||
},
|
||||
}),
|
||||
relationship: (inputObjectTypeConfig: InputObjectTypeConfig, field: RelationshipField) => {
|
||||
const { relationTo } = field
|
||||
@@ -230,7 +252,9 @@ export function buildMutationInputType({
|
||||
},
|
||||
richText: (inputObjectTypeConfig: InputObjectTypeConfig, field: RichTextField) => ({
|
||||
...inputObjectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, GraphQLJSON, forceNullable) },
|
||||
[field.name]: {
|
||||
type: withNullableType({ type: GraphQLJSON, field, forceNullable, parentIsLocalized }),
|
||||
},
|
||||
}),
|
||||
row: (inputObjectTypeConfig: InputObjectTypeConfig, field: RowField) =>
|
||||
field.fields.reduce((acc, subField: Field) => {
|
||||
@@ -264,7 +288,7 @@ export function buildMutationInputType({
|
||||
})
|
||||
|
||||
type = field.hasMany ? new GraphQLList(type) : type
|
||||
type = withNullableType(field, type, forceNullable)
|
||||
type = withNullableType({ type, field, forceNullable, parentIsLocalized })
|
||||
|
||||
return {
|
||||
...inputObjectTypeConfig,
|
||||
@@ -281,6 +305,7 @@ export function buildMutationInputType({
|
||||
config,
|
||||
fields: tab.fields,
|
||||
graphqlResult,
|
||||
parentIsLocalized: parentIsLocalized || tab.localized,
|
||||
parentName: fullName,
|
||||
})
|
||||
|
||||
@@ -312,16 +337,19 @@ export function buildMutationInputType({
|
||||
text: (inputObjectTypeConfig: InputObjectTypeConfig, field: TextField) => ({
|
||||
...inputObjectTypeConfig,
|
||||
[field.name]: {
|
||||
type: withNullableType(
|
||||
type: withNullableType({
|
||||
type: field.hasMany === true ? new GraphQLList(GraphQLString) : GraphQLString,
|
||||
field,
|
||||
field.hasMany === true ? new GraphQLList(GraphQLString) : GraphQLString,
|
||||
forceNullable,
|
||||
),
|
||||
parentIsLocalized,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
textarea: (inputObjectTypeConfig: InputObjectTypeConfig, field: TextareaField) => ({
|
||||
...inputObjectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, GraphQLString, forceNullable) },
|
||||
[field.name]: {
|
||||
type: withNullableType({ type: GraphQLString, field, forceNullable, parentIsLocalized }),
|
||||
},
|
||||
}),
|
||||
upload: (inputObjectTypeConfig: InputObjectTypeConfig, field: UploadField) => {
|
||||
const { relationTo } = field
|
||||
|
||||
@@ -62,6 +62,7 @@ type Args = {
|
||||
forceNullable?: boolean
|
||||
graphqlResult: GraphQLInfo
|
||||
name: string
|
||||
parentIsLocalized?: boolean
|
||||
parentName: string
|
||||
}
|
||||
|
||||
@@ -72,6 +73,7 @@ export function buildObjectType({
|
||||
fields,
|
||||
forceNullable,
|
||||
graphqlResult,
|
||||
parentIsLocalized,
|
||||
parentName,
|
||||
}: Args): GraphQLObjectType {
|
||||
const fieldToSchemaMap = {
|
||||
@@ -84,8 +86,9 @@ export function buildObjectType({
|
||||
name: interfaceName,
|
||||
config,
|
||||
fields: field.fields,
|
||||
forceNullable: isFieldNullable(field, forceNullable),
|
||||
forceNullable: isFieldNullable({ field, forceNullable, parentIsLocalized }),
|
||||
graphqlResult,
|
||||
parentIsLocalized: field.localized || parentIsLocalized,
|
||||
parentName: interfaceName,
|
||||
})
|
||||
|
||||
@@ -104,7 +107,7 @@ export function buildObjectType({
|
||||
|
||||
return {
|
||||
...objectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, arrayType) },
|
||||
[field.name]: { type: withNullableType({ type: arrayType, field, parentIsLocalized }) },
|
||||
}
|
||||
},
|
||||
blocks: (objectTypeConfig: ObjectTypeConfig, field: BlocksField) => {
|
||||
@@ -132,6 +135,7 @@ export function buildObjectType({
|
||||
],
|
||||
forceNullable,
|
||||
graphqlResult,
|
||||
parentIsLocalized,
|
||||
parentName: interfaceName,
|
||||
})
|
||||
|
||||
@@ -165,16 +169,20 @@ export function buildObjectType({
|
||||
|
||||
return {
|
||||
...objectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, type) },
|
||||
[field.name]: { type: withNullableType({ type, field, parentIsLocalized }) },
|
||||
}
|
||||
},
|
||||
checkbox: (objectTypeConfig: ObjectTypeConfig, field: CheckboxField) => ({
|
||||
...objectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, GraphQLBoolean, forceNullable) },
|
||||
[field.name]: {
|
||||
type: withNullableType({ type: GraphQLBoolean, field, forceNullable, parentIsLocalized }),
|
||||
},
|
||||
}),
|
||||
code: (objectTypeConfig: ObjectTypeConfig, field: CodeField) => ({
|
||||
...objectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, GraphQLString, forceNullable) },
|
||||
[field.name]: {
|
||||
type: withNullableType({ type: GraphQLString, field, forceNullable, parentIsLocalized }),
|
||||
},
|
||||
}),
|
||||
collapsible: (objectTypeConfig: ObjectTypeConfig, field: CollapsibleField) =>
|
||||
field.fields.reduce((objectTypeConfigWithCollapsibleFields, subField) => {
|
||||
@@ -186,11 +194,20 @@ export function buildObjectType({
|
||||
}, objectTypeConfig),
|
||||
date: (objectTypeConfig: ObjectTypeConfig, field: DateField) => ({
|
||||
...objectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, DateTimeResolver, forceNullable) },
|
||||
[field.name]: {
|
||||
type: withNullableType({ type: DateTimeResolver, field, forceNullable, parentIsLocalized }),
|
||||
},
|
||||
}),
|
||||
email: (objectTypeConfig: ObjectTypeConfig, field: EmailField) => ({
|
||||
...objectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, EmailAddressResolver, forceNullable) },
|
||||
[field.name]: {
|
||||
type: withNullableType({
|
||||
type: EmailAddressResolver,
|
||||
field,
|
||||
forceNullable,
|
||||
parentIsLocalized,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
group: (objectTypeConfig: ObjectTypeConfig, field: GroupField) => {
|
||||
const interfaceName =
|
||||
@@ -201,8 +218,9 @@ export function buildObjectType({
|
||||
name: interfaceName,
|
||||
config,
|
||||
fields: field.fields,
|
||||
forceNullable: isFieldNullable(field, forceNullable),
|
||||
forceNullable: isFieldNullable({ field, forceNullable, parentIsLocalized }),
|
||||
graphqlResult,
|
||||
parentIsLocalized: field.localized || parentIsLocalized,
|
||||
parentName: interfaceName,
|
||||
})
|
||||
|
||||
@@ -286,42 +304,47 @@ export function buildObjectType({
|
||||
},
|
||||
json: (objectTypeConfig: ObjectTypeConfig, field: JSONField) => ({
|
||||
...objectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, GraphQLJSON, forceNullable) },
|
||||
[field.name]: {
|
||||
type: withNullableType({ type: GraphQLJSON, field, forceNullable, parentIsLocalized }),
|
||||
},
|
||||
}),
|
||||
number: (objectTypeConfig: ObjectTypeConfig, field: NumberField) => {
|
||||
const type = field?.name === 'id' ? GraphQLInt : GraphQLFloat
|
||||
return {
|
||||
...objectTypeConfig,
|
||||
[field.name]: {
|
||||
type: withNullableType(
|
||||
type: withNullableType({
|
||||
type: field?.hasMany === true ? new GraphQLList(type) : type,
|
||||
field,
|
||||
field?.hasMany === true ? new GraphQLList(type) : type,
|
||||
forceNullable,
|
||||
),
|
||||
parentIsLocalized,
|
||||
}),
|
||||
},
|
||||
}
|
||||
},
|
||||
point: (objectTypeConfig: ObjectTypeConfig, field: PointField) => ({
|
||||
...objectTypeConfig,
|
||||
[field.name]: {
|
||||
type: withNullableType(
|
||||
type: withNullableType({
|
||||
type: new GraphQLList(new GraphQLNonNull(GraphQLFloat)),
|
||||
field,
|
||||
new GraphQLList(new GraphQLNonNull(GraphQLFloat)),
|
||||
forceNullable,
|
||||
),
|
||||
parentIsLocalized,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
radio: (objectTypeConfig: ObjectTypeConfig, field: RadioField) => ({
|
||||
...objectTypeConfig,
|
||||
[field.name]: {
|
||||
type: withNullableType(
|
||||
field,
|
||||
new GraphQLEnumType({
|
||||
type: withNullableType({
|
||||
type: new GraphQLEnumType({
|
||||
name: combineParentName(parentName, field.name),
|
||||
values: formatOptions(field),
|
||||
}),
|
||||
field,
|
||||
forceNullable,
|
||||
),
|
||||
parentIsLocalized,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
relationship: (objectTypeConfig: ObjectTypeConfig, field: RelationshipField) => {
|
||||
@@ -420,11 +443,12 @@ export function buildObjectType({
|
||||
}
|
||||
|
||||
const relationship = {
|
||||
type: withNullableType(
|
||||
type: withNullableType({
|
||||
type: hasManyValues ? new GraphQLList(new GraphQLNonNull(type)) : type,
|
||||
field,
|
||||
hasManyValues ? new GraphQLList(new GraphQLNonNull(type)) : type,
|
||||
forceNullable,
|
||||
),
|
||||
parentIsLocalized,
|
||||
}),
|
||||
args: relationshipArgs,
|
||||
extensions: {
|
||||
complexity:
|
||||
@@ -550,7 +574,7 @@ export function buildObjectType({
|
||||
richText: (objectTypeConfig: ObjectTypeConfig, field: RichTextField) => ({
|
||||
...objectTypeConfig,
|
||||
[field.name]: {
|
||||
type: withNullableType(field, GraphQLJSON, forceNullable),
|
||||
type: withNullableType({ type: GraphQLJSON, field, forceNullable, parentIsLocalized }),
|
||||
args: {
|
||||
depth: {
|
||||
type: GraphQLInt,
|
||||
@@ -591,6 +615,7 @@ export function buildObjectType({
|
||||
findMany: false,
|
||||
flattenLocales: false,
|
||||
overrideAccess: false,
|
||||
parentIsLocalized,
|
||||
populationPromises,
|
||||
req: context.req,
|
||||
showHiddenFields: false,
|
||||
@@ -621,7 +646,7 @@ export function buildObjectType({
|
||||
})
|
||||
|
||||
type = field.hasMany ? new GraphQLList(new GraphQLNonNull(type)) : type
|
||||
type = withNullableType(field, type, forceNullable)
|
||||
type = withNullableType({ type, field, forceNullable, parentIsLocalized })
|
||||
|
||||
return {
|
||||
...objectTypeConfig,
|
||||
@@ -641,6 +666,7 @@ export function buildObjectType({
|
||||
fields: tab.fields,
|
||||
forceNullable,
|
||||
graphqlResult,
|
||||
parentIsLocalized: tab.localized || parentIsLocalized,
|
||||
parentName: interfaceName,
|
||||
})
|
||||
|
||||
@@ -681,16 +707,19 @@ export function buildObjectType({
|
||||
text: (objectTypeConfig: ObjectTypeConfig, field: TextField) => ({
|
||||
...objectTypeConfig,
|
||||
[field.name]: {
|
||||
type: withNullableType(
|
||||
type: withNullableType({
|
||||
type: field.hasMany === true ? new GraphQLList(GraphQLString) : GraphQLString,
|
||||
field,
|
||||
field.hasMany === true ? new GraphQLList(GraphQLString) : GraphQLString,
|
||||
forceNullable,
|
||||
),
|
||||
parentIsLocalized,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
textarea: (objectTypeConfig: ObjectTypeConfig, field: TextareaField) => ({
|
||||
...objectTypeConfig,
|
||||
[field.name]: { type: withNullableType(field, GraphQLString, forceNullable) },
|
||||
[field.name]: {
|
||||
type: withNullableType({ type: GraphQLString, field, forceNullable, parentIsLocalized }),
|
||||
},
|
||||
}),
|
||||
upload: (objectTypeConfig: ObjectTypeConfig, field: UploadField) => {
|
||||
const { relationTo } = field
|
||||
@@ -775,11 +804,12 @@ export function buildObjectType({
|
||||
}
|
||||
|
||||
const relationship = {
|
||||
type: withNullableType(
|
||||
type: withNullableType({
|
||||
type: hasManyValues ? new GraphQLList(new GraphQLNonNull(type)) : type,
|
||||
field,
|
||||
hasManyValues ? new GraphQLList(new GraphQLNonNull(type)) : type,
|
||||
forceNullable,
|
||||
),
|
||||
parentIsLocalized,
|
||||
}),
|
||||
args: relationshipArgs,
|
||||
extensions: {
|
||||
complexity:
|
||||
|
||||
@@ -150,6 +150,7 @@ export function initCollections({ config, graphqlResult }: InitCollectionsGraphQ
|
||||
config,
|
||||
fields: mutationInputFields,
|
||||
graphqlResult,
|
||||
parentIsLocalized: false,
|
||||
parentName: singularName,
|
||||
})
|
||||
if (createMutationInputType) {
|
||||
@@ -164,6 +165,7 @@ export function initCollections({ config, graphqlResult }: InitCollectionsGraphQ
|
||||
),
|
||||
forceNullable: true,
|
||||
graphqlResult,
|
||||
parentIsLocalized: false,
|
||||
parentName: `${singularName}Update`,
|
||||
})
|
||||
if (updateMutationInputType) {
|
||||
|
||||
@@ -45,6 +45,7 @@ export function initGlobals({ config, graphqlResult }: InitGlobalsGraphQLArgs):
|
||||
config,
|
||||
fields,
|
||||
graphqlResult,
|
||||
parentIsLocalized: false,
|
||||
parentName: formattedName,
|
||||
})
|
||||
graphqlResult.globals.graphQL[slug] = {
|
||||
|
||||
@@ -2,15 +2,23 @@ import type { FieldAffectingData } from 'payload'
|
||||
|
||||
import { fieldAffectsData } from 'payload/shared'
|
||||
|
||||
export const isFieldNullable = (field: FieldAffectingData, force: boolean): boolean => {
|
||||
export const isFieldNullable = ({
|
||||
field,
|
||||
forceNullable,
|
||||
parentIsLocalized,
|
||||
}: {
|
||||
field: FieldAffectingData
|
||||
forceNullable: boolean
|
||||
parentIsLocalized: boolean
|
||||
}): boolean => {
|
||||
const hasReadAccessControl = field.access && field.access.read
|
||||
const condition = field.admin && field.admin.condition
|
||||
return !(
|
||||
force &&
|
||||
forceNullable &&
|
||||
fieldAffectsData(field) &&
|
||||
'required' in field &&
|
||||
field.required &&
|
||||
!field.localized &&
|
||||
(!field.localized || parentIsLocalized) &&
|
||||
!condition &&
|
||||
!hasReadAccessControl
|
||||
)
|
||||
|
||||
@@ -3,11 +3,17 @@ import type { FieldAffectingData } from 'payload'
|
||||
|
||||
import { GraphQLNonNull } from 'graphql'
|
||||
|
||||
export const withNullableType = (
|
||||
field: FieldAffectingData,
|
||||
type: GraphQLType,
|
||||
forceNullable = false,
|
||||
): GraphQLType => {
|
||||
export const withNullableType = ({
|
||||
type,
|
||||
field,
|
||||
forceNullable,
|
||||
parentIsLocalized,
|
||||
}: {
|
||||
field: FieldAffectingData
|
||||
forceNullable?: boolean
|
||||
parentIsLocalized: boolean
|
||||
type: GraphQLType
|
||||
}): GraphQLType => {
|
||||
const hasReadAccessControl = field.access && field.access.read
|
||||
const condition = field.admin && field.admin.condition
|
||||
const isTimestamp = field.name === 'createdAt' || field.name === 'updatedAt'
|
||||
@@ -16,7 +22,7 @@ export const withNullableType = (
|
||||
!forceNullable &&
|
||||
'required' in field &&
|
||||
field.required &&
|
||||
!field.localized &&
|
||||
(!field.localized || parentIsLocalized) &&
|
||||
!condition &&
|
||||
!hasReadAccessControl &&
|
||||
!isTimestamp
|
||||
|
||||
@@ -22,6 +22,7 @@ type Props =
|
||||
isIterable?: false
|
||||
label: React.ReactNode
|
||||
locales: string[] | undefined
|
||||
parentIsLocalized: boolean
|
||||
version: unknown
|
||||
}
|
||||
| {
|
||||
@@ -34,6 +35,7 @@ type Props =
|
||||
isIterable: true
|
||||
label: React.ReactNode
|
||||
locales: string[] | undefined
|
||||
parentIsLocalized: boolean
|
||||
version: unknown
|
||||
}
|
||||
|
||||
@@ -46,6 +48,7 @@ export const DiffCollapser: React.FC<Props> = ({
|
||||
isIterable = false,
|
||||
label,
|
||||
locales,
|
||||
parentIsLocalized,
|
||||
version,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
@@ -74,6 +77,7 @@ export const DiffCollapser: React.FC<Props> = ({
|
||||
config,
|
||||
field,
|
||||
locales,
|
||||
parentIsLocalized,
|
||||
versionRows,
|
||||
})
|
||||
} else {
|
||||
@@ -82,6 +86,7 @@ export const DiffCollapser: React.FC<Props> = ({
|
||||
config,
|
||||
fields,
|
||||
locales,
|
||||
parentIsLocalized,
|
||||
version,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import type { DiffMethod } from 'react-diff-viewer-continued'
|
||||
|
||||
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
|
||||
import { dequal } from 'dequal/lite'
|
||||
import { fieldIsID, getUniqueListBy, tabHasName } from 'payload/shared'
|
||||
import { fieldIsID, fieldShouldBeLocalized, getUniqueListBy, tabHasName } from 'payload/shared'
|
||||
|
||||
import { diffMethods } from './fields/diffMethods.js'
|
||||
import { diffComponents } from './fields/index.js'
|
||||
@@ -39,6 +39,7 @@ export type BuildVersionFieldsArgs = {
|
||||
i18n: I18nClient
|
||||
modifiedOnly: boolean
|
||||
parentIndexPath: string
|
||||
parentIsLocalized: boolean
|
||||
parentPath: string
|
||||
parentSchemaPath: string
|
||||
req: PayloadRequest
|
||||
@@ -63,6 +64,7 @@ export const buildVersionFields = ({
|
||||
i18n,
|
||||
modifiedOnly,
|
||||
parentIndexPath,
|
||||
parentIsLocalized,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
req,
|
||||
@@ -103,7 +105,8 @@ export const buildVersionFields = ({
|
||||
}
|
||||
|
||||
const versionField: VersionField = {}
|
||||
const isLocalized = 'localized' in field && field.localized
|
||||
const isLocalized = fieldShouldBeLocalized({ field, parentIsLocalized })
|
||||
|
||||
const fieldName: null | string = 'name' in field ? field.name : null
|
||||
|
||||
const versionValue = fieldName ? versionSiblingData?.[fieldName] : versionSiblingData
|
||||
@@ -126,6 +129,7 @@ export const buildVersionFields = ({
|
||||
indexPath,
|
||||
locale,
|
||||
modifiedOnly,
|
||||
parentIsLocalized: true,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
path,
|
||||
@@ -150,6 +154,7 @@ export const buildVersionFields = ({
|
||||
i18n,
|
||||
indexPath,
|
||||
modifiedOnly,
|
||||
parentIsLocalized: parentIsLocalized || ('localized' in field && field.localized),
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
path,
|
||||
@@ -184,6 +189,7 @@ const buildVersionField = ({
|
||||
indexPath,
|
||||
locale,
|
||||
modifiedOnly,
|
||||
parentIsLocalized,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
path,
|
||||
@@ -198,6 +204,7 @@ const buildVersionField = ({
|
||||
indexPath: string
|
||||
locale?: string
|
||||
modifiedOnly?: boolean
|
||||
parentIsLocalized: boolean
|
||||
path: string
|
||||
schemaPath: string
|
||||
versionValue: unknown
|
||||
@@ -272,6 +279,7 @@ const buildVersionField = ({
|
||||
i18n,
|
||||
modifiedOnly,
|
||||
parentIndexPath: isNamedTab ? '' : tabIndexPath,
|
||||
parentIsLocalized: parentIsLocalized || tab.localized,
|
||||
parentPath: tabPath,
|
||||
parentSchemaPath: tabSchemaPath,
|
||||
req,
|
||||
@@ -300,6 +308,7 @@ const buildVersionField = ({
|
||||
i18n,
|
||||
modifiedOnly,
|
||||
parentIndexPath: 'name' in field ? '' : indexPath,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentPath: path + '.' + i,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
@@ -318,6 +327,7 @@ const buildVersionField = ({
|
||||
i18n,
|
||||
modifiedOnly,
|
||||
parentIndexPath: 'name' in field ? '' : indexPath,
|
||||
parentIsLocalized: parentIsLocalized || ('localized' in field && field.localized),
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
@@ -374,6 +384,7 @@ const buildVersionField = ({
|
||||
i18n,
|
||||
modifiedOnly,
|
||||
parentIndexPath: 'name' in field ? '' : indexPath,
|
||||
parentIsLocalized: parentIsLocalized || ('localized' in field && field.localized),
|
||||
parentPath: path + '.' + i,
|
||||
parentSchemaPath: schemaPath + '.' + versionBlock.slug,
|
||||
req,
|
||||
@@ -392,6 +403,7 @@ const buildVersionField = ({
|
||||
diffMethod,
|
||||
field: clientField,
|
||||
fieldPermissions: subFieldPermissions,
|
||||
parentIsLocalized,
|
||||
versionValue,
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ export const Collapsible: CollapsibleFieldDiffClientComponent = ({
|
||||
baseVersionField,
|
||||
comparisonValue,
|
||||
field,
|
||||
parentIsLocalized,
|
||||
versionValue,
|
||||
}) => {
|
||||
const { i18n } = useTranslation()
|
||||
@@ -35,6 +36,7 @@ export const Collapsible: CollapsibleFieldDiffClientComponent = ({
|
||||
typeof field.label !== 'function' && <span>{getTranslation(field.label, i18n)}</span>
|
||||
}
|
||||
locales={selectedLocales}
|
||||
parentIsLocalized={parentIsLocalized || field.localized}
|
||||
version={versionValue}
|
||||
>
|
||||
<RenderVersionFieldsToDiff versionFields={baseVersionField.fields} />
|
||||
|
||||
@@ -19,6 +19,7 @@ export const Group: GroupFieldDiffClientComponent = ({
|
||||
comparisonValue,
|
||||
field,
|
||||
locale,
|
||||
parentIsLocalized,
|
||||
versionValue,
|
||||
}) => {
|
||||
const { i18n } = useTranslation()
|
||||
@@ -40,6 +41,7 @@ export const Group: GroupFieldDiffClientComponent = ({
|
||||
)
|
||||
}
|
||||
locales={selectedLocales}
|
||||
parentIsLocalized={parentIsLocalized || field.localized}
|
||||
version={versionValue}
|
||||
>
|
||||
<RenderVersionFieldsToDiff versionFields={baseVersionField.fields} />
|
||||
|
||||
@@ -22,6 +22,7 @@ export const Iterable: React.FC<FieldDiffClientProps> = ({
|
||||
comparisonValue,
|
||||
field,
|
||||
locale,
|
||||
parentIsLocalized,
|
||||
versionValue,
|
||||
}) => {
|
||||
const { i18n } = useTranslation()
|
||||
@@ -53,6 +54,7 @@ export const Iterable: React.FC<FieldDiffClientProps> = ({
|
||||
)
|
||||
}
|
||||
locales={selectedLocales}
|
||||
parentIsLocalized={parentIsLocalized}
|
||||
version={versionValue}
|
||||
>
|
||||
{maxRows > 0 && (
|
||||
@@ -80,6 +82,7 @@ export const Iterable: React.FC<FieldDiffClientProps> = ({
|
||||
fields={fields}
|
||||
label={rowLabel}
|
||||
locales={selectedLocales}
|
||||
parentIsLocalized={parentIsLocalized || field.localized}
|
||||
version={versionRow}
|
||||
>
|
||||
<RenderVersionFieldsToDiff versionFields={versionFields} />
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
'use client'
|
||||
import type {
|
||||
ClientCollectionConfig,
|
||||
ClientConfig,
|
||||
ClientField,
|
||||
RelationshipFieldDiffClientComponent,
|
||||
} from 'payload'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { useConfig, useTranslation } from '@payloadcms/ui'
|
||||
import { fieldAffectsData, fieldIsPresentationalOnly } from 'payload/shared'
|
||||
import { fieldAffectsData, fieldIsPresentationalOnly, fieldShouldBeLocalized } from 'payload/shared'
|
||||
import React from 'react'
|
||||
import ReactDiffViewer from 'react-diff-viewer-continued'
|
||||
|
||||
@@ -24,10 +25,12 @@ const generateLabelFromValue = (
|
||||
field: ClientField,
|
||||
locale: string,
|
||||
value: { relationTo: string; value: RelationshipValue } | RelationshipValue,
|
||||
config: ClientConfig,
|
||||
parentIsLocalized: boolean,
|
||||
): string => {
|
||||
if (Array.isArray(value)) {
|
||||
return value
|
||||
.map((v) => generateLabelFromValue(collections, field, locale, v))
|
||||
.map((v) => generateLabelFromValue(collections, field, locale, v, config, parentIsLocalized))
|
||||
.filter(Boolean) // Filters out any undefined or empty values
|
||||
.join(', ')
|
||||
}
|
||||
@@ -65,7 +68,7 @@ const generateLabelFromValue = (
|
||||
let titleFieldIsLocalized = false
|
||||
|
||||
if (useAsTitleField && fieldAffectsData(useAsTitleField)) {
|
||||
titleFieldIsLocalized = useAsTitleField.localized
|
||||
titleFieldIsLocalized = fieldShouldBeLocalized({ field: useAsTitleField, parentIsLocalized })
|
||||
}
|
||||
|
||||
if (typeof relatedDoc?.[useAsTitle] !== 'undefined') {
|
||||
@@ -102,9 +105,11 @@ export const Relationship: RelationshipFieldDiffClientComponent = ({
|
||||
comparisonValue,
|
||||
field,
|
||||
locale,
|
||||
parentIsLocalized,
|
||||
versionValue,
|
||||
}) => {
|
||||
const { i18n } = useTranslation()
|
||||
const { config } = useConfig()
|
||||
|
||||
const placeholder = `[${i18n.t('general:noValue')}]`
|
||||
|
||||
@@ -119,11 +124,20 @@ export const Relationship: RelationshipFieldDiffClientComponent = ({
|
||||
if ('hasMany' in field && field.hasMany && Array.isArray(versionValue)) {
|
||||
versionToRender =
|
||||
versionValue
|
||||
.map((val) => generateLabelFromValue(collections, field, locale, val))
|
||||
.map((val) =>
|
||||
generateLabelFromValue(collections, field, locale, val, config, parentIsLocalized),
|
||||
)
|
||||
.join(', ') || placeholder
|
||||
} else {
|
||||
versionToRender =
|
||||
generateLabelFromValue(collections, field, locale, versionValue) || placeholder
|
||||
generateLabelFromValue(
|
||||
collections,
|
||||
field,
|
||||
locale,
|
||||
versionValue,
|
||||
config,
|
||||
parentIsLocalized,
|
||||
) || placeholder
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,11 +145,20 @@ export const Relationship: RelationshipFieldDiffClientComponent = ({
|
||||
if ('hasMany' in field && field.hasMany && Array.isArray(comparisonValue)) {
|
||||
comparisonToRender =
|
||||
comparisonValue
|
||||
.map((val) => generateLabelFromValue(collections, field, locale, val))
|
||||
.map((val) =>
|
||||
generateLabelFromValue(collections, field, locale, val, config, parentIsLocalized),
|
||||
)
|
||||
.join(', ') || placeholder
|
||||
} else {
|
||||
comparisonToRender =
|
||||
generateLabelFromValue(collections, field, locale, comparisonValue) || placeholder
|
||||
generateLabelFromValue(
|
||||
collections,
|
||||
field,
|
||||
locale,
|
||||
comparisonValue,
|
||||
config,
|
||||
parentIsLocalized,
|
||||
) || placeholder
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,14 @@ type TabProps = {
|
||||
tab: VersionTab
|
||||
} & FieldDiffClientProps<TabsFieldClient>
|
||||
|
||||
const Tab: React.FC<TabProps> = ({ comparisonValue, fieldTab, locale, tab, versionValue }) => {
|
||||
const Tab: React.FC<TabProps> = ({
|
||||
comparisonValue,
|
||||
fieldTab,
|
||||
locale,
|
||||
parentIsLocalized,
|
||||
tab,
|
||||
versionValue,
|
||||
}) => {
|
||||
const { i18n } = useTranslation()
|
||||
const { selectedLocales } = useSelectedLocales()
|
||||
|
||||
@@ -102,6 +109,7 @@ const Tab: React.FC<TabProps> = ({ comparisonValue, fieldTab, locale, tab, versi
|
||||
)
|
||||
}
|
||||
locales={selectedLocales}
|
||||
parentIsLocalized={parentIsLocalized || fieldTab.localized}
|
||||
version={versionValue}
|
||||
>
|
||||
<RenderVersionFieldsToDiff versionFields={tab.fields} />
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { ArrayFieldClient, BlocksFieldClient, ClientConfig, ClientField } from 'payload'
|
||||
|
||||
import { fieldShouldBeLocalized } from 'payload/shared'
|
||||
|
||||
import { fieldHasChanges } from './fieldHasChanges.js'
|
||||
import { getFieldsForRowComparison } from './getFieldsForRowComparison.js'
|
||||
|
||||
@@ -8,6 +10,7 @@ type Args = {
|
||||
config: ClientConfig
|
||||
fields: ClientField[]
|
||||
locales: string[] | undefined
|
||||
parentIsLocalized: boolean
|
||||
version: unknown
|
||||
}
|
||||
|
||||
@@ -15,7 +18,14 @@ type Args = {
|
||||
* Recursively counts the number of changed fields between comparison and
|
||||
* version data for a given set of fields.
|
||||
*/
|
||||
export function countChangedFields({ comparison, config, fields, locales, version }: Args) {
|
||||
export function countChangedFields({
|
||||
comparison,
|
||||
config,
|
||||
fields,
|
||||
locales,
|
||||
parentIsLocalized,
|
||||
version,
|
||||
}: Args) {
|
||||
let count = 0
|
||||
|
||||
fields.forEach((field) => {
|
||||
@@ -29,7 +39,7 @@ export function countChangedFields({ comparison, config, fields, locales, versio
|
||||
// count the number of changed fields in each.
|
||||
case 'array':
|
||||
case 'blocks': {
|
||||
if (locales && field.localized) {
|
||||
if (locales && fieldShouldBeLocalized({ field, parentIsLocalized })) {
|
||||
locales.forEach((locale) => {
|
||||
const comparisonRows = comparison?.[field.name]?.[locale] ?? []
|
||||
const versionRows = version?.[field.name]?.[locale] ?? []
|
||||
@@ -38,13 +48,21 @@ export function countChangedFields({ comparison, config, fields, locales, versio
|
||||
config,
|
||||
field,
|
||||
locales,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
versionRows,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
const comparisonRows = comparison?.[field.name] ?? []
|
||||
const versionRows = version?.[field.name] ?? []
|
||||
count += countChangedFieldsInRows({ comparisonRows, config, field, locales, versionRows })
|
||||
count += countChangedFieldsInRows({
|
||||
comparisonRows,
|
||||
config,
|
||||
field,
|
||||
locales,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
versionRows,
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -66,7 +84,7 @@ export function countChangedFields({ comparison, config, fields, locales, versio
|
||||
case 'textarea':
|
||||
case 'upload': {
|
||||
// Fields that have a name and contain data. We can just check if the data has changed.
|
||||
if (locales && field.localized) {
|
||||
if (locales && fieldShouldBeLocalized({ field, parentIsLocalized })) {
|
||||
locales.forEach((locale) => {
|
||||
if (
|
||||
fieldHasChanges(version?.[field.name]?.[locale], comparison?.[field.name]?.[locale])
|
||||
@@ -87,6 +105,7 @@ export function countChangedFields({ comparison, config, fields, locales, versio
|
||||
config,
|
||||
fields: field.fields,
|
||||
locales,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
version,
|
||||
})
|
||||
|
||||
@@ -95,13 +114,14 @@ export function countChangedFields({ comparison, config, fields, locales, versio
|
||||
|
||||
// Fields that have nested fields and nest their fields' data.
|
||||
case 'group': {
|
||||
if (locales && field.localized) {
|
||||
if (locales && fieldShouldBeLocalized({ field, parentIsLocalized })) {
|
||||
locales.forEach((locale) => {
|
||||
count += countChangedFields({
|
||||
comparison: comparison?.[field.name]?.[locale],
|
||||
config,
|
||||
fields: field.fields,
|
||||
locales,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
version: version?.[field.name]?.[locale],
|
||||
})
|
||||
})
|
||||
@@ -111,6 +131,7 @@ export function countChangedFields({ comparison, config, fields, locales, versio
|
||||
config,
|
||||
fields: field.fields,
|
||||
locales,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
version: version?.[field.name],
|
||||
})
|
||||
}
|
||||
@@ -129,6 +150,7 @@ export function countChangedFields({ comparison, config, fields, locales, versio
|
||||
config,
|
||||
fields: tab.fields,
|
||||
locales,
|
||||
parentIsLocalized: parentIsLocalized || tab.localized,
|
||||
version: version?.[tab.name]?.[locale],
|
||||
})
|
||||
})
|
||||
@@ -139,6 +161,7 @@ export function countChangedFields({ comparison, config, fields, locales, versio
|
||||
config,
|
||||
fields: tab.fields,
|
||||
locales,
|
||||
parentIsLocalized: parentIsLocalized || tab.localized,
|
||||
version: version?.[tab.name],
|
||||
})
|
||||
} else {
|
||||
@@ -148,6 +171,7 @@ export function countChangedFields({ comparison, config, fields, locales, versio
|
||||
config,
|
||||
fields: tab.fields,
|
||||
locales,
|
||||
parentIsLocalized: parentIsLocalized || tab.localized,
|
||||
version,
|
||||
})
|
||||
}
|
||||
@@ -176,6 +200,7 @@ type countChangedFieldsInRowsArgs = {
|
||||
config: ClientConfig
|
||||
field: ArrayFieldClient | BlocksFieldClient
|
||||
locales: string[] | undefined
|
||||
parentIsLocalized: boolean
|
||||
versionRows: unknown[]
|
||||
}
|
||||
|
||||
@@ -184,6 +209,7 @@ export function countChangedFieldsInRows({
|
||||
config,
|
||||
field,
|
||||
locales,
|
||||
parentIsLocalized,
|
||||
versionRows = [],
|
||||
}: countChangedFieldsInRowsArgs) {
|
||||
let count = 0
|
||||
@@ -207,6 +233,7 @@ export function countChangedFieldsInRows({
|
||||
config,
|
||||
fields: rowFields,
|
||||
locales,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
version: versionRow,
|
||||
})
|
||||
|
||||
|
||||
@@ -228,6 +228,7 @@ export async function VersionView(props: DocumentViewServerProps) {
|
||||
i18n,
|
||||
modifiedOnly,
|
||||
parentIndexPath: '',
|
||||
parentIsLocalized: false,
|
||||
parentPath: '',
|
||||
parentSchemaPath: '',
|
||||
req,
|
||||
|
||||
@@ -120,11 +120,12 @@ export type BaseRichTextHookArgs<
|
||||
indexPath: number[]
|
||||
/** The full original document in `update` operations. In the `afterChange` hook, this is the resulting document of the operation. */
|
||||
originalDoc?: TData
|
||||
parentIsLocalized: boolean
|
||||
|
||||
/**
|
||||
* The path of the field, e.g. ["group", "myArray", 1, "textField"]. The path is the schemaPath but with indexes and would be used in the context of field data, not field schemas.
|
||||
*/
|
||||
path: (number | string)[]
|
||||
|
||||
/** The Express request object. It is mocked for Local API operations. */
|
||||
req: PayloadRequest
|
||||
/**
|
||||
@@ -208,6 +209,7 @@ type RichTextAdapterBase<
|
||||
findMany: boolean
|
||||
flattenLocales: boolean
|
||||
overrideAccess?: boolean
|
||||
parentIsLocalized: boolean
|
||||
populateArg?: PopulateType
|
||||
populationPromises: Promise<void>[]
|
||||
req: PayloadRequest
|
||||
|
||||
@@ -61,6 +61,7 @@ export type FieldDiffClientProps<TClientField extends ClientFieldWithOptionalTyp
|
||||
* If this field is localized, this will be the locale of the field
|
||||
*/
|
||||
locale?: string
|
||||
parentIsLocalized: boolean
|
||||
/**
|
||||
* Field value from the current version
|
||||
*/
|
||||
|
||||
@@ -516,6 +516,11 @@ export type SanitizedJoin = {
|
||||
* The path of the join field in dot notation
|
||||
*/
|
||||
joinPath: string
|
||||
/**
|
||||
* `parentIsLocalized` is true if any parent field of the
|
||||
* field configuration defining the join is localized
|
||||
*/
|
||||
parentIsLocalized: boolean
|
||||
targetField: RelationshipField | UploadField
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,10 @@ import { defaults } from './defaults.js'
|
||||
const sanitizeAdminConfig = (configToSanitize: Config): Partial<SanitizedConfig> => {
|
||||
const sanitizedConfig = { ...configToSanitize }
|
||||
|
||||
if (configToSanitize?.compatibility?.allowLocalizedWithinLocalized) {
|
||||
process.env.NEXT_PUBLIC_PAYLOAD_COMPATIBILITY_allowLocalizedWithinLocalized = 'true'
|
||||
}
|
||||
|
||||
// default logging level will be 'error' if not provided
|
||||
sanitizedConfig.loggingLevels = {
|
||||
Forbidden: 'info',
|
||||
|
||||
@@ -936,6 +936,8 @@ export type Config = {
|
||||
* to `true` only if you have an existing Payload database from pre-3.0
|
||||
* that you would like to maintain without migrating. This is only
|
||||
* relevant for MongoDB databases.
|
||||
*
|
||||
* @todo Remove in v4
|
||||
*/
|
||||
allowLocalizedWithinLocalized: true
|
||||
}
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
// @ts-strict-ignore
|
||||
import type { Field, FlattenedBlock, FlattenedField } from '../fields/config/types.js'
|
||||
import type { Payload } from '../index.js'
|
||||
import type { PathToQuery } from './queryValidation/types.js'
|
||||
|
||||
// @ts-strict-ignore
|
||||
import {
|
||||
type Field,
|
||||
fieldShouldBeLocalized,
|
||||
type FlattenedBlock,
|
||||
type FlattenedField,
|
||||
} from '../fields/config/types.js'
|
||||
|
||||
export function getLocalizedPaths({
|
||||
collectionSlug,
|
||||
fields,
|
||||
@@ -10,6 +16,7 @@ export function getLocalizedPaths({
|
||||
incomingPath,
|
||||
locale,
|
||||
overrideAccess = false,
|
||||
parentIsLocalized,
|
||||
payload,
|
||||
}: {
|
||||
collectionSlug?: string
|
||||
@@ -18,6 +25,10 @@ export function getLocalizedPaths({
|
||||
incomingPath: string
|
||||
locale?: string
|
||||
overrideAccess?: boolean
|
||||
/**
|
||||
* @todo make required in v4.0. Usually, you'd wanna pass this through
|
||||
*/
|
||||
parentIsLocalized?: boolean
|
||||
payload: Payload
|
||||
}): PathToQuery[] {
|
||||
const pathSegments = incomingPath.split('.')
|
||||
@@ -31,6 +42,7 @@ export function getLocalizedPaths({
|
||||
fields,
|
||||
globalSlug,
|
||||
invalid: false,
|
||||
parentIsLocalized,
|
||||
path: '',
|
||||
},
|
||||
]
|
||||
@@ -45,6 +57,7 @@ export function getLocalizedPaths({
|
||||
let currentPath = path ? `${path}.${segment}` : segment
|
||||
|
||||
let fieldsToSearch: FlattenedField[]
|
||||
let _parentIsLocalized = parentIsLocalized
|
||||
|
||||
let matchedField: FlattenedField
|
||||
|
||||
@@ -76,6 +89,7 @@ export function getLocalizedPaths({
|
||||
} else {
|
||||
fieldsToSearch = lastIncompletePath.fields
|
||||
}
|
||||
_parentIsLocalized = parentIsLocalized || lastIncompletePath.field?.localized
|
||||
|
||||
matchedField = fieldsToSearch.find((field) => field.name === segment)
|
||||
}
|
||||
@@ -117,7 +131,10 @@ export function getLocalizedPaths({
|
||||
// Skip the next iteration, because it's a locale
|
||||
i += 1
|
||||
currentPath = `${currentPath}.${nextSegment}`
|
||||
} else if (localizationConfig && 'localized' in matchedField && matchedField.localized) {
|
||||
} else if (
|
||||
localizationConfig &&
|
||||
fieldShouldBeLocalized({ field: matchedField, parentIsLocalized: _parentIsLocalized })
|
||||
) {
|
||||
currentPath = `${currentPath}.${locale}`
|
||||
}
|
||||
|
||||
@@ -167,6 +184,7 @@ export function getLocalizedPaths({
|
||||
globalSlug,
|
||||
incomingPath: nestedPathToQuery,
|
||||
locale,
|
||||
parentIsLocalized: false,
|
||||
payload,
|
||||
})
|
||||
|
||||
|
||||
@@ -17,5 +17,9 @@ export type PathToQuery = {
|
||||
fields?: FlattenedField[]
|
||||
globalSlug?: string
|
||||
invalid?: boolean
|
||||
/**
|
||||
* @todo make required in v4.0
|
||||
*/
|
||||
parentIsLocalized: boolean
|
||||
path: string
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ type Args = {
|
||||
globalConfig?: SanitizedGlobalConfig
|
||||
operator: string
|
||||
overrideAccess: boolean
|
||||
parentIsLocalized?: boolean
|
||||
path: string
|
||||
policies: EntityPolicies
|
||||
req: PayloadRequest
|
||||
@@ -35,6 +36,7 @@ export async function validateSearchParam({
|
||||
globalConfig,
|
||||
operator,
|
||||
overrideAccess,
|
||||
parentIsLocalized,
|
||||
path: incomingPath,
|
||||
policies,
|
||||
req,
|
||||
@@ -68,6 +70,7 @@ export async function validateSearchParam({
|
||||
incomingPath: sanitizedPath,
|
||||
locale: req.locale,
|
||||
overrideAccess,
|
||||
parentIsLocalized,
|
||||
payload: req.payload,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ export {
|
||||
fieldIsPresentationalOnly,
|
||||
fieldIsSidebar,
|
||||
fieldIsVirtual,
|
||||
fieldShouldBeLocalized,
|
||||
fieldSupportsMany,
|
||||
optionIsObject,
|
||||
optionIsValue,
|
||||
|
||||
@@ -104,7 +104,7 @@ export const sanitizeFields = async ({
|
||||
}
|
||||
|
||||
if (field.type === 'join') {
|
||||
sanitizeJoinField({ config, field, joinPath, joins })
|
||||
sanitizeJoinField({ config, field, joinPath, joins, parentIsLocalized })
|
||||
}
|
||||
|
||||
if (field.type === 'relationship' || field.type === 'upload') {
|
||||
@@ -160,7 +160,12 @@ export const sanitizeFields = async ({
|
||||
if (typeof field.localized !== 'undefined') {
|
||||
let shouldDisableLocalized = !config.localization
|
||||
|
||||
if (!config.compatibility?.allowLocalizedWithinLocalized && parentIsLocalized) {
|
||||
if (
|
||||
process.env.NEXT_PUBLIC_PAYLOAD_COMPATIBILITY_allowLocalizedWithinLocalized !== 'true' &&
|
||||
parentIsLocalized &&
|
||||
// @todo PAYLOAD_DO_NOT_SANITIZE_LOCALIZED_PROPERTY=true will be the default in 4.0
|
||||
process.env.PAYLOAD_DO_NOT_SANITIZE_LOCALIZED_PROPERTY !== 'true'
|
||||
) {
|
||||
shouldDisableLocalized = true
|
||||
}
|
||||
|
||||
@@ -185,7 +190,7 @@ export const sanitizeFields = async ({
|
||||
field.access = {}
|
||||
}
|
||||
|
||||
setDefaultBeforeDuplicate(field)
|
||||
setDefaultBeforeDuplicate(field, parentIsLocalized)
|
||||
}
|
||||
|
||||
if (!field.admin) {
|
||||
|
||||
@@ -1,21 +1,29 @@
|
||||
// @ts-strict-ignore
|
||||
import type { SanitizedJoin, SanitizedJoins } from '../../collections/config/types.js'
|
||||
import type { Config, SanitizedConfig } from '../../config/types.js'
|
||||
import type { FlattenedJoinField, JoinField, RelationshipField, UploadField } from './types.js'
|
||||
|
||||
import { APIError } from '../../errors/index.js'
|
||||
import { InvalidFieldJoin } from '../../errors/InvalidFieldJoin.js'
|
||||
import { traverseFields } from '../../utilities/traverseFields.js'
|
||||
import {
|
||||
fieldShouldBeLocalized,
|
||||
type FlattenedJoinField,
|
||||
type JoinField,
|
||||
type RelationshipField,
|
||||
type UploadField,
|
||||
} from './types.js'
|
||||
export const sanitizeJoinField = ({
|
||||
config,
|
||||
field,
|
||||
joinPath,
|
||||
joins,
|
||||
parentIsLocalized,
|
||||
}: {
|
||||
config: Config
|
||||
field: FlattenedJoinField | JoinField
|
||||
joinPath?: string
|
||||
joins?: SanitizedJoins
|
||||
parentIsLocalized: boolean
|
||||
}) => {
|
||||
// the `joins` arg is not passed for globals or when recursing on fields that do not allow a join field
|
||||
if (typeof joins === 'undefined') {
|
||||
@@ -27,6 +35,7 @@ export const sanitizeJoinField = ({
|
||||
const join: SanitizedJoin = {
|
||||
field,
|
||||
joinPath: `${joinPath ? joinPath + '.' : ''}${field.name}`,
|
||||
parentIsLocalized,
|
||||
targetField: undefined,
|
||||
}
|
||||
const joinCollection = config.collections.find(
|
||||
@@ -43,14 +52,14 @@ export const sanitizeJoinField = ({
|
||||
let localized = false
|
||||
// Traverse fields and match based on the schema path
|
||||
traverseFields({
|
||||
callback: ({ field, next }) => {
|
||||
callback: ({ field, next, parentIsLocalized }) => {
|
||||
if (!('name' in field) || !field.name) {
|
||||
return
|
||||
}
|
||||
const currentSegment = pathSegments[currentSegmentIndex]
|
||||
// match field on path segments
|
||||
if ('name' in field && field.name === currentSegment) {
|
||||
if ('localized' in field && field.localized) {
|
||||
if (fieldShouldBeLocalized({ field, parentIsLocalized })) {
|
||||
localized = true
|
||||
const fieldIndex = currentSegmentIndex
|
||||
|
||||
@@ -93,6 +102,7 @@ export const sanitizeJoinField = ({
|
||||
},
|
||||
config: config as unknown as SanitizedConfig,
|
||||
fields: joinCollection.fields,
|
||||
parentIsLocalized: false,
|
||||
})
|
||||
|
||||
if (!joinRelationship) {
|
||||
|
||||
@@ -1854,10 +1854,35 @@ export function tabHasName<TField extends ClientTab | Tab>(tab: TField): tab is
|
||||
return 'name' in tab
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a field has localized: true set. This does not check if a field *should*
|
||||
* be localized. To check if a field should be localized, use `fieldShouldBeLocalized`.
|
||||
*
|
||||
* @deprecated this will be removed or modified in v4.0, as `fieldIsLocalized` can easily lead to bugs due to
|
||||
* parent field localization not being taken into account.
|
||||
*/
|
||||
export function fieldIsLocalized(field: Field | Tab): boolean {
|
||||
return 'localized' in field && field.localized
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to `fieldIsLocalized`, but returns `false` if any parent field is localized.
|
||||
*/
|
||||
export function fieldShouldBeLocalized({
|
||||
field,
|
||||
parentIsLocalized,
|
||||
}: {
|
||||
field: ClientField | ClientTab | Field | Tab
|
||||
parentIsLocalized: boolean
|
||||
}): boolean {
|
||||
return (
|
||||
'localized' in field &&
|
||||
field.localized &&
|
||||
(!parentIsLocalized ||
|
||||
process.env.NEXT_PUBLIC_PAYLOAD_COMPATIBILITY_allowLocalizedWithinLocalized === 'true')
|
||||
)
|
||||
}
|
||||
|
||||
export function fieldIsVirtual(field: Field | Tab): boolean {
|
||||
return 'virtual' in field && field.virtual
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ export const afterChange = async <T extends JsonObject>({
|
||||
global,
|
||||
operation,
|
||||
parentIndexPath: '',
|
||||
parentIsLocalized: false,
|
||||
parentPath: '',
|
||||
parentSchemaPath: '',
|
||||
previousDoc,
|
||||
|
||||
@@ -25,6 +25,7 @@ type Args = {
|
||||
global: null | SanitizedGlobalConfig
|
||||
operation: 'create' | 'update'
|
||||
parentIndexPath: string
|
||||
parentIsLocalized: boolean
|
||||
parentPath: string
|
||||
parentSchemaPath: string
|
||||
previousDoc: JsonObject
|
||||
@@ -49,6 +50,7 @@ export const promise = async ({
|
||||
global,
|
||||
operation,
|
||||
parentIndexPath,
|
||||
parentIsLocalized,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
previousDoc,
|
||||
@@ -123,6 +125,7 @@ export const promise = async ({
|
||||
global,
|
||||
operation,
|
||||
parentIndexPath: '',
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentPath: path + '.' + rowIndex,
|
||||
parentSchemaPath: schemaPath,
|
||||
previousDoc,
|
||||
@@ -166,6 +169,7 @@ export const promise = async ({
|
||||
global,
|
||||
operation,
|
||||
parentIndexPath: '',
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentPath: path + '.' + rowIndex,
|
||||
parentSchemaPath: schemaPath + '.' + block.slug,
|
||||
previousDoc,
|
||||
@@ -196,6 +200,7 @@ export const promise = async ({
|
||||
global,
|
||||
operation,
|
||||
parentIndexPath: indexPath,
|
||||
parentIsLocalized,
|
||||
parentPath,
|
||||
parentSchemaPath: schemaPath,
|
||||
previousDoc,
|
||||
@@ -219,6 +224,7 @@ export const promise = async ({
|
||||
global,
|
||||
operation,
|
||||
parentIndexPath: '',
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
previousDoc,
|
||||
@@ -255,6 +261,7 @@ export const promise = async ({
|
||||
indexPath: indexPathSegments,
|
||||
operation,
|
||||
originalDoc: doc,
|
||||
parentIsLocalized,
|
||||
path: pathSegments,
|
||||
previousDoc,
|
||||
previousSiblingDoc,
|
||||
@@ -296,6 +303,7 @@ export const promise = async ({
|
||||
global,
|
||||
operation,
|
||||
parentIndexPath: isNamedTab ? '' : indexPath,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentPath: isNamedTab ? path : parentPath,
|
||||
parentSchemaPath: schemaPath,
|
||||
previousDoc,
|
||||
@@ -319,6 +327,7 @@ export const promise = async ({
|
||||
global,
|
||||
operation,
|
||||
parentIndexPath: indexPath,
|
||||
parentIsLocalized,
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
previousDoc,
|
||||
|
||||
@@ -20,6 +20,10 @@ type Args = {
|
||||
global: null | SanitizedGlobalConfig
|
||||
operation: 'create' | 'update'
|
||||
parentIndexPath: string
|
||||
/**
|
||||
* @todo make required in v4.0
|
||||
*/
|
||||
parentIsLocalized?: boolean
|
||||
parentPath: string
|
||||
parentSchemaPath: string
|
||||
previousDoc: JsonObject
|
||||
@@ -40,6 +44,7 @@ export const traverseFields = async ({
|
||||
global,
|
||||
operation,
|
||||
parentIndexPath,
|
||||
parentIsLocalized,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
previousDoc,
|
||||
@@ -64,6 +69,7 @@ export const traverseFields = async ({
|
||||
global,
|
||||
operation,
|
||||
parentIndexPath,
|
||||
parentIsLocalized,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
previousDoc,
|
||||
|
||||
@@ -85,6 +85,7 @@ export async function afterRead<T extends JsonObject>(args: Args<T>): Promise<T>
|
||||
locale,
|
||||
overrideAccess,
|
||||
parentIndexPath: '',
|
||||
parentIsLocalized: false,
|
||||
parentPath: '',
|
||||
parentSchemaPath: '',
|
||||
populate,
|
||||
|
||||
@@ -13,7 +13,7 @@ import type {
|
||||
import type { Block, Field, TabAsField } from '../../config/types.js'
|
||||
|
||||
import { MissingEditorProp } from '../../../errors/index.js'
|
||||
import { fieldAffectsData, tabHasName } from '../../config/types.js'
|
||||
import { fieldAffectsData, fieldShouldBeLocalized, tabHasName } from '../../config/types.js'
|
||||
import { getDefaultValue } from '../../getDefaultValue.js'
|
||||
import { getFieldPathsModified as getFieldPaths } from '../../getFieldPaths.js'
|
||||
import { relationshipPopulationPromise } from './relationshipPopulationPromise.js'
|
||||
@@ -43,6 +43,10 @@ type Args = {
|
||||
locale: null | string
|
||||
overrideAccess: boolean
|
||||
parentIndexPath: string
|
||||
/**
|
||||
* @todo make required in v4.0
|
||||
*/
|
||||
parentIsLocalized?: boolean
|
||||
parentPath: string
|
||||
parentSchemaPath: string
|
||||
populate?: PopulateType
|
||||
@@ -83,6 +87,7 @@ export const promise = async ({
|
||||
locale,
|
||||
overrideAccess,
|
||||
parentIndexPath,
|
||||
parentIsLocalized,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
populate,
|
||||
@@ -139,7 +144,7 @@ export const promise = async ({
|
||||
fieldAffectsData(field) &&
|
||||
typeof siblingDoc[field.name] === 'object' &&
|
||||
siblingDoc[field.name] !== null &&
|
||||
field.localized &&
|
||||
fieldShouldBeLocalized({ field, parentIsLocalized }) &&
|
||||
locale !== 'all' &&
|
||||
req.payload.config.localization
|
||||
|
||||
@@ -236,7 +241,7 @@ export const promise = async ({
|
||||
await priorHook
|
||||
|
||||
const shouldRunHookOnAllLocales =
|
||||
field.localized &&
|
||||
fieldShouldBeLocalized({ field, parentIsLocalized }) &&
|
||||
(locale === 'all' || !flattenLocales) &&
|
||||
typeof siblingDoc[field.name] === 'object'
|
||||
|
||||
@@ -352,6 +357,7 @@ export const promise = async ({
|
||||
field,
|
||||
locale,
|
||||
overrideAccess,
|
||||
parentIsLocalized,
|
||||
populate,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -393,6 +399,7 @@ export const promise = async ({
|
||||
locale,
|
||||
overrideAccess,
|
||||
parentIndexPath: '',
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentPath: path + '.' + rowIndex,
|
||||
parentSchemaPath: schemaPath,
|
||||
populate,
|
||||
@@ -427,6 +434,7 @@ export const promise = async ({
|
||||
locale,
|
||||
overrideAccess,
|
||||
parentIndexPath: '',
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentPath: path + '.' + rowIndex,
|
||||
parentSchemaPath: schemaPath,
|
||||
populate,
|
||||
@@ -511,6 +519,7 @@ export const promise = async ({
|
||||
locale,
|
||||
overrideAccess,
|
||||
parentIndexPath: '',
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentPath: path + '.' + rowIndex,
|
||||
parentSchemaPath: schemaPath + '.' + block.slug,
|
||||
populate,
|
||||
@@ -555,6 +564,7 @@ export const promise = async ({
|
||||
locale,
|
||||
overrideAccess,
|
||||
parentIndexPath: '',
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentPath: path + '.' + rowIndex,
|
||||
parentSchemaPath: schemaPath + '.' + block.slug,
|
||||
populate,
|
||||
@@ -595,6 +605,7 @@ export const promise = async ({
|
||||
locale,
|
||||
overrideAccess,
|
||||
parentIndexPath: indexPath,
|
||||
parentIsLocalized,
|
||||
parentPath,
|
||||
parentSchemaPath: schemaPath,
|
||||
populate,
|
||||
@@ -637,6 +648,7 @@ export const promise = async ({
|
||||
locale,
|
||||
overrideAccess,
|
||||
parentIndexPath: '',
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
populate,
|
||||
@@ -669,7 +681,7 @@ export const promise = async ({
|
||||
await priorHook
|
||||
|
||||
const shouldRunHookOnAllLocales =
|
||||
field.localized &&
|
||||
fieldShouldBeLocalized({ field, parentIsLocalized }) &&
|
||||
(locale === 'all' || !flattenLocales) &&
|
||||
typeof siblingDoc[field.name] === 'object'
|
||||
|
||||
@@ -694,6 +706,7 @@ export const promise = async ({
|
||||
operation: 'read',
|
||||
originalDoc: doc,
|
||||
overrideAccess,
|
||||
parentIsLocalized,
|
||||
path: pathSegments,
|
||||
populate,
|
||||
populationPromises,
|
||||
@@ -732,6 +745,7 @@ export const promise = async ({
|
||||
operation: 'read',
|
||||
originalDoc: doc,
|
||||
overrideAccess,
|
||||
parentIsLocalized,
|
||||
path: pathSegments,
|
||||
populate,
|
||||
populationPromises,
|
||||
@@ -790,6 +804,7 @@ export const promise = async ({
|
||||
locale,
|
||||
overrideAccess,
|
||||
parentIndexPath: isNamedTab ? '' : indexPath,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentPath: isNamedTab ? path : parentPath,
|
||||
parentSchemaPath: schemaPath,
|
||||
populate,
|
||||
@@ -824,6 +839,7 @@ export const promise = async ({
|
||||
locale,
|
||||
overrideAccess,
|
||||
parentIndexPath: indexPath,
|
||||
parentIsLocalized,
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
populate,
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { PayloadRequest, PopulateType } from '../../../types/index.js'
|
||||
import type { JoinField, RelationshipField, UploadField } from '../../config/types.js'
|
||||
|
||||
import { createDataloaderCacheKey } from '../../../collections/dataloader.js'
|
||||
import { fieldHasMaxDepth, fieldSupportsMany } from '../../config/types.js'
|
||||
import { fieldHasMaxDepth, fieldShouldBeLocalized, fieldSupportsMany } from '../../config/types.js'
|
||||
|
||||
type PopulateArgs = {
|
||||
currentDepth: number
|
||||
@@ -116,6 +116,7 @@ type PromiseArgs = {
|
||||
field: JoinField | RelationshipField | UploadField
|
||||
locale: null | string
|
||||
overrideAccess: boolean
|
||||
parentIsLocalized: boolean
|
||||
populate?: PopulateType
|
||||
req: PayloadRequest
|
||||
showHiddenFields: boolean
|
||||
@@ -130,6 +131,7 @@ export const relationshipPopulationPromise = async ({
|
||||
field,
|
||||
locale,
|
||||
overrideAccess,
|
||||
parentIsLocalized,
|
||||
populate: populateArg,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -141,7 +143,7 @@ export const relationshipPopulationPromise = async ({
|
||||
|
||||
if (field.type === 'join' || (fieldSupportsMany(field) && field.hasMany)) {
|
||||
if (
|
||||
field.localized &&
|
||||
fieldShouldBeLocalized({ field, parentIsLocalized }) &&
|
||||
locale === 'all' &&
|
||||
typeof siblingDoc[field.name] === 'object' &&
|
||||
siblingDoc[field.name] !== null
|
||||
|
||||
@@ -35,6 +35,10 @@ type Args = {
|
||||
locale: null | string
|
||||
overrideAccess: boolean
|
||||
parentIndexPath: string
|
||||
/**
|
||||
* @todo make required in v4.0
|
||||
*/
|
||||
parentIsLocalized?: boolean
|
||||
parentPath: string
|
||||
parentSchemaPath: string
|
||||
populate?: PopulateType
|
||||
@@ -65,6 +69,7 @@ export const traverseFields = ({
|
||||
locale,
|
||||
overrideAccess,
|
||||
parentIndexPath,
|
||||
parentIsLocalized,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
populate,
|
||||
@@ -97,6 +102,7 @@ export const traverseFields = ({
|
||||
locale,
|
||||
overrideAccess,
|
||||
parentIndexPath,
|
||||
parentIsLocalized,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
populate,
|
||||
|
||||
@@ -59,6 +59,7 @@ export const beforeChange = async <T extends JsonObject>({
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
parentIndexPath: '',
|
||||
parentIsLocalized: false,
|
||||
parentPath: '',
|
||||
parentSchemaPath: '',
|
||||
req,
|
||||
|
||||
@@ -11,7 +11,7 @@ import { MissingEditorProp } from '../../../errors/index.js'
|
||||
import { deepMergeWithSourceArrays } from '../../../utilities/deepMerge.js'
|
||||
import { getLabelFromPath } from '../../../utilities/getLabelFromPath.js'
|
||||
import { getTranslatedLabel } from '../../../utilities/getTranslatedLabel.js'
|
||||
import { fieldAffectsData, tabHasName } from '../../config/types.js'
|
||||
import { fieldAffectsData, fieldShouldBeLocalized, tabHasName } from '../../config/types.js'
|
||||
import { getFieldPathsModified as getFieldPaths } from '../../getFieldPaths.js'
|
||||
import { getExistingRowDoc } from './getExistingRowDoc.js'
|
||||
import { traverseFields } from './traverseFields.js'
|
||||
@@ -34,6 +34,7 @@ type Args = {
|
||||
mergeLocaleActions: (() => Promise<void>)[]
|
||||
operation: Operation
|
||||
parentIndexPath: string
|
||||
parentIsLocalized: boolean
|
||||
parentPath: string
|
||||
parentSchemaPath: string
|
||||
req: PayloadRequest
|
||||
@@ -67,6 +68,7 @@ export const promise = async ({
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
parentIndexPath,
|
||||
parentIsLocalized,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
req,
|
||||
@@ -98,7 +100,7 @@ export const promise = async ({
|
||||
|
||||
if (fieldAffectsData(field)) {
|
||||
// skip validation if the field is localized and the incoming data is null
|
||||
if (field.localized && operationLocale !== defaultLocale) {
|
||||
if (fieldShouldBeLocalized({ field, parentIsLocalized }) && operationLocale !== defaultLocale) {
|
||||
if (['array', 'blocks'].includes(field.type) && siblingData[field.name] === null) {
|
||||
skipValidationFromHere = true
|
||||
}
|
||||
@@ -189,7 +191,7 @@ export const promise = async ({
|
||||
}
|
||||
|
||||
// Push merge locale action if applicable
|
||||
if (localization && field.localized) {
|
||||
if (localization && fieldShouldBeLocalized({ field, parentIsLocalized })) {
|
||||
mergeLocaleActions.push(async () => {
|
||||
const localeData = await localization.localeCodes.reduce(
|
||||
async (localizedValuesPromise: Promise<JsonObject>, locale) => {
|
||||
@@ -244,6 +246,7 @@ export const promise = async ({
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
parentIndexPath: '',
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentPath: path + '.' + rowIndex,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
@@ -301,6 +304,7 @@ export const promise = async ({
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
parentIndexPath: '',
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentPath: path + '.' + rowIndex,
|
||||
parentSchemaPath: schemaPath + '.' + block.slug,
|
||||
req,
|
||||
@@ -335,6 +339,7 @@ export const promise = async ({
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
parentIndexPath: indexPath,
|
||||
parentIsLocalized,
|
||||
parentPath,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
@@ -374,6 +379,7 @@ export const promise = async ({
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
parentIndexPath: '',
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
@@ -432,6 +438,7 @@ export const promise = async ({
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
originalDoc: doc,
|
||||
parentIsLocalized,
|
||||
path: pathSegments,
|
||||
previousSiblingDoc: siblingDoc,
|
||||
previousValue: siblingDoc[field.name],
|
||||
@@ -491,6 +498,7 @@ export const promise = async ({
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
parentIndexPath: isNamedTab ? '' : indexPath,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentPath: isNamedTab ? path : parentPath,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
@@ -518,6 +526,7 @@ export const promise = async ({
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
parentIndexPath: indexPath,
|
||||
parentIsLocalized,
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
|
||||
@@ -31,6 +31,10 @@ type Args = {
|
||||
mergeLocaleActions: (() => Promise<void>)[]
|
||||
operation: Operation
|
||||
parentIndexPath: string
|
||||
/**
|
||||
* @todo make required in v4.0
|
||||
*/
|
||||
parentIsLocalized?: boolean
|
||||
parentPath: string
|
||||
parentSchemaPath: string
|
||||
req: PayloadRequest
|
||||
@@ -68,6 +72,7 @@ export const traverseFields = async ({
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
parentIndexPath,
|
||||
parentIsLocalized,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
req,
|
||||
@@ -95,6 +100,7 @@ export const traverseFields = async ({
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
parentIndexPath,
|
||||
parentIsLocalized,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
req,
|
||||
|
||||
@@ -36,6 +36,7 @@ export const beforeDuplicate = async <T extends JsonObject>({
|
||||
fields: collection?.fields,
|
||||
overrideAccess,
|
||||
parentIndexPath: '',
|
||||
parentIsLocalized: false,
|
||||
parentPath: '',
|
||||
parentSchemaPath: '',
|
||||
req,
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { RequestContext } from '../../../index.js'
|
||||
import type { JsonObject, PayloadRequest } from '../../../types/index.js'
|
||||
import type { Block, Field, FieldHookArgs, TabAsField } from '../../config/types.js'
|
||||
|
||||
import { fieldAffectsData } from '../../config/types.js'
|
||||
import { fieldAffectsData, fieldShouldBeLocalized } from '../../config/types.js'
|
||||
import { getFieldPathsModified as getFieldPaths } from '../../getFieldPaths.js'
|
||||
import { runBeforeDuplicateHooks } from './runHook.js'
|
||||
import { traverseFields } from './traverseFields.js'
|
||||
@@ -22,6 +22,7 @@ type Args<T> = {
|
||||
id?: number | string
|
||||
overrideAccess: boolean
|
||||
parentIndexPath: string
|
||||
parentIsLocalized: boolean
|
||||
parentPath: string
|
||||
parentSchemaPath: string
|
||||
req: PayloadRequest
|
||||
@@ -39,6 +40,7 @@ export const promise = async <T>({
|
||||
fieldIndex,
|
||||
overrideAccess,
|
||||
parentIndexPath,
|
||||
parentIsLocalized,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
req,
|
||||
@@ -61,7 +63,7 @@ export const promise = async <T>({
|
||||
|
||||
if (fieldAffectsData(field)) {
|
||||
let fieldData = siblingDoc?.[field.name]
|
||||
const fieldIsLocalized = field.localized && localization
|
||||
const fieldIsLocalized = localization && fieldShouldBeLocalized({ field, parentIsLocalized })
|
||||
|
||||
// Run field beforeDuplicate hooks
|
||||
if (Array.isArray(field.hooks?.beforeDuplicate)) {
|
||||
@@ -162,6 +164,7 @@ export const promise = async <T>({
|
||||
fields: field.fields,
|
||||
overrideAccess,
|
||||
parentIndexPath: '',
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentPath: path + '.' + rowIndex,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
@@ -200,6 +203,7 @@ export const promise = async <T>({
|
||||
fields: block.fields,
|
||||
overrideAccess,
|
||||
parentIndexPath: '',
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentPath: path + '.' + rowIndex,
|
||||
parentSchemaPath: schemaPath + '.' + block.slug,
|
||||
req,
|
||||
@@ -223,6 +227,7 @@ export const promise = async <T>({
|
||||
fields: field.fields,
|
||||
overrideAccess,
|
||||
parentIndexPath: '',
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
@@ -259,6 +264,7 @@ export const promise = async <T>({
|
||||
fields: field.fields,
|
||||
overrideAccess,
|
||||
parentIndexPath: '',
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentPath: path + '.' + rowIndex,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
@@ -301,6 +307,7 @@ export const promise = async <T>({
|
||||
fields: block.fields,
|
||||
overrideAccess,
|
||||
parentIndexPath: '',
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentPath: path + '.' + rowIndex,
|
||||
parentSchemaPath: schemaPath + '.' + block.slug,
|
||||
req,
|
||||
@@ -332,6 +339,7 @@ export const promise = async <T>({
|
||||
fields: field.fields,
|
||||
overrideAccess,
|
||||
parentIndexPath: '',
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
@@ -357,6 +365,7 @@ export const promise = async <T>({
|
||||
fields: field.fields,
|
||||
overrideAccess,
|
||||
parentIndexPath: '',
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
@@ -381,6 +390,7 @@ export const promise = async <T>({
|
||||
fields: field.fields,
|
||||
overrideAccess,
|
||||
parentIndexPath: indexPath,
|
||||
parentIsLocalized,
|
||||
parentPath,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
@@ -403,6 +413,7 @@ export const promise = async <T>({
|
||||
fields: field.fields,
|
||||
overrideAccess,
|
||||
parentIndexPath: indexPath,
|
||||
parentIsLocalized,
|
||||
parentPath,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
@@ -422,6 +433,7 @@ export const promise = async <T>({
|
||||
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
||||
overrideAccess,
|
||||
parentIndexPath: indexPath,
|
||||
parentIsLocalized,
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
|
||||
@@ -18,6 +18,7 @@ type Args<T> = {
|
||||
id?: number | string
|
||||
overrideAccess: boolean
|
||||
parentIndexPath: string
|
||||
parentIsLocalized: boolean
|
||||
parentPath: string
|
||||
parentSchemaPath: string
|
||||
req: PayloadRequest
|
||||
@@ -33,6 +34,7 @@ export const traverseFields = async <T>({
|
||||
fields,
|
||||
overrideAccess,
|
||||
parentIndexPath,
|
||||
parentIsLocalized,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
req,
|
||||
@@ -52,6 +54,7 @@ export const traverseFields = async <T>({
|
||||
fieldIndex,
|
||||
overrideAccess,
|
||||
parentIndexPath,
|
||||
parentIsLocalized,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
req,
|
||||
|
||||
@@ -49,6 +49,7 @@ export const beforeValidate = async <T extends JsonObject>({
|
||||
operation,
|
||||
overrideAccess,
|
||||
parentIndexPath: '',
|
||||
parentIsLocalized: false,
|
||||
parentPath: '',
|
||||
parentSchemaPath: '',
|
||||
req,
|
||||
|
||||
@@ -33,6 +33,7 @@ type Args<T> = {
|
||||
operation: 'create' | 'update'
|
||||
overrideAccess: boolean
|
||||
parentIndexPath: string
|
||||
parentIsLocalized: boolean
|
||||
parentPath: string
|
||||
parentSchemaPath: string
|
||||
req: PayloadRequest
|
||||
@@ -64,6 +65,7 @@ export const promise = async <T>({
|
||||
operation,
|
||||
overrideAccess,
|
||||
parentIndexPath,
|
||||
parentIsLocalized,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
req,
|
||||
@@ -355,6 +357,7 @@ export const promise = async <T>({
|
||||
operation,
|
||||
overrideAccess,
|
||||
parentIndexPath: '',
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentPath: path + '.' + rowIndex,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
@@ -401,6 +404,7 @@ export const promise = async <T>({
|
||||
operation,
|
||||
overrideAccess,
|
||||
parentIndexPath: '',
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentPath: path + '.' + rowIndex,
|
||||
parentSchemaPath: schemaPath + '.' + block.slug,
|
||||
req,
|
||||
@@ -431,6 +435,7 @@ export const promise = async <T>({
|
||||
operation,
|
||||
overrideAccess,
|
||||
parentIndexPath: indexPath,
|
||||
parentIsLocalized,
|
||||
parentPath,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
@@ -465,6 +470,7 @@ export const promise = async <T>({
|
||||
operation,
|
||||
overrideAccess,
|
||||
parentIndexPath: '',
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
@@ -500,6 +506,7 @@ export const promise = async <T>({
|
||||
operation,
|
||||
originalDoc: doc,
|
||||
overrideAccess,
|
||||
parentIsLocalized,
|
||||
path: pathSegments,
|
||||
previousSiblingDoc: siblingDoc,
|
||||
previousValue: siblingData[field.name],
|
||||
@@ -551,6 +558,7 @@ export const promise = async <T>({
|
||||
operation,
|
||||
overrideAccess,
|
||||
parentIndexPath: isNamedTab ? '' : indexPath,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentPath: isNamedTab ? path : parentPath,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
@@ -574,6 +582,7 @@ export const promise = async <T>({
|
||||
operation,
|
||||
overrideAccess,
|
||||
parentIndexPath: indexPath,
|
||||
parentIsLocalized,
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
|
||||
@@ -25,6 +25,10 @@ type Args<T> = {
|
||||
operation: 'create' | 'update'
|
||||
overrideAccess: boolean
|
||||
parentIndexPath: string
|
||||
/**
|
||||
* @todo make required in v4.0
|
||||
*/
|
||||
parentIsLocalized?: boolean
|
||||
parentPath: string
|
||||
parentSchemaPath: string
|
||||
req: PayloadRequest
|
||||
@@ -47,6 +51,7 @@ export const traverseFields = async <T>({
|
||||
operation,
|
||||
overrideAccess,
|
||||
parentIndexPath,
|
||||
parentIsLocalized,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
req,
|
||||
@@ -70,6 +75,7 @@ export const traverseFields = async <T>({
|
||||
operation,
|
||||
overrideAccess,
|
||||
parentIndexPath,
|
||||
parentIsLocalized,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
req,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @ts-strict-ignore
|
||||
// default beforeDuplicate hook for required and unique fields
|
||||
import type { FieldAffectingData, FieldHook } from './config/types.js'
|
||||
import { type FieldAffectingData, type FieldHook, fieldShouldBeLocalized } from './config/types.js'
|
||||
|
||||
const unique: FieldHook = ({ value }) => (typeof value === 'string' ? `${value} - Copy` : undefined)
|
||||
const localizedUnique: FieldHook = ({ req, value }) =>
|
||||
@@ -9,16 +9,25 @@ const uniqueRequired: FieldHook = ({ value }) => `${value} - Copy`
|
||||
const localizedUniqueRequired: FieldHook = ({ req, value }) =>
|
||||
`${value} - ${req?.t('general:copy') ?? 'Copy'}`
|
||||
|
||||
export const setDefaultBeforeDuplicate = (field: FieldAffectingData) => {
|
||||
export const setDefaultBeforeDuplicate = (
|
||||
field: FieldAffectingData,
|
||||
parentIsLocalized: boolean,
|
||||
) => {
|
||||
if (
|
||||
(('required' in field && field.required) || field.unique) &&
|
||||
(!field.hooks?.beforeDuplicate ||
|
||||
(Array.isArray(field.hooks.beforeDuplicate) && field.hooks.beforeDuplicate.length === 0))
|
||||
) {
|
||||
if ((field.type === 'text' || field.type === 'textarea') && field.required && field.unique) {
|
||||
field.hooks.beforeDuplicate = [field.localized ? localizedUniqueRequired : uniqueRequired]
|
||||
field.hooks.beforeDuplicate = [
|
||||
fieldShouldBeLocalized({ field, parentIsLocalized })
|
||||
? localizedUniqueRequired
|
||||
: uniqueRequired,
|
||||
]
|
||||
} else if (field.unique) {
|
||||
field.hooks.beforeDuplicate = [field.localized ? localizedUnique : unique]
|
||||
field.hooks.beforeDuplicate = [
|
||||
fieldShouldBeLocalized({ field, parentIsLocalized }) ? localizedUnique : unique,
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -616,7 +616,12 @@ export class BasePayload {
|
||||
}
|
||||
}
|
||||
|
||||
traverseFields({ callback: findCustomID, config: this.config, fields: collection.fields })
|
||||
traverseFields({
|
||||
callback: findCustomID,
|
||||
config: this.config,
|
||||
fields: collection.fields,
|
||||
parentIsLocalized: false,
|
||||
})
|
||||
|
||||
this.collections[collection.slug] = {
|
||||
config: collection,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { Config, SanitizedConfig } from '../config/types.js'
|
||||
import type { ArrayField, Block, BlocksField, Field, TabAsField } from '../fields/config/types.js'
|
||||
|
||||
import { fieldHasSubFields } from '../fields/config/types.js'
|
||||
import { fieldHasSubFields, fieldShouldBeLocalized } from '../fields/config/types.js'
|
||||
|
||||
const traverseArrayOrBlocksField = ({
|
||||
callback,
|
||||
@@ -12,6 +12,7 @@ const traverseArrayOrBlocksField = ({
|
||||
field,
|
||||
fillEmpty,
|
||||
leavesFirst,
|
||||
parentIsLocalized,
|
||||
parentRef,
|
||||
}: {
|
||||
callback: TraverseFieldsCallback
|
||||
@@ -21,6 +22,7 @@ const traverseArrayOrBlocksField = ({
|
||||
field: ArrayField | BlocksField
|
||||
fillEmpty: boolean
|
||||
leavesFirst: boolean
|
||||
parentIsLocalized: boolean
|
||||
parentRef?: unknown
|
||||
}) => {
|
||||
if (fillEmpty) {
|
||||
@@ -32,6 +34,7 @@ const traverseArrayOrBlocksField = ({
|
||||
fields: field.fields,
|
||||
isTopLevel: false,
|
||||
leavesFirst,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentRef,
|
||||
})
|
||||
}
|
||||
@@ -48,6 +51,7 @@ const traverseArrayOrBlocksField = ({
|
||||
fields: block.fields,
|
||||
isTopLevel: false,
|
||||
leavesFirst,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentRef,
|
||||
})
|
||||
}
|
||||
@@ -80,6 +84,7 @@ const traverseArrayOrBlocksField = ({
|
||||
fillEmpty,
|
||||
isTopLevel: false,
|
||||
leavesFirst,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentRef,
|
||||
ref,
|
||||
})
|
||||
@@ -96,6 +101,7 @@ export type TraverseFieldsCallback = (args: {
|
||||
* Function that when called will skip the current field and continue to the next
|
||||
*/
|
||||
next?: () => void
|
||||
parentIsLocalized: boolean
|
||||
/**
|
||||
* The parent reference object
|
||||
*/
|
||||
@@ -120,6 +126,7 @@ type TraverseFieldsArgs = {
|
||||
* The return value of the callback function will be ignored.
|
||||
*/
|
||||
leavesFirst?: boolean
|
||||
parentIsLocalized?: boolean
|
||||
parentRef?: Record<string, unknown> | unknown
|
||||
ref?: Record<string, unknown> | unknown
|
||||
}
|
||||
@@ -141,6 +148,7 @@ export const traverseFields = ({
|
||||
fillEmpty = true,
|
||||
isTopLevel = true,
|
||||
leavesFirst = false,
|
||||
parentIsLocalized,
|
||||
parentRef = {},
|
||||
ref = {},
|
||||
}: TraverseFieldsArgs): void => {
|
||||
@@ -158,10 +166,10 @@ export const traverseFields = ({
|
||||
return
|
||||
}
|
||||
|
||||
if (!leavesFirst && callback && callback({ field, next, parentRef, ref })) {
|
||||
if (!leavesFirst && callback && callback({ field, next, parentIsLocalized, parentRef, ref })) {
|
||||
return true
|
||||
} else if (leavesFirst) {
|
||||
callbackStack.push(() => callback({ field, next, parentRef, ref }))
|
||||
callbackStack.push(() => callback({ field, next, parentIsLocalized, parentRef, ref }))
|
||||
}
|
||||
|
||||
if (skip) {
|
||||
@@ -199,6 +207,7 @@ export const traverseFields = ({
|
||||
callback({
|
||||
field: { ...tab, type: 'tab' },
|
||||
next,
|
||||
parentIsLocalized,
|
||||
parentRef: currentParentRef,
|
||||
ref: tabRef,
|
||||
})
|
||||
@@ -209,6 +218,7 @@ export const traverseFields = ({
|
||||
callback({
|
||||
field: { ...tab, type: 'tab' },
|
||||
next,
|
||||
parentIsLocalized,
|
||||
parentRef: currentParentRef,
|
||||
ref: tabRef,
|
||||
}),
|
||||
@@ -228,6 +238,7 @@ export const traverseFields = ({
|
||||
fillEmpty,
|
||||
isTopLevel: false,
|
||||
leavesFirst,
|
||||
parentIsLocalized: true,
|
||||
parentRef: currentParentRef,
|
||||
ref: tabRef[key],
|
||||
})
|
||||
@@ -241,6 +252,7 @@ export const traverseFields = ({
|
||||
callback({
|
||||
field: { ...tab, type: 'tab' },
|
||||
next,
|
||||
parentIsLocalized,
|
||||
parentRef: currentParentRef,
|
||||
ref: tabRef,
|
||||
})
|
||||
@@ -251,6 +263,7 @@ export const traverseFields = ({
|
||||
callback({
|
||||
field: { ...tab, type: 'tab' },
|
||||
next,
|
||||
parentIsLocalized,
|
||||
parentRef: currentParentRef,
|
||||
ref: tabRef,
|
||||
}),
|
||||
@@ -267,6 +280,7 @@ export const traverseFields = ({
|
||||
fillEmpty,
|
||||
isTopLevel: false,
|
||||
leavesFirst,
|
||||
parentIsLocalized: false,
|
||||
parentRef: currentParentRef,
|
||||
ref: tabRef,
|
||||
})
|
||||
@@ -286,7 +300,7 @@ export const traverseFields = ({
|
||||
if (!ref[field.name]) {
|
||||
if (fillEmpty) {
|
||||
if (field.type === 'group') {
|
||||
if (field.localized) {
|
||||
if (fieldShouldBeLocalized({ field, parentIsLocalized })) {
|
||||
ref[field.name] = {
|
||||
en: {},
|
||||
}
|
||||
@@ -294,7 +308,7 @@ export const traverseFields = ({
|
||||
ref[field.name] = {}
|
||||
}
|
||||
} else if (field.type === 'array' || field.type === 'blocks') {
|
||||
if (field.localized) {
|
||||
if (fieldShouldBeLocalized({ field, parentIsLocalized })) {
|
||||
ref[field.name] = {
|
||||
en: [],
|
||||
}
|
||||
@@ -311,7 +325,7 @@ export const traverseFields = ({
|
||||
|
||||
if (
|
||||
field.type === 'group' &&
|
||||
field.localized &&
|
||||
fieldShouldBeLocalized({ field, parentIsLocalized }) &&
|
||||
currentRef &&
|
||||
typeof currentRef === 'object'
|
||||
) {
|
||||
@@ -325,6 +339,7 @@ export const traverseFields = ({
|
||||
fillEmpty,
|
||||
isTopLevel: false,
|
||||
leavesFirst,
|
||||
parentIsLocalized: true,
|
||||
parentRef: currentParentRef,
|
||||
ref: currentRef[key],
|
||||
})
|
||||
@@ -338,7 +353,7 @@ export const traverseFields = ({
|
||||
currentRef &&
|
||||
typeof currentRef === 'object'
|
||||
) {
|
||||
if (field.localized) {
|
||||
if (fieldShouldBeLocalized({ field, parentIsLocalized })) {
|
||||
if (Array.isArray(currentRef)) {
|
||||
return
|
||||
}
|
||||
@@ -357,6 +372,7 @@ export const traverseFields = ({
|
||||
field,
|
||||
fillEmpty,
|
||||
leavesFirst,
|
||||
parentIsLocalized: true,
|
||||
parentRef: currentParentRef,
|
||||
})
|
||||
}
|
||||
@@ -369,6 +385,7 @@ export const traverseFields = ({
|
||||
field,
|
||||
fillEmpty,
|
||||
leavesFirst,
|
||||
parentIsLocalized,
|
||||
parentRef: currentParentRef,
|
||||
})
|
||||
}
|
||||
@@ -381,6 +398,7 @@ export const traverseFields = ({
|
||||
fillEmpty,
|
||||
isTopLevel: false,
|
||||
leavesFirst,
|
||||
parentIsLocalized,
|
||||
parentRef: currentParentRef,
|
||||
ref: currentRef,
|
||||
})
|
||||
|
||||
@@ -17,11 +17,13 @@ export const blockPopulationPromiseHOC = (
|
||||
depth,
|
||||
draft,
|
||||
editorPopulationPromises,
|
||||
field,
|
||||
fieldPromises,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
node,
|
||||
overrideAccess,
|
||||
parentIsLocalized,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -46,6 +48,7 @@ export const blockPopulationPromiseHOC = (
|
||||
findMany,
|
||||
flattenLocales,
|
||||
overrideAccess,
|
||||
parentIsLocalized: parentIsLocalized || field.localized || false,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
|
||||
@@ -13,11 +13,13 @@ export const linkPopulationPromiseHOC = (
|
||||
depth,
|
||||
draft,
|
||||
editorPopulationPromises,
|
||||
field,
|
||||
fieldPromises,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
node,
|
||||
overrideAccess,
|
||||
parentIsLocalized,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -42,6 +44,7 @@ export const linkPopulationPromiseHOC = (
|
||||
findMany,
|
||||
flattenLocales,
|
||||
overrideAccess,
|
||||
parentIsLocalized: parentIsLocalized || field.localized || false,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
|
||||
@@ -30,23 +30,7 @@ import type { AdapterProps } from '../types.js'
|
||||
import type { HTMLConverter } from './converters/html/converter/types.js'
|
||||
import type { BaseClientFeatureProps } from './typesClient.js'
|
||||
|
||||
export type PopulationPromise<T extends SerializedLexicalNode = SerializedLexicalNode> = ({
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
editorPopulationPromises,
|
||||
field,
|
||||
fieldPromises,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
node,
|
||||
overrideAccess,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
siblingDoc,
|
||||
}: {
|
||||
export type PopulationPromise<T extends SerializedLexicalNode = SerializedLexicalNode> = (args: {
|
||||
context: RequestContext
|
||||
currentDepth: number
|
||||
depth: number
|
||||
@@ -64,6 +48,7 @@ export type PopulationPromise<T extends SerializedLexicalNode = SerializedLexica
|
||||
flattenLocales: boolean
|
||||
node: T
|
||||
overrideAccess: boolean
|
||||
parentIsLocalized: boolean
|
||||
populationPromises: Promise<void>[]
|
||||
req: PayloadRequest
|
||||
showHiddenFields: boolean
|
||||
|
||||
@@ -14,11 +14,13 @@ export const uploadPopulationPromiseHOC = (
|
||||
depth,
|
||||
draft,
|
||||
editorPopulationPromises,
|
||||
field,
|
||||
fieldPromises,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
node,
|
||||
overrideAccess,
|
||||
parentIsLocalized,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -59,6 +61,8 @@ export const uploadPopulationPromiseHOC = (
|
||||
currentDepth,
|
||||
data: node.fields || {},
|
||||
depth,
|
||||
parentIsLocalized: parentIsLocalized || field.localized || false,
|
||||
|
||||
draft,
|
||||
editorPopulationPromises,
|
||||
fieldPromises,
|
||||
|
||||
@@ -157,6 +157,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
findMany,
|
||||
flattenLocales,
|
||||
overrideAccess,
|
||||
parentIsLocalized,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -175,6 +176,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
findMany,
|
||||
flattenLocales,
|
||||
overrideAccess,
|
||||
parentIsLocalized,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -189,10 +191,12 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
collection,
|
||||
context: _context,
|
||||
data,
|
||||
field,
|
||||
global,
|
||||
indexPath,
|
||||
operation,
|
||||
originalDoc,
|
||||
parentIsLocalized,
|
||||
path,
|
||||
previousDoc,
|
||||
previousValue,
|
||||
@@ -268,7 +272,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
originalNode: originalNodeIDMap[id],
|
||||
parentRichTextFieldPath: path,
|
||||
parentRichTextFieldSchemaPath: schemaPath,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
|
||||
previousNode: previousNodeIDMap[id]!,
|
||||
req,
|
||||
})
|
||||
@@ -282,10 +286,9 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
if (subFieldFn && subFieldDataFn) {
|
||||
const subFields = subFieldFn({ node, req })
|
||||
const nodeSiblingData = subFieldDataFn({ node, req }) ?? {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
|
||||
const nodeSiblingDoc = subFieldDataFn({ node: originalNodeIDMap[id]!, req }) ?? {}
|
||||
const nodePreviousSiblingDoc =
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
subFieldDataFn({ node: previousNodeIDMap[id]!, req }) ?? {}
|
||||
|
||||
if (subFields?.length) {
|
||||
@@ -299,6 +302,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
global,
|
||||
operation,
|
||||
parentIndexPath: indexPath.join('-'),
|
||||
parentIsLocalized: parentIsLocalized || field.localized || false,
|
||||
parentPath: path.join('.'),
|
||||
parentSchemaPath: schemaPath.join('.'),
|
||||
previousDoc,
|
||||
@@ -325,6 +329,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
depth,
|
||||
draft,
|
||||
fallbackLocale,
|
||||
field,
|
||||
fieldPromises,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
@@ -333,6 +338,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
locale,
|
||||
originalDoc,
|
||||
overrideAccess,
|
||||
parentIsLocalized,
|
||||
path,
|
||||
populate,
|
||||
populationPromises,
|
||||
@@ -419,6 +425,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
locale: locale!,
|
||||
overrideAccess: overrideAccess!,
|
||||
parentIndexPath: indexPath.join('-'),
|
||||
parentIsLocalized: parentIsLocalized || field.localized || false,
|
||||
parentPath: path.join('.'),
|
||||
parentSchemaPath: schemaPath.join('.'),
|
||||
populate,
|
||||
@@ -450,6 +457,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
originalDoc,
|
||||
parentIsLocalized,
|
||||
path,
|
||||
previousValue,
|
||||
req,
|
||||
@@ -543,7 +551,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
originalNodeWithLocales: originalNodeWithLocalesIDMap[id],
|
||||
parentRichTextFieldPath: path,
|
||||
parentRichTextFieldSchemaPath: schemaPath,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
|
||||
previousNode: previousNodeIDMap[id]!,
|
||||
req,
|
||||
skipValidation: skipValidation!,
|
||||
@@ -561,12 +569,10 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
const nodeSiblingData = subFieldDataFn({ node, req }) ?? {}
|
||||
const nodeSiblingDocWithLocales =
|
||||
subFieldDataFn({
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
node: originalNodeWithLocalesIDMap[id]!,
|
||||
req,
|
||||
}) ?? {}
|
||||
const nodePreviousSiblingDoc =
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
subFieldDataFn({ node: previousNodeIDMap[id]!, req }) ?? {}
|
||||
|
||||
if (subFields?.length) {
|
||||
@@ -584,6 +590,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
mergeLocaleActions: mergeLocaleActions!,
|
||||
operation: operation!,
|
||||
parentIndexPath: indexPath.join('-'),
|
||||
parentIsLocalized: parentIsLocalized || field.localized || false,
|
||||
parentPath: path.join('.'),
|
||||
parentSchemaPath: schemaPath.join('.'),
|
||||
req,
|
||||
@@ -637,11 +644,13 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
field,
|
||||
global,
|
||||
indexPath,
|
||||
operation,
|
||||
originalDoc,
|
||||
overrideAccess,
|
||||
parentIsLocalized,
|
||||
path,
|
||||
previousValue,
|
||||
req,
|
||||
@@ -762,7 +771,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
if (subFieldFn && subFieldDataFn) {
|
||||
const subFields = subFieldFn({ node, req })
|
||||
const nodeSiblingData = subFieldDataFn({ node, req }) ?? {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
|
||||
const nodeSiblingDoc = subFieldDataFn({ node: originalNodeIDMap[id]!, req }) ?? {}
|
||||
|
||||
if (subFields?.length) {
|
||||
@@ -778,6 +787,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
operation,
|
||||
overrideAccess: overrideAccess!,
|
||||
parentIndexPath: indexPath.join('-'),
|
||||
parentIsLocalized: parentIsLocalized || field.localized || false,
|
||||
parentPath: path.join('.'),
|
||||
parentSchemaPath: schemaPath.join('.'),
|
||||
req,
|
||||
|
||||
@@ -8,6 +8,7 @@ import { recurseNodes } from '../utilities/forEachNodeRecursively.js'
|
||||
|
||||
export type Args = {
|
||||
editorPopulationPromises: Map<string, Array<PopulationPromise>>
|
||||
parentIsLocalized: boolean
|
||||
} & Parameters<
|
||||
NonNullable<RichTextAdapter<SerializedEditorState, AdapterProps>['graphQLPopulationPromises']>
|
||||
>[0]
|
||||
@@ -26,6 +27,7 @@ export const populateLexicalPopulationPromises = ({
|
||||
findMany,
|
||||
flattenLocales,
|
||||
overrideAccess,
|
||||
parentIsLocalized,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -54,6 +56,7 @@ export const populateLexicalPopulationPromises = ({
|
||||
flattenLocales,
|
||||
node,
|
||||
overrideAccess: overrideAccess!,
|
||||
parentIsLocalized,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
|
||||
@@ -22,6 +22,7 @@ type NestedRichTextFieldsArgs = {
|
||||
findMany: boolean
|
||||
flattenLocales: boolean
|
||||
overrideAccess: boolean
|
||||
parentIsLocalized: boolean
|
||||
populationPromises: Promise<void>[]
|
||||
req: PayloadRequest
|
||||
showHiddenFields: boolean
|
||||
@@ -39,6 +40,7 @@ export const recursivelyPopulateFieldsForGraphQL = ({
|
||||
findMany,
|
||||
flattenLocales,
|
||||
overrideAccess = false,
|
||||
parentIsLocalized,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -60,6 +62,7 @@ export const recursivelyPopulateFieldsForGraphQL = ({
|
||||
locale: req.locale!,
|
||||
overrideAccess,
|
||||
parentIndexPath: '',
|
||||
parentIsLocalized,
|
||||
parentPath: '',
|
||||
parentSchemaPath: '',
|
||||
populationPromises, // This is not the same as populationPromises passed into this recurseNestedFields. These are just promises resolved at the very end.
|
||||
|
||||
@@ -156,6 +156,7 @@ export const richTextRelationshipPromise = ({
|
||||
draft,
|
||||
field,
|
||||
overrideAccess,
|
||||
parentIsLocalized,
|
||||
populateArg,
|
||||
populationPromises,
|
||||
req,
|
||||
|
||||
@@ -119,6 +119,7 @@ export function slateEditor(
|
||||
findMany,
|
||||
flattenLocales,
|
||||
overrideAccess,
|
||||
parentIsLocalized,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -140,6 +141,7 @@ export function slateEditor(
|
||||
findMany,
|
||||
flattenLocales,
|
||||
overrideAccess,
|
||||
parentIsLocalized,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -159,6 +161,7 @@ export function slateEditor(
|
||||
findMany,
|
||||
flattenLocales,
|
||||
overrideAccess,
|
||||
parentIsLocalized,
|
||||
populate,
|
||||
populationPromises,
|
||||
req,
|
||||
@@ -183,6 +186,7 @@ export function slateEditor(
|
||||
findMany,
|
||||
flattenLocales,
|
||||
overrideAccess,
|
||||
parentIsLocalized,
|
||||
populateArg: populate,
|
||||
populationPromises,
|
||||
req,
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
formatErrors,
|
||||
type PayloadRequest,
|
||||
} from 'payload'
|
||||
import { fieldAffectsData, tabHasName } from 'payload/shared'
|
||||
import { fieldAffectsData, fieldShouldBeLocalized, tabHasName } from 'payload/shared'
|
||||
|
||||
const ObjectId = (ObjectIdImport.default ||
|
||||
ObjectIdImport) as unknown as typeof ObjectIdImport.default
|
||||
@@ -27,6 +27,7 @@ function iterateFields(
|
||||
fromLocaleData: Data,
|
||||
toLocaleData: Data,
|
||||
req: PayloadRequest,
|
||||
parentIsLocalized: boolean,
|
||||
): void {
|
||||
fields.map((field) => {
|
||||
if (fieldAffectsData(field)) {
|
||||
@@ -48,11 +49,17 @@ function iterateFields(
|
||||
toLocaleData[field.name].map((item: Data, index: number) => {
|
||||
if (fromLocaleData[field.name]?.[index]) {
|
||||
// Generate new IDs if the field is localized to prevent errors with relational DBs.
|
||||
if (field.localized) {
|
||||
if (fieldShouldBeLocalized({ field, parentIsLocalized })) {
|
||||
toLocaleData[field.name][index].id = new ObjectId().toHexString()
|
||||
}
|
||||
|
||||
iterateFields(field.fields, fromLocaleData[field.name][index], item, req)
|
||||
iterateFields(
|
||||
field.fields,
|
||||
fromLocaleData[field.name][index],
|
||||
item,
|
||||
req,
|
||||
parentIsLocalized || field.localized,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -80,12 +87,18 @@ function iterateFields(
|
||||
) as FlattenedBlock | undefined)
|
||||
|
||||
// Generate new IDs if the field is localized to prevent errors with relational DBs.
|
||||
if (field.localized) {
|
||||
if (fieldShouldBeLocalized({ field, parentIsLocalized })) {
|
||||
toLocaleData[field.name][index].id = new ObjectId().toHexString()
|
||||
}
|
||||
|
||||
if (block?.fields?.length) {
|
||||
iterateFields(block?.fields, fromLocaleData[field.name][index], blockData, req)
|
||||
iterateFields(
|
||||
block?.fields,
|
||||
fromLocaleData[field.name][index],
|
||||
blockData,
|
||||
req,
|
||||
parentIsLocalized || field.localized,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -118,7 +131,13 @@ function iterateFields(
|
||||
|
||||
case 'group': {
|
||||
if (field.name in toLocaleData && fromLocaleData?.[field.name] !== undefined) {
|
||||
iterateFields(field.fields, fromLocaleData[field.name], toLocaleData[field.name], req)
|
||||
iterateFields(
|
||||
field.fields,
|
||||
fromLocaleData[field.name],
|
||||
toLocaleData[field.name],
|
||||
req,
|
||||
parentIsLocalized || field.localized,
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -127,17 +146,23 @@ function iterateFields(
|
||||
switch (field.type) {
|
||||
case 'collapsible':
|
||||
case 'row':
|
||||
iterateFields(field.fields, fromLocaleData, toLocaleData, req)
|
||||
iterateFields(field.fields, fromLocaleData, toLocaleData, req, parentIsLocalized)
|
||||
break
|
||||
|
||||
case 'tabs':
|
||||
field.tabs.map((tab) => {
|
||||
if (tabHasName(tab)) {
|
||||
if (tab.name in toLocaleData && fromLocaleData?.[tab.name] !== undefined) {
|
||||
iterateFields(tab.fields, fromLocaleData[tab.name], toLocaleData[tab.name], req)
|
||||
iterateFields(
|
||||
tab.fields,
|
||||
fromLocaleData[tab.name],
|
||||
toLocaleData[tab.name],
|
||||
req,
|
||||
parentIsLocalized,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
iterateFields(tab.fields, fromLocaleData, toLocaleData, req)
|
||||
iterateFields(tab.fields, fromLocaleData, toLocaleData, req, parentIsLocalized)
|
||||
}
|
||||
})
|
||||
break
|
||||
@@ -151,8 +176,9 @@ function mergeData(
|
||||
toLocaleData: Data,
|
||||
fields: Field[],
|
||||
req: PayloadRequest,
|
||||
parentIsLocalized: boolean,
|
||||
): Data {
|
||||
iterateFields(fields, fromLocaleData, toLocaleData, req)
|
||||
iterateFields(fields, fromLocaleData, toLocaleData, req, parentIsLocalized)
|
||||
|
||||
return toLocaleData
|
||||
}
|
||||
@@ -272,6 +298,7 @@ export const copyDataFromLocale = async (args: CopyDataFromLocaleArgs) => {
|
||||
toLocaleData.value,
|
||||
globals[globalSlug].config.fields,
|
||||
req,
|
||||
false,
|
||||
),
|
||||
locale: toLocale,
|
||||
overrideAccess: false,
|
||||
@@ -288,6 +315,7 @@ export const copyDataFromLocale = async (args: CopyDataFromLocaleArgs) => {
|
||||
toLocaleData.value,
|
||||
collections[collectionSlug].config.fields,
|
||||
req,
|
||||
false,
|
||||
),
|
||||
locale: toLocale,
|
||||
overrideAccess: false,
|
||||
|
||||
@@ -83,7 +83,7 @@ export interface Config {
|
||||
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
|
||||
};
|
||||
db: {
|
||||
defaultIDType: number;
|
||||
defaultIDType: string;
|
||||
};
|
||||
globals: {
|
||||
menu: Menu;
|
||||
@@ -123,7 +123,7 @@ export interface UserAuthOperations {
|
||||
* via the `definition` "posts".
|
||||
*/
|
||||
export interface Post {
|
||||
id: number;
|
||||
id: string;
|
||||
title?: string | null;
|
||||
content?: {
|
||||
root: {
|
||||
@@ -148,7 +148,7 @@ export interface Post {
|
||||
* via the `definition` "media".
|
||||
*/
|
||||
export interface Media {
|
||||
id: number;
|
||||
id: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
url?: string | null;
|
||||
@@ -192,7 +192,7 @@ export interface Media {
|
||||
* via the `definition` "users".
|
||||
*/
|
||||
export interface User {
|
||||
id: number;
|
||||
id: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
email: string;
|
||||
@@ -209,24 +209,24 @@ export interface User {
|
||||
* via the `definition` "payload-locked-documents".
|
||||
*/
|
||||
export interface PayloadLockedDocument {
|
||||
id: number;
|
||||
id: string;
|
||||
document?:
|
||||
| ({
|
||||
relationTo: 'posts';
|
||||
value: number | Post;
|
||||
value: string | Post;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'media';
|
||||
value: number | Media;
|
||||
value: string | Media;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'users';
|
||||
value: number | User;
|
||||
value: string | User;
|
||||
} | null);
|
||||
globalSlug?: string | null;
|
||||
user: {
|
||||
relationTo: 'users';
|
||||
value: number | User;
|
||||
value: string | User;
|
||||
};
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
@@ -236,10 +236,10 @@ export interface PayloadLockedDocument {
|
||||
* via the `definition` "payload-preferences".
|
||||
*/
|
||||
export interface PayloadPreference {
|
||||
id: number;
|
||||
id: string;
|
||||
user: {
|
||||
relationTo: 'users';
|
||||
value: number | User;
|
||||
value: string | User;
|
||||
};
|
||||
key?: string | null;
|
||||
value?:
|
||||
@@ -259,7 +259,7 @@ export interface PayloadPreference {
|
||||
* via the `definition` "payload-migrations".
|
||||
*/
|
||||
export interface PayloadMigration {
|
||||
id: number;
|
||||
id: string;
|
||||
name?: string | null;
|
||||
batch?: number | null;
|
||||
updatedAt: string;
|
||||
@@ -378,7 +378,7 @@ export interface PayloadMigrationsSelect<T extends boolean = true> {
|
||||
* via the `definition` "menu".
|
||||
*/
|
||||
export interface Menu {
|
||||
id: number;
|
||||
id: string;
|
||||
globalText?: string | null;
|
||||
updatedAt?: string | null;
|
||||
createdAt?: string | null;
|
||||
|
||||
@@ -16,6 +16,9 @@ import { runInit } from './runInit.js'
|
||||
import { child } from './safelyRunScript.js'
|
||||
import { createTestHooks } from './testHooks.js'
|
||||
|
||||
// @todo remove in 4.0 - will behave like this by default in 4.0
|
||||
process.env.PAYLOAD_DO_NOT_SANITIZE_LOCALIZED_PROPERTY = 'true'
|
||||
|
||||
const prod = process.argv.includes('--prod')
|
||||
if (prod) {
|
||||
process.argv = process.argv.filter((arg) => arg !== '--prod')
|
||||
|
||||
@@ -8,7 +8,7 @@ import playwright from 'eslint-plugin-playwright'
|
||||
export const testEslintConfig = [
|
||||
...rootEslintConfig,
|
||||
{
|
||||
ignores: [...defaultESLintIgnores, '**/payload-types.ts'],
|
||||
ignores: [...defaultESLintIgnores, '**/payload-types.ts', 'jest.setup.js'],
|
||||
},
|
||||
{
|
||||
languageOptions: {
|
||||
|
||||
@@ -126,6 +126,26 @@ export const baseConfig: Partial<Config> = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: 'localizedTextReference',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: 'localizedTextReference2',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
custom: {
|
||||
client: {
|
||||
|
||||
@@ -409,6 +409,21 @@ const BlockFields: CollectionConfig = {
|
||||
blockReferences: ['ConfigBlockTest'],
|
||||
blocks: [],
|
||||
},
|
||||
{
|
||||
name: 'localizedReferencesLocalizedBlock',
|
||||
type: 'blocks',
|
||||
blockReferences: ['localizedTextReference'],
|
||||
blocks: [],
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'localizedReferences',
|
||||
type: 'blocks',
|
||||
// Needs to be a separate block - otherwise this will break in postgres. This is unrelated to block references
|
||||
// and an issue with all blocks.
|
||||
blockReferences: ['localizedTextReference2'],
|
||||
blocks: [],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -47,4 +47,16 @@ export const blocksDoc: Partial<BlockField> = {
|
||||
blockType: 'blockWithMinRows',
|
||||
},
|
||||
],
|
||||
localizedReferencesLocalizedBlock: [
|
||||
{
|
||||
blockType: 'localizedTextReference',
|
||||
text: 'localized text',
|
||||
},
|
||||
],
|
||||
localizedReferences: [
|
||||
{
|
||||
blockType: 'localizedTextReference2',
|
||||
text: 'localized text',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ import type { MongooseAdapter } from '@payloadcms/db-mongodb'
|
||||
import type { IndexDirection, IndexOptions } from 'mongoose'
|
||||
|
||||
import path from 'path'
|
||||
import { type PaginatedDocs, type Payload, reload, ValidationError } from 'payload'
|
||||
import { type PaginatedDocs, type Payload, reload } from 'payload'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import type { NextRESTClient } from '../helpers/NextRESTClient.js'
|
||||
import type { GroupField, RichTextField } from './payload-types.js'
|
||||
import type { BlockField, GroupField, RichTextField } from './payload-types.js'
|
||||
|
||||
import { devUser } from '../credentials.js'
|
||||
import { initPayloadInt } from '../helpers/initPayloadInt.js'
|
||||
@@ -2562,6 +2562,32 @@ describe('Fields', () => {
|
||||
|
||||
expect(result.blocksWithLocalizedArray[0].array[0].text).toEqual('localized')
|
||||
})
|
||||
|
||||
it('ensure localized field within block reference is saved correctly', async () => {
|
||||
const blockFields = await payload.find({
|
||||
collection: 'block-fields',
|
||||
locale: 'all',
|
||||
})
|
||||
|
||||
const doc: BlockField = blockFields.docs[0] as BlockField
|
||||
|
||||
expect(doc?.localizedReferences?.[0]?.blockType).toEqual('localizedTextReference2')
|
||||
expect(doc?.localizedReferences?.[0]?.text).toEqual({ en: 'localized text' })
|
||||
})
|
||||
|
||||
it('ensure localized property is stripped from localized field within localized block reference', async () => {
|
||||
const blockFields = await payload.find({
|
||||
collection: 'block-fields',
|
||||
locale: 'all',
|
||||
})
|
||||
|
||||
const doc: any = blockFields.docs[0]
|
||||
|
||||
expect(doc?.localizedReferencesLocalizedBlock?.en?.[0]?.blockType).toEqual(
|
||||
'localizedTextReference',
|
||||
)
|
||||
expect(doc?.localizedReferencesLocalizedBlock?.en?.[0]?.text).toEqual('localized text')
|
||||
})
|
||||
})
|
||||
|
||||
describe('collapsible', () => {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,7 @@ export const autoDedupeBlocksPlugin =
|
||||
traverseFields({
|
||||
config,
|
||||
leavesFirst: true,
|
||||
parentIsLocalized: false,
|
||||
isTopLevel: true,
|
||||
fields: [
|
||||
...(config.collections?.length
|
||||
|
||||
@@ -16,6 +16,8 @@ process.env.PAYLOAD_PUBLIC_CLOUD_STORAGE_ADAPTER = 's3'
|
||||
|
||||
process.env.NODE_OPTIONS = '--no-deprecation'
|
||||
process.env.PAYLOAD_CI_DEPENDENCY_CHECKER = 'true'
|
||||
// @todo remove in 4.0 - will behave like this by default in 4.0
|
||||
process.env.PAYLOAD_DO_NOT_SANITIZE_LOCALIZED_PROPERTY = 'true'
|
||||
|
||||
// Mock createTestAccount to prevent calling external services
|
||||
jest.spyOn(nodemailer, 'createTestAccount').mockImplementation(() => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user