chore: bump all eslint dependencies, run lint and prettier (#9128)
This fixes a peer dependency error in our monorepo, as eslint-plugin-jsx-a11y finally supports eslint v9. Additionally, this officially adds TypeScript 5.6 support for typescript-eslint.
This commit is contained in:
@@ -32,13 +32,13 @@ type InitNextArgs = {
|
||||
} & Pick<CliArgs, '--debug'>
|
||||
|
||||
type InitNextResult =
|
||||
| { isSrcDir: boolean; nextAppDir?: string; reason: string; success: false }
|
||||
| {
|
||||
isSrcDir: boolean
|
||||
nextAppDir: string
|
||||
payloadConfigPath: string
|
||||
success: true
|
||||
}
|
||||
| { isSrcDir: boolean; nextAppDir?: string; reason: string; success: false }
|
||||
|
||||
export async function initNext(args: InitNextArgs): Promise<InitNextResult> {
|
||||
const { dbType: dbType, packageManager, projectDir } = args
|
||||
|
||||
@@ -15,15 +15,9 @@ export async function installPackages(args: {
|
||||
let stderr = ''
|
||||
|
||||
switch (packageManager) {
|
||||
case 'npm': {
|
||||
;({ exitCode, stderr } = await execa('npm', ['install', '--save', ...packagesToInstall], {
|
||||
cwd: projectDir,
|
||||
}))
|
||||
break
|
||||
}
|
||||
case 'yarn':
|
||||
case 'bun':
|
||||
case 'pnpm':
|
||||
case 'bun': {
|
||||
case 'yarn': {
|
||||
if (packageManager === 'bun') {
|
||||
warning('Bun support is untested.')
|
||||
}
|
||||
@@ -32,6 +26,12 @@ export async function installPackages(args: {
|
||||
}))
|
||||
break
|
||||
}
|
||||
case 'npm': {
|
||||
;({ exitCode, stderr } = await execa('npm', ['install', '--save', ...packagesToInstall], {
|
||||
cwd: projectDir,
|
||||
}))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (exitCode !== 0) {
|
||||
|
||||
@@ -217,6 +217,16 @@ export class Main {
|
||||
}
|
||||
|
||||
switch (template.type) {
|
||||
case 'plugin': {
|
||||
await createProject({
|
||||
cliArgs: this.args,
|
||||
packageManager,
|
||||
projectDir,
|
||||
projectName,
|
||||
template,
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'starter': {
|
||||
const dbDetails = await selectDb(this.args, projectName)
|
||||
const payloadSecret = generateSecret()
|
||||
@@ -238,16 +248,6 @@ export class Main {
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'plugin': {
|
||||
await createProject({
|
||||
cliArgs: this.args,
|
||||
packageManager,
|
||||
projectDir,
|
||||
projectName,
|
||||
template,
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
info('Payload project successfully created!')
|
||||
|
||||
@@ -104,34 +104,10 @@ const traverseFields = ({
|
||||
}
|
||||
|
||||
switch (field.type) {
|
||||
case 'collapsible':
|
||||
case 'row':
|
||||
traverseFields({
|
||||
adapter,
|
||||
databaseSchemaPath,
|
||||
fields: field.fields,
|
||||
projection,
|
||||
select,
|
||||
selectMode,
|
||||
withinLocalizedField,
|
||||
})
|
||||
break
|
||||
|
||||
case 'tabs':
|
||||
traverseFields({
|
||||
adapter,
|
||||
databaseSchemaPath,
|
||||
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
||||
projection,
|
||||
select,
|
||||
selectMode,
|
||||
withinLocalizedField,
|
||||
})
|
||||
break
|
||||
|
||||
case 'array':
|
||||
case 'group':
|
||||
case 'tab':
|
||||
case 'array': {
|
||||
|
||||
case 'tab': {
|
||||
let fieldSelect: SelectType
|
||||
|
||||
if (field.type === 'tab' && !tabHasName(field)) {
|
||||
@@ -206,6 +182,30 @@ const traverseFields = ({
|
||||
|
||||
break
|
||||
}
|
||||
case 'collapsible':
|
||||
case 'row':
|
||||
traverseFields({
|
||||
adapter,
|
||||
databaseSchemaPath,
|
||||
fields: field.fields,
|
||||
projection,
|
||||
select,
|
||||
selectMode,
|
||||
withinLocalizedField,
|
||||
})
|
||||
break
|
||||
|
||||
case 'tabs':
|
||||
traverseFields({
|
||||
adapter,
|
||||
databaseSchemaPath,
|
||||
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
||||
projection,
|
||||
select,
|
||||
selectMode,
|
||||
withinLocalizedField,
|
||||
})
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
|
||||
@@ -13,44 +13,6 @@ type Args = {
|
||||
export const traverseFields = ({ doc, fields, locale, path, rows }: Args) => {
|
||||
fields.forEach((field) => {
|
||||
switch (field.type) {
|
||||
case 'group': {
|
||||
const newPath = `${path ? `${path}.` : ''}${field.name}`
|
||||
const newDoc = doc?.[field.name]
|
||||
|
||||
if (typeof newDoc === 'object' && newDoc !== null) {
|
||||
if (field.localized) {
|
||||
Object.entries(newDoc).forEach(([locale, localeDoc]) => {
|
||||
return traverseFields({
|
||||
doc: localeDoc,
|
||||
fields: field.fields,
|
||||
locale,
|
||||
path: newPath,
|
||||
rows,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
return traverseFields({
|
||||
doc: newDoc as Record<string, unknown>,
|
||||
fields: field.fields,
|
||||
path: newPath,
|
||||
rows,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'row':
|
||||
case 'collapsible': {
|
||||
return traverseFields({
|
||||
doc,
|
||||
fields: field.fields,
|
||||
path,
|
||||
rows,
|
||||
})
|
||||
}
|
||||
|
||||
case 'array': {
|
||||
const rowData = doc?.[field.name]
|
||||
|
||||
@@ -124,45 +86,47 @@ export const traverseFields = ({ doc, fields, locale, path, rows }: Args) => {
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'tabs': {
|
||||
return field.tabs.forEach((tab) => {
|
||||
if (tabHasName(tab)) {
|
||||
const newDoc = doc?.[tab.name]
|
||||
const newPath = `${path ? `${path}.` : ''}${tab.name}`
|
||||
|
||||
if (typeof newDoc === 'object' && newDoc !== null) {
|
||||
if (tab.localized) {
|
||||
Object.entries(newDoc).forEach(([locale, localeDoc]) => {
|
||||
return traverseFields({
|
||||
doc: localeDoc,
|
||||
fields: tab.fields,
|
||||
locale,
|
||||
path: newPath,
|
||||
rows,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
return traverseFields({
|
||||
doc: newDoc as Record<string, unknown>,
|
||||
fields: tab.fields,
|
||||
path: newPath,
|
||||
rows,
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
traverseFields({
|
||||
doc,
|
||||
fields: tab.fields,
|
||||
path,
|
||||
rows,
|
||||
})
|
||||
}
|
||||
case 'collapsible':
|
||||
// falls through
|
||||
case 'row': {
|
||||
return traverseFields({
|
||||
doc,
|
||||
fields: field.fields,
|
||||
path,
|
||||
rows,
|
||||
})
|
||||
}
|
||||
|
||||
case 'group': {
|
||||
const newPath = `${path ? `${path}.` : ''}${field.name}`
|
||||
const newDoc = doc?.[field.name]
|
||||
|
||||
if (typeof newDoc === 'object' && newDoc !== null) {
|
||||
if (field.localized) {
|
||||
Object.entries(newDoc).forEach(([locale, localeDoc]) => {
|
||||
return traverseFields({
|
||||
doc: localeDoc,
|
||||
fields: field.fields,
|
||||
locale,
|
||||
path: newPath,
|
||||
rows,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
return traverseFields({
|
||||
doc: newDoc as Record<string, unknown>,
|
||||
fields: field.fields,
|
||||
path: newPath,
|
||||
rows,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'relationship':
|
||||
// falls through
|
||||
case 'upload': {
|
||||
if (typeof field.relationTo === 'string') {
|
||||
if (field.type === 'upload' || !field.hasMany) {
|
||||
@@ -211,6 +175,43 @@ export const traverseFields = ({ doc, fields, locale, path, rows }: Args) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'tabs': {
|
||||
return field.tabs.forEach((tab) => {
|
||||
if (tabHasName(tab)) {
|
||||
const newDoc = doc?.[tab.name]
|
||||
const newPath = `${path ? `${path}.` : ''}${tab.name}`
|
||||
|
||||
if (typeof newDoc === 'object' && newDoc !== null) {
|
||||
if (tab.localized) {
|
||||
Object.entries(newDoc).forEach(([locale, localeDoc]) => {
|
||||
return traverseFields({
|
||||
doc: localeDoc,
|
||||
fields: tab.fields,
|
||||
locale,
|
||||
path: newPath,
|
||||
rows,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
return traverseFields({
|
||||
doc: newDoc as Record<string, unknown>,
|
||||
fields: tab.fields,
|
||||
path: newPath,
|
||||
rows,
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
traverseFields({
|
||||
doc,
|
||||
fields: tab.fields,
|
||||
path,
|
||||
rows,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -27,30 +27,6 @@ type Args = {
|
||||
export const traverseFields = (args: Args) => {
|
||||
args.fields.forEach((field) => {
|
||||
switch (field.type) {
|
||||
case 'group': {
|
||||
let newTableName = `${args.newTableName}_${toSnakeCase(field.name)}`
|
||||
|
||||
if (field.localized && args.payload.config.localization) {
|
||||
newTableName += args.adapter.localesSuffix
|
||||
}
|
||||
|
||||
return traverseFields({
|
||||
...args,
|
||||
columnPrefix: `${args.columnPrefix}${toSnakeCase(field.name)}_`,
|
||||
fields: field.fields,
|
||||
newTableName,
|
||||
path: `${args.path ? `${args.path}.` : ''}${field.name}`,
|
||||
})
|
||||
}
|
||||
|
||||
case 'row':
|
||||
case 'collapsible': {
|
||||
return traverseFields({
|
||||
...args,
|
||||
fields: field.fields,
|
||||
})
|
||||
}
|
||||
|
||||
case 'array': {
|
||||
const newTableName = args.adapter.tableNameMap.get(
|
||||
`${args.newTableName}_${toSnakeCase(field.name)}`,
|
||||
@@ -82,7 +58,42 @@ export const traverseFields = (args: Args) => {
|
||||
})
|
||||
})
|
||||
}
|
||||
case 'collapsible':
|
||||
|
||||
case 'row': {
|
||||
return traverseFields({
|
||||
...args,
|
||||
fields: field.fields,
|
||||
})
|
||||
}
|
||||
|
||||
case 'group': {
|
||||
let newTableName = `${args.newTableName}_${toSnakeCase(field.name)}`
|
||||
|
||||
if (field.localized && args.payload.config.localization) {
|
||||
newTableName += args.adapter.localesSuffix
|
||||
}
|
||||
|
||||
return traverseFields({
|
||||
...args,
|
||||
columnPrefix: `${args.columnPrefix}${toSnakeCase(field.name)}_`,
|
||||
fields: field.fields,
|
||||
newTableName,
|
||||
path: `${args.path ? `${args.path}.` : ''}${field.name}`,
|
||||
})
|
||||
}
|
||||
|
||||
case 'relationship':
|
||||
|
||||
case 'upload': {
|
||||
if (typeof field.relationTo === 'string') {
|
||||
if (field.type === 'upload' || !field.hasMany) {
|
||||
args.pathsToQuery.add(`${args.path ? `${args.path}.` : ''}${field.name}`)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
case 'tabs': {
|
||||
return field.tabs.forEach((tab) => {
|
||||
if (tabHasName(tab)) {
|
||||
@@ -101,17 +112,6 @@ export const traverseFields = (args: Args) => {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
case 'relationship':
|
||||
case 'upload': {
|
||||
if (typeof field.relationTo === 'string') {
|
||||
if (field.type === 'upload' || !field.hasMany) {
|
||||
args.pathsToQuery.add(`${args.path ? `${args.path}.` : ''}${field.name}`)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -179,183 +179,6 @@ export const traverseFields = ({
|
||||
}
|
||||
|
||||
switch (field.type) {
|
||||
case 'text': {
|
||||
if (field.hasMany) {
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock ||
|
||||
forceLocalized
|
||||
|
||||
if (isLocalized) {
|
||||
hasLocalizedManyTextField = true
|
||||
}
|
||||
|
||||
if (field.index) {
|
||||
hasManyTextField = 'index'
|
||||
} else if (!hasManyTextField) {
|
||||
hasManyTextField = true
|
||||
}
|
||||
|
||||
if (field.unique) {
|
||||
throw new InvalidConfiguration(
|
||||
'Unique is not supported in SQLite for hasMany text fields.',
|
||||
)
|
||||
}
|
||||
} else {
|
||||
targetTable[fieldName] = withDefault(text(columnName), field)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'email':
|
||||
case 'code':
|
||||
case 'textarea': {
|
||||
targetTable[fieldName] = withDefault(text(columnName), field)
|
||||
break
|
||||
}
|
||||
|
||||
case 'number': {
|
||||
if (field.hasMany) {
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock ||
|
||||
forceLocalized
|
||||
|
||||
if (isLocalized) {
|
||||
hasLocalizedManyNumberField = true
|
||||
}
|
||||
|
||||
if (field.index) {
|
||||
hasManyNumberField = 'index'
|
||||
} else if (!hasManyNumberField) {
|
||||
hasManyNumberField = true
|
||||
}
|
||||
|
||||
if (field.unique) {
|
||||
throw new InvalidConfiguration(
|
||||
'Unique is not supported in Postgres for hasMany number fields.',
|
||||
)
|
||||
}
|
||||
} else {
|
||||
targetTable[fieldName] = withDefault(numeric(columnName), field)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'richText':
|
||||
case 'json': {
|
||||
targetTable[fieldName] = withDefault(text(columnName, { mode: 'json' }), field)
|
||||
break
|
||||
}
|
||||
|
||||
case 'date': {
|
||||
targetTable[fieldName] = withDefault(text(columnName), field)
|
||||
break
|
||||
}
|
||||
|
||||
case 'point': {
|
||||
break
|
||||
}
|
||||
|
||||
case 'radio':
|
||||
case 'select': {
|
||||
const options = field.options.map((option) => {
|
||||
if (optionIsObject(option)) {
|
||||
return option.value
|
||||
}
|
||||
|
||||
return option
|
||||
}) as [string, ...string[]]
|
||||
|
||||
if (field.type === 'select' && field.hasMany) {
|
||||
const selectTableName = createTableName({
|
||||
adapter,
|
||||
config: field,
|
||||
parentTableName: newTableName,
|
||||
prefix: `${newTableName}_`,
|
||||
versionsCustomName: versions,
|
||||
})
|
||||
const baseColumns: Record<string, SQLiteColumnBuilder> = {
|
||||
order: integer('order').notNull(),
|
||||
parent: getIDColumn({
|
||||
name: 'parent_id',
|
||||
type: parentIDColType,
|
||||
notNull: true,
|
||||
primaryKey: false,
|
||||
}),
|
||||
value: text('value', { enum: options }),
|
||||
}
|
||||
|
||||
const baseExtraConfig: BaseExtraConfig = {
|
||||
orderIdx: (cols) => index(`${selectTableName}_order_idx`).on(cols.order),
|
||||
parentFk: (cols) =>
|
||||
foreignKey({
|
||||
name: `${selectTableName}_parent_fk`,
|
||||
columns: [cols.parent],
|
||||
foreignColumns: [adapter.tables[parentTableName].id],
|
||||
}).onDelete('cascade'),
|
||||
parentIdx: (cols) => index(`${selectTableName}_parent_idx`).on(cols.parent),
|
||||
}
|
||||
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock ||
|
||||
forceLocalized
|
||||
|
||||
if (isLocalized) {
|
||||
baseColumns.locale = text('locale', { enum: locales }).notNull()
|
||||
baseExtraConfig.localeIdx = (cols) =>
|
||||
index(`${selectTableName}_locale_idx`).on(cols.locale)
|
||||
}
|
||||
|
||||
if (field.index) {
|
||||
baseExtraConfig.value = (cols) => index(`${selectTableName}_value_idx`).on(cols.value)
|
||||
}
|
||||
|
||||
buildTable({
|
||||
adapter,
|
||||
baseColumns,
|
||||
baseExtraConfig,
|
||||
disableNotNull,
|
||||
disableUnique,
|
||||
fields: [],
|
||||
rootTableName,
|
||||
tableName: selectTableName,
|
||||
versions,
|
||||
})
|
||||
|
||||
relationsToBuild.set(fieldName, {
|
||||
type: 'many',
|
||||
// selects have their own localized table, independent of the base table.
|
||||
localized: false,
|
||||
target: selectTableName,
|
||||
})
|
||||
|
||||
adapter.relations[`relations_${selectTableName}`] = relations(
|
||||
adapter.tables[selectTableName],
|
||||
({ one }) => ({
|
||||
parent: one(adapter.tables[parentTableName], {
|
||||
fields: [adapter.tables[selectTableName].parent],
|
||||
references: [adapter.tables[parentTableName].id],
|
||||
relationName: fieldName,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
targetTable[fieldName] = withDefault(
|
||||
text(columnName, {
|
||||
enum: options,
|
||||
}),
|
||||
field,
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'checkbox': {
|
||||
targetTable[fieldName] = withDefault(integer(columnName, { mode: 'boolean' }), field)
|
||||
break
|
||||
}
|
||||
|
||||
case 'array': {
|
||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||
|
||||
@@ -493,7 +316,6 @@ export const traverseFields = ({
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'blocks': {
|
||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||
|
||||
@@ -646,9 +468,82 @@ export const traverseFields = ({
|
||||
|
||||
break
|
||||
}
|
||||
case 'checkbox': {
|
||||
targetTable[fieldName] = withDefault(integer(columnName, { mode: 'boolean' }), field)
|
||||
break
|
||||
}
|
||||
case 'code':
|
||||
|
||||
case 'tab':
|
||||
case 'group': {
|
||||
case 'email':
|
||||
|
||||
case 'textarea': {
|
||||
targetTable[fieldName] = withDefault(text(columnName), field)
|
||||
break
|
||||
}
|
||||
case 'collapsible':
|
||||
|
||||
case 'row': {
|
||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||
const {
|
||||
hasLocalizedField: rowHasLocalizedField,
|
||||
hasLocalizedManyNumberField: rowHasLocalizedManyNumberField,
|
||||
hasLocalizedManyTextField: rowHasLocalizedManyTextField,
|
||||
hasLocalizedRelationshipField: rowHasLocalizedRelationshipField,
|
||||
hasManyNumberField: rowHasManyNumberField,
|
||||
hasManyTextField: rowHasManyTextField,
|
||||
} = traverseFields({
|
||||
adapter,
|
||||
columnPrefix,
|
||||
columns,
|
||||
disableNotNull: disableNotNullFromHere,
|
||||
disableUnique,
|
||||
fieldPrefix,
|
||||
fields: field.fields,
|
||||
forceLocalized,
|
||||
indexes,
|
||||
locales,
|
||||
localesColumns,
|
||||
localesIndexes,
|
||||
newTableName,
|
||||
parentTableName,
|
||||
relationships,
|
||||
relationsToBuild,
|
||||
rootRelationsToBuild,
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
uniqueRelationships,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock,
|
||||
})
|
||||
|
||||
if (rowHasLocalizedField) {
|
||||
hasLocalizedField = true
|
||||
}
|
||||
if (rowHasLocalizedRelationshipField) {
|
||||
hasLocalizedRelationshipField = true
|
||||
}
|
||||
if (rowHasManyTextField) {
|
||||
hasManyTextField = true
|
||||
}
|
||||
if (rowHasLocalizedManyTextField) {
|
||||
hasLocalizedManyTextField = true
|
||||
}
|
||||
if (rowHasManyNumberField) {
|
||||
hasManyNumberField = true
|
||||
}
|
||||
if (rowHasLocalizedManyNumberField) {
|
||||
hasLocalizedManyNumberField = true
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'date': {
|
||||
targetTable[fieldName] = withDefault(text(columnName), field)
|
||||
break
|
||||
}
|
||||
|
||||
case 'group':
|
||||
case 'tab': {
|
||||
if (!('name' in field)) {
|
||||
const {
|
||||
hasLocalizedField: groupHasLocalizedField,
|
||||
@@ -758,114 +653,136 @@ export const traverseFields = ({
|
||||
break
|
||||
}
|
||||
|
||||
case 'tabs': {
|
||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||
case 'json':
|
||||
|
||||
const {
|
||||
hasLocalizedField: tabHasLocalizedField,
|
||||
hasLocalizedManyNumberField: tabHasLocalizedManyNumberField,
|
||||
hasLocalizedManyTextField: tabHasLocalizedManyTextField,
|
||||
hasLocalizedRelationshipField: tabHasLocalizedRelationshipField,
|
||||
hasManyNumberField: tabHasManyNumberField,
|
||||
hasManyTextField: tabHasManyTextField,
|
||||
} = traverseFields({
|
||||
adapter,
|
||||
columnPrefix,
|
||||
columns,
|
||||
disableNotNull: disableNotNullFromHere,
|
||||
disableUnique,
|
||||
fieldPrefix,
|
||||
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
||||
forceLocalized,
|
||||
indexes,
|
||||
locales,
|
||||
localesColumns,
|
||||
localesIndexes,
|
||||
newTableName,
|
||||
parentTableName,
|
||||
relationships,
|
||||
relationsToBuild,
|
||||
rootRelationsToBuild,
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
uniqueRelationships,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock,
|
||||
})
|
||||
case 'richText': {
|
||||
targetTable[fieldName] = withDefault(text(columnName, { mode: 'json' }), field)
|
||||
break
|
||||
}
|
||||
|
||||
if (tabHasLocalizedField) {
|
||||
hasLocalizedField = true
|
||||
}
|
||||
if (tabHasLocalizedRelationshipField) {
|
||||
hasLocalizedRelationshipField = true
|
||||
}
|
||||
if (tabHasManyTextField) {
|
||||
hasManyTextField = true
|
||||
}
|
||||
if (tabHasLocalizedManyTextField) {
|
||||
hasLocalizedManyTextField = true
|
||||
}
|
||||
if (tabHasManyNumberField) {
|
||||
hasManyNumberField = true
|
||||
}
|
||||
if (tabHasLocalizedManyNumberField) {
|
||||
hasLocalizedManyNumberField = true
|
||||
case 'number': {
|
||||
if (field.hasMany) {
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock ||
|
||||
forceLocalized
|
||||
|
||||
if (isLocalized) {
|
||||
hasLocalizedManyNumberField = true
|
||||
}
|
||||
|
||||
if (field.index) {
|
||||
hasManyNumberField = 'index'
|
||||
} else if (!hasManyNumberField) {
|
||||
hasManyNumberField = true
|
||||
}
|
||||
|
||||
if (field.unique) {
|
||||
throw new InvalidConfiguration(
|
||||
'Unique is not supported in Postgres for hasMany number fields.',
|
||||
)
|
||||
}
|
||||
} else {
|
||||
targetTable[fieldName] = withDefault(numeric(columnName), field)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'row':
|
||||
case 'collapsible': {
|
||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||
const {
|
||||
hasLocalizedField: rowHasLocalizedField,
|
||||
hasLocalizedManyNumberField: rowHasLocalizedManyNumberField,
|
||||
hasLocalizedManyTextField: rowHasLocalizedManyTextField,
|
||||
hasLocalizedRelationshipField: rowHasLocalizedRelationshipField,
|
||||
hasManyNumberField: rowHasManyNumberField,
|
||||
hasManyTextField: rowHasManyTextField,
|
||||
} = traverseFields({
|
||||
adapter,
|
||||
columnPrefix,
|
||||
columns,
|
||||
disableNotNull: disableNotNullFromHere,
|
||||
disableUnique,
|
||||
fieldPrefix,
|
||||
fields: field.fields,
|
||||
forceLocalized,
|
||||
indexes,
|
||||
locales,
|
||||
localesColumns,
|
||||
localesIndexes,
|
||||
newTableName,
|
||||
parentTableName,
|
||||
relationships,
|
||||
relationsToBuild,
|
||||
rootRelationsToBuild,
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
uniqueRelationships,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock,
|
||||
})
|
||||
case 'point': {
|
||||
break
|
||||
}
|
||||
case 'radio':
|
||||
|
||||
if (rowHasLocalizedField) {
|
||||
hasLocalizedField = true
|
||||
}
|
||||
if (rowHasLocalizedRelationshipField) {
|
||||
hasLocalizedRelationshipField = true
|
||||
}
|
||||
if (rowHasManyTextField) {
|
||||
hasManyTextField = true
|
||||
}
|
||||
if (rowHasLocalizedManyTextField) {
|
||||
hasLocalizedManyTextField = true
|
||||
}
|
||||
if (rowHasManyNumberField) {
|
||||
hasManyNumberField = true
|
||||
}
|
||||
if (rowHasLocalizedManyNumberField) {
|
||||
hasLocalizedManyNumberField = true
|
||||
case 'select': {
|
||||
const options = field.options.map((option) => {
|
||||
if (optionIsObject(option)) {
|
||||
return option.value
|
||||
}
|
||||
|
||||
return option
|
||||
}) as [string, ...string[]]
|
||||
|
||||
if (field.type === 'select' && field.hasMany) {
|
||||
const selectTableName = createTableName({
|
||||
adapter,
|
||||
config: field,
|
||||
parentTableName: newTableName,
|
||||
prefix: `${newTableName}_`,
|
||||
versionsCustomName: versions,
|
||||
})
|
||||
const baseColumns: Record<string, SQLiteColumnBuilder> = {
|
||||
order: integer('order').notNull(),
|
||||
parent: getIDColumn({
|
||||
name: 'parent_id',
|
||||
type: parentIDColType,
|
||||
notNull: true,
|
||||
primaryKey: false,
|
||||
}),
|
||||
value: text('value', { enum: options }),
|
||||
}
|
||||
|
||||
const baseExtraConfig: BaseExtraConfig = {
|
||||
orderIdx: (cols) => index(`${selectTableName}_order_idx`).on(cols.order),
|
||||
parentFk: (cols) =>
|
||||
foreignKey({
|
||||
name: `${selectTableName}_parent_fk`,
|
||||
columns: [cols.parent],
|
||||
foreignColumns: [adapter.tables[parentTableName].id],
|
||||
}).onDelete('cascade'),
|
||||
parentIdx: (cols) => index(`${selectTableName}_parent_idx`).on(cols.parent),
|
||||
}
|
||||
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock ||
|
||||
forceLocalized
|
||||
|
||||
if (isLocalized) {
|
||||
baseColumns.locale = text('locale', { enum: locales }).notNull()
|
||||
baseExtraConfig.localeIdx = (cols) =>
|
||||
index(`${selectTableName}_locale_idx`).on(cols.locale)
|
||||
}
|
||||
|
||||
if (field.index) {
|
||||
baseExtraConfig.value = (cols) => index(`${selectTableName}_value_idx`).on(cols.value)
|
||||
}
|
||||
|
||||
buildTable({
|
||||
adapter,
|
||||
baseColumns,
|
||||
baseExtraConfig,
|
||||
disableNotNull,
|
||||
disableUnique,
|
||||
fields: [],
|
||||
rootTableName,
|
||||
tableName: selectTableName,
|
||||
versions,
|
||||
})
|
||||
|
||||
relationsToBuild.set(fieldName, {
|
||||
type: 'many',
|
||||
// selects have their own localized table, independent of the base table.
|
||||
localized: false,
|
||||
target: selectTableName,
|
||||
})
|
||||
|
||||
adapter.relations[`relations_${selectTableName}`] = relations(
|
||||
adapter.tables[selectTableName],
|
||||
({ one }) => ({
|
||||
parent: one(adapter.tables[parentTableName], {
|
||||
fields: [adapter.tables[selectTableName].parent],
|
||||
references: [adapter.tables[parentTableName].id],
|
||||
relationName: fieldName,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
targetTable[fieldName] = withDefault(
|
||||
text(columnName, {
|
||||
enum: options,
|
||||
}),
|
||||
field,
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -931,6 +848,89 @@ export const traverseFields = ({
|
||||
|
||||
break
|
||||
|
||||
case 'tabs': {
|
||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||
|
||||
const {
|
||||
hasLocalizedField: tabHasLocalizedField,
|
||||
hasLocalizedManyNumberField: tabHasLocalizedManyNumberField,
|
||||
hasLocalizedManyTextField: tabHasLocalizedManyTextField,
|
||||
hasLocalizedRelationshipField: tabHasLocalizedRelationshipField,
|
||||
hasManyNumberField: tabHasManyNumberField,
|
||||
hasManyTextField: tabHasManyTextField,
|
||||
} = traverseFields({
|
||||
adapter,
|
||||
columnPrefix,
|
||||
columns,
|
||||
disableNotNull: disableNotNullFromHere,
|
||||
disableUnique,
|
||||
fieldPrefix,
|
||||
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
||||
forceLocalized,
|
||||
indexes,
|
||||
locales,
|
||||
localesColumns,
|
||||
localesIndexes,
|
||||
newTableName,
|
||||
parentTableName,
|
||||
relationships,
|
||||
relationsToBuild,
|
||||
rootRelationsToBuild,
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
uniqueRelationships,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock,
|
||||
})
|
||||
|
||||
if (tabHasLocalizedField) {
|
||||
hasLocalizedField = true
|
||||
}
|
||||
if (tabHasLocalizedRelationshipField) {
|
||||
hasLocalizedRelationshipField = true
|
||||
}
|
||||
if (tabHasManyTextField) {
|
||||
hasManyTextField = true
|
||||
}
|
||||
if (tabHasLocalizedManyTextField) {
|
||||
hasLocalizedManyTextField = true
|
||||
}
|
||||
if (tabHasManyNumberField) {
|
||||
hasManyNumberField = true
|
||||
}
|
||||
if (tabHasLocalizedManyNumberField) {
|
||||
hasLocalizedManyNumberField = true
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'text': {
|
||||
if (field.hasMany) {
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock ||
|
||||
forceLocalized
|
||||
|
||||
if (isLocalized) {
|
||||
hasLocalizedManyTextField = true
|
||||
}
|
||||
|
||||
if (field.index) {
|
||||
hasManyTextField = 'index'
|
||||
} else if (!hasManyTextField) {
|
||||
hasManyTextField = true
|
||||
}
|
||||
|
||||
if (field.unique) {
|
||||
throw new InvalidConfiguration(
|
||||
'Unique is not supported in SQLite for hasMany text fields.',
|
||||
)
|
||||
}
|
||||
} else {
|
||||
targetTable[fieldName] = withDefault(text(columnName), field)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
@@ -13,44 +13,6 @@ type Args = {
|
||||
export const traverseFields = ({ doc, fields, locale, path, rows }: Args) => {
|
||||
fields.forEach((field) => {
|
||||
switch (field.type) {
|
||||
case 'group': {
|
||||
const newPath = `${path ? `${path}.` : ''}${field.name}`
|
||||
const newDoc = doc?.[field.name]
|
||||
|
||||
if (typeof newDoc === 'object' && newDoc !== null) {
|
||||
if (field.localized) {
|
||||
Object.entries(newDoc).forEach(([locale, localeDoc]) => {
|
||||
return traverseFields({
|
||||
doc: localeDoc,
|
||||
fields: field.fields,
|
||||
locale,
|
||||
path: newPath,
|
||||
rows,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
return traverseFields({
|
||||
doc: newDoc as Record<string, unknown>,
|
||||
fields: field.fields,
|
||||
path: newPath,
|
||||
rows,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'row':
|
||||
case 'collapsible': {
|
||||
return traverseFields({
|
||||
doc,
|
||||
fields: field.fields,
|
||||
path,
|
||||
rows,
|
||||
})
|
||||
}
|
||||
|
||||
case 'array': {
|
||||
const rowData = doc?.[field.name]
|
||||
|
||||
@@ -124,45 +86,47 @@ export const traverseFields = ({ doc, fields, locale, path, rows }: Args) => {
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'tabs': {
|
||||
return field.tabs.forEach((tab) => {
|
||||
if (tabHasName(tab)) {
|
||||
const newDoc = doc?.[tab.name]
|
||||
const newPath = `${path ? `${path}.` : ''}${tab.name}`
|
||||
|
||||
if (typeof newDoc === 'object' && newDoc !== null) {
|
||||
if (tab.localized) {
|
||||
Object.entries(newDoc).forEach(([locale, localeDoc]) => {
|
||||
return traverseFields({
|
||||
doc: localeDoc,
|
||||
fields: tab.fields,
|
||||
locale,
|
||||
path: newPath,
|
||||
rows,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
return traverseFields({
|
||||
doc: newDoc as Record<string, unknown>,
|
||||
fields: tab.fields,
|
||||
path: newPath,
|
||||
rows,
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
traverseFields({
|
||||
doc,
|
||||
fields: tab.fields,
|
||||
path,
|
||||
rows,
|
||||
})
|
||||
}
|
||||
case 'collapsible':
|
||||
// falls through
|
||||
case 'row': {
|
||||
return traverseFields({
|
||||
doc,
|
||||
fields: field.fields,
|
||||
path,
|
||||
rows,
|
||||
})
|
||||
}
|
||||
|
||||
case 'group': {
|
||||
const newPath = `${path ? `${path}.` : ''}${field.name}`
|
||||
const newDoc = doc?.[field.name]
|
||||
|
||||
if (typeof newDoc === 'object' && newDoc !== null) {
|
||||
if (field.localized) {
|
||||
Object.entries(newDoc).forEach(([locale, localeDoc]) => {
|
||||
return traverseFields({
|
||||
doc: localeDoc,
|
||||
fields: field.fields,
|
||||
locale,
|
||||
path: newPath,
|
||||
rows,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
return traverseFields({
|
||||
doc: newDoc as Record<string, unknown>,
|
||||
fields: field.fields,
|
||||
path: newPath,
|
||||
rows,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'relationship':
|
||||
// falls through
|
||||
case 'upload': {
|
||||
if (typeof field.relationTo === 'string') {
|
||||
if (field.type === 'upload' || !field.hasMany) {
|
||||
@@ -211,6 +175,43 @@ export const traverseFields = ({ doc, fields, locale, path, rows }: Args) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'tabs': {
|
||||
return field.tabs.forEach((tab) => {
|
||||
if (tabHasName(tab)) {
|
||||
const newDoc = doc?.[tab.name]
|
||||
const newPath = `${path ? `${path}.` : ''}${tab.name}`
|
||||
|
||||
if (typeof newDoc === 'object' && newDoc !== null) {
|
||||
if (tab.localized) {
|
||||
Object.entries(newDoc).forEach(([locale, localeDoc]) => {
|
||||
return traverseFields({
|
||||
doc: localeDoc,
|
||||
fields: tab.fields,
|
||||
locale,
|
||||
path: newPath,
|
||||
rows,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
return traverseFields({
|
||||
doc: newDoc as Record<string, unknown>,
|
||||
fields: tab.fields,
|
||||
path: newPath,
|
||||
rows,
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
traverseFields({
|
||||
doc,
|
||||
fields: tab.fields,
|
||||
path,
|
||||
rows,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -27,30 +27,6 @@ type Args = {
|
||||
export const traverseFields = (args: Args) => {
|
||||
args.fields.forEach((field) => {
|
||||
switch (field.type) {
|
||||
case 'group': {
|
||||
let newTableName = `${args.newTableName}_${toSnakeCase(field.name)}`
|
||||
|
||||
if (field.localized && args.payload.config.localization) {
|
||||
newTableName += args.adapter.localesSuffix
|
||||
}
|
||||
|
||||
return traverseFields({
|
||||
...args,
|
||||
columnPrefix: `${args.columnPrefix}${toSnakeCase(field.name)}_`,
|
||||
fields: field.fields,
|
||||
newTableName,
|
||||
path: `${args.path ? `${args.path}.` : ''}${field.name}`,
|
||||
})
|
||||
}
|
||||
|
||||
case 'row':
|
||||
case 'collapsible': {
|
||||
return traverseFields({
|
||||
...args,
|
||||
fields: field.fields,
|
||||
})
|
||||
}
|
||||
|
||||
case 'array': {
|
||||
const newTableName = args.adapter.tableNameMap.get(
|
||||
`${args.newTableName}_${toSnakeCase(field.name)}`,
|
||||
@@ -82,7 +58,42 @@ export const traverseFields = (args: Args) => {
|
||||
})
|
||||
})
|
||||
}
|
||||
case 'collapsible':
|
||||
|
||||
case 'row': {
|
||||
return traverseFields({
|
||||
...args,
|
||||
fields: field.fields,
|
||||
})
|
||||
}
|
||||
|
||||
case 'group': {
|
||||
let newTableName = `${args.newTableName}_${toSnakeCase(field.name)}`
|
||||
|
||||
if (field.localized && args.payload.config.localization) {
|
||||
newTableName += args.adapter.localesSuffix
|
||||
}
|
||||
|
||||
return traverseFields({
|
||||
...args,
|
||||
columnPrefix: `${args.columnPrefix}${toSnakeCase(field.name)}_`,
|
||||
fields: field.fields,
|
||||
newTableName,
|
||||
path: `${args.path ? `${args.path}.` : ''}${field.name}`,
|
||||
})
|
||||
}
|
||||
|
||||
case 'relationship':
|
||||
|
||||
case 'upload': {
|
||||
if (typeof field.relationTo === 'string') {
|
||||
if (field.type === 'upload' || !field.hasMany) {
|
||||
args.pathsToQuery.add(`${args.path ? `${args.path}.` : ''}${field.name}`)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
case 'tabs': {
|
||||
return field.tabs.forEach((tab) => {
|
||||
if (tabHasName(tab)) {
|
||||
@@ -101,17 +112,6 @@ export const traverseFields = (args: Args) => {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
case 'relationship':
|
||||
case 'upload': {
|
||||
if (typeof field.relationTo === 'string') {
|
||||
if (field.type === 'upload' || !field.hasMany) {
|
||||
args.pathsToQuery.add(`${args.path ? `${args.path}.` : ''}${field.name}`)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -218,32 +218,6 @@ export const traverseFields = ({
|
||||
break
|
||||
}
|
||||
|
||||
case 'select': {
|
||||
if (field.hasMany) {
|
||||
if (select) {
|
||||
if (
|
||||
(selectMode === 'include' && !select[field.name]) ||
|
||||
(selectMode === 'exclude' && select[field.name] === false)
|
||||
) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const withSelect: Result = {
|
||||
columns: {
|
||||
id: false,
|
||||
order: false,
|
||||
parent: false,
|
||||
},
|
||||
orderBy: ({ order }, { asc }) => [asc(order)],
|
||||
}
|
||||
|
||||
currentArgs.with[`${path}${field.name}`] = withSelect
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'blocks': {
|
||||
const blocksSelect = selectAllOnCurrentLevel ? true : select?.[field.name]
|
||||
|
||||
@@ -356,6 +330,7 @@ export const traverseFields = ({
|
||||
}
|
||||
|
||||
case 'group':
|
||||
|
||||
case 'tab': {
|
||||
const fieldSelect = select?.[field.name]
|
||||
|
||||
@@ -389,47 +364,6 @@ export const traverseFields = ({
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'point': {
|
||||
if (adapter.name === 'sqlite') {
|
||||
break
|
||||
}
|
||||
|
||||
const args = field.localized ? _locales : currentArgs
|
||||
if (!args.columns) {
|
||||
args.columns = {}
|
||||
}
|
||||
|
||||
if (!args.extras) {
|
||||
args.extras = {}
|
||||
}
|
||||
|
||||
const name = `${path}${field.name}`
|
||||
|
||||
// Drizzle handles that poorly. See https://github.com/drizzle-team/drizzle-orm/issues/2526
|
||||
// Additionally, this way we format the column value straight in the database using ST_AsGeoJSON
|
||||
args.columns[name] = false
|
||||
|
||||
let shouldSelect = false
|
||||
|
||||
if (select || selectAllOnCurrentLevel) {
|
||||
if (
|
||||
selectAllOnCurrentLevel ||
|
||||
(selectMode === 'include' && select[field.name] === true) ||
|
||||
(selectMode === 'exclude' && typeof select[field.name] === 'undefined')
|
||||
) {
|
||||
shouldSelect = true
|
||||
}
|
||||
} else {
|
||||
shouldSelect = true
|
||||
}
|
||||
|
||||
if (shouldSelect) {
|
||||
args.extras[name] = sql.raw(`ST_AsGeoJSON(${toSnakeCase(name)})::jsonb`).as(name)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'join': {
|
||||
// when `joinsQuery` is false, do not join
|
||||
if (joinQuery === false) {
|
||||
@@ -621,6 +555,72 @@ export const traverseFields = ({
|
||||
break
|
||||
}
|
||||
|
||||
case 'point': {
|
||||
if (adapter.name === 'sqlite') {
|
||||
break
|
||||
}
|
||||
|
||||
const args = field.localized ? _locales : currentArgs
|
||||
if (!args.columns) {
|
||||
args.columns = {}
|
||||
}
|
||||
|
||||
if (!args.extras) {
|
||||
args.extras = {}
|
||||
}
|
||||
|
||||
const name = `${path}${field.name}`
|
||||
|
||||
// Drizzle handles that poorly. See https://github.com/drizzle-team/drizzle-orm/issues/2526
|
||||
// Additionally, this way we format the column value straight in the database using ST_AsGeoJSON
|
||||
args.columns[name] = false
|
||||
|
||||
let shouldSelect = false
|
||||
|
||||
if (select || selectAllOnCurrentLevel) {
|
||||
if (
|
||||
selectAllOnCurrentLevel ||
|
||||
(selectMode === 'include' && select[field.name] === true) ||
|
||||
(selectMode === 'exclude' && typeof select[field.name] === 'undefined')
|
||||
) {
|
||||
shouldSelect = true
|
||||
}
|
||||
} else {
|
||||
shouldSelect = true
|
||||
}
|
||||
|
||||
if (shouldSelect) {
|
||||
args.extras[name] = sql.raw(`ST_AsGeoJSON(${toSnakeCase(name)})::jsonb`).as(name)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'select': {
|
||||
if (field.hasMany) {
|
||||
if (select) {
|
||||
if (
|
||||
(selectMode === 'include' && !select[field.name]) ||
|
||||
(selectMode === 'exclude' && select[field.name] === false)
|
||||
) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const withSelect: Result = {
|
||||
columns: {
|
||||
id: false,
|
||||
order: false,
|
||||
parent: false,
|
||||
},
|
||||
orderBy: ({ order }, { asc }) => [asc(order)],
|
||||
}
|
||||
|
||||
currentArgs.with[`${path}${field.name}`] = withSelect
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
if (!select && !selectAllOnCurrentLevel) {
|
||||
break
|
||||
|
||||
@@ -182,197 +182,6 @@ export const traverseFields = ({
|
||||
}
|
||||
|
||||
switch (field.type) {
|
||||
case 'text': {
|
||||
if (field.hasMany) {
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock ||
|
||||
forceLocalized
|
||||
|
||||
if (isLocalized) {
|
||||
hasLocalizedManyTextField = true
|
||||
}
|
||||
|
||||
if (field.index) {
|
||||
hasManyTextField = 'index'
|
||||
} else if (!hasManyTextField) {
|
||||
hasManyTextField = true
|
||||
}
|
||||
|
||||
if (field.unique) {
|
||||
throw new InvalidConfiguration(
|
||||
'Unique is not supported in Postgres for hasMany text fields.',
|
||||
)
|
||||
}
|
||||
} else {
|
||||
targetTable[fieldName] = withDefault(varchar(columnName), field)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'email':
|
||||
case 'code':
|
||||
case 'textarea': {
|
||||
targetTable[fieldName] = withDefault(varchar(columnName), field)
|
||||
break
|
||||
}
|
||||
|
||||
case 'number': {
|
||||
if (field.hasMany) {
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock ||
|
||||
forceLocalized
|
||||
|
||||
if (isLocalized) {
|
||||
hasLocalizedManyNumberField = true
|
||||
}
|
||||
|
||||
if (field.index) {
|
||||
hasManyNumberField = 'index'
|
||||
} else if (!hasManyNumberField) {
|
||||
hasManyNumberField = true
|
||||
}
|
||||
|
||||
if (field.unique) {
|
||||
throw new InvalidConfiguration(
|
||||
'Unique is not supported in Postgres for hasMany number fields.',
|
||||
)
|
||||
}
|
||||
} else {
|
||||
targetTable[fieldName] = withDefault(numeric(columnName), field)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'richText':
|
||||
case 'json': {
|
||||
targetTable[fieldName] = withDefault(jsonb(columnName), field)
|
||||
break
|
||||
}
|
||||
|
||||
case 'date': {
|
||||
targetTable[fieldName] = withDefault(
|
||||
timestamp(columnName, {
|
||||
mode: 'string',
|
||||
precision: 3,
|
||||
withTimezone: true,
|
||||
}),
|
||||
field,
|
||||
)
|
||||
break
|
||||
}
|
||||
|
||||
case 'point': {
|
||||
targetTable[fieldName] = withDefault(geometryColumn(columnName), field)
|
||||
if (!adapter.extensions.postgis) {
|
||||
adapter.extensions.postgis = true
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'radio':
|
||||
case 'select': {
|
||||
const enumName = createTableName({
|
||||
adapter,
|
||||
config: field,
|
||||
parentTableName: newTableName,
|
||||
prefix: `enum_${newTableName}_`,
|
||||
target: 'enumName',
|
||||
throwValidationError,
|
||||
})
|
||||
|
||||
adapter.enums[enumName] = adapter.pgSchema.enum(
|
||||
enumName,
|
||||
field.options.map((option) => {
|
||||
if (optionIsObject(option)) {
|
||||
return option.value
|
||||
}
|
||||
|
||||
return option
|
||||
}) as [string, ...string[]],
|
||||
)
|
||||
|
||||
if (field.type === 'select' && field.hasMany) {
|
||||
const selectTableName = createTableName({
|
||||
adapter,
|
||||
config: field,
|
||||
parentTableName: newTableName,
|
||||
prefix: `${newTableName}_`,
|
||||
throwValidationError,
|
||||
versionsCustomName: versions,
|
||||
})
|
||||
const baseColumns: Record<string, PgColumnBuilder> = {
|
||||
order: integer('order').notNull(),
|
||||
parent: parentIDColumnMap[parentIDColType]('parent_id').notNull(),
|
||||
value: adapter.enums[enumName]('value'),
|
||||
}
|
||||
|
||||
const baseExtraConfig: BaseExtraConfig = {
|
||||
orderIdx: (cols) => index(`${selectTableName}_order_idx`).on(cols.order),
|
||||
parentFk: (cols) =>
|
||||
foreignKey({
|
||||
name: `${selectTableName}_parent_fk`,
|
||||
columns: [cols.parent],
|
||||
foreignColumns: [adapter.tables[parentTableName].id],
|
||||
}).onDelete('cascade'),
|
||||
parentIdx: (cols) => index(`${selectTableName}_parent_idx`).on(cols.parent),
|
||||
}
|
||||
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock ||
|
||||
forceLocalized
|
||||
|
||||
if (isLocalized) {
|
||||
baseColumns.locale = adapter.enums.enum__locales('locale').notNull()
|
||||
baseExtraConfig.localeIdx = (cols) =>
|
||||
index(`${selectTableName}_locale_idx`).on(cols.locale)
|
||||
}
|
||||
|
||||
if (field.index) {
|
||||
baseExtraConfig.value = (cols) => index(`${selectTableName}_value_idx`).on(cols.value)
|
||||
}
|
||||
|
||||
buildTable({
|
||||
adapter,
|
||||
baseColumns,
|
||||
baseExtraConfig,
|
||||
disableNotNull,
|
||||
disableUnique,
|
||||
fields: [],
|
||||
rootTableName,
|
||||
tableName: selectTableName,
|
||||
versions,
|
||||
})
|
||||
|
||||
relationsToBuild.set(fieldName, {
|
||||
type: 'many',
|
||||
// selects have their own localized table, independent of the base table.
|
||||
localized: false,
|
||||
target: selectTableName,
|
||||
})
|
||||
|
||||
adapter.relations[`relations_${selectTableName}`] = relations(
|
||||
adapter.tables[selectTableName],
|
||||
({ one }) => ({
|
||||
parent: one(adapter.tables[parentTableName], {
|
||||
fields: [adapter.tables[selectTableName].parent],
|
||||
references: [adapter.tables[parentTableName].id],
|
||||
relationName: fieldName,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
targetTable[fieldName] = withDefault(adapter.enums[enumName](columnName), field)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'checkbox': {
|
||||
targetTable[fieldName] = withDefault(boolean(columnName), field)
|
||||
break
|
||||
}
|
||||
|
||||
case 'array': {
|
||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||
|
||||
@@ -506,7 +315,6 @@ export const traverseFields = ({
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'blocks': {
|
||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||
|
||||
@@ -655,9 +463,88 @@ export const traverseFields = ({
|
||||
|
||||
break
|
||||
}
|
||||
case 'checkbox': {
|
||||
targetTable[fieldName] = withDefault(boolean(columnName), field)
|
||||
break
|
||||
}
|
||||
case 'code':
|
||||
|
||||
case 'tab':
|
||||
case 'group': {
|
||||
case 'email':
|
||||
|
||||
case 'textarea': {
|
||||
targetTable[fieldName] = withDefault(varchar(columnName), field)
|
||||
break
|
||||
}
|
||||
case 'collapsible':
|
||||
|
||||
case 'row': {
|
||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||
const {
|
||||
hasLocalizedField: rowHasLocalizedField,
|
||||
hasLocalizedManyNumberField: rowHasLocalizedManyNumberField,
|
||||
hasLocalizedManyTextField: rowHasLocalizedManyTextField,
|
||||
hasLocalizedRelationshipField: rowHasLocalizedRelationshipField,
|
||||
hasManyNumberField: rowHasManyNumberField,
|
||||
hasManyTextField: rowHasManyTextField,
|
||||
} = traverseFields({
|
||||
adapter,
|
||||
columnPrefix,
|
||||
columns,
|
||||
disableNotNull: disableNotNullFromHere,
|
||||
disableUnique,
|
||||
fieldPrefix,
|
||||
fields: field.fields,
|
||||
forceLocalized,
|
||||
indexes,
|
||||
localesColumns,
|
||||
localesIndexes,
|
||||
newTableName,
|
||||
parentTableName,
|
||||
relationships,
|
||||
relationsToBuild,
|
||||
rootRelationsToBuild,
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
uniqueRelationships,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock,
|
||||
})
|
||||
|
||||
if (rowHasLocalizedField) {
|
||||
hasLocalizedField = true
|
||||
}
|
||||
if (rowHasLocalizedRelationshipField) {
|
||||
hasLocalizedRelationshipField = true
|
||||
}
|
||||
if (rowHasManyTextField) {
|
||||
hasManyTextField = true
|
||||
}
|
||||
if (rowHasLocalizedManyTextField) {
|
||||
hasLocalizedManyTextField = true
|
||||
}
|
||||
if (rowHasManyNumberField) {
|
||||
hasManyNumberField = true
|
||||
}
|
||||
if (rowHasLocalizedManyNumberField) {
|
||||
hasLocalizedManyNumberField = true
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'date': {
|
||||
targetTable[fieldName] = withDefault(
|
||||
timestamp(columnName, {
|
||||
mode: 'string',
|
||||
precision: 3,
|
||||
withTimezone: true,
|
||||
}),
|
||||
field,
|
||||
)
|
||||
break
|
||||
}
|
||||
|
||||
case 'group':
|
||||
case 'tab': {
|
||||
if (!('name' in field)) {
|
||||
const {
|
||||
hasLocalizedField: groupHasLocalizedField,
|
||||
@@ -765,112 +652,143 @@ export const traverseFields = ({
|
||||
break
|
||||
}
|
||||
|
||||
case 'tabs': {
|
||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||
case 'json':
|
||||
|
||||
const {
|
||||
hasLocalizedField: tabHasLocalizedField,
|
||||
hasLocalizedManyNumberField: tabHasLocalizedManyNumberField,
|
||||
hasLocalizedManyTextField: tabHasLocalizedManyTextField,
|
||||
hasLocalizedRelationshipField: tabHasLocalizedRelationshipField,
|
||||
hasManyNumberField: tabHasManyNumberField,
|
||||
hasManyTextField: tabHasManyTextField,
|
||||
} = traverseFields({
|
||||
adapter,
|
||||
columnPrefix,
|
||||
columns,
|
||||
disableNotNull: disableNotNullFromHere,
|
||||
disableUnique,
|
||||
fieldPrefix,
|
||||
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
||||
forceLocalized,
|
||||
indexes,
|
||||
localesColumns,
|
||||
localesIndexes,
|
||||
newTableName,
|
||||
parentTableName,
|
||||
relationships,
|
||||
relationsToBuild,
|
||||
rootRelationsToBuild,
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
uniqueRelationships,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock,
|
||||
})
|
||||
case 'richText': {
|
||||
targetTable[fieldName] = withDefault(jsonb(columnName), field)
|
||||
break
|
||||
}
|
||||
|
||||
if (tabHasLocalizedField) {
|
||||
hasLocalizedField = true
|
||||
}
|
||||
if (tabHasLocalizedRelationshipField) {
|
||||
hasLocalizedRelationshipField = true
|
||||
}
|
||||
if (tabHasManyTextField) {
|
||||
hasManyTextField = true
|
||||
}
|
||||
if (tabHasLocalizedManyTextField) {
|
||||
hasLocalizedManyTextField = true
|
||||
}
|
||||
if (tabHasManyNumberField) {
|
||||
hasManyNumberField = true
|
||||
}
|
||||
if (tabHasLocalizedManyNumberField) {
|
||||
hasLocalizedManyNumberField = true
|
||||
case 'number': {
|
||||
if (field.hasMany) {
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock ||
|
||||
forceLocalized
|
||||
|
||||
if (isLocalized) {
|
||||
hasLocalizedManyNumberField = true
|
||||
}
|
||||
|
||||
if (field.index) {
|
||||
hasManyNumberField = 'index'
|
||||
} else if (!hasManyNumberField) {
|
||||
hasManyNumberField = true
|
||||
}
|
||||
|
||||
if (field.unique) {
|
||||
throw new InvalidConfiguration(
|
||||
'Unique is not supported in Postgres for hasMany number fields.',
|
||||
)
|
||||
}
|
||||
} else {
|
||||
targetTable[fieldName] = withDefault(numeric(columnName), field)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'row':
|
||||
case 'collapsible': {
|
||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||
const {
|
||||
hasLocalizedField: rowHasLocalizedField,
|
||||
hasLocalizedManyNumberField: rowHasLocalizedManyNumberField,
|
||||
hasLocalizedManyTextField: rowHasLocalizedManyTextField,
|
||||
hasLocalizedRelationshipField: rowHasLocalizedRelationshipField,
|
||||
hasManyNumberField: rowHasManyNumberField,
|
||||
hasManyTextField: rowHasManyTextField,
|
||||
} = traverseFields({
|
||||
case 'point': {
|
||||
targetTable[fieldName] = withDefault(geometryColumn(columnName), field)
|
||||
if (!adapter.extensions.postgis) {
|
||||
adapter.extensions.postgis = true
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'radio':
|
||||
|
||||
case 'select': {
|
||||
const enumName = createTableName({
|
||||
adapter,
|
||||
columnPrefix,
|
||||
columns,
|
||||
disableNotNull: disableNotNullFromHere,
|
||||
disableUnique,
|
||||
fieldPrefix,
|
||||
fields: field.fields,
|
||||
forceLocalized,
|
||||
indexes,
|
||||
localesColumns,
|
||||
localesIndexes,
|
||||
newTableName,
|
||||
parentTableName,
|
||||
relationships,
|
||||
relationsToBuild,
|
||||
rootRelationsToBuild,
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
uniqueRelationships,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock,
|
||||
config: field,
|
||||
parentTableName: newTableName,
|
||||
prefix: `enum_${newTableName}_`,
|
||||
target: 'enumName',
|
||||
throwValidationError,
|
||||
})
|
||||
|
||||
if (rowHasLocalizedField) {
|
||||
hasLocalizedField = true
|
||||
}
|
||||
if (rowHasLocalizedRelationshipField) {
|
||||
hasLocalizedRelationshipField = true
|
||||
}
|
||||
if (rowHasManyTextField) {
|
||||
hasManyTextField = true
|
||||
}
|
||||
if (rowHasLocalizedManyTextField) {
|
||||
hasLocalizedManyTextField = true
|
||||
}
|
||||
if (rowHasManyNumberField) {
|
||||
hasManyNumberField = true
|
||||
}
|
||||
if (rowHasLocalizedManyNumberField) {
|
||||
hasLocalizedManyNumberField = true
|
||||
adapter.enums[enumName] = adapter.pgSchema.enum(
|
||||
enumName,
|
||||
field.options.map((option) => {
|
||||
if (optionIsObject(option)) {
|
||||
return option.value
|
||||
}
|
||||
|
||||
return option
|
||||
}) as [string, ...string[]],
|
||||
)
|
||||
|
||||
if (field.type === 'select' && field.hasMany) {
|
||||
const selectTableName = createTableName({
|
||||
adapter,
|
||||
config: field,
|
||||
parentTableName: newTableName,
|
||||
prefix: `${newTableName}_`,
|
||||
throwValidationError,
|
||||
versionsCustomName: versions,
|
||||
})
|
||||
const baseColumns: Record<string, PgColumnBuilder> = {
|
||||
order: integer('order').notNull(),
|
||||
parent: parentIDColumnMap[parentIDColType]('parent_id').notNull(),
|
||||
value: adapter.enums[enumName]('value'),
|
||||
}
|
||||
|
||||
const baseExtraConfig: BaseExtraConfig = {
|
||||
orderIdx: (cols) => index(`${selectTableName}_order_idx`).on(cols.order),
|
||||
parentFk: (cols) =>
|
||||
foreignKey({
|
||||
name: `${selectTableName}_parent_fk`,
|
||||
columns: [cols.parent],
|
||||
foreignColumns: [adapter.tables[parentTableName].id],
|
||||
}).onDelete('cascade'),
|
||||
parentIdx: (cols) => index(`${selectTableName}_parent_idx`).on(cols.parent),
|
||||
}
|
||||
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock ||
|
||||
forceLocalized
|
||||
|
||||
if (isLocalized) {
|
||||
baseColumns.locale = adapter.enums.enum__locales('locale').notNull()
|
||||
baseExtraConfig.localeIdx = (cols) =>
|
||||
index(`${selectTableName}_locale_idx`).on(cols.locale)
|
||||
}
|
||||
|
||||
if (field.index) {
|
||||
baseExtraConfig.value = (cols) => index(`${selectTableName}_value_idx`).on(cols.value)
|
||||
}
|
||||
|
||||
buildTable({
|
||||
adapter,
|
||||
baseColumns,
|
||||
baseExtraConfig,
|
||||
disableNotNull,
|
||||
disableUnique,
|
||||
fields: [],
|
||||
rootTableName,
|
||||
tableName: selectTableName,
|
||||
versions,
|
||||
})
|
||||
|
||||
relationsToBuild.set(fieldName, {
|
||||
type: 'many',
|
||||
// selects have their own localized table, independent of the base table.
|
||||
localized: false,
|
||||
target: selectTableName,
|
||||
})
|
||||
|
||||
adapter.relations[`relations_${selectTableName}`] = relations(
|
||||
adapter.tables[selectTableName],
|
||||
({ one }) => ({
|
||||
parent: one(adapter.tables[parentTableName], {
|
||||
fields: [adapter.tables[selectTableName].parent],
|
||||
references: [adapter.tables[parentTableName].id],
|
||||
relationName: fieldName,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
targetTable[fieldName] = withDefault(adapter.enums[enumName](columnName), field)
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -936,6 +854,88 @@ export const traverseFields = ({
|
||||
|
||||
break
|
||||
|
||||
case 'tabs': {
|
||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||
|
||||
const {
|
||||
hasLocalizedField: tabHasLocalizedField,
|
||||
hasLocalizedManyNumberField: tabHasLocalizedManyNumberField,
|
||||
hasLocalizedManyTextField: tabHasLocalizedManyTextField,
|
||||
hasLocalizedRelationshipField: tabHasLocalizedRelationshipField,
|
||||
hasManyNumberField: tabHasManyNumberField,
|
||||
hasManyTextField: tabHasManyTextField,
|
||||
} = traverseFields({
|
||||
adapter,
|
||||
columnPrefix,
|
||||
columns,
|
||||
disableNotNull: disableNotNullFromHere,
|
||||
disableUnique,
|
||||
fieldPrefix,
|
||||
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
||||
forceLocalized,
|
||||
indexes,
|
||||
localesColumns,
|
||||
localesIndexes,
|
||||
newTableName,
|
||||
parentTableName,
|
||||
relationships,
|
||||
relationsToBuild,
|
||||
rootRelationsToBuild,
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
uniqueRelationships,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock,
|
||||
})
|
||||
|
||||
if (tabHasLocalizedField) {
|
||||
hasLocalizedField = true
|
||||
}
|
||||
if (tabHasLocalizedRelationshipField) {
|
||||
hasLocalizedRelationshipField = true
|
||||
}
|
||||
if (tabHasManyTextField) {
|
||||
hasManyTextField = true
|
||||
}
|
||||
if (tabHasLocalizedManyTextField) {
|
||||
hasLocalizedManyTextField = true
|
||||
}
|
||||
if (tabHasManyNumberField) {
|
||||
hasManyNumberField = true
|
||||
}
|
||||
if (tabHasLocalizedManyNumberField) {
|
||||
hasLocalizedManyNumberField = true
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'text': {
|
||||
if (field.hasMany) {
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock ||
|
||||
forceLocalized
|
||||
|
||||
if (isLocalized) {
|
||||
hasLocalizedManyTextField = true
|
||||
}
|
||||
|
||||
if (field.index) {
|
||||
hasManyTextField = 'index'
|
||||
} else if (!hasManyTextField) {
|
||||
hasManyTextField = true
|
||||
}
|
||||
|
||||
if (field.unique) {
|
||||
throw new InvalidConfiguration(
|
||||
'Unique is not supported in Postgres for hasMany text fields.',
|
||||
)
|
||||
}
|
||||
} else {
|
||||
targetTable[fieldName] = withDefault(varchar(columnName), field)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
@@ -121,185 +121,6 @@ export const getTableColumnFromPath = ({
|
||||
}
|
||||
|
||||
switch (field.type) {
|
||||
case 'tabs': {
|
||||
return getTableColumnFromPath({
|
||||
adapter,
|
||||
aliasTable,
|
||||
collectionPath,
|
||||
columnPrefix,
|
||||
constraintPath,
|
||||
constraints,
|
||||
fields: field.tabs.map((tab) => ({
|
||||
...tab,
|
||||
type: 'tab',
|
||||
})),
|
||||
joins,
|
||||
locale,
|
||||
pathSegments: pathSegments.slice(1),
|
||||
rootTableName,
|
||||
selectFields,
|
||||
tableName: newTableName,
|
||||
tableNameSuffix,
|
||||
value,
|
||||
})
|
||||
}
|
||||
case 'tab': {
|
||||
if (tabHasName(field)) {
|
||||
return getTableColumnFromPath({
|
||||
adapter,
|
||||
aliasTable,
|
||||
collectionPath,
|
||||
columnPrefix: `${columnPrefix}${field.name}_`,
|
||||
constraintPath: `${constraintPath}${field.name}.`,
|
||||
constraints,
|
||||
fields: field.fields,
|
||||
joins,
|
||||
locale,
|
||||
pathSegments: pathSegments.slice(1),
|
||||
rootTableName,
|
||||
selectFields,
|
||||
tableName: newTableName,
|
||||
tableNameSuffix: `${tableNameSuffix}${toSnakeCase(field.name)}_`,
|
||||
value,
|
||||
})
|
||||
}
|
||||
return getTableColumnFromPath({
|
||||
adapter,
|
||||
aliasTable,
|
||||
collectionPath,
|
||||
columnPrefix,
|
||||
constraintPath,
|
||||
constraints,
|
||||
fields: field.fields,
|
||||
joins,
|
||||
locale,
|
||||
pathSegments: pathSegments.slice(1),
|
||||
rootTableName,
|
||||
selectFields,
|
||||
tableName: newTableName,
|
||||
tableNameSuffix,
|
||||
value,
|
||||
})
|
||||
}
|
||||
|
||||
case 'group': {
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
newTableName = `${tableName}${adapter.localesSuffix}`
|
||||
|
||||
let condition = eq(adapter.tables[tableName].id, adapter.tables[newTableName]._parentID)
|
||||
|
||||
if (locale !== 'all') {
|
||||
condition = and(condition, eq(adapter.tables[newTableName]._locale, locale))
|
||||
}
|
||||
|
||||
addJoinTable({
|
||||
condition,
|
||||
joins,
|
||||
table: adapter.tables[newTableName],
|
||||
})
|
||||
}
|
||||
return getTableColumnFromPath({
|
||||
adapter,
|
||||
aliasTable,
|
||||
collectionPath,
|
||||
columnPrefix: `${columnPrefix}${field.name}_`,
|
||||
constraintPath: `${constraintPath}${field.name}.`,
|
||||
constraints,
|
||||
fields: field.fields,
|
||||
joins,
|
||||
locale,
|
||||
pathSegments: pathSegments.slice(1),
|
||||
rootTableName,
|
||||
selectFields,
|
||||
tableName: newTableName,
|
||||
tableNameSuffix: `${tableNameSuffix}${toSnakeCase(field.name)}_`,
|
||||
value,
|
||||
})
|
||||
}
|
||||
|
||||
case 'select': {
|
||||
if (field.hasMany) {
|
||||
const newTableName = adapter.tableNameMap.get(
|
||||
`${tableName}_${tableNameSuffix}${toSnakeCase(field.name)}`,
|
||||
)
|
||||
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
const conditions = [
|
||||
eq(adapter.tables[tableName].id, adapter.tables[newTableName].parent),
|
||||
eq(adapter.tables[newTableName]._locale, locale),
|
||||
]
|
||||
|
||||
if (locale !== 'all') {
|
||||
conditions.push(eq(adapter.tables[newTableName]._locale, locale))
|
||||
}
|
||||
|
||||
addJoinTable({
|
||||
condition: and(...conditions),
|
||||
joins,
|
||||
table: adapter.tables[newTableName],
|
||||
})
|
||||
} else {
|
||||
addJoinTable({
|
||||
condition: eq(adapter.tables[tableName].id, adapter.tables[newTableName].parent),
|
||||
joins,
|
||||
table: adapter.tables[newTableName],
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
columnName: 'value',
|
||||
constraints,
|
||||
field,
|
||||
table: adapter.tables[newTableName],
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'text':
|
||||
case 'number': {
|
||||
if (field.hasMany) {
|
||||
let tableType = 'texts'
|
||||
let columnName = 'text'
|
||||
if (field.type === 'number') {
|
||||
tableType = 'numbers'
|
||||
columnName = 'number'
|
||||
}
|
||||
newTableName = `${rootTableName}_${tableType}`
|
||||
const joinConstraints = [
|
||||
eq(adapter.tables[rootTableName].id, adapter.tables[newTableName].parent),
|
||||
like(adapter.tables[newTableName].path, `${constraintPath}${field.name}`),
|
||||
]
|
||||
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
const conditions = [...joinConstraints]
|
||||
|
||||
if (locale !== 'all') {
|
||||
conditions.push(eq(adapter.tables[newTableName]._locale, locale))
|
||||
}
|
||||
addJoinTable({
|
||||
condition: and(...conditions),
|
||||
joins,
|
||||
table: adapter.tables[newTableName],
|
||||
})
|
||||
} else {
|
||||
addJoinTable({
|
||||
condition: and(...joinConstraints),
|
||||
joins,
|
||||
table: adapter.tables[newTableName],
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
columnName,
|
||||
constraints,
|
||||
field,
|
||||
table: adapter.tables[newTableName],
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'array': {
|
||||
newTableName = adapter.tableNameMap.get(
|
||||
`${tableName}_${tableNameSuffix}${toSnakeCase(field.name)}`,
|
||||
@@ -341,7 +162,6 @@ export const getTableColumnFromPath = ({
|
||||
value,
|
||||
})
|
||||
}
|
||||
|
||||
case 'blocks': {
|
||||
let blockTableColumn: TableColumn
|
||||
let newTableName: string
|
||||
@@ -447,7 +267,87 @@ export const getTableColumnFromPath = ({
|
||||
break
|
||||
}
|
||||
|
||||
case 'group': {
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
newTableName = `${tableName}${adapter.localesSuffix}`
|
||||
|
||||
let condition = eq(adapter.tables[tableName].id, adapter.tables[newTableName]._parentID)
|
||||
|
||||
if (locale !== 'all') {
|
||||
condition = and(condition, eq(adapter.tables[newTableName]._locale, locale))
|
||||
}
|
||||
|
||||
addJoinTable({
|
||||
condition,
|
||||
joins,
|
||||
table: adapter.tables[newTableName],
|
||||
})
|
||||
}
|
||||
return getTableColumnFromPath({
|
||||
adapter,
|
||||
aliasTable,
|
||||
collectionPath,
|
||||
columnPrefix: `${columnPrefix}${field.name}_`,
|
||||
constraintPath: `${constraintPath}${field.name}.`,
|
||||
constraints,
|
||||
fields: field.fields,
|
||||
joins,
|
||||
locale,
|
||||
pathSegments: pathSegments.slice(1),
|
||||
rootTableName,
|
||||
selectFields,
|
||||
tableName: newTableName,
|
||||
tableNameSuffix: `${tableNameSuffix}${toSnakeCase(field.name)}_`,
|
||||
value,
|
||||
})
|
||||
}
|
||||
|
||||
case 'number':
|
||||
|
||||
case 'text': {
|
||||
if (field.hasMany) {
|
||||
let tableType = 'texts'
|
||||
let columnName = 'text'
|
||||
if (field.type === 'number') {
|
||||
tableType = 'numbers'
|
||||
columnName = 'number'
|
||||
}
|
||||
newTableName = `${rootTableName}_${tableType}`
|
||||
const joinConstraints = [
|
||||
eq(adapter.tables[rootTableName].id, adapter.tables[newTableName].parent),
|
||||
like(adapter.tables[newTableName].path, `${constraintPath}${field.name}`),
|
||||
]
|
||||
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
const conditions = [...joinConstraints]
|
||||
|
||||
if (locale !== 'all') {
|
||||
conditions.push(eq(adapter.tables[newTableName]._locale, locale))
|
||||
}
|
||||
addJoinTable({
|
||||
condition: and(...conditions),
|
||||
joins,
|
||||
table: adapter.tables[newTableName],
|
||||
})
|
||||
} else {
|
||||
addJoinTable({
|
||||
condition: and(...joinConstraints),
|
||||
joins,
|
||||
table: adapter.tables[newTableName],
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
columnName,
|
||||
constraints,
|
||||
field,
|
||||
table: adapter.tables[newTableName],
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'relationship':
|
||||
|
||||
case 'upload': {
|
||||
const newCollectionPath = pathSegments.slice(1).join('.')
|
||||
if (Array.isArray(field.relationTo) || field.hasMany) {
|
||||
@@ -692,6 +592,106 @@ export const getTableColumnFromPath = ({
|
||||
break
|
||||
}
|
||||
|
||||
case 'select': {
|
||||
if (field.hasMany) {
|
||||
const newTableName = adapter.tableNameMap.get(
|
||||
`${tableName}_${tableNameSuffix}${toSnakeCase(field.name)}`,
|
||||
)
|
||||
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
const conditions = [
|
||||
eq(adapter.tables[tableName].id, adapter.tables[newTableName].parent),
|
||||
eq(adapter.tables[newTableName]._locale, locale),
|
||||
]
|
||||
|
||||
if (locale !== 'all') {
|
||||
conditions.push(eq(adapter.tables[newTableName]._locale, locale))
|
||||
}
|
||||
|
||||
addJoinTable({
|
||||
condition: and(...conditions),
|
||||
joins,
|
||||
table: adapter.tables[newTableName],
|
||||
})
|
||||
} else {
|
||||
addJoinTable({
|
||||
condition: eq(adapter.tables[tableName].id, adapter.tables[newTableName].parent),
|
||||
joins,
|
||||
table: adapter.tables[newTableName],
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
columnName: 'value',
|
||||
constraints,
|
||||
field,
|
||||
table: adapter.tables[newTableName],
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'tab': {
|
||||
if (tabHasName(field)) {
|
||||
return getTableColumnFromPath({
|
||||
adapter,
|
||||
aliasTable,
|
||||
collectionPath,
|
||||
columnPrefix: `${columnPrefix}${field.name}_`,
|
||||
constraintPath: `${constraintPath}${field.name}.`,
|
||||
constraints,
|
||||
fields: field.fields,
|
||||
joins,
|
||||
locale,
|
||||
pathSegments: pathSegments.slice(1),
|
||||
rootTableName,
|
||||
selectFields,
|
||||
tableName: newTableName,
|
||||
tableNameSuffix: `${tableNameSuffix}${toSnakeCase(field.name)}_`,
|
||||
value,
|
||||
})
|
||||
}
|
||||
return getTableColumnFromPath({
|
||||
adapter,
|
||||
aliasTable,
|
||||
collectionPath,
|
||||
columnPrefix,
|
||||
constraintPath,
|
||||
constraints,
|
||||
fields: field.fields,
|
||||
joins,
|
||||
locale,
|
||||
pathSegments: pathSegments.slice(1),
|
||||
rootTableName,
|
||||
selectFields,
|
||||
tableName: newTableName,
|
||||
tableNameSuffix,
|
||||
value,
|
||||
})
|
||||
}
|
||||
case 'tabs': {
|
||||
return getTableColumnFromPath({
|
||||
adapter,
|
||||
aliasTable,
|
||||
collectionPath,
|
||||
columnPrefix,
|
||||
constraintPath,
|
||||
constraints,
|
||||
fields: field.tabs.map((tab) => ({
|
||||
...tab,
|
||||
type: 'tab',
|
||||
})),
|
||||
joins,
|
||||
locale,
|
||||
pathSegments: pathSegments.slice(1),
|
||||
rootTableName,
|
||||
selectFields,
|
||||
tableName: newTableName,
|
||||
tableNameSuffix,
|
||||
value,
|
||||
})
|
||||
}
|
||||
|
||||
default: {
|
||||
// fall through
|
||||
break
|
||||
|
||||
@@ -295,6 +295,13 @@ export function parseParams({
|
||||
|
||||
if (field.type === 'point' && adapter.name === 'postgres') {
|
||||
switch (operator) {
|
||||
case 'intersects': {
|
||||
constraints.push(
|
||||
sql`ST_Intersects(${table[columnName]}, ST_GeomFromGeoJSON(${JSON.stringify(queryValue)}))`,
|
||||
)
|
||||
break
|
||||
}
|
||||
|
||||
case 'near': {
|
||||
const [lng, lat, maxDistance, minDistance] = queryValue as number[]
|
||||
|
||||
@@ -313,13 +320,6 @@ export function parseParams({
|
||||
break
|
||||
}
|
||||
|
||||
case 'intersects': {
|
||||
constraints.push(
|
||||
sql`ST_Intersects(${table[columnName]}, ST_GeomFromGeoJSON(${JSON.stringify(queryValue)}))`,
|
||||
)
|
||||
break
|
||||
}
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
@@ -593,8 +593,16 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
let val = fieldData
|
||||
|
||||
switch (field.type) {
|
||||
case 'tab':
|
||||
case 'group': {
|
||||
case 'date': {
|
||||
if (typeof fieldData === 'string') {
|
||||
val = new Date(fieldData).toISOString()
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
case 'group':
|
||||
|
||||
case 'tab': {
|
||||
const groupFieldPrefix = `${fieldPrefix || ''}${field.name}_`
|
||||
const groupData = {}
|
||||
const locale = table._locale as string
|
||||
@@ -626,14 +634,6 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
return
|
||||
}
|
||||
|
||||
case 'text': {
|
||||
if (typeof fieldData === 'string') {
|
||||
val = String(fieldData)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'number': {
|
||||
if (typeof fieldData === 'string') {
|
||||
val = Number.parseFloat(fieldData)
|
||||
@@ -642,15 +642,8 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
break
|
||||
}
|
||||
|
||||
case 'date': {
|
||||
if (typeof fieldData === 'string') {
|
||||
val = new Date(fieldData).toISOString()
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'relationship':
|
||||
|
||||
case 'upload': {
|
||||
if (
|
||||
val &&
|
||||
@@ -662,6 +655,13 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
|
||||
break
|
||||
}
|
||||
case 'text': {
|
||||
if (typeof fieldData === 'string') {
|
||||
val = String(fieldData)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
break
|
||||
|
||||
@@ -17,23 +17,23 @@
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@eslint-react/eslint-plugin": "1.12.3",
|
||||
"@eslint/js": "9.9.1",
|
||||
"@eslint-react/eslint-plugin": "1.16.1",
|
||||
"@eslint/js": "9.14.0",
|
||||
"@payloadcms/eslint-plugin": "workspace:*",
|
||||
"@types/eslint": "9.6.1",
|
||||
"@types/eslint__js": "8.42.3",
|
||||
"@typescript-eslint/parser": "8.3.0",
|
||||
"eslint": "9.9.1",
|
||||
"@typescript-eslint/parser": "8.14.0",
|
||||
"eslint": "9.14.0",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-plugin-import-x": "4.1.1",
|
||||
"eslint-plugin-jest": "28.8.1",
|
||||
"eslint-plugin-import-x": "4.4.2",
|
||||
"eslint-plugin-jest": "28.9.0",
|
||||
"eslint-plugin-jest-dom": "5.4.0",
|
||||
"eslint-plugin-jsx-a11y": "6.9.0",
|
||||
"eslint-plugin-perfectionist": "3.3.0",
|
||||
"eslint-plugin-react-hooks": "5.1.0-rc-a19a8ab4-20240829",
|
||||
"eslint-plugin-jsx-a11y": "6.10.2",
|
||||
"eslint-plugin-perfectionist": "3.9.1",
|
||||
"eslint-plugin-react-hooks": "5.0.0",
|
||||
"eslint-plugin-regexp": "2.6.0",
|
||||
"globals": "15.9.0",
|
||||
"globals": "15.12.0",
|
||||
"typescript": "5.6.3",
|
||||
"typescript-eslint": "8.3.0"
|
||||
"typescript-eslint": "8.14.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,22 +17,22 @@
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@eslint-react/eslint-plugin": "1.12.3",
|
||||
"@eslint/js": "9.9.1",
|
||||
"@eslint-react/eslint-plugin": "1.16.1",
|
||||
"@eslint/js": "9.14.0",
|
||||
"@types/eslint": "9.6.1",
|
||||
"@types/eslint__js": "8.42.3",
|
||||
"@typescript-eslint/parser": "8.3.0",
|
||||
"eslint": "9.9.1",
|
||||
"@typescript-eslint/parser": "8.14.0",
|
||||
"eslint": "9.14.0",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-plugin-import-x": "4.1.1",
|
||||
"eslint-plugin-jest": "28.8.1",
|
||||
"eslint-plugin-import-x": "4.4.2",
|
||||
"eslint-plugin-jest": "28.9.0",
|
||||
"eslint-plugin-jest-dom": "5.4.0",
|
||||
"eslint-plugin-jsx-a11y": "6.9.0",
|
||||
"eslint-plugin-perfectionist": "3.3.0",
|
||||
"eslint-plugin-react-hooks": "5.1.0-rc-a19a8ab4-20240829",
|
||||
"eslint-plugin-jsx-a11y": "6.10.2",
|
||||
"eslint-plugin-perfectionist": "3.9.1",
|
||||
"eslint-plugin-react-hooks": "5.0.0",
|
||||
"eslint-plugin-regexp": "2.6.0",
|
||||
"globals": "15.9.0",
|
||||
"globals": "15.12.0",
|
||||
"typescript": "5.6.3",
|
||||
"typescript-eslint": "8.3.0"
|
||||
"typescript-eslint": "8.14.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,15 +394,15 @@ export class QueryComplexity {
|
||||
this.variableValues = coerced
|
||||
|
||||
switch (operation.operation) {
|
||||
case 'query':
|
||||
this.complexity += this.nodeComplexity(operation, this.context.getSchema().getQueryType())
|
||||
break
|
||||
case 'mutation':
|
||||
this.complexity += this.nodeComplexity(
|
||||
operation,
|
||||
this.context.getSchema().getMutationType(),
|
||||
)
|
||||
break
|
||||
case 'query':
|
||||
this.complexity += this.nodeComplexity(operation, this.context.getSchema().getQueryType())
|
||||
break
|
||||
case 'subscription':
|
||||
this.complexity += this.nodeComplexity(
|
||||
operation,
|
||||
|
||||
@@ -24,18 +24,18 @@ function parseObject(typeName, ast, variables) {
|
||||
|
||||
function parseLiteral(typeName, ast, variables) {
|
||||
switch (ast.kind) {
|
||||
case Kind.STRING:
|
||||
case Kind.BOOLEAN:
|
||||
case Kind.STRING:
|
||||
return ast.value
|
||||
case Kind.INT:
|
||||
case Kind.FLOAT:
|
||||
case Kind.INT:
|
||||
return parseFloat(ast.value)
|
||||
case Kind.OBJECT:
|
||||
return parseObject(typeName, ast, variables)
|
||||
case Kind.LIST:
|
||||
return ast.values.map((n) => parseLiteral(typeName, n, variables))
|
||||
case Kind.NULL:
|
||||
return null
|
||||
case Kind.OBJECT:
|
||||
return parseObject(typeName, ast, variables)
|
||||
case Kind.VARIABLE:
|
||||
return variables ? variables[ast.name.value] : undefined
|
||||
default:
|
||||
|
||||
@@ -24,16 +24,6 @@ export const traverseFields = <T>(args: {
|
||||
const fieldName = fieldSchema.name
|
||||
|
||||
switch (fieldSchema.type) {
|
||||
case 'richText':
|
||||
result[fieldName] = traverseRichText({
|
||||
externallyUpdatedRelationship,
|
||||
incomingData: incomingData[fieldName],
|
||||
populationsByCollection,
|
||||
result: result[fieldName],
|
||||
})
|
||||
|
||||
break
|
||||
|
||||
case 'array':
|
||||
if (Array.isArray(incomingData[fieldName])) {
|
||||
result[fieldName] = incomingData[fieldName].map((incomingRow, i) => {
|
||||
@@ -94,8 +84,9 @@ export const traverseFields = <T>(args: {
|
||||
|
||||
break
|
||||
|
||||
case 'tabs':
|
||||
case 'group':
|
||||
|
||||
case 'tabs':
|
||||
if (!result[fieldName]) {
|
||||
result[fieldName] = {}
|
||||
}
|
||||
@@ -109,9 +100,9 @@ export const traverseFields = <T>(args: {
|
||||
})
|
||||
|
||||
break
|
||||
case 'relationship':
|
||||
|
||||
case 'upload':
|
||||
case 'relationship':
|
||||
// Handle `hasMany` relationships
|
||||
if (fieldSchema.hasMany && Array.isArray(incomingData[fieldName])) {
|
||||
if (!result[fieldName] || !incomingData[fieldName].length) {
|
||||
@@ -271,6 +262,15 @@ export const traverseFields = <T>(args: {
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
case 'richText':
|
||||
result[fieldName] = traverseRichText({
|
||||
externallyUpdatedRelationship,
|
||||
incomingData: incomingData[fieldName],
|
||||
populationsByCollection,
|
||||
result: result[fieldName],
|
||||
})
|
||||
|
||||
break
|
||||
|
||||
default:
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"babel-plugin-react-compiler": "0.0.0-experimental-24ec0eb-20240918",
|
||||
"esbuild": "0.23.1",
|
||||
"esbuild-sass-plugin": "3.3.1",
|
||||
"eslint-plugin-react-compiler": "0.0.0-experimental-7670337-20240918",
|
||||
"eslint-plugin-react-compiler": "19.0.0-beta-a7bf2bd-20241110",
|
||||
"payload": "workspace:*",
|
||||
"swc-plugin-transform-remove-imports": "1.15.0"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const ACCEPTABLE_CONTENT_TYPE = /multipart\/['"()+-_]+(?:; ?['"()+-_]*)+$/i
|
||||
const UNACCEPTABLE_METHODS = new Set(['GET', 'HEAD', 'DELETE', 'OPTIONS', 'CONNECT', 'TRACE'])
|
||||
const UNACCEPTABLE_METHODS = new Set(['CONNECT', 'DELETE', 'GET', 'HEAD', 'OPTIONS', 'TRACE'])
|
||||
|
||||
const hasBody = (req: Request): boolean => {
|
||||
return Boolean(
|
||||
|
||||
@@ -29,10 +29,10 @@ export type SizeReducerAction =
|
||||
|
||||
export const sizeReducer = (state: SizeReducerState, action: SizeReducerAction) => {
|
||||
switch (action.type) {
|
||||
case 'width':
|
||||
return { ...state, width: action.value }
|
||||
case 'height':
|
||||
return { ...state, height: action.value }
|
||||
case 'width':
|
||||
return { ...state, width: action.value }
|
||||
default:
|
||||
return { ...state, ...(action?.value || {}) }
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ import type {
|
||||
} from '../types.js'
|
||||
|
||||
export type ClientTab =
|
||||
| ({ fields: ClientField[] } & Omit<UnnamedTab, 'fields'>)
|
||||
| ({ fields: ClientField[]; readonly path?: string } & Omit<NamedTab, 'fields'>)
|
||||
| ({ fields: ClientField[] } & Omit<UnnamedTab, 'fields'>)
|
||||
|
||||
type TabsFieldBaseClientProps = {} & Pick<ServerFieldBase, 'permissions'>
|
||||
|
||||
|
||||
@@ -28,9 +28,9 @@ export type AdminViewProps = {
|
||||
readonly initialData?: Data
|
||||
readonly initPageResult: InitPageResult
|
||||
readonly params?: { [key: string]: string | string[] | undefined }
|
||||
readonly searchParams: { [key: string]: string | string[] | undefined }
|
||||
readonly redirectAfterDelete?: boolean
|
||||
readonly redirectAfterDuplicate?: boolean
|
||||
readonly searchParams: { [key: string]: string | string[] | undefined }
|
||||
}
|
||||
|
||||
export type AdminViewComponent = PayloadComponent<AdminViewProps>
|
||||
|
||||
@@ -17,8 +17,8 @@ const traverseFields = ({
|
||||
}: TraverseFieldsArgs) => {
|
||||
fields.forEach((field) => {
|
||||
switch (field.type) {
|
||||
case 'row':
|
||||
case 'collapsible': {
|
||||
case 'collapsible':
|
||||
case 'row': {
|
||||
traverseFields({
|
||||
data,
|
||||
fields: field.fields,
|
||||
@@ -47,14 +47,6 @@ const traverseFields = ({
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'tabs': {
|
||||
traverseFields({
|
||||
data,
|
||||
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
||||
result,
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'tab': {
|
||||
if (tabHasName(field)) {
|
||||
let targetResult
|
||||
@@ -84,6 +76,14 @@ const traverseFields = ({
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'tabs': {
|
||||
traverseFields({
|
||||
data,
|
||||
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
||||
result,
|
||||
})
|
||||
break
|
||||
}
|
||||
default:
|
||||
if (fieldAffectsData(field)) {
|
||||
if (field.saveToJWT) {
|
||||
|
||||
@@ -65,21 +65,6 @@ export const migrate = async ({ config, parsedArgs }: Args): Promise<void> => {
|
||||
case 'migrate':
|
||||
await adapter.migrate()
|
||||
break
|
||||
case 'migrate:status':
|
||||
await adapter.migrateStatus()
|
||||
break
|
||||
case 'migrate:down':
|
||||
await adapter.migrateDown()
|
||||
break
|
||||
case 'migrate:refresh':
|
||||
await adapter.migrateRefresh()
|
||||
break
|
||||
case 'migrate:reset':
|
||||
await adapter.migrateReset()
|
||||
break
|
||||
case 'migrate:fresh':
|
||||
await adapter.migrateFresh({ forceAcceptWarning })
|
||||
break
|
||||
case 'migrate:create':
|
||||
try {
|
||||
await adapter.createMigration({
|
||||
@@ -92,6 +77,21 @@ export const migrate = async ({ config, parsedArgs }: Args): Promise<void> => {
|
||||
throw new Error(`Error creating migration: ${err.message}`)
|
||||
}
|
||||
break
|
||||
case 'migrate:down':
|
||||
await adapter.migrateDown()
|
||||
break
|
||||
case 'migrate:fresh':
|
||||
await adapter.migrateFresh({ forceAcceptWarning })
|
||||
break
|
||||
case 'migrate:refresh':
|
||||
await adapter.migrateRefresh()
|
||||
break
|
||||
case 'migrate:reset':
|
||||
await adapter.migrateReset()
|
||||
break
|
||||
case 'migrate:status':
|
||||
await adapter.migrateStatus()
|
||||
break
|
||||
|
||||
default:
|
||||
payload.logger.error({
|
||||
|
||||
@@ -92,8 +92,8 @@ export async function getLocalizedPaths({
|
||||
|
||||
switch (matchedField.type) {
|
||||
case 'blocks':
|
||||
case 'richText':
|
||||
case 'json': {
|
||||
case 'json':
|
||||
case 'richText': {
|
||||
const upcomingSegments = pathSegments.slice(i + 1).join('.')
|
||||
lastIncompletePath.complete = true
|
||||
lastIncompletePath.path = upcomingSegments
|
||||
|
||||
@@ -111,8 +111,8 @@ export const createClientField = ({
|
||||
|
||||
switch (incomingField.type) {
|
||||
case 'array':
|
||||
case 'group':
|
||||
case 'collapsible':
|
||||
case 'group':
|
||||
case 'row': {
|
||||
const field = clientField as unknown as RowFieldClient
|
||||
|
||||
@@ -182,6 +182,31 @@ export const createClientField = ({
|
||||
break
|
||||
}
|
||||
|
||||
case 'radio':
|
||||
|
||||
case 'select': {
|
||||
const field = clientField as RadioFieldClient | SelectFieldClient
|
||||
|
||||
if (incomingField.options?.length) {
|
||||
for (let i = 0; i < incomingField.options.length; i++) {
|
||||
const option = incomingField.options[i]
|
||||
|
||||
if (typeof option === 'object' && typeof option.label === 'function') {
|
||||
if (!field.options) {
|
||||
field.options = []
|
||||
}
|
||||
|
||||
field.options[i] = {
|
||||
label: option.label({ t: i18n.t }),
|
||||
value: option.value,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'richText': {
|
||||
if (!incomingField?.editor) {
|
||||
throw new MissingEditorProp(incomingField) // while we allow disabling editor functionality, you should not have any richText fields defined if you do not have an editor
|
||||
@@ -193,7 +218,6 @@ export const createClientField = ({
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'tabs': {
|
||||
const field = clientField as unknown as TabsFieldClient
|
||||
|
||||
@@ -221,30 +245,6 @@ export const createClientField = ({
|
||||
break
|
||||
}
|
||||
|
||||
case 'select':
|
||||
case 'radio': {
|
||||
const field = clientField as RadioFieldClient | SelectFieldClient
|
||||
|
||||
if (incomingField.options?.length) {
|
||||
for (let i = 0; i < incomingField.options.length; i++) {
|
||||
const option = incomingField.options[i]
|
||||
|
||||
if (typeof option === 'object' && typeof option.label === 'function') {
|
||||
if (!field.options) {
|
||||
field.options = []
|
||||
}
|
||||
|
||||
field.options[i] = {
|
||||
label: option.label({ t: i18n.t }),
|
||||
value: option.value,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
@@ -97,27 +97,6 @@ export const promise = async ({
|
||||
|
||||
// Traverse subfields
|
||||
switch (field.type) {
|
||||
case 'group': {
|
||||
await traverseFields({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
fields: field.fields,
|
||||
global,
|
||||
operation,
|
||||
path: fieldPath,
|
||||
previousDoc,
|
||||
previousSiblingDoc: previousDoc[field.name] as JsonObject,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: (siblingData?.[field.name] as JsonObject) || {},
|
||||
siblingDoc: siblingDoc[field.name] as JsonObject,
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'array': {
|
||||
const rows = siblingDoc[field.name]
|
||||
|
||||
@@ -185,8 +164,9 @@ export const promise = async ({
|
||||
break
|
||||
}
|
||||
|
||||
case 'row':
|
||||
case 'collapsible': {
|
||||
case 'collapsible':
|
||||
|
||||
case 'row': {
|
||||
await traverseFields({
|
||||
collection,
|
||||
context,
|
||||
@@ -206,6 +186,66 @@ export const promise = async ({
|
||||
|
||||
break
|
||||
}
|
||||
case 'group': {
|
||||
await traverseFields({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
fields: field.fields,
|
||||
global,
|
||||
operation,
|
||||
path: fieldPath,
|
||||
previousDoc,
|
||||
previousSiblingDoc: previousDoc[field.name] as JsonObject,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: (siblingData?.[field.name] as JsonObject) || {},
|
||||
siblingDoc: siblingDoc[field.name] as JsonObject,
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'richText': {
|
||||
if (!field?.editor) {
|
||||
throw new MissingEditorProp(field) // while we allow disabling editor functionality, you should not have any richText fields defined if you do not have an editor
|
||||
}
|
||||
if (typeof field?.editor === 'function') {
|
||||
throw new Error('Attempted to access unsanitized rich text editor.')
|
||||
}
|
||||
|
||||
const editor: RichTextAdapter = field?.editor
|
||||
|
||||
if (editor?.hooks?.afterChange?.length) {
|
||||
await editor.hooks.afterChange.reduce(async (priorHook, currentHook) => {
|
||||
await priorHook
|
||||
|
||||
const hookedValue = await currentHook({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
field,
|
||||
global,
|
||||
operation,
|
||||
originalDoc: doc,
|
||||
path: fieldPath,
|
||||
previousDoc,
|
||||
previousSiblingDoc,
|
||||
previousValue: previousDoc[field.name],
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData,
|
||||
value: siblingDoc[field.name],
|
||||
})
|
||||
|
||||
if (hookedValue !== undefined) {
|
||||
siblingDoc[field.name] = hookedValue
|
||||
}
|
||||
}, Promise.resolve())
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'tab': {
|
||||
let tabSiblingData = siblingData
|
||||
@@ -258,46 +298,6 @@ export const promise = async ({
|
||||
break
|
||||
}
|
||||
|
||||
case 'richText': {
|
||||
if (!field?.editor) {
|
||||
throw new MissingEditorProp(field) // while we allow disabling editor functionality, you should not have any richText fields defined if you do not have an editor
|
||||
}
|
||||
if (typeof field?.editor === 'function') {
|
||||
throw new Error('Attempted to access unsanitized rich text editor.')
|
||||
}
|
||||
|
||||
const editor: RichTextAdapter = field?.editor
|
||||
|
||||
if (editor?.hooks?.afterChange?.length) {
|
||||
await editor.hooks.afterChange.reduce(async (priorHook, currentHook) => {
|
||||
await priorHook
|
||||
|
||||
const hookedValue = await currentHook({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
field,
|
||||
global,
|
||||
operation,
|
||||
originalDoc: doc,
|
||||
path: fieldPath,
|
||||
previousDoc,
|
||||
previousSiblingDoc,
|
||||
previousValue: previousDoc[field.name],
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData,
|
||||
value: siblingDoc[field.name],
|
||||
})
|
||||
|
||||
if (hookedValue !== undefined) {
|
||||
siblingDoc[field.name] = hookedValue
|
||||
}
|
||||
}, Promise.resolve())
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -181,15 +181,13 @@ export const promise = async ({
|
||||
|
||||
break
|
||||
}
|
||||
case 'tabs': {
|
||||
field.tabs.forEach((tab) => {
|
||||
if (
|
||||
tabHasName(tab) &&
|
||||
(typeof siblingDoc[tab.name] === 'undefined' || siblingDoc[tab.name] === null)
|
||||
) {
|
||||
siblingDoc[tab.name] = {}
|
||||
}
|
||||
})
|
||||
case 'point': {
|
||||
const pointDoc = siblingDoc[field.name] as Record<string, unknown>
|
||||
if (Array.isArray(pointDoc?.coordinates) && pointDoc.coordinates.length === 2) {
|
||||
siblingDoc[field.name] = pointDoc.coordinates
|
||||
} else {
|
||||
siblingDoc[field.name] = undefined
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
@@ -206,13 +204,15 @@ export const promise = async ({
|
||||
break
|
||||
}
|
||||
|
||||
case 'point': {
|
||||
const pointDoc = siblingDoc[field.name] as Record<string, unknown>
|
||||
if (Array.isArray(pointDoc?.coordinates) && pointDoc.coordinates.length === 2) {
|
||||
siblingDoc[field.name] = pointDoc.coordinates
|
||||
} else {
|
||||
siblingDoc[field.name] = undefined
|
||||
}
|
||||
case 'tabs': {
|
||||
field.tabs.forEach((tab) => {
|
||||
if (
|
||||
tabHasName(tab) &&
|
||||
(typeof siblingDoc[tab.name] === 'undefined' || siblingDoc[tab.name] === null)
|
||||
) {
|
||||
siblingDoc[tab.name] = {}
|
||||
}
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
@@ -347,45 +347,6 @@ export const promise = async ({
|
||||
}
|
||||
|
||||
switch (field.type) {
|
||||
case 'group': {
|
||||
let groupDoc = siblingDoc[field.name] as JsonObject
|
||||
if (typeof siblingDoc[field.name] !== 'object') {
|
||||
groupDoc = {}
|
||||
}
|
||||
|
||||
const groupSelect = select?.[field.name]
|
||||
|
||||
traverseFields({
|
||||
collection,
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
doc,
|
||||
draft,
|
||||
fallbackLocale,
|
||||
fieldPromises,
|
||||
fields: field.fields,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
global,
|
||||
locale,
|
||||
overrideAccess,
|
||||
path: fieldPath,
|
||||
populate,
|
||||
populationPromises,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
select: typeof groupSelect === 'object' ? groupSelect : undefined,
|
||||
selectMode,
|
||||
showHiddenFields,
|
||||
siblingDoc: groupDoc,
|
||||
triggerAccessControl,
|
||||
triggerHooks,
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'array': {
|
||||
const rows = siblingDoc[field.name] as JsonObject
|
||||
|
||||
@@ -573,8 +534,9 @@ export const promise = async ({
|
||||
break
|
||||
}
|
||||
|
||||
case 'row':
|
||||
case 'collapsible': {
|
||||
case 'collapsible':
|
||||
|
||||
case 'row': {
|
||||
traverseFields({
|
||||
collection,
|
||||
context,
|
||||
@@ -605,23 +567,14 @@ export const promise = async ({
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'tab': {
|
||||
let tabDoc = siblingDoc
|
||||
let tabSelect: SelectType | undefined
|
||||
if (tabHasName(field)) {
|
||||
tabDoc = siblingDoc[field.name] as JsonObject
|
||||
if (typeof siblingDoc[field.name] !== 'object') {
|
||||
tabDoc = {}
|
||||
}
|
||||
|
||||
if (typeof select?.[field.name] === 'object') {
|
||||
tabSelect = select?.[field.name] as SelectType
|
||||
}
|
||||
} else {
|
||||
tabSelect = select
|
||||
case 'group': {
|
||||
let groupDoc = siblingDoc[field.name] as JsonObject
|
||||
if (typeof siblingDoc[field.name] !== 'object') {
|
||||
groupDoc = {}
|
||||
}
|
||||
|
||||
const groupSelect = select?.[field.name]
|
||||
|
||||
traverseFields({
|
||||
collection,
|
||||
context,
|
||||
@@ -642,10 +595,10 @@ export const promise = async ({
|
||||
populationPromises,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
select: tabSelect,
|
||||
select: typeof groupSelect === 'object' ? groupSelect : undefined,
|
||||
selectMode,
|
||||
showHiddenFields,
|
||||
siblingDoc: tabDoc,
|
||||
siblingDoc: groupDoc,
|
||||
triggerAccessControl,
|
||||
triggerHooks,
|
||||
})
|
||||
@@ -653,37 +606,6 @@ export const promise = async ({
|
||||
break
|
||||
}
|
||||
|
||||
case 'tabs': {
|
||||
traverseFields({
|
||||
collection,
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
doc,
|
||||
draft,
|
||||
fallbackLocale,
|
||||
fieldPromises,
|
||||
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
||||
findMany,
|
||||
flattenLocales,
|
||||
global,
|
||||
locale,
|
||||
overrideAccess,
|
||||
path: fieldPath,
|
||||
populate,
|
||||
populationPromises,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
select,
|
||||
selectMode,
|
||||
showHiddenFields,
|
||||
siblingDoc,
|
||||
triggerAccessControl,
|
||||
triggerHooks,
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
case 'richText': {
|
||||
if (!field?.editor) {
|
||||
throw new MissingEditorProp(field) // while we allow disabling editor functionality, you should not have any richText fields defined if you do not have an editor
|
||||
@@ -781,6 +703,84 @@ export const promise = async ({
|
||||
break
|
||||
}
|
||||
|
||||
case 'tab': {
|
||||
let tabDoc = siblingDoc
|
||||
let tabSelect: SelectType | undefined
|
||||
if (tabHasName(field)) {
|
||||
tabDoc = siblingDoc[field.name] as JsonObject
|
||||
if (typeof siblingDoc[field.name] !== 'object') {
|
||||
tabDoc = {}
|
||||
}
|
||||
|
||||
if (typeof select?.[field.name] === 'object') {
|
||||
tabSelect = select?.[field.name] as SelectType
|
||||
}
|
||||
} else {
|
||||
tabSelect = select
|
||||
}
|
||||
|
||||
traverseFields({
|
||||
collection,
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
doc,
|
||||
draft,
|
||||
fallbackLocale,
|
||||
fieldPromises,
|
||||
fields: field.fields,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
global,
|
||||
locale,
|
||||
overrideAccess,
|
||||
path: fieldPath,
|
||||
populate,
|
||||
populationPromises,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
select: tabSelect,
|
||||
selectMode,
|
||||
showHiddenFields,
|
||||
siblingDoc: tabDoc,
|
||||
triggerAccessControl,
|
||||
triggerHooks,
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'tabs': {
|
||||
traverseFields({
|
||||
collection,
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
doc,
|
||||
draft,
|
||||
fallbackLocale,
|
||||
fieldPromises,
|
||||
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
||||
findMany,
|
||||
flattenLocales,
|
||||
global,
|
||||
locale,
|
||||
overrideAccess,
|
||||
path: fieldPath,
|
||||
populate,
|
||||
populationPromises,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
select,
|
||||
selectMode,
|
||||
showHiddenFields,
|
||||
siblingDoc,
|
||||
triggerAccessControl,
|
||||
triggerHooks,
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ import type { RichTextAdapter } from '../../../admin/RichText.js'
|
||||
import type { SanitizedCollectionConfig } from '../../../collections/config/types.js'
|
||||
import type { ValidationFieldError } from '../../../errors/index.js'
|
||||
import type { SanitizedGlobalConfig } from '../../../globals/config/types.js'
|
||||
import type { RequestContext } from '../../../index.js'
|
||||
import type { JsonObject, Operation, PayloadRequest } from '../../../types/index.js'
|
||||
import type { Field, TabAsField } from '../../config/types.js'
|
||||
import type { RequestContext } from '../../../index.js'
|
||||
|
||||
import { MissingEditorProp } from '../../../errors/index.js'
|
||||
import { deepMergeWithSourceArrays } from '../../../utilities/deepMerge.js'
|
||||
@@ -200,60 +200,6 @@ export const promise = async ({
|
||||
}
|
||||
|
||||
switch (field.type) {
|
||||
case 'point': {
|
||||
// Transform point data for storage
|
||||
if (
|
||||
Array.isArray(siblingData[field.name]) &&
|
||||
siblingData[field.name][0] !== null &&
|
||||
siblingData[field.name][1] !== null
|
||||
) {
|
||||
siblingData[field.name] = {
|
||||
type: 'Point',
|
||||
coordinates: [
|
||||
parseFloat(siblingData[field.name][0]),
|
||||
parseFloat(siblingData[field.name][1]),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'group': {
|
||||
if (typeof siblingData[field.name] !== 'object') {
|
||||
siblingData[field.name] = {}
|
||||
}
|
||||
if (typeof siblingDoc[field.name] !== 'object') {
|
||||
siblingDoc[field.name] = {}
|
||||
}
|
||||
if (typeof siblingDocWithLocales[field.name] !== 'object') {
|
||||
siblingDocWithLocales[field.name] = {}
|
||||
}
|
||||
|
||||
await traverseFields({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
docWithLocales,
|
||||
errors,
|
||||
fields: field.fields,
|
||||
global,
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
path: fieldPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: siblingData[field.name] as JsonObject,
|
||||
siblingDoc: siblingDoc[field.name] as JsonObject,
|
||||
siblingDocWithLocales: siblingDocWithLocales[field.name] as JsonObject,
|
||||
skipValidation: skipValidationFromHere,
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'array': {
|
||||
const rows = siblingData[field.name]
|
||||
|
||||
@@ -339,8 +285,9 @@ export const promise = async ({
|
||||
break
|
||||
}
|
||||
|
||||
case 'row':
|
||||
case 'collapsible': {
|
||||
case 'collapsible':
|
||||
|
||||
case 'row': {
|
||||
await traverseFields({
|
||||
id,
|
||||
collection,
|
||||
@@ -365,6 +312,104 @@ export const promise = async ({
|
||||
break
|
||||
}
|
||||
|
||||
case 'group': {
|
||||
if (typeof siblingData[field.name] !== 'object') {
|
||||
siblingData[field.name] = {}
|
||||
}
|
||||
if (typeof siblingDoc[field.name] !== 'object') {
|
||||
siblingDoc[field.name] = {}
|
||||
}
|
||||
if (typeof siblingDocWithLocales[field.name] !== 'object') {
|
||||
siblingDocWithLocales[field.name] = {}
|
||||
}
|
||||
|
||||
await traverseFields({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
docWithLocales,
|
||||
errors,
|
||||
fields: field.fields,
|
||||
global,
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
path: fieldPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: siblingData[field.name] as JsonObject,
|
||||
siblingDoc: siblingDoc[field.name] as JsonObject,
|
||||
siblingDocWithLocales: siblingDocWithLocales[field.name] as JsonObject,
|
||||
skipValidation: skipValidationFromHere,
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
case 'point': {
|
||||
// Transform point data for storage
|
||||
if (
|
||||
Array.isArray(siblingData[field.name]) &&
|
||||
siblingData[field.name][0] !== null &&
|
||||
siblingData[field.name][1] !== null
|
||||
) {
|
||||
siblingData[field.name] = {
|
||||
type: 'Point',
|
||||
coordinates: [
|
||||
parseFloat(siblingData[field.name][0]),
|
||||
parseFloat(siblingData[field.name][1]),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'richText': {
|
||||
if (!field?.editor) {
|
||||
throw new MissingEditorProp(field) // while we allow disabling editor functionality, you should not have any richText fields defined if you do not have an editor
|
||||
}
|
||||
if (typeof field?.editor === 'function') {
|
||||
throw new Error('Attempted to access unsanitized rich text editor.')
|
||||
}
|
||||
|
||||
const editor: RichTextAdapter = field?.editor
|
||||
|
||||
if (editor?.hooks?.beforeChange?.length) {
|
||||
await editor.hooks.beforeChange.reduce(async (priorHook, currentHook) => {
|
||||
await priorHook
|
||||
|
||||
const hookedValue = await currentHook({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
docWithLocales,
|
||||
errors,
|
||||
field,
|
||||
global,
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
originalDoc: doc,
|
||||
path: fieldPath,
|
||||
previousSiblingDoc: siblingDoc,
|
||||
previousValue: siblingDoc[field.name],
|
||||
req,
|
||||
schemaPath: parentSchemaPath,
|
||||
siblingData,
|
||||
siblingDocWithLocales,
|
||||
skipValidation,
|
||||
value: siblingData[field.name],
|
||||
})
|
||||
|
||||
if (hookedValue !== undefined) {
|
||||
siblingData[field.name] = hookedValue
|
||||
}
|
||||
}, Promise.resolve())
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'tab': {
|
||||
let tabSiblingData = siblingData
|
||||
let tabSiblingDoc = siblingDoc
|
||||
@@ -435,51 +480,6 @@ export const promise = async ({
|
||||
break
|
||||
}
|
||||
|
||||
case 'richText': {
|
||||
if (!field?.editor) {
|
||||
throw new MissingEditorProp(field) // while we allow disabling editor functionality, you should not have any richText fields defined if you do not have an editor
|
||||
}
|
||||
if (typeof field?.editor === 'function') {
|
||||
throw new Error('Attempted to access unsanitized rich text editor.')
|
||||
}
|
||||
|
||||
const editor: RichTextAdapter = field?.editor
|
||||
|
||||
if (editor?.hooks?.beforeChange?.length) {
|
||||
await editor.hooks.beforeChange.reduce(async (priorHook, currentHook) => {
|
||||
await priorHook
|
||||
|
||||
const hookedValue = await currentHook({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
docWithLocales,
|
||||
errors,
|
||||
field,
|
||||
global,
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
originalDoc: doc,
|
||||
path: fieldPath,
|
||||
previousSiblingDoc: siblingDoc,
|
||||
previousValue: siblingDoc[field.name],
|
||||
req,
|
||||
schemaPath: parentSchemaPath,
|
||||
siblingData,
|
||||
siblingDocWithLocales,
|
||||
skipValidation,
|
||||
value: siblingData[field.name],
|
||||
})
|
||||
|
||||
if (hookedValue !== undefined) {
|
||||
siblingData[field.name] = hookedValue
|
||||
}
|
||||
}, Promise.resolve())
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -145,26 +145,6 @@ export const promise = async <T>({
|
||||
localization.localeCodes.forEach((locale) => {
|
||||
if (fieldData[locale]) {
|
||||
switch (field.type) {
|
||||
case 'tab':
|
||||
case 'group': {
|
||||
promises.push(
|
||||
traverseFields({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
doc,
|
||||
fields: field.fields,
|
||||
overrideAccess,
|
||||
path: fieldSchemaPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingDoc: fieldData[locale],
|
||||
}),
|
||||
)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'array': {
|
||||
const rows = fieldData[locale]
|
||||
|
||||
@@ -189,7 +169,6 @@ export const promise = async <T>({
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'blocks': {
|
||||
const rows = fieldData[locale]
|
||||
|
||||
@@ -220,6 +199,27 @@ export const promise = async <T>({
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'group':
|
||||
|
||||
case 'tab': {
|
||||
promises.push(
|
||||
traverseFields({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
doc,
|
||||
fields: field.fields,
|
||||
overrideAccess,
|
||||
path: fieldSchemaPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingDoc: fieldData[locale],
|
||||
}),
|
||||
)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -230,30 +230,6 @@ export const promise = async <T>({
|
||||
// we need to further traverse its children
|
||||
// so the child fields can run beforeDuplicate hooks
|
||||
switch (field.type) {
|
||||
case 'tab':
|
||||
case 'group': {
|
||||
if (typeof siblingDoc[field.name] !== 'object') {
|
||||
siblingDoc[field.name] = {}
|
||||
}
|
||||
|
||||
const groupDoc = siblingDoc[field.name] as Record<string, unknown>
|
||||
|
||||
await traverseFields({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
doc,
|
||||
fields: field.fields,
|
||||
overrideAccess,
|
||||
path: fieldPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingDoc: groupDoc as JsonObject,
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'array': {
|
||||
const rows = siblingDoc[field.name]
|
||||
|
||||
@@ -279,7 +255,6 @@ export const promise = async <T>({
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'blocks': {
|
||||
const rows = siblingDoc[field.name]
|
||||
|
||||
@@ -313,13 +288,38 @@ export const promise = async <T>({
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'group':
|
||||
|
||||
case 'tab': {
|
||||
if (typeof siblingDoc[field.name] !== 'object') {
|
||||
siblingDoc[field.name] = {}
|
||||
}
|
||||
|
||||
const groupDoc = siblingDoc[field.name] as Record<string, unknown>
|
||||
|
||||
await traverseFields({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
doc,
|
||||
fields: field.fields,
|
||||
overrideAccess,
|
||||
path: fieldPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingDoc: groupDoc as JsonObject,
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Finally, we traverse fields which do not affect data here
|
||||
switch (field.type) {
|
||||
case 'row':
|
||||
case 'collapsible': {
|
||||
case 'collapsible':
|
||||
case 'row': {
|
||||
await traverseFields({
|
||||
id,
|
||||
collection,
|
||||
|
||||
@@ -90,6 +90,31 @@ export const promise = async <T>({
|
||||
|
||||
// Sanitize incoming data
|
||||
switch (field.type) {
|
||||
case 'array':
|
||||
|
||||
case 'blocks': {
|
||||
// Handle cases of arrays being intentionally set to 0
|
||||
if (siblingData[field.name] === '0' || siblingData[field.name] === 0) {
|
||||
siblingData[field.name] = []
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'checkbox': {
|
||||
if (siblingData[field.name] === 'true') {
|
||||
siblingData[field.name] = true
|
||||
}
|
||||
if (siblingData[field.name] === 'false') {
|
||||
siblingData[field.name] = false
|
||||
}
|
||||
if (siblingData[field.name] === '') {
|
||||
siblingData[field.name] = false
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'number': {
|
||||
if (typeof siblingData[field.name] === 'string') {
|
||||
const value = siblingData[field.name] as string
|
||||
@@ -114,36 +139,8 @@ export const promise = async <T>({
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'checkbox': {
|
||||
if (siblingData[field.name] === 'true') {
|
||||
siblingData[field.name] = true
|
||||
}
|
||||
if (siblingData[field.name] === 'false') {
|
||||
siblingData[field.name] = false
|
||||
}
|
||||
if (siblingData[field.name] === '') {
|
||||
siblingData[field.name] = false
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'richText': {
|
||||
if (typeof siblingData[field.name] === 'string') {
|
||||
try {
|
||||
const richTextJSON = JSON.parse(siblingData[field.name] as string)
|
||||
siblingData[field.name] = richTextJSON
|
||||
} catch {
|
||||
// Disregard this data as it is not valid.
|
||||
// Will be reported to user by field validation
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'relationship':
|
||||
|
||||
case 'upload': {
|
||||
if (
|
||||
siblingData[field.name] === '' ||
|
||||
@@ -230,12 +227,15 @@ export const promise = async <T>({
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'array':
|
||||
case 'blocks': {
|
||||
// Handle cases of arrays being intentionally set to 0
|
||||
if (siblingData[field.name] === '0' || siblingData[field.name] === 0) {
|
||||
siblingData[field.name] = []
|
||||
case 'richText': {
|
||||
if (typeof siblingData[field.name] === 'string') {
|
||||
try {
|
||||
const richTextJSON = JSON.parse(siblingData[field.name] as string)
|
||||
siblingData[field.name] = richTextJSON
|
||||
} catch {
|
||||
// Disregard this data as it is not valid.
|
||||
// Will be reported to user by field validation
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
@@ -305,37 +305,6 @@ export const promise = async <T>({
|
||||
|
||||
// Traverse subfields
|
||||
switch (field.type) {
|
||||
case 'group': {
|
||||
if (typeof siblingData[field.name] !== 'object') {
|
||||
siblingData[field.name] = {}
|
||||
}
|
||||
if (typeof siblingDoc[field.name] !== 'object') {
|
||||
siblingDoc[field.name] = {}
|
||||
}
|
||||
|
||||
const groupData = siblingData[field.name] as Record<string, unknown>
|
||||
const groupDoc = siblingDoc[field.name] as Record<string, unknown>
|
||||
|
||||
await traverseFields({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
fields: field.fields,
|
||||
global,
|
||||
operation,
|
||||
overrideAccess,
|
||||
path: fieldPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: groupData as JsonObject,
|
||||
siblingDoc: groupDoc as JsonObject,
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'array': {
|
||||
const rows = siblingData[field.name]
|
||||
|
||||
@@ -405,8 +374,9 @@ export const promise = async <T>({
|
||||
break
|
||||
}
|
||||
|
||||
case 'row':
|
||||
case 'collapsible': {
|
||||
case 'collapsible':
|
||||
|
||||
case 'row': {
|
||||
await traverseFields({
|
||||
id,
|
||||
collection,
|
||||
@@ -426,6 +396,76 @@ export const promise = async <T>({
|
||||
|
||||
break
|
||||
}
|
||||
case 'group': {
|
||||
if (typeof siblingData[field.name] !== 'object') {
|
||||
siblingData[field.name] = {}
|
||||
}
|
||||
if (typeof siblingDoc[field.name] !== 'object') {
|
||||
siblingDoc[field.name] = {}
|
||||
}
|
||||
|
||||
const groupData = siblingData[field.name] as Record<string, unknown>
|
||||
const groupDoc = siblingDoc[field.name] as Record<string, unknown>
|
||||
|
||||
await traverseFields({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
fields: field.fields,
|
||||
global,
|
||||
operation,
|
||||
overrideAccess,
|
||||
path: fieldPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: groupData as JsonObject,
|
||||
siblingDoc: groupDoc as JsonObject,
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'richText': {
|
||||
if (!field?.editor) {
|
||||
throw new MissingEditorProp(field) // while we allow disabling editor functionality, you should not have any richText fields defined if you do not have an editor
|
||||
}
|
||||
if (typeof field?.editor === 'function') {
|
||||
throw new Error('Attempted to access unsanitized rich text editor.')
|
||||
}
|
||||
|
||||
const editor: RichTextAdapter = field?.editor
|
||||
|
||||
if (editor?.hooks?.beforeValidate?.length) {
|
||||
await editor.hooks.beforeValidate.reduce(async (priorHook, currentHook) => {
|
||||
await priorHook
|
||||
|
||||
const hookedValue = await currentHook({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
field,
|
||||
global,
|
||||
operation,
|
||||
originalDoc: doc,
|
||||
overrideAccess,
|
||||
path: fieldPath,
|
||||
previousSiblingDoc: siblingDoc,
|
||||
previousValue: siblingData[field.name],
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData,
|
||||
value: siblingData[field.name],
|
||||
})
|
||||
|
||||
if (hookedValue !== undefined) {
|
||||
siblingData[field.name] = hookedValue
|
||||
}
|
||||
}, Promise.resolve())
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'tab': {
|
||||
let tabSiblingData
|
||||
@@ -486,46 +526,6 @@ export const promise = async <T>({
|
||||
break
|
||||
}
|
||||
|
||||
case 'richText': {
|
||||
if (!field?.editor) {
|
||||
throw new MissingEditorProp(field) // while we allow disabling editor functionality, you should not have any richText fields defined if you do not have an editor
|
||||
}
|
||||
if (typeof field?.editor === 'function') {
|
||||
throw new Error('Attempted to access unsanitized rich text editor.')
|
||||
}
|
||||
|
||||
const editor: RichTextAdapter = field?.editor
|
||||
|
||||
if (editor?.hooks?.beforeValidate?.length) {
|
||||
await editor.hooks.beforeValidate.reduce(async (priorHook, currentHook) => {
|
||||
await priorHook
|
||||
|
||||
const hookedValue = await currentHook({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
field,
|
||||
global,
|
||||
operation,
|
||||
originalDoc: doc,
|
||||
overrideAccess,
|
||||
path: fieldPath,
|
||||
previousSiblingDoc: siblingDoc,
|
||||
previousValue: siblingData[field.name],
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData,
|
||||
value: siblingData[field.name],
|
||||
})
|
||||
|
||||
if (hookedValue !== undefined) {
|
||||
siblingData[field.name] = hookedValue
|
||||
}
|
||||
}, Promise.resolve())
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -239,123 +239,125 @@ export function fieldsToJSONSchema(
|
||||
let fieldSchema: JSONSchema4
|
||||
|
||||
switch (field.type) {
|
||||
case 'text':
|
||||
if (field.hasMany === true) {
|
||||
fieldSchema = {
|
||||
type: withNullableJSONSchemaType('array', isRequired),
|
||||
items: { type: 'string' },
|
||||
}
|
||||
} else {
|
||||
fieldSchema = { type: withNullableJSONSchemaType('string', isRequired) }
|
||||
case 'array': {
|
||||
fieldSchema = {
|
||||
type: withNullableJSONSchemaType('array', isRequired),
|
||||
items: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
...fieldsToJSONSchema(
|
||||
collectionIDFieldTypes,
|
||||
field.fields,
|
||||
interfaceNameDefinitions,
|
||||
config,
|
||||
),
|
||||
},
|
||||
}
|
||||
break
|
||||
case 'textarea':
|
||||
case 'code':
|
||||
case 'email':
|
||||
case 'date': {
|
||||
fieldSchema = { type: withNullableJSONSchemaType('string', isRequired) }
|
||||
break
|
||||
}
|
||||
|
||||
case 'number': {
|
||||
if (field.hasMany === true) {
|
||||
if (field.interfaceName) {
|
||||
interfaceNameDefinitions.set(field.interfaceName, fieldSchema)
|
||||
|
||||
fieldSchema = {
|
||||
type: withNullableJSONSchemaType('array', isRequired),
|
||||
items: { type: 'number' },
|
||||
$ref: `#/definitions/${field.interfaceName}`,
|
||||
}
|
||||
} else {
|
||||
fieldSchema = { type: withNullableJSONSchemaType('number', isRequired) }
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'blocks': {
|
||||
// Check for a case where no blocks are provided.
|
||||
// We need to generate an empty array for this case, note that JSON schema 4 doesn't support empty arrays
|
||||
// so the best we can get is `unknown[]`
|
||||
const hasBlocks = Boolean(field.blocks.length)
|
||||
|
||||
fieldSchema = {
|
||||
type: withNullableJSONSchemaType('array', isRequired),
|
||||
items: hasBlocks
|
||||
? {
|
||||
oneOf: field.blocks.map((block) => {
|
||||
const blockFieldSchemas = fieldsToJSONSchema(
|
||||
collectionIDFieldTypes,
|
||||
block.fields,
|
||||
interfaceNameDefinitions,
|
||||
config,
|
||||
)
|
||||
|
||||
const blockSchema: JSONSchema4 = {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
...blockFieldSchemas.properties,
|
||||
blockType: {
|
||||
const: block.slug,
|
||||
},
|
||||
},
|
||||
required: ['blockType', ...blockFieldSchemas.required],
|
||||
}
|
||||
|
||||
if (block.interfaceName) {
|
||||
interfaceNameDefinitions.set(block.interfaceName, blockSchema)
|
||||
|
||||
return {
|
||||
$ref: `#/definitions/${block.interfaceName}`,
|
||||
}
|
||||
}
|
||||
|
||||
return blockSchema
|
||||
}),
|
||||
}
|
||||
: {},
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'checkbox': {
|
||||
fieldSchema = { type: withNullableJSONSchemaType('boolean', isRequired) }
|
||||
break
|
||||
}
|
||||
case 'code':
|
||||
case 'date':
|
||||
|
||||
case 'json': {
|
||||
fieldSchema = field.jsonSchema?.schema || {
|
||||
type: ['object', 'array', 'string', 'number', 'boolean', 'null'],
|
||||
}
|
||||
case 'email':
|
||||
|
||||
case 'textarea': {
|
||||
fieldSchema = { type: withNullableJSONSchemaType('string', isRequired) }
|
||||
break
|
||||
}
|
||||
|
||||
case 'richText': {
|
||||
if (!field?.editor) {
|
||||
throw new MissingEditorProp(field) // while we allow disabling editor functionality, you should not have any richText fields defined if you do not have an editor
|
||||
}
|
||||
if (typeof field.editor === 'function') {
|
||||
throw new Error('Attempted to access unsanitized rich text editor.')
|
||||
}
|
||||
if (field.editor.outputSchema) {
|
||||
fieldSchema = field.editor.outputSchema({
|
||||
case 'collapsible':
|
||||
|
||||
case 'row': {
|
||||
const childSchema = fieldsToJSONSchema(
|
||||
collectionIDFieldTypes,
|
||||
field.fields,
|
||||
interfaceNameDefinitions,
|
||||
config,
|
||||
)
|
||||
Object.entries(childSchema.properties).forEach(([propName, propSchema]) => {
|
||||
fieldSchemas.set(propName, propSchema)
|
||||
})
|
||||
childSchema.required.forEach((propName) => {
|
||||
requiredFieldNames.add(propName)
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
case 'group': {
|
||||
fieldSchema = {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
...fieldsToJSONSchema(
|
||||
collectionIDFieldTypes,
|
||||
config,
|
||||
field,
|
||||
field.fields,
|
||||
interfaceNameDefinitions,
|
||||
isRequired,
|
||||
})
|
||||
} else {
|
||||
// Maintain backwards compatibility with existing rich text editors
|
||||
fieldSchema = {
|
||||
type: withNullableJSONSchemaType('array', isRequired),
|
||||
items: {
|
||||
type: 'object',
|
||||
},
|
||||
}
|
||||
config,
|
||||
),
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
if (field.interfaceName) {
|
||||
interfaceNameDefinitions.set(field.interfaceName, fieldSchema)
|
||||
|
||||
case 'radio': {
|
||||
fieldSchema = {
|
||||
type: withNullableJSONSchemaType('string', isRequired),
|
||||
enum: buildOptionEnums(field.options),
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'select': {
|
||||
const optionEnums = buildOptionEnums(field.options)
|
||||
|
||||
if (field.hasMany) {
|
||||
fieldSchema = {
|
||||
type: withNullableJSONSchemaType('array', isRequired),
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
$ref: `#/definitions/${field.interfaceName}`,
|
||||
}
|
||||
if (optionEnums?.length) {
|
||||
;(fieldSchema.items as JSONSchema4).enum = optionEnums
|
||||
}
|
||||
} else {
|
||||
fieldSchema = {
|
||||
type: withNullableJSONSchemaType('string', isRequired),
|
||||
}
|
||||
if (optionEnums?.length) {
|
||||
fieldSchema.enum = optionEnums
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'point': {
|
||||
fieldSchema = {
|
||||
type: withNullableJSONSchemaType('array', isRequired),
|
||||
items: [
|
||||
{
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
},
|
||||
],
|
||||
maxItems: 2,
|
||||
minItems: 2,
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -384,8 +386,53 @@ export function fieldsToJSONSchema(
|
||||
break
|
||||
}
|
||||
|
||||
case 'upload':
|
||||
case 'relationship': {
|
||||
case 'json': {
|
||||
fieldSchema = field.jsonSchema?.schema || {
|
||||
type: ['object', 'array', 'string', 'number', 'boolean', 'null'],
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'number': {
|
||||
if (field.hasMany === true) {
|
||||
fieldSchema = {
|
||||
type: withNullableJSONSchemaType('array', isRequired),
|
||||
items: { type: 'number' },
|
||||
}
|
||||
} else {
|
||||
fieldSchema = { type: withNullableJSONSchemaType('number', isRequired) }
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'point': {
|
||||
fieldSchema = {
|
||||
type: withNullableJSONSchemaType('array', isRequired),
|
||||
items: [
|
||||
{
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
},
|
||||
],
|
||||
maxItems: 2,
|
||||
minItems: 2,
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'radio': {
|
||||
fieldSchema = {
|
||||
type: withNullableJSONSchemaType('string', isRequired),
|
||||
enum: buildOptionEnums(field.options),
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'relationship':
|
||||
|
||||
case 'upload': {
|
||||
if (Array.isArray(field.relationTo)) {
|
||||
if (field.hasMany) {
|
||||
fieldSchema = {
|
||||
@@ -474,91 +521,55 @@ export function fieldsToJSONSchema(
|
||||
break
|
||||
}
|
||||
|
||||
case 'blocks': {
|
||||
// Check for a case where no blocks are provided.
|
||||
// We need to generate an empty array for this case, note that JSON schema 4 doesn't support empty arrays
|
||||
// so the best we can get is `unknown[]`
|
||||
const hasBlocks = Boolean(field.blocks.length)
|
||||
|
||||
fieldSchema = {
|
||||
type: withNullableJSONSchemaType('array', isRequired),
|
||||
items: hasBlocks
|
||||
? {
|
||||
oneOf: field.blocks.map((block) => {
|
||||
const blockFieldSchemas = fieldsToJSONSchema(
|
||||
collectionIDFieldTypes,
|
||||
block.fields,
|
||||
interfaceNameDefinitions,
|
||||
config,
|
||||
)
|
||||
|
||||
const blockSchema: JSONSchema4 = {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
...blockFieldSchemas.properties,
|
||||
blockType: {
|
||||
const: block.slug,
|
||||
},
|
||||
},
|
||||
required: ['blockType', ...blockFieldSchemas.required],
|
||||
}
|
||||
|
||||
if (block.interfaceName) {
|
||||
interfaceNameDefinitions.set(block.interfaceName, blockSchema)
|
||||
|
||||
return {
|
||||
$ref: `#/definitions/${block.interfaceName}`,
|
||||
}
|
||||
}
|
||||
|
||||
return blockSchema
|
||||
}),
|
||||
}
|
||||
: {},
|
||||
case 'richText': {
|
||||
if (!field?.editor) {
|
||||
throw new MissingEditorProp(field) // while we allow disabling editor functionality, you should not have any richText fields defined if you do not have an editor
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'array': {
|
||||
fieldSchema = {
|
||||
type: withNullableJSONSchemaType('array', isRequired),
|
||||
items: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
...fieldsToJSONSchema(
|
||||
collectionIDFieldTypes,
|
||||
field.fields,
|
||||
interfaceNameDefinitions,
|
||||
config,
|
||||
),
|
||||
},
|
||||
if (typeof field.editor === 'function') {
|
||||
throw new Error('Attempted to access unsanitized rich text editor.')
|
||||
}
|
||||
|
||||
if (field.interfaceName) {
|
||||
interfaceNameDefinitions.set(field.interfaceName, fieldSchema)
|
||||
|
||||
if (field.editor.outputSchema) {
|
||||
fieldSchema = field.editor.outputSchema({
|
||||
collectionIDFieldTypes,
|
||||
config,
|
||||
field,
|
||||
interfaceNameDefinitions,
|
||||
isRequired,
|
||||
})
|
||||
} else {
|
||||
// Maintain backwards compatibility with existing rich text editors
|
||||
fieldSchema = {
|
||||
$ref: `#/definitions/${field.interfaceName}`,
|
||||
type: withNullableJSONSchemaType('array', isRequired),
|
||||
items: {
|
||||
type: 'object',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
case 'select': {
|
||||
const optionEnums = buildOptionEnums(field.options)
|
||||
|
||||
if (field.hasMany) {
|
||||
fieldSchema = {
|
||||
type: withNullableJSONSchemaType('array', isRequired),
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
}
|
||||
if (optionEnums?.length) {
|
||||
;(fieldSchema.items as JSONSchema4).enum = optionEnums
|
||||
}
|
||||
} else {
|
||||
fieldSchema = {
|
||||
type: withNullableJSONSchemaType('string', isRequired),
|
||||
}
|
||||
if (optionEnums?.length) {
|
||||
fieldSchema.enum = optionEnums
|
||||
}
|
||||
}
|
||||
|
||||
case 'row':
|
||||
case 'collapsible': {
|
||||
const childSchema = fieldsToJSONSchema(
|
||||
collectionIDFieldTypes,
|
||||
field.fields,
|
||||
interfaceNameDefinitions,
|
||||
config,
|
||||
)
|
||||
Object.entries(childSchema.properties).forEach(([propName, propSchema]) => {
|
||||
fieldSchemas.set(propName, propSchema)
|
||||
})
|
||||
childSchema.required.forEach((propName) => {
|
||||
requiredFieldNames.add(propName)
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
@@ -596,27 +607,16 @@ export function fieldsToJSONSchema(
|
||||
break
|
||||
}
|
||||
|
||||
case 'group': {
|
||||
fieldSchema = {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
...fieldsToJSONSchema(
|
||||
collectionIDFieldTypes,
|
||||
field.fields,
|
||||
interfaceNameDefinitions,
|
||||
config,
|
||||
),
|
||||
}
|
||||
|
||||
if (field.interfaceName) {
|
||||
interfaceNameDefinitions.set(field.interfaceName, fieldSchema)
|
||||
|
||||
case 'text':
|
||||
if (field.hasMany === true) {
|
||||
fieldSchema = {
|
||||
$ref: `#/definitions/${field.interfaceName}`,
|
||||
type: withNullableJSONSchemaType('array', isRequired),
|
||||
items: { type: 'string' },
|
||||
}
|
||||
} else {
|
||||
fieldSchema = { type: withNullableJSONSchemaType('string', isRequired) }
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
break
|
||||
@@ -704,15 +704,6 @@ export function fieldsToSelectJSONSchema({ fields }: { fields: Field[] }): JSONS
|
||||
|
||||
for (const field of fields) {
|
||||
switch (field.type) {
|
||||
case 'row':
|
||||
case 'collapsible':
|
||||
schema.properties = {
|
||||
...schema.properties,
|
||||
...fieldsToSelectJSONSchema({ fields: field.fields }).properties,
|
||||
}
|
||||
|
||||
break
|
||||
|
||||
case 'array':
|
||||
case 'group':
|
||||
schema.properties[field.name] = {
|
||||
@@ -725,27 +716,6 @@ export function fieldsToSelectJSONSchema({ fields }: { fields: Field[] }): JSONS
|
||||
}
|
||||
break
|
||||
|
||||
case 'tabs':
|
||||
for (const tab of field.tabs) {
|
||||
if (tabHasName(tab)) {
|
||||
schema.properties[tab.name] = {
|
||||
oneOf: [
|
||||
{
|
||||
type: 'boolean',
|
||||
},
|
||||
fieldsToSelectJSONSchema({ fields: tab.fields }),
|
||||
],
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
schema.properties = {
|
||||
...schema.properties,
|
||||
...fieldsToSelectJSONSchema({ fields: tab.fields }).properties,
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
case 'blocks': {
|
||||
const blocksSchema: JSONSchema4 = {
|
||||
type: 'object',
|
||||
@@ -775,6 +745,36 @@ export function fieldsToSelectJSONSchema({ fields }: { fields: Field[] }): JSONS
|
||||
|
||||
break
|
||||
}
|
||||
case 'collapsible':
|
||||
|
||||
case 'row':
|
||||
schema.properties = {
|
||||
...schema.properties,
|
||||
...fieldsToSelectJSONSchema({ fields: field.fields }).properties,
|
||||
}
|
||||
|
||||
break
|
||||
|
||||
case 'tabs':
|
||||
for (const tab of field.tabs) {
|
||||
if (tabHasName(tab)) {
|
||||
schema.properties[tab.name] = {
|
||||
oneOf: [
|
||||
{
|
||||
type: 'boolean',
|
||||
},
|
||||
fieldsToSelectJSONSchema({ fields: tab.fields }),
|
||||
],
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
schema.properties = {
|
||||
...schema.properties,
|
||||
...fieldsToSelectJSONSchema({ fields: tab.fields }).properties,
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
schema.properties[field.name] = {
|
||||
@@ -800,6 +800,34 @@ const generateAuthFieldTypes = ({
|
||||
}): JSONSchema4 => {
|
||||
if (loginWithUsername) {
|
||||
switch (type) {
|
||||
case 'forgotOrUnlock': {
|
||||
if (loginWithUsername.allowEmailLogin) {
|
||||
// allow email or username for unlock/forgot-password
|
||||
return {
|
||||
additionalProperties: false,
|
||||
oneOf: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: { email: fieldType },
|
||||
required: ['email'],
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: { username: fieldType },
|
||||
required: ['username'],
|
||||
},
|
||||
],
|
||||
}
|
||||
} else {
|
||||
// allow only username for unlock/forgot-password
|
||||
return {
|
||||
additionalProperties: false,
|
||||
properties: { username: fieldType },
|
||||
required: ['username'],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case 'login': {
|
||||
if (loginWithUsername.allowEmailLogin) {
|
||||
// allow username or email and require password for login
|
||||
@@ -858,34 +886,6 @@ const generateAuthFieldTypes = ({
|
||||
required: requiredFields,
|
||||
}
|
||||
}
|
||||
|
||||
case 'forgotOrUnlock': {
|
||||
if (loginWithUsername.allowEmailLogin) {
|
||||
// allow email or username for unlock/forgot-password
|
||||
return {
|
||||
additionalProperties: false,
|
||||
oneOf: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: { email: fieldType },
|
||||
required: ['email'],
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: { username: fieldType },
|
||||
required: ['username'],
|
||||
},
|
||||
],
|
||||
}
|
||||
} else {
|
||||
// allow only username for unlock/forgot-password
|
||||
return {
|
||||
additionalProperties: false,
|
||||
properties: { username: fieldType },
|
||||
required: ['username'],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,15 +16,6 @@ export const fieldSchemaToJSON = (fields: ClientField[]): FieldSchemaJSON => {
|
||||
let result = acc
|
||||
|
||||
switch (field.type) {
|
||||
case 'group':
|
||||
acc.push({
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
fields: fieldSchemaToJSON(field.fields),
|
||||
})
|
||||
|
||||
break
|
||||
|
||||
case 'array':
|
||||
acc.push({
|
||||
name: field.name,
|
||||
@@ -61,11 +52,31 @@ export const fieldSchemaToJSON = (fields: ClientField[]): FieldSchemaJSON => {
|
||||
|
||||
break
|
||||
|
||||
case 'row':
|
||||
case 'collapsible':
|
||||
|
||||
case 'row':
|
||||
result = result.concat(fieldSchemaToJSON(field.fields))
|
||||
break
|
||||
case 'group':
|
||||
acc.push({
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
fields: fieldSchemaToJSON(field.fields),
|
||||
})
|
||||
|
||||
break
|
||||
|
||||
case 'relationship':
|
||||
|
||||
case 'upload':
|
||||
acc.push({
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
hasMany: 'hasMany' in field ? Boolean(field.hasMany) : false, // TODO: type this
|
||||
relationTo: field.relationTo,
|
||||
})
|
||||
|
||||
break
|
||||
case 'tabs': {
|
||||
let tabFields = []
|
||||
|
||||
@@ -87,17 +98,6 @@ export const fieldSchemaToJSON = (fields: ClientField[]): FieldSchemaJSON => {
|
||||
break
|
||||
}
|
||||
|
||||
case 'relationship':
|
||||
case 'upload':
|
||||
acc.push({
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
hasMany: 'hasMany' in field ? Boolean(field.hasMany) : false, // TODO: type this
|
||||
relationTo: field.relationTo,
|
||||
})
|
||||
|
||||
break
|
||||
|
||||
default:
|
||||
if ('name' in field) {
|
||||
acc.push({
|
||||
|
||||
@@ -21,7 +21,7 @@ import { envPaths } from './envPaths.js'
|
||||
const createPlainObject = <T = Record<string, unknown>>(): T => Object.create(null)
|
||||
|
||||
const checkValueType = (key: string, value: unknown): void => {
|
||||
const nonJsonTypes = new Set(['undefined', 'symbol', 'function'])
|
||||
const nonJsonTypes = new Set(['function', 'symbol', 'undefined'])
|
||||
|
||||
const type = typeof value
|
||||
|
||||
|
||||
@@ -28,16 +28,16 @@ export const getPaymentTotal = (
|
||||
total += valueToUse
|
||||
break
|
||||
}
|
||||
case 'subtract': {
|
||||
total -= valueToUse
|
||||
case 'divide': {
|
||||
total /= valueToUse
|
||||
break
|
||||
}
|
||||
case 'multiply': {
|
||||
total *= valueToUse
|
||||
break
|
||||
}
|
||||
case 'divide': {
|
||||
total /= valueToUse
|
||||
case 'subtract': {
|
||||
total -= valueToUse
|
||||
break
|
||||
}
|
||||
default: {
|
||||
|
||||
@@ -91,6 +91,30 @@ export const serializeSlate = (children?: Node[], submissionData?: any): string
|
||||
<h6>
|
||||
${serializeSlate(node.children, submissionData)}
|
||||
</h6>
|
||||
`
|
||||
case 'indent':
|
||||
return `
|
||||
<p style="padding-left: 20px">
|
||||
${serializeSlate(node.children, submissionData)}
|
||||
</p>
|
||||
`
|
||||
case 'li':
|
||||
return `
|
||||
<li>
|
||||
${serializeSlate(node.children, submissionData)}
|
||||
</li>
|
||||
`
|
||||
case 'link':
|
||||
return `
|
||||
<a href={${escapeHTML(replaceDoubleCurlys(node.url, submissionData))}}>
|
||||
${serializeSlate(node.children, submissionData)}
|
||||
</a>
|
||||
`
|
||||
case 'ol':
|
||||
return `
|
||||
<ol>
|
||||
${serializeSlate(node.children, submissionData)}
|
||||
</ol>
|
||||
`
|
||||
case 'quote':
|
||||
return `
|
||||
@@ -104,30 +128,6 @@ export const serializeSlate = (children?: Node[], submissionData?: any): string
|
||||
${serializeSlate(node.children, submissionData)}
|
||||
</ul>
|
||||
`
|
||||
case 'ol':
|
||||
return `
|
||||
<ol>
|
||||
${serializeSlate(node.children, submissionData)}
|
||||
</ol>
|
||||
`
|
||||
case 'li':
|
||||
return `
|
||||
<li>
|
||||
${serializeSlate(node.children, submissionData)}
|
||||
</li>
|
||||
`
|
||||
case 'indent':
|
||||
return `
|
||||
<p style="padding-left: 20px">
|
||||
${serializeSlate(node.children, submissionData)}
|
||||
</p>
|
||||
`
|
||||
case 'link':
|
||||
return `
|
||||
<a href={${escapeHTML(replaceDoubleCurlys(node.url, submissionData))}}>
|
||||
${serializeSlate(node.children, submissionData)}
|
||||
</a>
|
||||
`
|
||||
|
||||
default:
|
||||
return `
|
||||
|
||||
@@ -30,8 +30,8 @@ export const handleWebhooks: StripeWebhookHandler = (args) => {
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'updated': {
|
||||
void handleCreatedOrUpdated({
|
||||
case 'deleted': {
|
||||
void handleDeleted({
|
||||
...args,
|
||||
pluginConfig,
|
||||
resourceType,
|
||||
@@ -39,8 +39,8 @@ export const handleWebhooks: StripeWebhookHandler = (args) => {
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'deleted': {
|
||||
void handleDeleted({
|
||||
case 'updated': {
|
||||
void handleCreatedOrUpdated({
|
||||
...args,
|
||||
pluginConfig,
|
||||
resourceType,
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
"babel-plugin-transform-remove-imports": "^1.8.0",
|
||||
"esbuild": "0.23.1",
|
||||
"esbuild-sass-plugin": "3.3.1",
|
||||
"eslint-plugin-react-compiler": "0.0.0-experimental-7670337-20240918",
|
||||
"eslint-plugin-react-compiler": "19.0.0-beta-a7bf2bd-20241110",
|
||||
"payload": "workspace:*",
|
||||
"swc-plugin-transform-remove-imports": "1.15.0"
|
||||
},
|
||||
|
||||
@@ -45,17 +45,14 @@ const formatStep = (step: Step) => {
|
||||
case 'click': {
|
||||
return ` await page.mouse.click(${value.x}, ${value.y});`
|
||||
}
|
||||
case 'press': {
|
||||
return ` await page.keyboard.press('${value}');`
|
||||
}
|
||||
case 'keydown': {
|
||||
return ` await page.keyboard.keydown('${value}');`
|
||||
}
|
||||
case 'keyup': {
|
||||
return ` await page.keyboard.keyup('${value}');`
|
||||
}
|
||||
case 'type': {
|
||||
return ` await page.keyboard.type('${value}');`
|
||||
case 'press': {
|
||||
return ` await page.keyboard.press('${value}');`
|
||||
}
|
||||
case 'selectAll': {
|
||||
return ` await selectAll(page);`
|
||||
@@ -70,6 +67,9 @@ const formatStep = (step: Step) => {
|
||||
});
|
||||
`
|
||||
}
|
||||
case 'type': {
|
||||
return ` await page.keyboard.type('${value}');`
|
||||
}
|
||||
default:
|
||||
return ``
|
||||
}
|
||||
@@ -119,14 +119,14 @@ function getPathFromNodeToEditor(node: Node, rootElement: HTMLElement | null) {
|
||||
}
|
||||
|
||||
const keyPresses = new Set([
|
||||
'Enter',
|
||||
'Backspace',
|
||||
'Delete',
|
||||
'Escape',
|
||||
'ArrowDown',
|
||||
'ArrowLeft',
|
||||
'ArrowRight',
|
||||
'ArrowUp',
|
||||
'ArrowDown',
|
||||
'Backspace',
|
||||
'Delete',
|
||||
'Enter',
|
||||
'Escape',
|
||||
])
|
||||
|
||||
type Step = {
|
||||
|
||||
@@ -84,7 +84,7 @@ export const ToolbarButton = ({
|
||||
className={className}
|
||||
onClick={() => {
|
||||
if (enabled !== false) {
|
||||
editor._updateTags = new Set([...editor._updateTags, 'toolbar']) // without setting the tags, our onSelect will not be able to trigger our onChange as focus onChanges are ignored.
|
||||
editor._updateTags = new Set(['toolbar', ...editor._updateTags]) // without setting the tags, our onSelect will not be able to trigger our onChange as focus onChanges are ignored.
|
||||
|
||||
editor.focus(() => {
|
||||
// We need to wrap the onSelect in the callback, so the editor is properly focused before the onSelect is called.
|
||||
|
||||
@@ -65,7 +65,7 @@ export function DropDownItem({
|
||||
className={className}
|
||||
onClick={() => {
|
||||
if (enabled !== false) {
|
||||
editor._updateTags = new Set([...editor._updateTags, 'toolbar']) // without setting the tags, our onSelect will not be able to trigger our onChange as focus onChanges are ignored.
|
||||
editor._updateTags = new Set(['toolbar', ...editor._updateTags]) // without setting the tags, our onSelect will not be able to trigger our onChange as focus onChanges are ignored.
|
||||
|
||||
editor.focus(() => {
|
||||
// We need to wrap the onSelect in the callback, so the editor is properly focused before the onSelect is called.
|
||||
|
||||
@@ -124,27 +124,27 @@ const RichTextField: React.FC<LoadedSlateFieldProps> = (props) => {
|
||||
if (element.textAlign) {
|
||||
if (element.type === 'relationship' || element.type === 'upload') {
|
||||
switch (element.textAlign) {
|
||||
case 'center':
|
||||
attr = { ...attr, style: { marginLeft: 'auto', marginRight: 'auto' } }
|
||||
break
|
||||
case 'left':
|
||||
attr = { ...attr, style: { marginRight: 'auto' } }
|
||||
break
|
||||
case 'right':
|
||||
attr = { ...attr, style: { marginLeft: 'auto' } }
|
||||
break
|
||||
case 'center':
|
||||
attr = { ...attr, style: { marginLeft: 'auto', marginRight: 'auto' } }
|
||||
break
|
||||
default:
|
||||
attr = { ...attr, style: { textAlign: element.textAlign } }
|
||||
break
|
||||
}
|
||||
} else if (element.type === 'li') {
|
||||
switch (element.textAlign) {
|
||||
case 'right':
|
||||
attr = { ...attr, style: { listStylePosition: 'inside', textAlign: 'right' } }
|
||||
break
|
||||
case 'center':
|
||||
attr = { ...attr, style: { listStylePosition: 'inside', textAlign: 'center' } }
|
||||
break
|
||||
case 'right':
|
||||
attr = { ...attr, style: { listStylePosition: 'inside', textAlign: 'right' } }
|
||||
break
|
||||
case 'left':
|
||||
default:
|
||||
attr = { ...attr, style: { listStylePosition: 'outside', textAlign: 'left' } }
|
||||
|
||||
@@ -150,6 +150,9 @@ export const RscEntrySlateField: React.FC<
|
||||
break
|
||||
}
|
||||
|
||||
case 'relationship':
|
||||
break
|
||||
|
||||
case 'upload': {
|
||||
const uploadEnabledCollections = payload.config.collections.filter(
|
||||
({ admin: { enableRichTextRelationship, hidden }, upload }) => {
|
||||
@@ -179,9 +182,6 @@ export const RscEntrySlateField: React.FC<
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'relationship':
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -42,6 +42,9 @@ export const getGenerateSchemaMap =
|
||||
break
|
||||
}
|
||||
|
||||
case 'relationship':
|
||||
break
|
||||
|
||||
case 'upload': {
|
||||
const uploadEnabledCollections = config.collections.filter(
|
||||
({ admin: { enableRichTextRelationship, hidden }, upload }) => {
|
||||
@@ -73,9 +76,6 @@ export const getGenerateSchemaMap =
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'relationship':
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -161,12 +161,12 @@ export type InitTFunction<
|
||||
}
|
||||
|
||||
export type InitI18n =
|
||||
| ((args: { config: I18nOptions; context: 'api'; language: AcceptedLanguages }) => Promise<I18n>)
|
||||
| ((args: {
|
||||
config: I18nOptions<ClientTranslationsObject>
|
||||
context: 'client'
|
||||
language: AcceptedLanguages
|
||||
}) => Promise<I18n<ClientTranslationsObject, ClientTranslationKeys>>)
|
||||
| ((args: { config: I18nOptions; context: 'api'; language: AcceptedLanguages }) => Promise<I18n>)
|
||||
|
||||
export type LanguagePreference = {
|
||||
language: AcceptedLanguages
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
"babel-plugin-react-compiler": "0.0.0-experimental-24ec0eb-20240918",
|
||||
"esbuild": "0.23.1",
|
||||
"esbuild-sass-plugin": "3.3.1",
|
||||
"eslint-plugin-react-compiler": "0.0.0-experimental-7670337-20240918",
|
||||
"eslint-plugin-react-compiler": "19.0.0-beta-a7bf2bd-20241110",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import LinkImport from 'next/link.js' // TODO: abstract this out to support all routers
|
||||
import type { MouseEvent } from 'react'
|
||||
|
||||
import LinkImport from 'next/link.js' // TODO: abstract this out to support all routers
|
||||
import React from 'react'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
@@ -26,11 +26,11 @@ export function AddingFilesView() {
|
||||
activeIndex,
|
||||
collectionSlug,
|
||||
docPermissions,
|
||||
documentSlots,
|
||||
forms,
|
||||
hasPublishPermission,
|
||||
hasSavePermission,
|
||||
hasSubmitted,
|
||||
documentSlots,
|
||||
} = useFormsManager()
|
||||
const activeForm = forms[activeIndex]
|
||||
const { getEntityConfig } = useConfig()
|
||||
|
||||
@@ -35,18 +35,6 @@ type Action =
|
||||
|
||||
export function formsManagementReducer(state: State, action: Action): State {
|
||||
switch (action.type) {
|
||||
case 'REPLACE': {
|
||||
return {
|
||||
...state,
|
||||
...action.state,
|
||||
}
|
||||
}
|
||||
case 'SET_ACTIVE_INDEX': {
|
||||
return {
|
||||
...state,
|
||||
activeIndex: action.index,
|
||||
}
|
||||
}
|
||||
case 'ADD_FORMS': {
|
||||
const newForms: State['forms'] = []
|
||||
for (let i = 0; i < action.files.length; i++) {
|
||||
@@ -89,6 +77,18 @@ export function formsManagementReducer(state: State, action: Action): State {
|
||||
totalErrorCount: state.totalErrorCount - removedForm.errorCount,
|
||||
}
|
||||
}
|
||||
case 'REPLACE': {
|
||||
return {
|
||||
...state,
|
||||
...action.state,
|
||||
}
|
||||
}
|
||||
case 'SET_ACTIVE_INDEX': {
|
||||
return {
|
||||
...state,
|
||||
activeIndex: action.index,
|
||||
}
|
||||
}
|
||||
case 'UPDATE_ERROR_COUNT': {
|
||||
const forms = [...state.forms]
|
||||
forms[action.index].errorCount = action.count
|
||||
|
||||
@@ -8,6 +8,8 @@ import { useRouter } from 'next/navigation.js'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import type { DocumentDrawerContextType } from '../DocumentDrawer/Provider.js'
|
||||
|
||||
import { useForm, useFormModified } from '../../forms/Form/context.js'
|
||||
import { useConfig } from '../../providers/Config/index.js'
|
||||
import { useEditDepth } from '../../providers/EditDepth/index.js'
|
||||
@@ -18,7 +20,6 @@ import { formatAdminURL } from '../../utilities/formatAdminURL.js'
|
||||
import { Button } from '../Button/index.js'
|
||||
import { drawerZBase } from '../Drawer/index.js'
|
||||
import { PopupList } from '../Popup/index.js'
|
||||
import { DocumentDrawerContextType } from '../DocumentDrawer/Provider.jsx'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'duplicate'
|
||||
|
||||
@@ -119,8 +119,8 @@ export const RelationshipCell: React.FC<RelationshipCellProps> = ({
|
||||
if (previewAllowed && document) {
|
||||
fileField = (
|
||||
<FileCell
|
||||
collectionConfig={relatedCollection}
|
||||
cellData={label}
|
||||
collectionConfig={relatedCollection}
|
||||
customCellProps={customCellContext}
|
||||
field={field}
|
||||
rowData={document}
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
'use client'
|
||||
import LinkImport from 'next/link.js'
|
||||
import React from 'react' // TODO: abstract this out to support all routers
|
||||
|
||||
import type { DefaultCellComponentProps, UploadFieldClient } from 'payload'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import LinkImport from 'next/link.js'
|
||||
import { fieldAffectsData } from 'payload/shared'
|
||||
import React from 'react' // TODO: abstract this out to support all routers
|
||||
|
||||
import { useConfig } from '../../../providers/Config/index.js'
|
||||
import { useTranslation } from '../../../providers/Translation/index.js'
|
||||
import { formatAdminURL } from '../../../utilities/formatAdminURL.js'
|
||||
import { CodeCell } from './fields/Code/index.js'
|
||||
import { cellComponents } from './fields/index.js'
|
||||
|
||||
const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default
|
||||
|
||||
export const DefaultCell: React.FC<DefaultCellComponentProps> = (props) => {
|
||||
|
||||
@@ -18,19 +18,6 @@ type Action = AddLoadedDocuments | RequestDocuments
|
||||
|
||||
export function reducer(state: Documents, action: Action): Documents {
|
||||
switch (action.type) {
|
||||
case 'REQUEST': {
|
||||
const newState = { ...state }
|
||||
|
||||
action.docs.forEach(({ relationTo, value }) => {
|
||||
if (typeof newState[relationTo] !== 'object') {
|
||||
newState[relationTo] = {}
|
||||
}
|
||||
newState[relationTo][value] = null
|
||||
})
|
||||
|
||||
return newState
|
||||
}
|
||||
|
||||
case 'ADD_LOADED': {
|
||||
const newState = { ...state }
|
||||
if (typeof newState[action.relationTo] !== 'object') {
|
||||
@@ -52,6 +39,19 @@ export function reducer(state: Documents, action: Action): Documents {
|
||||
return newState
|
||||
}
|
||||
|
||||
case 'REQUEST': {
|
||||
const newState = { ...state }
|
||||
|
||||
action.docs.forEach(({ relationTo, value }) => {
|
||||
if (typeof newState[relationTo] !== 'object') {
|
||||
newState[relationTo] = {}
|
||||
}
|
||||
newState[relationTo][value] = null
|
||||
})
|
||||
|
||||
return newState
|
||||
}
|
||||
|
||||
default: {
|
||||
return state
|
||||
}
|
||||
|
||||
@@ -43,9 +43,9 @@ export const DefaultFilter: React.FC<Props> = ({
|
||||
}
|
||||
|
||||
switch (internalField?.field?.type) {
|
||||
case 'number': {
|
||||
case 'date': {
|
||||
return (
|
||||
<NumberField
|
||||
<DateField
|
||||
disabled={disabled}
|
||||
field={internalField.field}
|
||||
onChange={onChange}
|
||||
@@ -55,9 +55,9 @@ export const DefaultFilter: React.FC<Props> = ({
|
||||
)
|
||||
}
|
||||
|
||||
case 'date': {
|
||||
case 'number': {
|
||||
return (
|
||||
<DateField
|
||||
<NumberField
|
||||
disabled={disabled}
|
||||
field={internalField.field}
|
||||
onChange={onChange}
|
||||
|
||||
@@ -14,10 +14,6 @@ const reduceToIDs = (options) =>
|
||||
|
||||
const optionsReducer = (state: Option[], action: Action): Option[] => {
|
||||
switch (action.type) {
|
||||
case 'CLEAR': {
|
||||
return action.required ? [] : [{ label: action.i18n.t('general:none'), value: 'null' }]
|
||||
}
|
||||
|
||||
case 'ADD': {
|
||||
const { collection, data, hasMultipleRelations, i18n, relation } = action
|
||||
|
||||
@@ -79,6 +75,10 @@ const optionsReducer = (state: Option[], action: Action): Option[] => {
|
||||
return newOptions
|
||||
}
|
||||
|
||||
case 'CLEAR': {
|
||||
return action.required ? [] : [{ label: action.i18n.t('general:none'), value: 'null' }]
|
||||
}
|
||||
|
||||
default: {
|
||||
return state
|
||||
}
|
||||
|
||||
@@ -28,61 +28,6 @@ const sortOptions = (options: Option[]): Option[] =>
|
||||
|
||||
export const optionsReducer = (state: OptionGroup[], action: Action): OptionGroup[] => {
|
||||
switch (action.type) {
|
||||
case 'CLEAR': {
|
||||
const exemptValues = action.exemptValues
|
||||
? Array.isArray(action.exemptValues)
|
||||
? action.exemptValues
|
||||
: [action.exemptValues]
|
||||
: []
|
||||
|
||||
const clearedStateWithExemptValues = state.filter((optionGroup) => {
|
||||
const clearedOptions = optionGroup.options.filter((option) => {
|
||||
if (exemptValues) {
|
||||
return exemptValues.some((exemptValue) => {
|
||||
return (
|
||||
exemptValue &&
|
||||
option.value === (typeof exemptValue === 'object' ? exemptValue.value : exemptValue)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
optionGroup.options = clearedOptions
|
||||
|
||||
return clearedOptions.length > 0
|
||||
})
|
||||
|
||||
return clearedStateWithExemptValues
|
||||
}
|
||||
|
||||
case 'UPDATE': {
|
||||
const { collection, config, doc, i18n } = action
|
||||
const relation = collection.slug
|
||||
const newOptions = [...state]
|
||||
|
||||
const docTitle = formatDocTitle({
|
||||
collectionConfig: collection,
|
||||
data: doc,
|
||||
dateFormat: config.admin.dateFormat,
|
||||
fallback: `${i18n.t('general:untitled')} - ID: ${doc.id}`,
|
||||
i18n,
|
||||
})
|
||||
|
||||
const foundOptionGroup = newOptions.find(
|
||||
(optionGroup) => optionGroup.label === collection.labels.plural,
|
||||
)
|
||||
const foundOption = foundOptionGroup?.options?.find((option) => option.value === doc.id)
|
||||
|
||||
if (foundOption) {
|
||||
foundOption.label = docTitle
|
||||
foundOption.relationTo = relation
|
||||
}
|
||||
|
||||
return newOptions
|
||||
}
|
||||
|
||||
case 'ADD': {
|
||||
const { collection, config, docs, i18n, ids = [], sort } = action
|
||||
const relation = collection.slug
|
||||
@@ -146,6 +91,35 @@ export const optionsReducer = (state: OptionGroup[], action: Action): OptionGrou
|
||||
return newOptions
|
||||
}
|
||||
|
||||
case 'CLEAR': {
|
||||
const exemptValues = action.exemptValues
|
||||
? Array.isArray(action.exemptValues)
|
||||
? action.exemptValues
|
||||
: [action.exemptValues]
|
||||
: []
|
||||
|
||||
const clearedStateWithExemptValues = state.filter((optionGroup) => {
|
||||
const clearedOptions = optionGroup.options.filter((option) => {
|
||||
if (exemptValues) {
|
||||
return exemptValues.some((exemptValue) => {
|
||||
return (
|
||||
exemptValue &&
|
||||
option.value === (typeof exemptValue === 'object' ? exemptValue.value : exemptValue)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
optionGroup.options = clearedOptions
|
||||
|
||||
return clearedOptions.length > 0
|
||||
})
|
||||
|
||||
return clearedStateWithExemptValues
|
||||
}
|
||||
|
||||
case 'REMOVE': {
|
||||
const { id, collection } = action
|
||||
|
||||
@@ -167,6 +141,32 @@ export const optionsReducer = (state: OptionGroup[], action: Action): OptionGrou
|
||||
return newOptions
|
||||
}
|
||||
|
||||
case 'UPDATE': {
|
||||
const { collection, config, doc, i18n } = action
|
||||
const relation = collection.slug
|
||||
const newOptions = [...state]
|
||||
|
||||
const docTitle = formatDocTitle({
|
||||
collectionConfig: collection,
|
||||
data: doc,
|
||||
dateFormat: config.admin.dateFormat,
|
||||
fallback: `${i18n.t('general:untitled')} - ID: ${doc.id}`,
|
||||
i18n,
|
||||
})
|
||||
|
||||
const foundOptionGroup = newOptions.find(
|
||||
(optionGroup) => optionGroup.label === collection.labels.plural,
|
||||
)
|
||||
const foundOption = foundOptionGroup?.options?.find((option) => option.value === doc.id)
|
||||
|
||||
if (foundOption) {
|
||||
foundOption.label = docTitle
|
||||
foundOption.relationTo = relation
|
||||
}
|
||||
|
||||
return newOptions
|
||||
}
|
||||
|
||||
default: {
|
||||
return state
|
||||
}
|
||||
|
||||
@@ -17,36 +17,53 @@ const ObjectId = (ObjectIdImport.default ||
|
||||
*/
|
||||
export function fieldReducer(state: FormState, action: FieldAction): FormState {
|
||||
switch (action.type) {
|
||||
case 'REPLACE_STATE': {
|
||||
if (action.optimize !== false) {
|
||||
// Only update fields that have changed
|
||||
// by comparing old value / initialValue to new
|
||||
// ..
|
||||
// This is a performance enhancement for saving
|
||||
// large documents with hundreds of fields
|
||||
const newState = {}
|
||||
case 'ADD_ROW': {
|
||||
const { blockType, path, rowIndex: rowIndexFromArgs, subFieldState = {} } = action
|
||||
|
||||
Object.entries(action.state).forEach(([path, field]) => {
|
||||
const oldField = state[path]
|
||||
const newField = field
|
||||
const rowIndex =
|
||||
typeof rowIndexFromArgs === 'number' ? rowIndexFromArgs : state[path]?.rows?.length || 0
|
||||
|
||||
if (!dequal(oldField, newField)) {
|
||||
newState[path] = newField
|
||||
} else if (oldField) {
|
||||
newState[path] = oldField
|
||||
}
|
||||
})
|
||||
return newState
|
||||
const withNewRow = [...(state[path]?.rows || [])]
|
||||
|
||||
const newRow: Row = {
|
||||
id: (subFieldState?.id?.value as string) || new ObjectId().toHexString(),
|
||||
blockType: blockType || undefined,
|
||||
collapsed: false,
|
||||
}
|
||||
// If we're not optimizing, just set the state to the new state
|
||||
return action.state
|
||||
}
|
||||
|
||||
case 'REMOVE': {
|
||||
const newState = { ...state }
|
||||
if (newState[action.path]) {
|
||||
delete newState[action.path]
|
||||
withNewRow.splice(rowIndex, 0, newRow)
|
||||
|
||||
if (blockType) {
|
||||
subFieldState.blockType = {
|
||||
initialValue: blockType,
|
||||
valid: true,
|
||||
value: blockType,
|
||||
}
|
||||
}
|
||||
|
||||
// add new row to array _field state_
|
||||
const { remainingFields, rows: siblingRows } = separateRows(path, state)
|
||||
siblingRows.splice(rowIndex, 0, subFieldState)
|
||||
|
||||
const newState: FormState = {
|
||||
...remainingFields,
|
||||
...flattenRows(path, siblingRows),
|
||||
[`${path}.${rowIndex}.id`]: {
|
||||
initialValue: newRow.id,
|
||||
passesCondition: true,
|
||||
requiresRender: true,
|
||||
valid: true,
|
||||
value: newRow.id,
|
||||
},
|
||||
[path]: {
|
||||
...state[path],
|
||||
disableFormData: true,
|
||||
requiresRender: true,
|
||||
rows: withNewRow,
|
||||
value: siblingRows.length,
|
||||
},
|
||||
}
|
||||
|
||||
return newState
|
||||
}
|
||||
|
||||
@@ -112,160 +129,6 @@ export function fieldReducer(state: FormState, action: FieldAction): FormState {
|
||||
return newState
|
||||
}
|
||||
|
||||
case 'UPDATE': {
|
||||
const newField = Object.entries(action).reduce(
|
||||
(field, [key, value]) => {
|
||||
if (
|
||||
[
|
||||
'disableFormData',
|
||||
'errorMessage',
|
||||
'initialValue',
|
||||
'rows',
|
||||
'valid',
|
||||
'validate',
|
||||
'value',
|
||||
].includes(key)
|
||||
) {
|
||||
return {
|
||||
...field,
|
||||
[key]: value,
|
||||
}
|
||||
}
|
||||
|
||||
return field
|
||||
},
|
||||
state[action.path] || ({} as FormField),
|
||||
)
|
||||
|
||||
const newState = {
|
||||
...state,
|
||||
[action.path]: newField,
|
||||
}
|
||||
|
||||
return newState
|
||||
}
|
||||
|
||||
case 'UPDATE_MANY': {
|
||||
const newState = { ...state }
|
||||
|
||||
Object.entries(action.formState).forEach(([path, field]) => {
|
||||
newState[path] = field
|
||||
})
|
||||
|
||||
return newState
|
||||
}
|
||||
|
||||
case 'REMOVE_ROW': {
|
||||
const { path, rowIndex } = action
|
||||
const { remainingFields, rows } = separateRows(path, state)
|
||||
const rowsMetadata = [...(state[path]?.rows || [])]
|
||||
|
||||
rows.splice(rowIndex, 1)
|
||||
rowsMetadata.splice(rowIndex, 1)
|
||||
|
||||
const newState: FormState = {
|
||||
...remainingFields,
|
||||
[path]: {
|
||||
...state[path],
|
||||
disableFormData: rows.length > 0,
|
||||
requiresRender: true,
|
||||
rows: rowsMetadata,
|
||||
value: rows.length,
|
||||
},
|
||||
...flattenRows(path, rows),
|
||||
}
|
||||
|
||||
return newState
|
||||
}
|
||||
|
||||
case 'ADD_ROW': {
|
||||
const { blockType, path, rowIndex: rowIndexFromArgs, subFieldState = {} } = action
|
||||
|
||||
const rowIndex =
|
||||
typeof rowIndexFromArgs === 'number' ? rowIndexFromArgs : state[path]?.rows?.length || 0
|
||||
|
||||
const withNewRow = [...(state[path]?.rows || [])]
|
||||
|
||||
const newRow: Row = {
|
||||
id: (subFieldState?.id?.value as string) || new ObjectId().toHexString(),
|
||||
blockType: blockType || undefined,
|
||||
collapsed: false,
|
||||
}
|
||||
|
||||
withNewRow.splice(rowIndex, 0, newRow)
|
||||
|
||||
if (blockType) {
|
||||
subFieldState.blockType = {
|
||||
initialValue: blockType,
|
||||
valid: true,
|
||||
value: blockType,
|
||||
}
|
||||
}
|
||||
|
||||
// add new row to array _field state_
|
||||
const { remainingFields, rows: siblingRows } = separateRows(path, state)
|
||||
siblingRows.splice(rowIndex, 0, subFieldState)
|
||||
|
||||
const newState: FormState = {
|
||||
...remainingFields,
|
||||
...flattenRows(path, siblingRows),
|
||||
[`${path}.${rowIndex}.id`]: {
|
||||
initialValue: newRow.id,
|
||||
passesCondition: true,
|
||||
requiresRender: true,
|
||||
valid: true,
|
||||
value: newRow.id,
|
||||
},
|
||||
[path]: {
|
||||
...state[path],
|
||||
disableFormData: true,
|
||||
requiresRender: true,
|
||||
rows: withNewRow,
|
||||
value: siblingRows.length,
|
||||
},
|
||||
}
|
||||
|
||||
return newState
|
||||
}
|
||||
|
||||
case 'REPLACE_ROW': {
|
||||
const { blockType, path, rowIndex: rowIndexArg, subFieldState = {} } = action
|
||||
|
||||
const { remainingFields, rows: siblingRows } = separateRows(path, state)
|
||||
const rowIndex = Math.max(0, Math.min(rowIndexArg, siblingRows?.length - 1 || 0))
|
||||
|
||||
const rowsMetadata = [...(state[path]?.rows || [])]
|
||||
rowsMetadata[rowIndex] = {
|
||||
id: new ObjectId().toHexString(),
|
||||
blockType: blockType || undefined,
|
||||
collapsed: false,
|
||||
}
|
||||
|
||||
if (blockType) {
|
||||
subFieldState.blockType = {
|
||||
initialValue: blockType,
|
||||
valid: true,
|
||||
value: blockType,
|
||||
}
|
||||
}
|
||||
|
||||
// replace form _field state_
|
||||
siblingRows[rowIndex] = subFieldState
|
||||
|
||||
const newState: FormState = {
|
||||
...remainingFields,
|
||||
...flattenRows(path, siblingRows),
|
||||
[path]: {
|
||||
...state[path],
|
||||
disableFormData: true,
|
||||
rows: rowsMetadata,
|
||||
value: siblingRows.length,
|
||||
},
|
||||
}
|
||||
|
||||
return newState
|
||||
}
|
||||
|
||||
case 'DUPLICATE_ROW': {
|
||||
const { path, rowIndex } = action
|
||||
const { remainingFields, rows } = separateRows(path, state)
|
||||
@@ -342,20 +205,100 @@ export function fieldReducer(state: FormState, action: FieldAction): FormState {
|
||||
return newState
|
||||
}
|
||||
|
||||
case 'SET_ROW_COLLAPSED': {
|
||||
const { path, updatedRows } = action
|
||||
case 'REMOVE': {
|
||||
const newState = { ...state }
|
||||
if (newState[action.path]) {
|
||||
delete newState[action.path]
|
||||
}
|
||||
return newState
|
||||
}
|
||||
|
||||
const newState = {
|
||||
...state,
|
||||
case 'REMOVE_ROW': {
|
||||
const { path, rowIndex } = action
|
||||
const { remainingFields, rows } = separateRows(path, state)
|
||||
const rowsMetadata = [...(state[path]?.rows || [])]
|
||||
|
||||
rows.splice(rowIndex, 1)
|
||||
rowsMetadata.splice(rowIndex, 1)
|
||||
|
||||
const newState: FormState = {
|
||||
...remainingFields,
|
||||
[path]: {
|
||||
...state[path],
|
||||
rows: updatedRows,
|
||||
disableFormData: rows.length > 0,
|
||||
requiresRender: true,
|
||||
rows: rowsMetadata,
|
||||
value: rows.length,
|
||||
},
|
||||
...flattenRows(path, rows),
|
||||
}
|
||||
|
||||
return newState
|
||||
}
|
||||
|
||||
case 'REPLACE_ROW': {
|
||||
const { blockType, path, rowIndex: rowIndexArg, subFieldState = {} } = action
|
||||
|
||||
const { remainingFields, rows: siblingRows } = separateRows(path, state)
|
||||
const rowIndex = Math.max(0, Math.min(rowIndexArg, siblingRows?.length - 1 || 0))
|
||||
|
||||
const rowsMetadata = [...(state[path]?.rows || [])]
|
||||
rowsMetadata[rowIndex] = {
|
||||
id: new ObjectId().toHexString(),
|
||||
blockType: blockType || undefined,
|
||||
collapsed: false,
|
||||
}
|
||||
|
||||
if (blockType) {
|
||||
subFieldState.blockType = {
|
||||
initialValue: blockType,
|
||||
valid: true,
|
||||
value: blockType,
|
||||
}
|
||||
}
|
||||
|
||||
// replace form _field state_
|
||||
siblingRows[rowIndex] = subFieldState
|
||||
|
||||
const newState: FormState = {
|
||||
...remainingFields,
|
||||
...flattenRows(path, siblingRows),
|
||||
[path]: {
|
||||
...state[path],
|
||||
disableFormData: true,
|
||||
rows: rowsMetadata,
|
||||
value: siblingRows.length,
|
||||
},
|
||||
}
|
||||
|
||||
return newState
|
||||
}
|
||||
|
||||
case 'REPLACE_STATE': {
|
||||
if (action.optimize !== false) {
|
||||
// Only update fields that have changed
|
||||
// by comparing old value / initialValue to new
|
||||
// ..
|
||||
// This is a performance enhancement for saving
|
||||
// large documents with hundreds of fields
|
||||
const newState = {}
|
||||
|
||||
Object.entries(action.state).forEach(([path, field]) => {
|
||||
const oldField = state[path]
|
||||
const newField = field
|
||||
|
||||
if (!dequal(oldField, newField)) {
|
||||
newState[path] = newField
|
||||
} else if (oldField) {
|
||||
newState[path] = oldField
|
||||
}
|
||||
})
|
||||
return newState
|
||||
}
|
||||
// If we're not optimizing, just set the state to the new state
|
||||
return action.state
|
||||
}
|
||||
|
||||
case 'SET_ALL_ROWS_COLLAPSED': {
|
||||
const { path, updatedRows } = action
|
||||
|
||||
@@ -368,6 +311,63 @@ export function fieldReducer(state: FormState, action: FieldAction): FormState {
|
||||
}
|
||||
}
|
||||
|
||||
case 'SET_ROW_COLLAPSED': {
|
||||
const { path, updatedRows } = action
|
||||
|
||||
const newState = {
|
||||
...state,
|
||||
[path]: {
|
||||
...state[path],
|
||||
rows: updatedRows,
|
||||
},
|
||||
}
|
||||
|
||||
return newState
|
||||
}
|
||||
|
||||
case 'UPDATE': {
|
||||
const newField = Object.entries(action).reduce(
|
||||
(field, [key, value]) => {
|
||||
if (
|
||||
[
|
||||
'disableFormData',
|
||||
'errorMessage',
|
||||
'initialValue',
|
||||
'rows',
|
||||
'valid',
|
||||
'validate',
|
||||
'value',
|
||||
].includes(key)
|
||||
) {
|
||||
return {
|
||||
...field,
|
||||
[key]: value,
|
||||
}
|
||||
}
|
||||
|
||||
return field
|
||||
},
|
||||
state[action.path] || ({} as FormField),
|
||||
)
|
||||
|
||||
const newState = {
|
||||
...state,
|
||||
[action.path]: newField,
|
||||
}
|
||||
|
||||
return newState
|
||||
}
|
||||
|
||||
case 'UPDATE_MANY': {
|
||||
const newState = { ...state }
|
||||
|
||||
Object.entries(action.formState).forEach(([path, field]) => {
|
||||
newState[path] = field
|
||||
})
|
||||
|
||||
return newState
|
||||
}
|
||||
|
||||
default: {
|
||||
return state
|
||||
}
|
||||
|
||||
@@ -78,18 +78,18 @@ export function RenderField({
|
||||
return <ArrayField {...sharedProps} field={clientFieldConfig} permissions={permissions} />
|
||||
case 'blocks':
|
||||
return <BlocksField {...sharedProps} field={clientFieldConfig} permissions={permissions} />
|
||||
case 'group':
|
||||
return <GroupField {...sharedProps} field={clientFieldConfig} permissions={permissions} />
|
||||
case 'tabs':
|
||||
return <TabsField {...sharedProps} field={clientFieldConfig} permissions={permissions} />
|
||||
|
||||
// unnamed fields with subfields
|
||||
case 'row':
|
||||
return <RowField {...sharedProps} field={clientFieldConfig} permissions={permissions} />
|
||||
case 'collapsible':
|
||||
return (
|
||||
<CollapsibleField {...sharedProps} field={clientFieldConfig} permissions={permissions} />
|
||||
)
|
||||
case 'group':
|
||||
return <GroupField {...sharedProps} field={clientFieldConfig} permissions={permissions} />
|
||||
|
||||
// unnamed fields with subfields
|
||||
case 'row':
|
||||
return <RowField {...sharedProps} field={clientFieldConfig} permissions={permissions} />
|
||||
case 'tabs':
|
||||
return <TabsField {...sharedProps} field={clientFieldConfig} permissions={permissions} />
|
||||
|
||||
default:
|
||||
return DefaultField ? <DefaultField field={clientFieldConfig} {...sharedProps} /> : null
|
||||
|
||||
@@ -473,8 +473,8 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
||||
|
||||
break
|
||||
}
|
||||
case 'upload':
|
||||
case 'relationship': {
|
||||
case 'relationship':
|
||||
case 'upload': {
|
||||
if (field.filterOptions) {
|
||||
if (typeof field.filterOptions === 'object') {
|
||||
if (typeof field.relationTo === 'string') {
|
||||
|
||||
@@ -39,25 +39,6 @@ export const defaultValuePromise = async <T>({
|
||||
|
||||
// Traverse subfields
|
||||
switch (field.type) {
|
||||
case 'group': {
|
||||
if (typeof siblingData[field.name] !== 'object') {
|
||||
siblingData[field.name] = {}
|
||||
}
|
||||
|
||||
const groupData = siblingData[field.name] as Record<string, unknown>
|
||||
|
||||
await iterateFields({
|
||||
id,
|
||||
data,
|
||||
fields: field.fields,
|
||||
locale,
|
||||
siblingData: groupData,
|
||||
user,
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'array': {
|
||||
const rows = siblingData[field.name]
|
||||
|
||||
@@ -112,8 +93,9 @@ export const defaultValuePromise = async <T>({
|
||||
break
|
||||
}
|
||||
|
||||
case 'row':
|
||||
case 'collapsible': {
|
||||
case 'collapsible':
|
||||
|
||||
case 'row': {
|
||||
await iterateFields({
|
||||
id,
|
||||
data,
|
||||
@@ -125,6 +107,24 @@ export const defaultValuePromise = async <T>({
|
||||
|
||||
break
|
||||
}
|
||||
case 'group': {
|
||||
if (typeof siblingData[field.name] !== 'object') {
|
||||
siblingData[field.name] = {}
|
||||
}
|
||||
|
||||
const groupData = siblingData[field.name] as Record<string, unknown>
|
||||
|
||||
await iterateFields({
|
||||
id,
|
||||
data,
|
||||
fields: field.fields,
|
||||
locale,
|
||||
siblingData: groupData,
|
||||
user,
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'tab': {
|
||||
let tabSiblingData
|
||||
|
||||
@@ -33,8 +33,8 @@ export const traverseFields = ({
|
||||
schemaMap.set(schemaPath, field)
|
||||
|
||||
switch (field.type) {
|
||||
case 'group':
|
||||
case 'array':
|
||||
case 'group':
|
||||
traverseFields({
|
||||
config,
|
||||
fields: field.fields,
|
||||
@@ -46,19 +46,6 @@ export const traverseFields = ({
|
||||
|
||||
break
|
||||
|
||||
case 'collapsible':
|
||||
case 'row':
|
||||
traverseFields({
|
||||
config,
|
||||
fields: field.fields,
|
||||
i18n,
|
||||
parentIndexPath: indexPath,
|
||||
parentSchemaPath,
|
||||
schemaMap,
|
||||
})
|
||||
|
||||
break
|
||||
|
||||
case 'blocks':
|
||||
field.blocks.map((block) => {
|
||||
const blockSchemaPath = `${schemaPath}.${block.slug}`
|
||||
@@ -74,6 +61,19 @@ export const traverseFields = ({
|
||||
})
|
||||
})
|
||||
|
||||
break
|
||||
case 'collapsible':
|
||||
|
||||
case 'row':
|
||||
traverseFields({
|
||||
config,
|
||||
fields: field.fields,
|
||||
i18n,
|
||||
parentIndexPath: indexPath,
|
||||
parentSchemaPath,
|
||||
schemaMap,
|
||||
})
|
||||
|
||||
break
|
||||
|
||||
case 'richText':
|
||||
|
||||
886
pnpm-lock.yaml
generated
886
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user