Compare commits
24 Commits
main
...
perf/optim
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5801528ae | ||
|
|
42c8f15161 | ||
|
|
7d9fbafe5f | ||
|
|
4e7266e927 | ||
|
|
7653386552 | ||
|
|
63eb41617e | ||
|
|
8c68ea9677 | ||
|
|
2a199d7724 | ||
|
|
475fd5be92 | ||
|
|
35e9c27286 | ||
|
|
618491d694 | ||
|
|
2c8701b89d | ||
|
|
88f3ec562e | ||
|
|
869461b0ad | ||
|
|
63e07ff481 | ||
|
|
603418fe83 | ||
|
|
ae2b3bb984 | ||
|
|
6d9972ee8f | ||
|
|
a61431e9d8 | ||
|
|
b17520dac6 | ||
|
|
cffe888e84 | ||
|
|
505db12ff3 | ||
|
|
209b9ad291 | ||
|
|
41353f2d94 |
@@ -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 [
|
||||
|
||||
@@ -45,6 +45,7 @@ export const find: Find = async function find(
|
||||
config: this.payload.config,
|
||||
fields: collectionConfig.flattenedFields,
|
||||
locale,
|
||||
parentIsLocalized: false,
|
||||
sort: sortArg || collectionConfig.defaultSort,
|
||||
timestamps: true,
|
||||
})
|
||||
|
||||
@@ -41,6 +41,7 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
|
||||
config: this.payload.config,
|
||||
fields: versionFields,
|
||||
locale,
|
||||
parentIsLocalized: false,
|
||||
sort: sortArg || '-updatedAt',
|
||||
timestamps: true,
|
||||
})
|
||||
|
||||
@@ -36,6 +36,7 @@ export const findVersions: FindVersions = async function findVersions(
|
||||
config: this.payload.config,
|
||||
fields: collectionConfig.flattenedFields,
|
||||
locale,
|
||||
parentIsLocalized: false,
|
||||
sort: sortArg || '-updatedAt',
|
||||
timestamps: true,
|
||||
})
|
||||
|
||||
@@ -26,15 +26,20 @@ 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,
|
||||
parentIsLocalized: false,
|
||||
payload: this.payload,
|
||||
})
|
||||
|
||||
versionSchema.plugin<any, PaginateOptions>(paginate, { useEstimatedCount: true }).plugin(
|
||||
@@ -77,14 +82,19 @@ 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,
|
||||
parentIsLocalized: false,
|
||||
payload: this.payload,
|
||||
})
|
||||
|
||||
versionSchema.plugin<any, PaginateOptions>(paginate, { useEstimatedCount: true }).plugin(
|
||||
|
||||
@@ -12,14 +12,21 @@ 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,
|
||||
parentIsLocalized: false,
|
||||
payload,
|
||||
})
|
||||
|
||||
if (Array.isArray(collection.upload.filenameCompoundIndex)) {
|
||||
|
||||
@@ -19,10 +19,15 @@ 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,
|
||||
parentIsLocalized: false,
|
||||
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}`
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ export const queryDrafts: QueryDrafts = async function queryDrafts(
|
||||
config: this.payload.config,
|
||||
fields: collectionConfig.flattenedFields,
|
||||
locale,
|
||||
parentIsLocalized: false,
|
||||
sort: sortArg || collectionConfig.defaultSort,
|
||||
timestamps: true,
|
||||
})
|
||||
|
||||
@@ -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'
|
||||
@@ -76,6 +78,7 @@ export const buildJoinAggregation = async ({
|
||||
config: adapter.payload.config,
|
||||
fields: adapter.payload.collections[slug].config.flattenedFields,
|
||||
locale,
|
||||
parentIsLocalized: false,
|
||||
sort: sortJoin,
|
||||
timestamps: true,
|
||||
})
|
||||
@@ -148,7 +151,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
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ export const deleteOne: DeleteOne = async function deleteOne(
|
||||
data: docToDelete,
|
||||
fields: collection.flattenedFields,
|
||||
joinQuery: false,
|
||||
parentIsLocalized: false,
|
||||
})
|
||||
|
||||
await this.deleteWhere({
|
||||
|
||||
@@ -165,6 +165,7 @@ export const findMany = async function find({
|
||||
data,
|
||||
fields,
|
||||
joinQuery,
|
||||
parentIsLocalized: false,
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -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]) {
|
||||
|
||||
@@ -38,6 +38,7 @@ export const upsertRow = async <T extends Record<string, unknown> | TypeWithID>(
|
||||
adapter,
|
||||
data,
|
||||
fields,
|
||||
parentIsLocalized: false,
|
||||
path,
|
||||
tableName,
|
||||
})
|
||||
@@ -459,6 +460,7 @@ export const upsertRow = async <T extends Record<string, unknown> | TypeWithID>(
|
||||
data: doc,
|
||||
fields,
|
||||
joinQuery: false,
|
||||
parentIsLocalized: false,
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
|
||||
@@ -229,6 +229,7 @@ export const VersionView: PayloadServerReactComponent<EditViewComponent> = async
|
||||
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
|
||||
*/
|
||||
|
||||
@@ -522,6 +522,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',
|
||||
|
||||
@@ -940,6 +940,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,23 +1,22 @@
|
||||
// @ts-strict-ignore
|
||||
import type { Where } from '../types/index.js'
|
||||
|
||||
import { hasWhereAccessResult } from '../auth/index.js'
|
||||
|
||||
/**
|
||||
* Combines two queries into a single query, using an AND operator
|
||||
*/
|
||||
export const combineQueries = (where: Where, access: boolean | Where): Where => {
|
||||
if (!where && !access) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const result: Where = {
|
||||
and: [],
|
||||
}
|
||||
const and: Where[] = where ? [where] : []
|
||||
|
||||
if (where) {
|
||||
result.and.push(where)
|
||||
}
|
||||
if (hasWhereAccessResult(access)) {
|
||||
result.and.push(access)
|
||||
and.push(access)
|
||||
}
|
||||
|
||||
return result
|
||||
return {
|
||||
and,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -115,6 +115,7 @@ type PromiseArgs = {
|
||||
field: JoinField | RelationshipField | UploadField
|
||||
locale: null | string
|
||||
overrideAccess: boolean
|
||||
parentIsLocalized: boolean
|
||||
populate?: PopulateType
|
||||
req: PayloadRequest
|
||||
showHiddenFields: boolean
|
||||
@@ -129,6 +130,7 @@ export const relationshipPopulationPromise = async ({
|
||||
field,
|
||||
locale,
|
||||
overrideAccess,
|
||||
parentIsLocalized,
|
||||
populate: populateArg,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -140,7 +142,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,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// @ts-strict-ignore
|
||||
import type { CollectionPermission, GlobalPermission } from '../auth/types.js'
|
||||
import type { CollectionPermission, FieldsPermissions, GlobalPermission } from '../auth/types.js'
|
||||
import type { SanitizedCollectionConfig, TypeWithID } from '../collections/config/types.js'
|
||||
import type { Access } from '../config/types.js'
|
||||
import type { Field, FieldAccess } from '../fields/config/types.js'
|
||||
import type { SanitizedGlobalConfig } from '../globals/config/types.js'
|
||||
import type { AllOperations, Document, PayloadRequest, Where } from '../types/index.js'
|
||||
import type { AllOperations, JsonObject, Payload, PayloadRequest, Where } from '../types/index.js'
|
||||
|
||||
import { combineQueries } from '../database/combineQueries.js'
|
||||
import { tabHasName } from '../fields/config/types.js'
|
||||
@@ -26,11 +26,14 @@ type CreateAccessPromise = (args: {
|
||||
accessLevel: 'entity' | 'field'
|
||||
disableWhere?: boolean
|
||||
operation: AllOperations
|
||||
policiesObj: {
|
||||
[key: string]: any
|
||||
}
|
||||
policiesObj: CollectionPermission | GlobalPermission
|
||||
}) => Promise<void>
|
||||
|
||||
type EntityDoc = JsonObject | TypeWithID
|
||||
|
||||
/**
|
||||
* Build up permissions object for an entity (collection or global)
|
||||
*/
|
||||
export async function getEntityPolicies<T extends Args>(args: T): Promise<ReturnType<T>> {
|
||||
const { id, type, entity, operations, req } = args
|
||||
const { data, locale, payload, user } = req
|
||||
@@ -40,50 +43,51 @@ export async function getEntityPolicies<T extends Args>(args: T): Promise<Return
|
||||
fields: {},
|
||||
} as ReturnType<T>
|
||||
|
||||
let docBeingAccessed
|
||||
let docBeingAccessed: EntityDoc | Promise<EntityDoc | undefined> | undefined
|
||||
|
||||
async function getEntityDoc({ where }: { where?: Where } = {}): Promise<Document & TypeWithID> {
|
||||
if (entity.slug) {
|
||||
if (type === 'global') {
|
||||
return payload.findGlobal({
|
||||
slug: entity.slug,
|
||||
fallbackLocale: null,
|
||||
locale,
|
||||
overrideAccess: true,
|
||||
req,
|
||||
})
|
||||
}
|
||||
async function getEntityDoc({ where }: { where?: Where } = {}): Promise<EntityDoc | undefined> {
|
||||
if (!entity.slug) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (type === 'collection' && id) {
|
||||
if (typeof where === 'object') {
|
||||
const paginatedRes = await payload.find({
|
||||
collection: entity.slug,
|
||||
depth: 0,
|
||||
fallbackLocale: null,
|
||||
limit: 1,
|
||||
locale,
|
||||
overrideAccess: true,
|
||||
pagination: false,
|
||||
req,
|
||||
where: combineQueries(where, { id: { equals: id } }),
|
||||
})
|
||||
if (type === 'global') {
|
||||
return payload.findGlobal({
|
||||
slug: entity.slug,
|
||||
depth: 0,
|
||||
fallbackLocale: null,
|
||||
locale,
|
||||
overrideAccess: true,
|
||||
req,
|
||||
})
|
||||
}
|
||||
|
||||
return paginatedRes?.docs?.[0] || undefined
|
||||
}
|
||||
|
||||
return payload.findByID({
|
||||
id,
|
||||
if (type === 'collection' && id) {
|
||||
if (typeof where === 'object') {
|
||||
const paginatedRes = await payload.find({
|
||||
collection: entity.slug,
|
||||
depth: 0,
|
||||
fallbackLocale: null,
|
||||
limit: 1,
|
||||
locale,
|
||||
overrideAccess: true,
|
||||
pagination: false,
|
||||
req,
|
||||
where: combineQueries(where, { id: { equals: id } }),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
return paginatedRes?.docs?.[0] || undefined
|
||||
}
|
||||
|
||||
return payload.findByID({
|
||||
id,
|
||||
collection: entity.slug,
|
||||
depth: 0,
|
||||
fallbackLocale: null,
|
||||
locale,
|
||||
overrideAccess: true,
|
||||
req,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const createAccessPromise: CreateAccessPromise = async ({
|
||||
@@ -91,10 +95,8 @@ export async function getEntityPolicies<T extends Args>(args: T): Promise<Return
|
||||
accessLevel,
|
||||
disableWhere = false,
|
||||
operation,
|
||||
policiesObj,
|
||||
policiesObj: mutablePolicies,
|
||||
}) => {
|
||||
const mutablePolicies = policiesObj
|
||||
|
||||
if (accessLevel === 'field' && docBeingAccessed === undefined) {
|
||||
// assign docBeingAccessed first as the promise to avoid multiple calls to getEntityDoc
|
||||
docBeingAccessed = getEntityDoc().then((doc) => {
|
||||
@@ -107,6 +109,7 @@ export async function getEntityPolicies<T extends Args>(args: T): Promise<Return
|
||||
// https://payloadcms.slack.com/archives/C048Z9C2BEX/p1702054928343769
|
||||
const accessResult = await access({ id, data, doc: docBeingAccessed, req })
|
||||
|
||||
// Where query was returned from access function => check if document is returned when querying with where
|
||||
if (typeof accessResult === 'object' && !disableWhere) {
|
||||
mutablePolicies[operation] = {
|
||||
permission:
|
||||
@@ -120,137 +123,9 @@ export async function getEntityPolicies<T extends Args>(args: T): Promise<Return
|
||||
}
|
||||
}
|
||||
|
||||
const executeFieldPolicies = async ({
|
||||
entityPermission,
|
||||
fields,
|
||||
operation,
|
||||
policiesObj,
|
||||
}: {
|
||||
entityPermission
|
||||
fields: Field[]
|
||||
operation: AllOperations
|
||||
policiesObj
|
||||
}) => {
|
||||
const mutablePolicies = policiesObj.fields
|
||||
|
||||
// Fields don't have all operations of a collection
|
||||
if (operation === 'delete' || operation === 'readVersions' || operation === 'unlock') {
|
||||
return
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
fields.map(async (field) => {
|
||||
if ('name' in field && field.name) {
|
||||
if (!mutablePolicies[field.name]) {
|
||||
mutablePolicies[field.name] = {}
|
||||
}
|
||||
|
||||
if ('access' in field && field.access && typeof field.access[operation] === 'function') {
|
||||
await createAccessPromise({
|
||||
access: field.access[operation],
|
||||
accessLevel: 'field',
|
||||
disableWhere: true,
|
||||
operation,
|
||||
policiesObj: mutablePolicies[field.name],
|
||||
})
|
||||
} else {
|
||||
mutablePolicies[field.name][operation] = {
|
||||
permission: policiesObj[operation]?.permission,
|
||||
}
|
||||
}
|
||||
|
||||
if ('fields' in field && field.fields) {
|
||||
if (!mutablePolicies[field.name].fields) {
|
||||
mutablePolicies[field.name].fields = {}
|
||||
}
|
||||
|
||||
await executeFieldPolicies({
|
||||
entityPermission,
|
||||
fields: field.fields,
|
||||
operation,
|
||||
policiesObj: mutablePolicies[field.name],
|
||||
})
|
||||
}
|
||||
|
||||
if (
|
||||
('blocks' in field && field.blocks) ||
|
||||
('blockReferences' in field && field.blockReferences)
|
||||
) {
|
||||
if (!mutablePolicies[field.name]?.blocks) {
|
||||
mutablePolicies[field.name].blocks = {}
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
(field.blockReferences ?? field.blocks).map(async (_block) => {
|
||||
const block = typeof _block === 'string' ? payload.blocks[_block] : _block // TODO: Skip over string blocks
|
||||
|
||||
if (!mutablePolicies[field.name].blocks?.[block.slug]) {
|
||||
mutablePolicies[field.name].blocks[block.slug] = {
|
||||
fields: {},
|
||||
[operation]: { permission: entityPermission },
|
||||
}
|
||||
} else if (!mutablePolicies[field.name].blocks[block.slug][operation]) {
|
||||
mutablePolicies[field.name].blocks[block.slug][operation] = {
|
||||
permission: entityPermission,
|
||||
}
|
||||
}
|
||||
|
||||
await executeFieldPolicies({
|
||||
entityPermission,
|
||||
fields: block.fields,
|
||||
operation,
|
||||
policiesObj: mutablePolicies[field.name].blocks[block.slug],
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
} else if ('fields' in field && field.fields) {
|
||||
await executeFieldPolicies({
|
||||
entityPermission,
|
||||
fields: field.fields,
|
||||
operation,
|
||||
policiesObj,
|
||||
})
|
||||
} else if (field.type === 'tabs') {
|
||||
await Promise.all(
|
||||
field.tabs.map(async (tab) => {
|
||||
if (tabHasName(tab)) {
|
||||
if (!mutablePolicies[tab.name]) {
|
||||
mutablePolicies[tab.name] = {
|
||||
fields: {},
|
||||
[operation]: { permission: entityPermission },
|
||||
}
|
||||
} else if (!mutablePolicies[tab.name][operation]) {
|
||||
mutablePolicies[tab.name][operation] = { permission: entityPermission }
|
||||
}
|
||||
await executeFieldPolicies({
|
||||
entityPermission,
|
||||
fields: tab.fields,
|
||||
operation,
|
||||
policiesObj: mutablePolicies[tab.name],
|
||||
})
|
||||
} else {
|
||||
await executeFieldPolicies({
|
||||
entityPermission,
|
||||
fields: tab.fields,
|
||||
operation,
|
||||
policiesObj,
|
||||
})
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
await operations.reduce(async (priorOperation, operation) => {
|
||||
await priorOperation
|
||||
|
||||
let entityAccessPromise: Promise<void>
|
||||
|
||||
for (const operation of operations) {
|
||||
if (typeof entity.access[operation] === 'function') {
|
||||
entityAccessPromise = createAccessPromise({
|
||||
await createAccessPromise({
|
||||
access: entity.access[operation],
|
||||
accessLevel: 'entity',
|
||||
operation,
|
||||
@@ -262,15 +137,156 @@ export async function getEntityPolicies<T extends Args>(args: T): Promise<Return
|
||||
}
|
||||
}
|
||||
|
||||
await entityAccessPromise
|
||||
|
||||
await executeFieldPolicies({
|
||||
entityPermission: policies[operation].permission,
|
||||
createAccessPromise,
|
||||
entityPermission: policies[operation].permission as boolean,
|
||||
fields: entity.fields,
|
||||
operation,
|
||||
payload,
|
||||
policiesObj: policies,
|
||||
})
|
||||
}, Promise.resolve())
|
||||
}
|
||||
|
||||
return policies
|
||||
}
|
||||
|
||||
/**
|
||||
* Build up permissions object and run access functions for each field of an entity
|
||||
*/
|
||||
const executeFieldPolicies = async ({
|
||||
createAccessPromise,
|
||||
entityPermission,
|
||||
fields,
|
||||
operation,
|
||||
payload,
|
||||
policiesObj,
|
||||
}: {
|
||||
createAccessPromise: CreateAccessPromise
|
||||
entityPermission: boolean
|
||||
fields: Field[]
|
||||
operation: AllOperations
|
||||
payload: Payload
|
||||
policiesObj: CollectionPermission | FieldsPermissions | GlobalPermission
|
||||
}) => {
|
||||
const mutablePolicies = policiesObj.fields
|
||||
|
||||
// Fields don't have all operations of a collection
|
||||
if (operation === 'delete' || operation === 'readVersions' || operation === 'unlock') {
|
||||
return
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
fields.map(async (field) => {
|
||||
if ('name' in field && field.name) {
|
||||
if (!mutablePolicies[field.name]) {
|
||||
mutablePolicies[field.name] = {}
|
||||
}
|
||||
|
||||
if ('access' in field && field.access && typeof field.access[operation] === 'function') {
|
||||
await createAccessPromise({
|
||||
access: field.access[operation],
|
||||
accessLevel: 'field',
|
||||
disableWhere: true,
|
||||
operation,
|
||||
policiesObj: mutablePolicies[field.name],
|
||||
})
|
||||
} else {
|
||||
mutablePolicies[field.name][operation] = {
|
||||
permission: policiesObj[operation]?.permission,
|
||||
}
|
||||
}
|
||||
|
||||
if ('fields' in field && field.fields) {
|
||||
if (!mutablePolicies[field.name].fields) {
|
||||
mutablePolicies[field.name].fields = {}
|
||||
}
|
||||
|
||||
await executeFieldPolicies({
|
||||
createAccessPromise,
|
||||
entityPermission,
|
||||
fields: field.fields,
|
||||
operation,
|
||||
payload,
|
||||
policiesObj: mutablePolicies[field.name],
|
||||
})
|
||||
}
|
||||
|
||||
if (
|
||||
('blocks' in field && field.blocks?.length) ||
|
||||
('blockReferences' in field && field.blockReferences?.length)
|
||||
) {
|
||||
if (!mutablePolicies[field.name]?.blocks) {
|
||||
mutablePolicies[field.name].blocks = {}
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
(field.blockReferences ?? field.blocks).map(async (_block) => {
|
||||
const block = typeof _block === 'string' ? payload.blocks[_block] : _block // TODO: Skip over string blocks
|
||||
|
||||
if (!mutablePolicies[field.name].blocks?.[block.slug]) {
|
||||
mutablePolicies[field.name].blocks[block.slug] = {
|
||||
fields: {},
|
||||
[operation]: { permission: entityPermission },
|
||||
}
|
||||
} else if (!mutablePolicies[field.name].blocks[block.slug][operation]) {
|
||||
mutablePolicies[field.name].blocks[block.slug][operation] = {
|
||||
permission: entityPermission,
|
||||
}
|
||||
}
|
||||
|
||||
await executeFieldPolicies({
|
||||
createAccessPromise,
|
||||
entityPermission,
|
||||
fields: block.fields,
|
||||
operation,
|
||||
payload,
|
||||
policiesObj: mutablePolicies[field.name].blocks[block.slug],
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
} else if ('fields' in field && field.fields) {
|
||||
await executeFieldPolicies({
|
||||
createAccessPromise,
|
||||
entityPermission,
|
||||
fields: field.fields,
|
||||
operation,
|
||||
payload,
|
||||
policiesObj,
|
||||
})
|
||||
} else if (field.type === 'tabs') {
|
||||
await Promise.all(
|
||||
field.tabs.map(async (tab) => {
|
||||
if (tabHasName(tab)) {
|
||||
if (!mutablePolicies[tab.name]) {
|
||||
mutablePolicies[tab.name] = {
|
||||
fields: {},
|
||||
[operation]: { permission: entityPermission },
|
||||
}
|
||||
} else if (!mutablePolicies[tab.name][operation]) {
|
||||
mutablePolicies[tab.name][operation] = { permission: entityPermission }
|
||||
}
|
||||
await executeFieldPolicies({
|
||||
createAccessPromise,
|
||||
entityPermission,
|
||||
fields: tab.fields,
|
||||
operation,
|
||||
payload,
|
||||
policiesObj: mutablePolicies[tab.name],
|
||||
})
|
||||
} else {
|
||||
await executeFieldPolicies({
|
||||
createAccessPromise,
|
||||
entityPermission,
|
||||
fields: tab.fields,
|
||||
operation,
|
||||
payload,
|
||||
policiesObj,
|
||||
})
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user