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'>
|
} & Pick<CliArgs, '--debug'>
|
||||||
|
|
||||||
type InitNextResult =
|
type InitNextResult =
|
||||||
|
| { isSrcDir: boolean; nextAppDir?: string; reason: string; success: false }
|
||||||
| {
|
| {
|
||||||
isSrcDir: boolean
|
isSrcDir: boolean
|
||||||
nextAppDir: string
|
nextAppDir: string
|
||||||
payloadConfigPath: string
|
payloadConfigPath: string
|
||||||
success: true
|
success: true
|
||||||
}
|
}
|
||||||
| { isSrcDir: boolean; nextAppDir?: string; reason: string; success: false }
|
|
||||||
|
|
||||||
export async function initNext(args: InitNextArgs): Promise<InitNextResult> {
|
export async function initNext(args: InitNextArgs): Promise<InitNextResult> {
|
||||||
const { dbType: dbType, packageManager, projectDir } = args
|
const { dbType: dbType, packageManager, projectDir } = args
|
||||||
|
|||||||
@@ -15,15 +15,9 @@ export async function installPackages(args: {
|
|||||||
let stderr = ''
|
let stderr = ''
|
||||||
|
|
||||||
switch (packageManager) {
|
switch (packageManager) {
|
||||||
case 'npm': {
|
case 'bun':
|
||||||
;({ exitCode, stderr } = await execa('npm', ['install', '--save', ...packagesToInstall], {
|
|
||||||
cwd: projectDir,
|
|
||||||
}))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case 'yarn':
|
|
||||||
case 'pnpm':
|
case 'pnpm':
|
||||||
case 'bun': {
|
case 'yarn': {
|
||||||
if (packageManager === 'bun') {
|
if (packageManager === 'bun') {
|
||||||
warning('Bun support is untested.')
|
warning('Bun support is untested.')
|
||||||
}
|
}
|
||||||
@@ -32,6 +26,12 @@ export async function installPackages(args: {
|
|||||||
}))
|
}))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
case 'npm': {
|
||||||
|
;({ exitCode, stderr } = await execa('npm', ['install', '--save', ...packagesToInstall], {
|
||||||
|
cwd: projectDir,
|
||||||
|
}))
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
|
|||||||
@@ -217,6 +217,16 @@ export class Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (template.type) {
|
switch (template.type) {
|
||||||
|
case 'plugin': {
|
||||||
|
await createProject({
|
||||||
|
cliArgs: this.args,
|
||||||
|
packageManager,
|
||||||
|
projectDir,
|
||||||
|
projectName,
|
||||||
|
template,
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
case 'starter': {
|
case 'starter': {
|
||||||
const dbDetails = await selectDb(this.args, projectName)
|
const dbDetails = await selectDb(this.args, projectName)
|
||||||
const payloadSecret = generateSecret()
|
const payloadSecret = generateSecret()
|
||||||
@@ -238,16 +248,6 @@ export class Main {
|
|||||||
})
|
})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'plugin': {
|
|
||||||
await createProject({
|
|
||||||
cliArgs: this.args,
|
|
||||||
packageManager,
|
|
||||||
projectDir,
|
|
||||||
projectName,
|
|
||||||
template,
|
|
||||||
})
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
info('Payload project successfully created!')
|
info('Payload project successfully created!')
|
||||||
|
|||||||
@@ -104,34 +104,10 @@ const traverseFields = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (field.type) {
|
switch (field.type) {
|
||||||
case 'collapsible':
|
case 'array':
|
||||||
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 'group':
|
case 'group':
|
||||||
case 'tab':
|
|
||||||
case 'array': {
|
case 'tab': {
|
||||||
let fieldSelect: SelectType
|
let fieldSelect: SelectType
|
||||||
|
|
||||||
if (field.type === 'tab' && !tabHasName(field)) {
|
if (field.type === 'tab' && !tabHasName(field)) {
|
||||||
@@ -206,6 +182,30 @@ const traverseFields = ({
|
|||||||
|
|
||||||
break
|
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:
|
default:
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -13,44 +13,6 @@ type Args = {
|
|||||||
export const traverseFields = ({ doc, fields, locale, path, rows }: Args) => {
|
export const traverseFields = ({ doc, fields, locale, path, rows }: Args) => {
|
||||||
fields.forEach((field) => {
|
fields.forEach((field) => {
|
||||||
switch (field.type) {
|
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': {
|
case 'array': {
|
||||||
const rowData = doc?.[field.name]
|
const rowData = doc?.[field.name]
|
||||||
|
|
||||||
@@ -124,45 +86,47 @@ export const traverseFields = ({ doc, fields, locale, path, rows }: Args) => {
|
|||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
case 'collapsible':
|
||||||
case 'tabs': {
|
// falls through
|
||||||
return field.tabs.forEach((tab) => {
|
case 'row': {
|
||||||
if (tabHasName(tab)) {
|
return traverseFields({
|
||||||
const newDoc = doc?.[tab.name]
|
doc,
|
||||||
const newPath = `${path ? `${path}.` : ''}${tab.name}`
|
fields: field.fields,
|
||||||
|
path,
|
||||||
if (typeof newDoc === 'object' && newDoc !== null) {
|
rows,
|
||||||
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 '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':
|
case 'relationship':
|
||||||
|
// falls through
|
||||||
case 'upload': {
|
case 'upload': {
|
||||||
if (typeof field.relationTo === 'string') {
|
if (typeof field.relationTo === 'string') {
|
||||||
if (field.type === 'upload' || !field.hasMany) {
|
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) => {
|
export const traverseFields = (args: Args) => {
|
||||||
args.fields.forEach((field) => {
|
args.fields.forEach((field) => {
|
||||||
switch (field.type) {
|
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': {
|
case 'array': {
|
||||||
const newTableName = args.adapter.tableNameMap.get(
|
const newTableName = args.adapter.tableNameMap.get(
|
||||||
`${args.newTableName}_${toSnakeCase(field.name)}`,
|
`${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': {
|
case 'tabs': {
|
||||||
return field.tabs.forEach((tab) => {
|
return field.tabs.forEach((tab) => {
|
||||||
if (tabHasName(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) {
|
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': {
|
case 'array': {
|
||||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||||
|
|
||||||
@@ -493,7 +316,6 @@ export const traverseFields = ({
|
|||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'blocks': {
|
case 'blocks': {
|
||||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||||
|
|
||||||
@@ -646,9 +468,82 @@ export const traverseFields = ({
|
|||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
case 'checkbox': {
|
||||||
|
targetTable[fieldName] = withDefault(integer(columnName, { mode: 'boolean' }), field)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'code':
|
||||||
|
|
||||||
case 'tab':
|
case 'email':
|
||||||
case 'group': {
|
|
||||||
|
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)) {
|
if (!('name' in field)) {
|
||||||
const {
|
const {
|
||||||
hasLocalizedField: groupHasLocalizedField,
|
hasLocalizedField: groupHasLocalizedField,
|
||||||
@@ -758,114 +653,136 @@ export const traverseFields = ({
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'tabs': {
|
case 'json':
|
||||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
|
||||||
|
|
||||||
const {
|
case 'richText': {
|
||||||
hasLocalizedField: tabHasLocalizedField,
|
targetTable[fieldName] = withDefault(text(columnName, { mode: 'json' }), field)
|
||||||
hasLocalizedManyNumberField: tabHasLocalizedManyNumberField,
|
break
|
||||||
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) {
|
case 'number': {
|
||||||
hasLocalizedField = true
|
if (field.hasMany) {
|
||||||
}
|
const isLocalized =
|
||||||
if (tabHasLocalizedRelationshipField) {
|
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||||
hasLocalizedRelationshipField = true
|
withinLocalizedArrayOrBlock ||
|
||||||
}
|
forceLocalized
|
||||||
if (tabHasManyTextField) {
|
|
||||||
hasManyTextField = true
|
if (isLocalized) {
|
||||||
}
|
hasLocalizedManyNumberField = true
|
||||||
if (tabHasLocalizedManyTextField) {
|
}
|
||||||
hasLocalizedManyTextField = true
|
|
||||||
}
|
if (field.index) {
|
||||||
if (tabHasManyNumberField) {
|
hasManyNumberField = 'index'
|
||||||
hasManyNumberField = true
|
} else if (!hasManyNumberField) {
|
||||||
}
|
hasManyNumberField = true
|
||||||
if (tabHasLocalizedManyNumberField) {
|
}
|
||||||
hasLocalizedManyNumberField = 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
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'row':
|
case 'point': {
|
||||||
case 'collapsible': {
|
break
|
||||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
}
|
||||||
const {
|
case 'radio':
|
||||||
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) {
|
case 'select': {
|
||||||
hasLocalizedField = true
|
const options = field.options.map((option) => {
|
||||||
}
|
if (optionIsObject(option)) {
|
||||||
if (rowHasLocalizedRelationshipField) {
|
return option.value
|
||||||
hasLocalizedRelationshipField = true
|
}
|
||||||
}
|
|
||||||
if (rowHasManyTextField) {
|
return option
|
||||||
hasManyTextField = true
|
}) as [string, ...string[]]
|
||||||
}
|
|
||||||
if (rowHasLocalizedManyTextField) {
|
if (field.type === 'select' && field.hasMany) {
|
||||||
hasLocalizedManyTextField = true
|
const selectTableName = createTableName({
|
||||||
}
|
adapter,
|
||||||
if (rowHasManyNumberField) {
|
config: field,
|
||||||
hasManyNumberField = true
|
parentTableName: newTableName,
|
||||||
}
|
prefix: `${newTableName}_`,
|
||||||
if (rowHasLocalizedManyNumberField) {
|
versionsCustomName: versions,
|
||||||
hasLocalizedManyNumberField = true
|
})
|
||||||
|
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
|
break
|
||||||
}
|
}
|
||||||
@@ -931,6 +848,89 @@ export const traverseFields = ({
|
|||||||
|
|
||||||
break
|
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:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,44 +13,6 @@ type Args = {
|
|||||||
export const traverseFields = ({ doc, fields, locale, path, rows }: Args) => {
|
export const traverseFields = ({ doc, fields, locale, path, rows }: Args) => {
|
||||||
fields.forEach((field) => {
|
fields.forEach((field) => {
|
||||||
switch (field.type) {
|
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': {
|
case 'array': {
|
||||||
const rowData = doc?.[field.name]
|
const rowData = doc?.[field.name]
|
||||||
|
|
||||||
@@ -124,45 +86,47 @@ export const traverseFields = ({ doc, fields, locale, path, rows }: Args) => {
|
|||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
case 'collapsible':
|
||||||
case 'tabs': {
|
// falls through
|
||||||
return field.tabs.forEach((tab) => {
|
case 'row': {
|
||||||
if (tabHasName(tab)) {
|
return traverseFields({
|
||||||
const newDoc = doc?.[tab.name]
|
doc,
|
||||||
const newPath = `${path ? `${path}.` : ''}${tab.name}`
|
fields: field.fields,
|
||||||
|
path,
|
||||||
if (typeof newDoc === 'object' && newDoc !== null) {
|
rows,
|
||||||
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 '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':
|
case 'relationship':
|
||||||
|
// falls through
|
||||||
case 'upload': {
|
case 'upload': {
|
||||||
if (typeof field.relationTo === 'string') {
|
if (typeof field.relationTo === 'string') {
|
||||||
if (field.type === 'upload' || !field.hasMany) {
|
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) => {
|
export const traverseFields = (args: Args) => {
|
||||||
args.fields.forEach((field) => {
|
args.fields.forEach((field) => {
|
||||||
switch (field.type) {
|
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': {
|
case 'array': {
|
||||||
const newTableName = args.adapter.tableNameMap.get(
|
const newTableName = args.adapter.tableNameMap.get(
|
||||||
`${args.newTableName}_${toSnakeCase(field.name)}`,
|
`${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': {
|
case 'tabs': {
|
||||||
return field.tabs.forEach((tab) => {
|
return field.tabs.forEach((tab) => {
|
||||||
if (tabHasName(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
|
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': {
|
case 'blocks': {
|
||||||
const blocksSelect = selectAllOnCurrentLevel ? true : select?.[field.name]
|
const blocksSelect = selectAllOnCurrentLevel ? true : select?.[field.name]
|
||||||
|
|
||||||
@@ -356,6 +330,7 @@ export const traverseFields = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'group':
|
case 'group':
|
||||||
|
|
||||||
case 'tab': {
|
case 'tab': {
|
||||||
const fieldSelect = select?.[field.name]
|
const fieldSelect = select?.[field.name]
|
||||||
|
|
||||||
@@ -389,47 +364,6 @@ export const traverseFields = ({
|
|||||||
|
|
||||||
break
|
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': {
|
case 'join': {
|
||||||
// when `joinsQuery` is false, do not join
|
// when `joinsQuery` is false, do not join
|
||||||
if (joinQuery === false) {
|
if (joinQuery === false) {
|
||||||
@@ -621,6 +555,72 @@ export const traverseFields = ({
|
|||||||
break
|
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: {
|
default: {
|
||||||
if (!select && !selectAllOnCurrentLevel) {
|
if (!select && !selectAllOnCurrentLevel) {
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -182,197 +182,6 @@ export const traverseFields = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (field.type) {
|
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': {
|
case 'array': {
|
||||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||||
|
|
||||||
@@ -506,7 +315,6 @@ export const traverseFields = ({
|
|||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'blocks': {
|
case 'blocks': {
|
||||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||||
|
|
||||||
@@ -655,9 +463,88 @@ export const traverseFields = ({
|
|||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
case 'checkbox': {
|
||||||
|
targetTable[fieldName] = withDefault(boolean(columnName), field)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'code':
|
||||||
|
|
||||||
case 'tab':
|
case 'email':
|
||||||
case 'group': {
|
|
||||||
|
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)) {
|
if (!('name' in field)) {
|
||||||
const {
|
const {
|
||||||
hasLocalizedField: groupHasLocalizedField,
|
hasLocalizedField: groupHasLocalizedField,
|
||||||
@@ -765,112 +652,143 @@ export const traverseFields = ({
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'tabs': {
|
case 'json':
|
||||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
|
||||||
|
|
||||||
const {
|
case 'richText': {
|
||||||
hasLocalizedField: tabHasLocalizedField,
|
targetTable[fieldName] = withDefault(jsonb(columnName), field)
|
||||||
hasLocalizedManyNumberField: tabHasLocalizedManyNumberField,
|
break
|
||||||
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) {
|
case 'number': {
|
||||||
hasLocalizedField = true
|
if (field.hasMany) {
|
||||||
}
|
const isLocalized =
|
||||||
if (tabHasLocalizedRelationshipField) {
|
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||||
hasLocalizedRelationshipField = true
|
withinLocalizedArrayOrBlock ||
|
||||||
}
|
forceLocalized
|
||||||
if (tabHasManyTextField) {
|
|
||||||
hasManyTextField = true
|
if (isLocalized) {
|
||||||
}
|
hasLocalizedManyNumberField = true
|
||||||
if (tabHasLocalizedManyTextField) {
|
}
|
||||||
hasLocalizedManyTextField = true
|
|
||||||
}
|
if (field.index) {
|
||||||
if (tabHasManyNumberField) {
|
hasManyNumberField = 'index'
|
||||||
hasManyNumberField = true
|
} else if (!hasManyNumberField) {
|
||||||
}
|
hasManyNumberField = true
|
||||||
if (tabHasLocalizedManyNumberField) {
|
}
|
||||||
hasLocalizedManyNumberField = 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
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'row':
|
case 'point': {
|
||||||
case 'collapsible': {
|
targetTable[fieldName] = withDefault(geometryColumn(columnName), field)
|
||||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
if (!adapter.extensions.postgis) {
|
||||||
const {
|
adapter.extensions.postgis = true
|
||||||
hasLocalizedField: rowHasLocalizedField,
|
}
|
||||||
hasLocalizedManyNumberField: rowHasLocalizedManyNumberField,
|
break
|
||||||
hasLocalizedManyTextField: rowHasLocalizedManyTextField,
|
}
|
||||||
hasLocalizedRelationshipField: rowHasLocalizedRelationshipField,
|
case 'radio':
|
||||||
hasManyNumberField: rowHasManyNumberField,
|
|
||||||
hasManyTextField: rowHasManyTextField,
|
case 'select': {
|
||||||
} = traverseFields({
|
const enumName = createTableName({
|
||||||
adapter,
|
adapter,
|
||||||
columnPrefix,
|
config: field,
|
||||||
columns,
|
parentTableName: newTableName,
|
||||||
disableNotNull: disableNotNullFromHere,
|
prefix: `enum_${newTableName}_`,
|
||||||
disableUnique,
|
target: 'enumName',
|
||||||
fieldPrefix,
|
throwValidationError,
|
||||||
fields: field.fields,
|
|
||||||
forceLocalized,
|
|
||||||
indexes,
|
|
||||||
localesColumns,
|
|
||||||
localesIndexes,
|
|
||||||
newTableName,
|
|
||||||
parentTableName,
|
|
||||||
relationships,
|
|
||||||
relationsToBuild,
|
|
||||||
rootRelationsToBuild,
|
|
||||||
rootTableIDColType,
|
|
||||||
rootTableName,
|
|
||||||
uniqueRelationships,
|
|
||||||
versions,
|
|
||||||
withinLocalizedArrayOrBlock,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (rowHasLocalizedField) {
|
adapter.enums[enumName] = adapter.pgSchema.enum(
|
||||||
hasLocalizedField = true
|
enumName,
|
||||||
}
|
field.options.map((option) => {
|
||||||
if (rowHasLocalizedRelationshipField) {
|
if (optionIsObject(option)) {
|
||||||
hasLocalizedRelationshipField = true
|
return option.value
|
||||||
}
|
}
|
||||||
if (rowHasManyTextField) {
|
|
||||||
hasManyTextField = true
|
return option
|
||||||
}
|
}) as [string, ...string[]],
|
||||||
if (rowHasLocalizedManyTextField) {
|
)
|
||||||
hasLocalizedManyTextField = true
|
|
||||||
}
|
if (field.type === 'select' && field.hasMany) {
|
||||||
if (rowHasManyNumberField) {
|
const selectTableName = createTableName({
|
||||||
hasManyNumberField = true
|
adapter,
|
||||||
}
|
config: field,
|
||||||
if (rowHasLocalizedManyNumberField) {
|
parentTableName: newTableName,
|
||||||
hasLocalizedManyNumberField = true
|
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
|
break
|
||||||
}
|
}
|
||||||
@@ -936,6 +854,88 @@ export const traverseFields = ({
|
|||||||
|
|
||||||
break
|
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:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,185 +121,6 @@ export const getTableColumnFromPath = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (field.type) {
|
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': {
|
case 'array': {
|
||||||
newTableName = adapter.tableNameMap.get(
|
newTableName = adapter.tableNameMap.get(
|
||||||
`${tableName}_${tableNameSuffix}${toSnakeCase(field.name)}`,
|
`${tableName}_${tableNameSuffix}${toSnakeCase(field.name)}`,
|
||||||
@@ -341,7 +162,6 @@ export const getTableColumnFromPath = ({
|
|||||||
value,
|
value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'blocks': {
|
case 'blocks': {
|
||||||
let blockTableColumn: TableColumn
|
let blockTableColumn: TableColumn
|
||||||
let newTableName: string
|
let newTableName: string
|
||||||
@@ -447,7 +267,87 @@ export const getTableColumnFromPath = ({
|
|||||||
break
|
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 'relationship':
|
||||||
|
|
||||||
case 'upload': {
|
case 'upload': {
|
||||||
const newCollectionPath = pathSegments.slice(1).join('.')
|
const newCollectionPath = pathSegments.slice(1).join('.')
|
||||||
if (Array.isArray(field.relationTo) || field.hasMany) {
|
if (Array.isArray(field.relationTo) || field.hasMany) {
|
||||||
@@ -692,6 +592,106 @@ export const getTableColumnFromPath = ({
|
|||||||
break
|
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: {
|
default: {
|
||||||
// fall through
|
// fall through
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -295,6 +295,13 @@ export function parseParams({
|
|||||||
|
|
||||||
if (field.type === 'point' && adapter.name === 'postgres') {
|
if (field.type === 'point' && adapter.name === 'postgres') {
|
||||||
switch (operator) {
|
switch (operator) {
|
||||||
|
case 'intersects': {
|
||||||
|
constraints.push(
|
||||||
|
sql`ST_Intersects(${table[columnName]}, ST_GeomFromGeoJSON(${JSON.stringify(queryValue)}))`,
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
case 'near': {
|
case 'near': {
|
||||||
const [lng, lat, maxDistance, minDistance] = queryValue as number[]
|
const [lng, lat, maxDistance, minDistance] = queryValue as number[]
|
||||||
|
|
||||||
@@ -313,13 +320,6 @@ export function parseParams({
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'intersects': {
|
|
||||||
constraints.push(
|
|
||||||
sql`ST_Intersects(${table[columnName]}, ST_GeomFromGeoJSON(${JSON.stringify(queryValue)}))`,
|
|
||||||
)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -593,8 +593,16 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
|||||||
let val = fieldData
|
let val = fieldData
|
||||||
|
|
||||||
switch (field.type) {
|
switch (field.type) {
|
||||||
case 'tab':
|
case 'date': {
|
||||||
case 'group': {
|
if (typeof fieldData === 'string') {
|
||||||
|
val = new Date(fieldData).toISOString()
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'group':
|
||||||
|
|
||||||
|
case 'tab': {
|
||||||
const groupFieldPrefix = `${fieldPrefix || ''}${field.name}_`
|
const groupFieldPrefix = `${fieldPrefix || ''}${field.name}_`
|
||||||
const groupData = {}
|
const groupData = {}
|
||||||
const locale = table._locale as string
|
const locale = table._locale as string
|
||||||
@@ -626,14 +634,6 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'text': {
|
|
||||||
if (typeof fieldData === 'string') {
|
|
||||||
val = String(fieldData)
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'number': {
|
case 'number': {
|
||||||
if (typeof fieldData === 'string') {
|
if (typeof fieldData === 'string') {
|
||||||
val = Number.parseFloat(fieldData)
|
val = Number.parseFloat(fieldData)
|
||||||
@@ -642,15 +642,8 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'date': {
|
|
||||||
if (typeof fieldData === 'string') {
|
|
||||||
val = new Date(fieldData).toISOString()
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'relationship':
|
case 'relationship':
|
||||||
|
|
||||||
case 'upload': {
|
case 'upload': {
|
||||||
if (
|
if (
|
||||||
val &&
|
val &&
|
||||||
@@ -662,6 +655,13 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
|||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
case 'text': {
|
||||||
|
if (typeof fieldData === 'string') {
|
||||||
|
val = String(fieldData)
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -17,23 +17,23 @@
|
|||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-react/eslint-plugin": "1.12.3",
|
"@eslint-react/eslint-plugin": "1.16.1",
|
||||||
"@eslint/js": "9.9.1",
|
"@eslint/js": "9.14.0",
|
||||||
"@payloadcms/eslint-plugin": "workspace:*",
|
"@payloadcms/eslint-plugin": "workspace:*",
|
||||||
"@types/eslint": "9.6.1",
|
"@types/eslint": "9.6.1",
|
||||||
"@types/eslint__js": "8.42.3",
|
"@types/eslint__js": "8.42.3",
|
||||||
"@typescript-eslint/parser": "8.3.0",
|
"@typescript-eslint/parser": "8.14.0",
|
||||||
"eslint": "9.9.1",
|
"eslint": "9.14.0",
|
||||||
"eslint-config-prettier": "9.1.0",
|
"eslint-config-prettier": "9.1.0",
|
||||||
"eslint-plugin-import-x": "4.1.1",
|
"eslint-plugin-import-x": "4.4.2",
|
||||||
"eslint-plugin-jest": "28.8.1",
|
"eslint-plugin-jest": "28.9.0",
|
||||||
"eslint-plugin-jest-dom": "5.4.0",
|
"eslint-plugin-jest-dom": "5.4.0",
|
||||||
"eslint-plugin-jsx-a11y": "6.9.0",
|
"eslint-plugin-jsx-a11y": "6.10.2",
|
||||||
"eslint-plugin-perfectionist": "3.3.0",
|
"eslint-plugin-perfectionist": "3.9.1",
|
||||||
"eslint-plugin-react-hooks": "5.1.0-rc-a19a8ab4-20240829",
|
"eslint-plugin-react-hooks": "5.0.0",
|
||||||
"eslint-plugin-regexp": "2.6.0",
|
"eslint-plugin-regexp": "2.6.0",
|
||||||
"globals": "15.9.0",
|
"globals": "15.12.0",
|
||||||
"typescript": "5.6.3",
|
"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"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-react/eslint-plugin": "1.12.3",
|
"@eslint-react/eslint-plugin": "1.16.1",
|
||||||
"@eslint/js": "9.9.1",
|
"@eslint/js": "9.14.0",
|
||||||
"@types/eslint": "9.6.1",
|
"@types/eslint": "9.6.1",
|
||||||
"@types/eslint__js": "8.42.3",
|
"@types/eslint__js": "8.42.3",
|
||||||
"@typescript-eslint/parser": "8.3.0",
|
"@typescript-eslint/parser": "8.14.0",
|
||||||
"eslint": "9.9.1",
|
"eslint": "9.14.0",
|
||||||
"eslint-config-prettier": "9.1.0",
|
"eslint-config-prettier": "9.1.0",
|
||||||
"eslint-plugin-import-x": "4.1.1",
|
"eslint-plugin-import-x": "4.4.2",
|
||||||
"eslint-plugin-jest": "28.8.1",
|
"eslint-plugin-jest": "28.9.0",
|
||||||
"eslint-plugin-jest-dom": "5.4.0",
|
"eslint-plugin-jest-dom": "5.4.0",
|
||||||
"eslint-plugin-jsx-a11y": "6.9.0",
|
"eslint-plugin-jsx-a11y": "6.10.2",
|
||||||
"eslint-plugin-perfectionist": "3.3.0",
|
"eslint-plugin-perfectionist": "3.9.1",
|
||||||
"eslint-plugin-react-hooks": "5.1.0-rc-a19a8ab4-20240829",
|
"eslint-plugin-react-hooks": "5.0.0",
|
||||||
"eslint-plugin-regexp": "2.6.0",
|
"eslint-plugin-regexp": "2.6.0",
|
||||||
"globals": "15.9.0",
|
"globals": "15.12.0",
|
||||||
"typescript": "5.6.3",
|
"typescript": "5.6.3",
|
||||||
"typescript-eslint": "8.3.0"
|
"typescript-eslint": "8.14.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -394,15 +394,15 @@ export class QueryComplexity {
|
|||||||
this.variableValues = coerced
|
this.variableValues = coerced
|
||||||
|
|
||||||
switch (operation.operation) {
|
switch (operation.operation) {
|
||||||
case 'query':
|
|
||||||
this.complexity += this.nodeComplexity(operation, this.context.getSchema().getQueryType())
|
|
||||||
break
|
|
||||||
case 'mutation':
|
case 'mutation':
|
||||||
this.complexity += this.nodeComplexity(
|
this.complexity += this.nodeComplexity(
|
||||||
operation,
|
operation,
|
||||||
this.context.getSchema().getMutationType(),
|
this.context.getSchema().getMutationType(),
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
|
case 'query':
|
||||||
|
this.complexity += this.nodeComplexity(operation, this.context.getSchema().getQueryType())
|
||||||
|
break
|
||||||
case 'subscription':
|
case 'subscription':
|
||||||
this.complexity += this.nodeComplexity(
|
this.complexity += this.nodeComplexity(
|
||||||
operation,
|
operation,
|
||||||
|
|||||||
@@ -24,18 +24,18 @@ function parseObject(typeName, ast, variables) {
|
|||||||
|
|
||||||
function parseLiteral(typeName, ast, variables) {
|
function parseLiteral(typeName, ast, variables) {
|
||||||
switch (ast.kind) {
|
switch (ast.kind) {
|
||||||
case Kind.STRING:
|
|
||||||
case Kind.BOOLEAN:
|
case Kind.BOOLEAN:
|
||||||
|
case Kind.STRING:
|
||||||
return ast.value
|
return ast.value
|
||||||
case Kind.INT:
|
|
||||||
case Kind.FLOAT:
|
case Kind.FLOAT:
|
||||||
|
case Kind.INT:
|
||||||
return parseFloat(ast.value)
|
return parseFloat(ast.value)
|
||||||
case Kind.OBJECT:
|
|
||||||
return parseObject(typeName, ast, variables)
|
|
||||||
case Kind.LIST:
|
case Kind.LIST:
|
||||||
return ast.values.map((n) => parseLiteral(typeName, n, variables))
|
return ast.values.map((n) => parseLiteral(typeName, n, variables))
|
||||||
case Kind.NULL:
|
case Kind.NULL:
|
||||||
return null
|
return null
|
||||||
|
case Kind.OBJECT:
|
||||||
|
return parseObject(typeName, ast, variables)
|
||||||
case Kind.VARIABLE:
|
case Kind.VARIABLE:
|
||||||
return variables ? variables[ast.name.value] : undefined
|
return variables ? variables[ast.name.value] : undefined
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -24,16 +24,6 @@ export const traverseFields = <T>(args: {
|
|||||||
const fieldName = fieldSchema.name
|
const fieldName = fieldSchema.name
|
||||||
|
|
||||||
switch (fieldSchema.type) {
|
switch (fieldSchema.type) {
|
||||||
case 'richText':
|
|
||||||
result[fieldName] = traverseRichText({
|
|
||||||
externallyUpdatedRelationship,
|
|
||||||
incomingData: incomingData[fieldName],
|
|
||||||
populationsByCollection,
|
|
||||||
result: result[fieldName],
|
|
||||||
})
|
|
||||||
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'array':
|
case 'array':
|
||||||
if (Array.isArray(incomingData[fieldName])) {
|
if (Array.isArray(incomingData[fieldName])) {
|
||||||
result[fieldName] = incomingData[fieldName].map((incomingRow, i) => {
|
result[fieldName] = incomingData[fieldName].map((incomingRow, i) => {
|
||||||
@@ -94,8 +84,9 @@ export const traverseFields = <T>(args: {
|
|||||||
|
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'tabs':
|
|
||||||
case 'group':
|
case 'group':
|
||||||
|
|
||||||
|
case 'tabs':
|
||||||
if (!result[fieldName]) {
|
if (!result[fieldName]) {
|
||||||
result[fieldName] = {}
|
result[fieldName] = {}
|
||||||
}
|
}
|
||||||
@@ -109,9 +100,9 @@ export const traverseFields = <T>(args: {
|
|||||||
})
|
})
|
||||||
|
|
||||||
break
|
break
|
||||||
|
case 'relationship':
|
||||||
|
|
||||||
case 'upload':
|
case 'upload':
|
||||||
case 'relationship':
|
|
||||||
// Handle `hasMany` relationships
|
// Handle `hasMany` relationships
|
||||||
if (fieldSchema.hasMany && Array.isArray(incomingData[fieldName])) {
|
if (fieldSchema.hasMany && Array.isArray(incomingData[fieldName])) {
|
||||||
if (!result[fieldName] || !incomingData[fieldName].length) {
|
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
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -106,7 +106,7 @@
|
|||||||
"babel-plugin-react-compiler": "0.0.0-experimental-24ec0eb-20240918",
|
"babel-plugin-react-compiler": "0.0.0-experimental-24ec0eb-20240918",
|
||||||
"esbuild": "0.23.1",
|
"esbuild": "0.23.1",
|
||||||
"esbuild-sass-plugin": "3.3.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:*",
|
"payload": "workspace:*",
|
||||||
"swc-plugin-transform-remove-imports": "1.15.0"
|
"swc-plugin-transform-remove-imports": "1.15.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const ACCEPTABLE_CONTENT_TYPE = /multipart\/['"()+-_]+(?:; ?['"()+-_]*)+$/i
|
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 => {
|
const hasBody = (req: Request): boolean => {
|
||||||
return Boolean(
|
return Boolean(
|
||||||
|
|||||||
@@ -29,10 +29,10 @@ export type SizeReducerAction =
|
|||||||
|
|
||||||
export const sizeReducer = (state: SizeReducerState, action: SizeReducerAction) => {
|
export const sizeReducer = (state: SizeReducerState, action: SizeReducerAction) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'width':
|
|
||||||
return { ...state, width: action.value }
|
|
||||||
case 'height':
|
case 'height':
|
||||||
return { ...state, height: action.value }
|
return { ...state, height: action.value }
|
||||||
|
case 'width':
|
||||||
|
return { ...state, width: action.value }
|
||||||
default:
|
default:
|
||||||
return { ...state, ...(action?.value || {}) }
|
return { ...state, ...(action?.value || {}) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ import type {
|
|||||||
} from '../types.js'
|
} from '../types.js'
|
||||||
|
|
||||||
export type ClientTab =
|
export type ClientTab =
|
||||||
| ({ fields: ClientField[] } & Omit<UnnamedTab, 'fields'>)
|
|
||||||
| ({ fields: ClientField[]; readonly path?: string } & Omit<NamedTab, 'fields'>)
|
| ({ fields: ClientField[]; readonly path?: string } & Omit<NamedTab, 'fields'>)
|
||||||
|
| ({ fields: ClientField[] } & Omit<UnnamedTab, 'fields'>)
|
||||||
|
|
||||||
type TabsFieldBaseClientProps = {} & Pick<ServerFieldBase, 'permissions'>
|
type TabsFieldBaseClientProps = {} & Pick<ServerFieldBase, 'permissions'>
|
||||||
|
|
||||||
|
|||||||
@@ -28,9 +28,9 @@ export type AdminViewProps = {
|
|||||||
readonly initialData?: Data
|
readonly initialData?: Data
|
||||||
readonly initPageResult: InitPageResult
|
readonly initPageResult: InitPageResult
|
||||||
readonly params?: { [key: string]: string | string[] | undefined }
|
readonly params?: { [key: string]: string | string[] | undefined }
|
||||||
readonly searchParams: { [key: string]: string | string[] | undefined }
|
|
||||||
readonly redirectAfterDelete?: boolean
|
readonly redirectAfterDelete?: boolean
|
||||||
readonly redirectAfterDuplicate?: boolean
|
readonly redirectAfterDuplicate?: boolean
|
||||||
|
readonly searchParams: { [key: string]: string | string[] | undefined }
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AdminViewComponent = PayloadComponent<AdminViewProps>
|
export type AdminViewComponent = PayloadComponent<AdminViewProps>
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ const traverseFields = ({
|
|||||||
}: TraverseFieldsArgs) => {
|
}: TraverseFieldsArgs) => {
|
||||||
fields.forEach((field) => {
|
fields.forEach((field) => {
|
||||||
switch (field.type) {
|
switch (field.type) {
|
||||||
case 'row':
|
case 'collapsible':
|
||||||
case 'collapsible': {
|
case 'row': {
|
||||||
traverseFields({
|
traverseFields({
|
||||||
data,
|
data,
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
@@ -47,14 +47,6 @@ const traverseFields = ({
|
|||||||
})
|
})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'tabs': {
|
|
||||||
traverseFields({
|
|
||||||
data,
|
|
||||||
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
|
||||||
result,
|
|
||||||
})
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case 'tab': {
|
case 'tab': {
|
||||||
if (tabHasName(field)) {
|
if (tabHasName(field)) {
|
||||||
let targetResult
|
let targetResult
|
||||||
@@ -84,6 +76,14 @@ const traverseFields = ({
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
case 'tabs': {
|
||||||
|
traverseFields({
|
||||||
|
data,
|
||||||
|
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
||||||
|
result,
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
if (fieldAffectsData(field)) {
|
if (fieldAffectsData(field)) {
|
||||||
if (field.saveToJWT) {
|
if (field.saveToJWT) {
|
||||||
|
|||||||
@@ -65,21 +65,6 @@ export const migrate = async ({ config, parsedArgs }: Args): Promise<void> => {
|
|||||||
case 'migrate':
|
case 'migrate':
|
||||||
await adapter.migrate()
|
await adapter.migrate()
|
||||||
break
|
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':
|
case 'migrate:create':
|
||||||
try {
|
try {
|
||||||
await adapter.createMigration({
|
await adapter.createMigration({
|
||||||
@@ -92,6 +77,21 @@ export const migrate = async ({ config, parsedArgs }: Args): Promise<void> => {
|
|||||||
throw new Error(`Error creating migration: ${err.message}`)
|
throw new Error(`Error creating migration: ${err.message}`)
|
||||||
}
|
}
|
||||||
break
|
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:
|
default:
|
||||||
payload.logger.error({
|
payload.logger.error({
|
||||||
|
|||||||
@@ -92,8 +92,8 @@ export async function getLocalizedPaths({
|
|||||||
|
|
||||||
switch (matchedField.type) {
|
switch (matchedField.type) {
|
||||||
case 'blocks':
|
case 'blocks':
|
||||||
case 'richText':
|
case 'json':
|
||||||
case 'json': {
|
case 'richText': {
|
||||||
const upcomingSegments = pathSegments.slice(i + 1).join('.')
|
const upcomingSegments = pathSegments.slice(i + 1).join('.')
|
||||||
lastIncompletePath.complete = true
|
lastIncompletePath.complete = true
|
||||||
lastIncompletePath.path = upcomingSegments
|
lastIncompletePath.path = upcomingSegments
|
||||||
|
|||||||
@@ -111,8 +111,8 @@ export const createClientField = ({
|
|||||||
|
|
||||||
switch (incomingField.type) {
|
switch (incomingField.type) {
|
||||||
case 'array':
|
case 'array':
|
||||||
case 'group':
|
|
||||||
case 'collapsible':
|
case 'collapsible':
|
||||||
|
case 'group':
|
||||||
case 'row': {
|
case 'row': {
|
||||||
const field = clientField as unknown as RowFieldClient
|
const field = clientField as unknown as RowFieldClient
|
||||||
|
|
||||||
@@ -182,6 +182,31 @@ export const createClientField = ({
|
|||||||
break
|
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': {
|
case 'richText': {
|
||||||
if (!incomingField?.editor) {
|
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
|
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
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'tabs': {
|
case 'tabs': {
|
||||||
const field = clientField as unknown as TabsFieldClient
|
const field = clientField as unknown as TabsFieldClient
|
||||||
|
|
||||||
@@ -221,30 +245,6 @@ export const createClientField = ({
|
|||||||
break
|
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:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,27 +97,6 @@ export const promise = async ({
|
|||||||
|
|
||||||
// Traverse subfields
|
// Traverse subfields
|
||||||
switch (field.type) {
|
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': {
|
case 'array': {
|
||||||
const rows = siblingDoc[field.name]
|
const rows = siblingDoc[field.name]
|
||||||
|
|
||||||
@@ -185,8 +164,9 @@ export const promise = async ({
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'row':
|
case 'collapsible':
|
||||||
case 'collapsible': {
|
|
||||||
|
case 'row': {
|
||||||
await traverseFields({
|
await traverseFields({
|
||||||
collection,
|
collection,
|
||||||
context,
|
context,
|
||||||
@@ -206,6 +186,66 @@ export const promise = async ({
|
|||||||
|
|
||||||
break
|
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': {
|
case 'tab': {
|
||||||
let tabSiblingData = siblingData
|
let tabSiblingData = siblingData
|
||||||
@@ -258,46 +298,6 @@ export const promise = async ({
|
|||||||
break
|
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: {
|
default: {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -181,15 +181,13 @@ export const promise = async ({
|
|||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'tabs': {
|
case 'point': {
|
||||||
field.tabs.forEach((tab) => {
|
const pointDoc = siblingDoc[field.name] as Record<string, unknown>
|
||||||
if (
|
if (Array.isArray(pointDoc?.coordinates) && pointDoc.coordinates.length === 2) {
|
||||||
tabHasName(tab) &&
|
siblingDoc[field.name] = pointDoc.coordinates
|
||||||
(typeof siblingDoc[tab.name] === 'undefined' || siblingDoc[tab.name] === null)
|
} else {
|
||||||
) {
|
siblingDoc[field.name] = undefined
|
||||||
siblingDoc[tab.name] = {}
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -206,13 +204,15 @@ export const promise = async ({
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'point': {
|
case 'tabs': {
|
||||||
const pointDoc = siblingDoc[field.name] as Record<string, unknown>
|
field.tabs.forEach((tab) => {
|
||||||
if (Array.isArray(pointDoc?.coordinates) && pointDoc.coordinates.length === 2) {
|
if (
|
||||||
siblingDoc[field.name] = pointDoc.coordinates
|
tabHasName(tab) &&
|
||||||
} else {
|
(typeof siblingDoc[tab.name] === 'undefined' || siblingDoc[tab.name] === null)
|
||||||
siblingDoc[field.name] = undefined
|
) {
|
||||||
}
|
siblingDoc[tab.name] = {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -347,45 +347,6 @@ export const promise = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (field.type) {
|
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': {
|
case 'array': {
|
||||||
const rows = siblingDoc[field.name] as JsonObject
|
const rows = siblingDoc[field.name] as JsonObject
|
||||||
|
|
||||||
@@ -573,8 +534,9 @@ export const promise = async ({
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'row':
|
case 'collapsible':
|
||||||
case 'collapsible': {
|
|
||||||
|
case 'row': {
|
||||||
traverseFields({
|
traverseFields({
|
||||||
collection,
|
collection,
|
||||||
context,
|
context,
|
||||||
@@ -605,23 +567,14 @@ export const promise = async ({
|
|||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
case 'group': {
|
||||||
case 'tab': {
|
let groupDoc = siblingDoc[field.name] as JsonObject
|
||||||
let tabDoc = siblingDoc
|
if (typeof siblingDoc[field.name] !== 'object') {
|
||||||
let tabSelect: SelectType | undefined
|
groupDoc = {}
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const groupSelect = select?.[field.name]
|
||||||
|
|
||||||
traverseFields({
|
traverseFields({
|
||||||
collection,
|
collection,
|
||||||
context,
|
context,
|
||||||
@@ -642,10 +595,10 @@ export const promise = async ({
|
|||||||
populationPromises,
|
populationPromises,
|
||||||
req,
|
req,
|
||||||
schemaPath: fieldSchemaPath,
|
schemaPath: fieldSchemaPath,
|
||||||
select: tabSelect,
|
select: typeof groupSelect === 'object' ? groupSelect : undefined,
|
||||||
selectMode,
|
selectMode,
|
||||||
showHiddenFields,
|
showHiddenFields,
|
||||||
siblingDoc: tabDoc,
|
siblingDoc: groupDoc,
|
||||||
triggerAccessControl,
|
triggerAccessControl,
|
||||||
triggerHooks,
|
triggerHooks,
|
||||||
})
|
})
|
||||||
@@ -653,37 +606,6 @@ export const promise = async ({
|
|||||||
break
|
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': {
|
case 'richText': {
|
||||||
if (!field?.editor) {
|
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
|
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
|
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: {
|
default: {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import type { RichTextAdapter } from '../../../admin/RichText.js'
|
|||||||
import type { SanitizedCollectionConfig } from '../../../collections/config/types.js'
|
import type { SanitizedCollectionConfig } from '../../../collections/config/types.js'
|
||||||
import type { ValidationFieldError } from '../../../errors/index.js'
|
import type { ValidationFieldError } from '../../../errors/index.js'
|
||||||
import type { SanitizedGlobalConfig } from '../../../globals/config/types.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 { JsonObject, Operation, PayloadRequest } from '../../../types/index.js'
|
||||||
import type { Field, TabAsField } from '../../config/types.js'
|
import type { Field, TabAsField } from '../../config/types.js'
|
||||||
import type { RequestContext } from '../../../index.js'
|
|
||||||
|
|
||||||
import { MissingEditorProp } from '../../../errors/index.js'
|
import { MissingEditorProp } from '../../../errors/index.js'
|
||||||
import { deepMergeWithSourceArrays } from '../../../utilities/deepMerge.js'
|
import { deepMergeWithSourceArrays } from '../../../utilities/deepMerge.js'
|
||||||
@@ -200,60 +200,6 @@ export const promise = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (field.type) {
|
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': {
|
case 'array': {
|
||||||
const rows = siblingData[field.name]
|
const rows = siblingData[field.name]
|
||||||
|
|
||||||
@@ -339,8 +285,9 @@ export const promise = async ({
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'row':
|
case 'collapsible':
|
||||||
case 'collapsible': {
|
|
||||||
|
case 'row': {
|
||||||
await traverseFields({
|
await traverseFields({
|
||||||
id,
|
id,
|
||||||
collection,
|
collection,
|
||||||
@@ -365,6 +312,104 @@ export const promise = async ({
|
|||||||
break
|
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': {
|
case 'tab': {
|
||||||
let tabSiblingData = siblingData
|
let tabSiblingData = siblingData
|
||||||
let tabSiblingDoc = siblingDoc
|
let tabSiblingDoc = siblingDoc
|
||||||
@@ -435,51 +480,6 @@ export const promise = async ({
|
|||||||
break
|
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: {
|
default: {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,26 +145,6 @@ export const promise = async <T>({
|
|||||||
localization.localeCodes.forEach((locale) => {
|
localization.localeCodes.forEach((locale) => {
|
||||||
if (fieldData[locale]) {
|
if (fieldData[locale]) {
|
||||||
switch (field.type) {
|
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': {
|
case 'array': {
|
||||||
const rows = fieldData[locale]
|
const rows = fieldData[locale]
|
||||||
|
|
||||||
@@ -189,7 +169,6 @@ export const promise = async <T>({
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'blocks': {
|
case 'blocks': {
|
||||||
const rows = fieldData[locale]
|
const rows = fieldData[locale]
|
||||||
|
|
||||||
@@ -220,6 +199,27 @@ export const promise = async <T>({
|
|||||||
}
|
}
|
||||||
break
|
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
|
// we need to further traverse its children
|
||||||
// so the child fields can run beforeDuplicate hooks
|
// so the child fields can run beforeDuplicate hooks
|
||||||
switch (field.type) {
|
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': {
|
case 'array': {
|
||||||
const rows = siblingDoc[field.name]
|
const rows = siblingDoc[field.name]
|
||||||
|
|
||||||
@@ -279,7 +255,6 @@ export const promise = async <T>({
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'blocks': {
|
case 'blocks': {
|
||||||
const rows = siblingDoc[field.name]
|
const rows = siblingDoc[field.name]
|
||||||
|
|
||||||
@@ -313,13 +288,38 @@ export const promise = async <T>({
|
|||||||
|
|
||||||
break
|
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 {
|
} else {
|
||||||
// Finally, we traverse fields which do not affect data here
|
// Finally, we traverse fields which do not affect data here
|
||||||
switch (field.type) {
|
switch (field.type) {
|
||||||
case 'row':
|
case 'collapsible':
|
||||||
case 'collapsible': {
|
case 'row': {
|
||||||
await traverseFields({
|
await traverseFields({
|
||||||
id,
|
id,
|
||||||
collection,
|
collection,
|
||||||
|
|||||||
@@ -90,6 +90,31 @@ export const promise = async <T>({
|
|||||||
|
|
||||||
// Sanitize incoming data
|
// Sanitize incoming data
|
||||||
switch (field.type) {
|
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': {
|
case 'number': {
|
||||||
if (typeof siblingData[field.name] === 'string') {
|
if (typeof siblingData[field.name] === 'string') {
|
||||||
const value = siblingData[field.name] as string
|
const value = siblingData[field.name] as string
|
||||||
@@ -114,36 +139,8 @@ export const promise = async <T>({
|
|||||||
|
|
||||||
break
|
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 'relationship':
|
||||||
|
|
||||||
case 'upload': {
|
case 'upload': {
|
||||||
if (
|
if (
|
||||||
siblingData[field.name] === '' ||
|
siblingData[field.name] === '' ||
|
||||||
@@ -230,12 +227,15 @@ export const promise = async <T>({
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
case 'richText': {
|
||||||
case 'array':
|
if (typeof siblingData[field.name] === 'string') {
|
||||||
case 'blocks': {
|
try {
|
||||||
// Handle cases of arrays being intentionally set to 0
|
const richTextJSON = JSON.parse(siblingData[field.name] as string)
|
||||||
if (siblingData[field.name] === '0' || siblingData[field.name] === 0) {
|
siblingData[field.name] = richTextJSON
|
||||||
siblingData[field.name] = []
|
} catch {
|
||||||
|
// Disregard this data as it is not valid.
|
||||||
|
// Will be reported to user by field validation
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
@@ -305,37 +305,6 @@ export const promise = async <T>({
|
|||||||
|
|
||||||
// Traverse subfields
|
// Traverse subfields
|
||||||
switch (field.type) {
|
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': {
|
case 'array': {
|
||||||
const rows = siblingData[field.name]
|
const rows = siblingData[field.name]
|
||||||
|
|
||||||
@@ -405,8 +374,9 @@ export const promise = async <T>({
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'row':
|
case 'collapsible':
|
||||||
case 'collapsible': {
|
|
||||||
|
case 'row': {
|
||||||
await traverseFields({
|
await traverseFields({
|
||||||
id,
|
id,
|
||||||
collection,
|
collection,
|
||||||
@@ -426,6 +396,76 @@ export const promise = async <T>({
|
|||||||
|
|
||||||
break
|
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': {
|
case 'tab': {
|
||||||
let tabSiblingData
|
let tabSiblingData
|
||||||
@@ -486,46 +526,6 @@ export const promise = async <T>({
|
|||||||
break
|
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: {
|
default: {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -239,123 +239,125 @@ export function fieldsToJSONSchema(
|
|||||||
let fieldSchema: JSONSchema4
|
let fieldSchema: JSONSchema4
|
||||||
|
|
||||||
switch (field.type) {
|
switch (field.type) {
|
||||||
case 'text':
|
case 'array': {
|
||||||
if (field.hasMany === true) {
|
fieldSchema = {
|
||||||
fieldSchema = {
|
type: withNullableJSONSchemaType('array', isRequired),
|
||||||
type: withNullableJSONSchemaType('array', isRequired),
|
items: {
|
||||||
items: { type: 'string' },
|
type: 'object',
|
||||||
}
|
additionalProperties: false,
|
||||||
} else {
|
...fieldsToJSONSchema(
|
||||||
fieldSchema = { type: withNullableJSONSchemaType('string', isRequired) }
|
collectionIDFieldTypes,
|
||||||
|
field.fields,
|
||||||
|
interfaceNameDefinitions,
|
||||||
|
config,
|
||||||
|
),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
break
|
|
||||||
case 'textarea':
|
|
||||||
case 'code':
|
|
||||||
case 'email':
|
|
||||||
case 'date': {
|
|
||||||
fieldSchema = { type: withNullableJSONSchemaType('string', isRequired) }
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'number': {
|
if (field.interfaceName) {
|
||||||
if (field.hasMany === true) {
|
interfaceNameDefinitions.set(field.interfaceName, fieldSchema)
|
||||||
|
|
||||||
fieldSchema = {
|
fieldSchema = {
|
||||||
type: withNullableJSONSchemaType('array', isRequired),
|
$ref: `#/definitions/${field.interfaceName}`,
|
||||||
items: { type: 'number' },
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
fieldSchema = { type: withNullableJSONSchemaType('number', isRequired) }
|
|
||||||
}
|
}
|
||||||
break
|
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': {
|
case 'checkbox': {
|
||||||
fieldSchema = { type: withNullableJSONSchemaType('boolean', isRequired) }
|
fieldSchema = { type: withNullableJSONSchemaType('boolean', isRequired) }
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
case 'code':
|
||||||
|
case 'date':
|
||||||
|
|
||||||
case 'json': {
|
case 'email':
|
||||||
fieldSchema = field.jsonSchema?.schema || {
|
|
||||||
type: ['object', 'array', 'string', 'number', 'boolean', 'null'],
|
case 'textarea': {
|
||||||
}
|
fieldSchema = { type: withNullableJSONSchemaType('string', isRequired) }
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'richText': {
|
case 'collapsible':
|
||||||
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
|
case 'row': {
|
||||||
}
|
const childSchema = fieldsToJSONSchema(
|
||||||
if (typeof field.editor === 'function') {
|
collectionIDFieldTypes,
|
||||||
throw new Error('Attempted to access unsanitized rich text editor.')
|
field.fields,
|
||||||
}
|
interfaceNameDefinitions,
|
||||||
if (field.editor.outputSchema) {
|
config,
|
||||||
fieldSchema = field.editor.outputSchema({
|
)
|
||||||
|
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,
|
collectionIDFieldTypes,
|
||||||
config,
|
field.fields,
|
||||||
field,
|
|
||||||
interfaceNameDefinitions,
|
interfaceNameDefinitions,
|
||||||
isRequired,
|
config,
|
||||||
})
|
),
|
||||||
} else {
|
|
||||||
// Maintain backwards compatibility with existing rich text editors
|
|
||||||
fieldSchema = {
|
|
||||||
type: withNullableJSONSchemaType('array', isRequired),
|
|
||||||
items: {
|
|
||||||
type: 'object',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = {
|
fieldSchema = {
|
||||||
type: withNullableJSONSchemaType('array', isRequired),
|
$ref: `#/definitions/${field.interfaceName}`,
|
||||||
items: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
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
|
break
|
||||||
}
|
}
|
||||||
@@ -384,8 +386,53 @@ export function fieldsToJSONSchema(
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'upload':
|
case 'json': {
|
||||||
case 'relationship': {
|
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 (Array.isArray(field.relationTo)) {
|
||||||
if (field.hasMany) {
|
if (field.hasMany) {
|
||||||
fieldSchema = {
|
fieldSchema = {
|
||||||
@@ -474,91 +521,55 @@ export function fieldsToJSONSchema(
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'blocks': {
|
case 'richText': {
|
||||||
// Check for a case where no blocks are provided.
|
if (!field?.editor) {
|
||||||
// We need to generate an empty array for this case, note that JSON schema 4 doesn't support empty arrays
|
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
|
||||||
// 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
|
if (typeof field.editor === 'function') {
|
||||||
}
|
throw new Error('Attempted to access unsanitized rich text editor.')
|
||||||
|
|
||||||
case 'array': {
|
|
||||||
fieldSchema = {
|
|
||||||
type: withNullableJSONSchemaType('array', isRequired),
|
|
||||||
items: {
|
|
||||||
type: 'object',
|
|
||||||
additionalProperties: false,
|
|
||||||
...fieldsToJSONSchema(
|
|
||||||
collectionIDFieldTypes,
|
|
||||||
field.fields,
|
|
||||||
interfaceNameDefinitions,
|
|
||||||
config,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
if (field.editor.outputSchema) {
|
||||||
if (field.interfaceName) {
|
fieldSchema = field.editor.outputSchema({
|
||||||
interfaceNameDefinitions.set(field.interfaceName, fieldSchema)
|
collectionIDFieldTypes,
|
||||||
|
config,
|
||||||
|
field,
|
||||||
|
interfaceNameDefinitions,
|
||||||
|
isRequired,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Maintain backwards compatibility with existing rich text editors
|
||||||
fieldSchema = {
|
fieldSchema = {
|
||||||
$ref: `#/definitions/${field.interfaceName}`,
|
type: withNullableJSONSchemaType('array', isRequired),
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
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
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -596,27 +607,16 @@ export function fieldsToJSONSchema(
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'group': {
|
case 'text':
|
||||||
fieldSchema = {
|
if (field.hasMany === true) {
|
||||||
type: 'object',
|
|
||||||
additionalProperties: false,
|
|
||||||
...fieldsToJSONSchema(
|
|
||||||
collectionIDFieldTypes,
|
|
||||||
field.fields,
|
|
||||||
interfaceNameDefinitions,
|
|
||||||
config,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
if (field.interfaceName) {
|
|
||||||
interfaceNameDefinitions.set(field.interfaceName, fieldSchema)
|
|
||||||
|
|
||||||
fieldSchema = {
|
fieldSchema = {
|
||||||
$ref: `#/definitions/${field.interfaceName}`,
|
type: withNullableJSONSchemaType('array', isRequired),
|
||||||
|
items: { type: 'string' },
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
fieldSchema = { type: withNullableJSONSchemaType('string', isRequired) }
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
break
|
break
|
||||||
@@ -704,15 +704,6 @@ export function fieldsToSelectJSONSchema({ fields }: { fields: Field[] }): JSONS
|
|||||||
|
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
switch (field.type) {
|
switch (field.type) {
|
||||||
case 'row':
|
|
||||||
case 'collapsible':
|
|
||||||
schema.properties = {
|
|
||||||
...schema.properties,
|
|
||||||
...fieldsToSelectJSONSchema({ fields: field.fields }).properties,
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'array':
|
case 'array':
|
||||||
case 'group':
|
case 'group':
|
||||||
schema.properties[field.name] = {
|
schema.properties[field.name] = {
|
||||||
@@ -725,27 +716,6 @@ export function fieldsToSelectJSONSchema({ fields }: { fields: Field[] }): JSONS
|
|||||||
}
|
}
|
||||||
break
|
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': {
|
case 'blocks': {
|
||||||
const blocksSchema: JSONSchema4 = {
|
const blocksSchema: JSONSchema4 = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -775,6 +745,36 @@ export function fieldsToSelectJSONSchema({ fields }: { fields: Field[] }): JSONS
|
|||||||
|
|
||||||
break
|
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:
|
default:
|
||||||
schema.properties[field.name] = {
|
schema.properties[field.name] = {
|
||||||
@@ -800,6 +800,34 @@ const generateAuthFieldTypes = ({
|
|||||||
}): JSONSchema4 => {
|
}): JSONSchema4 => {
|
||||||
if (loginWithUsername) {
|
if (loginWithUsername) {
|
||||||
switch (type) {
|
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': {
|
case 'login': {
|
||||||
if (loginWithUsername.allowEmailLogin) {
|
if (loginWithUsername.allowEmailLogin) {
|
||||||
// allow username or email and require password for login
|
// allow username or email and require password for login
|
||||||
@@ -858,34 +886,6 @@ const generateAuthFieldTypes = ({
|
|||||||
required: requiredFields,
|
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
|
let result = acc
|
||||||
|
|
||||||
switch (field.type) {
|
switch (field.type) {
|
||||||
case 'group':
|
|
||||||
acc.push({
|
|
||||||
name: field.name,
|
|
||||||
type: field.type,
|
|
||||||
fields: fieldSchemaToJSON(field.fields),
|
|
||||||
})
|
|
||||||
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'array':
|
case 'array':
|
||||||
acc.push({
|
acc.push({
|
||||||
name: field.name,
|
name: field.name,
|
||||||
@@ -61,11 +52,31 @@ export const fieldSchemaToJSON = (fields: ClientField[]): FieldSchemaJSON => {
|
|||||||
|
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'row':
|
|
||||||
case 'collapsible':
|
case 'collapsible':
|
||||||
|
|
||||||
|
case 'row':
|
||||||
result = result.concat(fieldSchemaToJSON(field.fields))
|
result = result.concat(fieldSchemaToJSON(field.fields))
|
||||||
break
|
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': {
|
case 'tabs': {
|
||||||
let tabFields = []
|
let tabFields = []
|
||||||
|
|
||||||
@@ -87,17 +98,6 @@ export const fieldSchemaToJSON = (fields: ClientField[]): FieldSchemaJSON => {
|
|||||||
break
|
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:
|
default:
|
||||||
if ('name' in field) {
|
if ('name' in field) {
|
||||||
acc.push({
|
acc.push({
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { envPaths } from './envPaths.js'
|
|||||||
const createPlainObject = <T = Record<string, unknown>>(): T => Object.create(null)
|
const createPlainObject = <T = Record<string, unknown>>(): T => Object.create(null)
|
||||||
|
|
||||||
const checkValueType = (key: string, value: unknown): void => {
|
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
|
const type = typeof value
|
||||||
|
|
||||||
|
|||||||
@@ -28,16 +28,16 @@ export const getPaymentTotal = (
|
|||||||
total += valueToUse
|
total += valueToUse
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'subtract': {
|
case 'divide': {
|
||||||
total -= valueToUse
|
total /= valueToUse
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'multiply': {
|
case 'multiply': {
|
||||||
total *= valueToUse
|
total *= valueToUse
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'divide': {
|
case 'subtract': {
|
||||||
total /= valueToUse
|
total -= valueToUse
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
|||||||
@@ -91,6 +91,30 @@ export const serializeSlate = (children?: Node[], submissionData?: any): string
|
|||||||
<h6>
|
<h6>
|
||||||
${serializeSlate(node.children, submissionData)}
|
${serializeSlate(node.children, submissionData)}
|
||||||
</h6>
|
</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':
|
case 'quote':
|
||||||
return `
|
return `
|
||||||
@@ -104,30 +128,6 @@ export const serializeSlate = (children?: Node[], submissionData?: any): string
|
|||||||
${serializeSlate(node.children, submissionData)}
|
${serializeSlate(node.children, submissionData)}
|
||||||
</ul>
|
</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:
|
default:
|
||||||
return `
|
return `
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ export const handleWebhooks: StripeWebhookHandler = (args) => {
|
|||||||
})
|
})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'updated': {
|
case 'deleted': {
|
||||||
void handleCreatedOrUpdated({
|
void handleDeleted({
|
||||||
...args,
|
...args,
|
||||||
pluginConfig,
|
pluginConfig,
|
||||||
resourceType,
|
resourceType,
|
||||||
@@ -39,8 +39,8 @@ export const handleWebhooks: StripeWebhookHandler = (args) => {
|
|||||||
})
|
})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'deleted': {
|
case 'updated': {
|
||||||
void handleDeleted({
|
void handleCreatedOrUpdated({
|
||||||
...args,
|
...args,
|
||||||
pluginConfig,
|
pluginConfig,
|
||||||
resourceType,
|
resourceType,
|
||||||
|
|||||||
@@ -93,7 +93,7 @@
|
|||||||
"babel-plugin-transform-remove-imports": "^1.8.0",
|
"babel-plugin-transform-remove-imports": "^1.8.0",
|
||||||
"esbuild": "0.23.1",
|
"esbuild": "0.23.1",
|
||||||
"esbuild-sass-plugin": "3.3.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:*",
|
"payload": "workspace:*",
|
||||||
"swc-plugin-transform-remove-imports": "1.15.0"
|
"swc-plugin-transform-remove-imports": "1.15.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -45,17 +45,14 @@ const formatStep = (step: Step) => {
|
|||||||
case 'click': {
|
case 'click': {
|
||||||
return ` await page.mouse.click(${value.x}, ${value.y});`
|
return ` await page.mouse.click(${value.x}, ${value.y});`
|
||||||
}
|
}
|
||||||
case 'press': {
|
|
||||||
return ` await page.keyboard.press('${value}');`
|
|
||||||
}
|
|
||||||
case 'keydown': {
|
case 'keydown': {
|
||||||
return ` await page.keyboard.keydown('${value}');`
|
return ` await page.keyboard.keydown('${value}');`
|
||||||
}
|
}
|
||||||
case 'keyup': {
|
case 'keyup': {
|
||||||
return ` await page.keyboard.keyup('${value}');`
|
return ` await page.keyboard.keyup('${value}');`
|
||||||
}
|
}
|
||||||
case 'type': {
|
case 'press': {
|
||||||
return ` await page.keyboard.type('${value}');`
|
return ` await page.keyboard.press('${value}');`
|
||||||
}
|
}
|
||||||
case 'selectAll': {
|
case 'selectAll': {
|
||||||
return ` await selectAll(page);`
|
return ` await selectAll(page);`
|
||||||
@@ -70,6 +67,9 @@ const formatStep = (step: Step) => {
|
|||||||
});
|
});
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
case 'type': {
|
||||||
|
return ` await page.keyboard.type('${value}');`
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return ``
|
return ``
|
||||||
}
|
}
|
||||||
@@ -119,14 +119,14 @@ function getPathFromNodeToEditor(node: Node, rootElement: HTMLElement | null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const keyPresses = new Set([
|
const keyPresses = new Set([
|
||||||
'Enter',
|
'ArrowDown',
|
||||||
'Backspace',
|
|
||||||
'Delete',
|
|
||||||
'Escape',
|
|
||||||
'ArrowLeft',
|
'ArrowLeft',
|
||||||
'ArrowRight',
|
'ArrowRight',
|
||||||
'ArrowUp',
|
'ArrowUp',
|
||||||
'ArrowDown',
|
'Backspace',
|
||||||
|
'Delete',
|
||||||
|
'Enter',
|
||||||
|
'Escape',
|
||||||
])
|
])
|
||||||
|
|
||||||
type Step = {
|
type Step = {
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export const ToolbarButton = ({
|
|||||||
className={className}
|
className={className}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (enabled !== false) {
|
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(() => {
|
editor.focus(() => {
|
||||||
// We need to wrap the onSelect in the callback, so the editor is properly focused before the onSelect is called.
|
// 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}
|
className={className}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (enabled !== false) {
|
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(() => {
|
editor.focus(() => {
|
||||||
// We need to wrap the onSelect in the callback, so the editor is properly focused before the onSelect is called.
|
// 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.textAlign) {
|
||||||
if (element.type === 'relationship' || element.type === 'upload') {
|
if (element.type === 'relationship' || element.type === 'upload') {
|
||||||
switch (element.textAlign) {
|
switch (element.textAlign) {
|
||||||
|
case 'center':
|
||||||
|
attr = { ...attr, style: { marginLeft: 'auto', marginRight: 'auto' } }
|
||||||
|
break
|
||||||
case 'left':
|
case 'left':
|
||||||
attr = { ...attr, style: { marginRight: 'auto' } }
|
attr = { ...attr, style: { marginRight: 'auto' } }
|
||||||
break
|
break
|
||||||
case 'right':
|
case 'right':
|
||||||
attr = { ...attr, style: { marginLeft: 'auto' } }
|
attr = { ...attr, style: { marginLeft: 'auto' } }
|
||||||
break
|
break
|
||||||
case 'center':
|
|
||||||
attr = { ...attr, style: { marginLeft: 'auto', marginRight: 'auto' } }
|
|
||||||
break
|
|
||||||
default:
|
default:
|
||||||
attr = { ...attr, style: { textAlign: element.textAlign } }
|
attr = { ...attr, style: { textAlign: element.textAlign } }
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else if (element.type === 'li') {
|
} else if (element.type === 'li') {
|
||||||
switch (element.textAlign) {
|
switch (element.textAlign) {
|
||||||
case 'right':
|
|
||||||
attr = { ...attr, style: { listStylePosition: 'inside', textAlign: 'right' } }
|
|
||||||
break
|
|
||||||
case 'center':
|
case 'center':
|
||||||
attr = { ...attr, style: { listStylePosition: 'inside', textAlign: 'center' } }
|
attr = { ...attr, style: { listStylePosition: 'inside', textAlign: 'center' } }
|
||||||
break
|
break
|
||||||
|
case 'right':
|
||||||
|
attr = { ...attr, style: { listStylePosition: 'inside', textAlign: 'right' } }
|
||||||
|
break
|
||||||
case 'left':
|
case 'left':
|
||||||
default:
|
default:
|
||||||
attr = { ...attr, style: { listStylePosition: 'outside', textAlign: 'left' } }
|
attr = { ...attr, style: { listStylePosition: 'outside', textAlign: 'left' } }
|
||||||
|
|||||||
@@ -150,6 +150,9 @@ export const RscEntrySlateField: React.FC<
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'relationship':
|
||||||
|
break
|
||||||
|
|
||||||
case 'upload': {
|
case 'upload': {
|
||||||
const uploadEnabledCollections = payload.config.collections.filter(
|
const uploadEnabledCollections = payload.config.collections.filter(
|
||||||
({ admin: { enableRichTextRelationship, hidden }, upload }) => {
|
({ admin: { enableRichTextRelationship, hidden }, upload }) => {
|
||||||
@@ -179,9 +182,6 @@ export const RscEntrySlateField: React.FC<
|
|||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'relationship':
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ export const getGenerateSchemaMap =
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'relationship':
|
||||||
|
break
|
||||||
|
|
||||||
case 'upload': {
|
case 'upload': {
|
||||||
const uploadEnabledCollections = config.collections.filter(
|
const uploadEnabledCollections = config.collections.filter(
|
||||||
({ admin: { enableRichTextRelationship, hidden }, upload }) => {
|
({ admin: { enableRichTextRelationship, hidden }, upload }) => {
|
||||||
@@ -73,9 +76,6 @@ export const getGenerateSchemaMap =
|
|||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'relationship':
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -161,12 +161,12 @@ export type InitTFunction<
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type InitI18n =
|
export type InitI18n =
|
||||||
|
| ((args: { config: I18nOptions; context: 'api'; language: AcceptedLanguages }) => Promise<I18n>)
|
||||||
| ((args: {
|
| ((args: {
|
||||||
config: I18nOptions<ClientTranslationsObject>
|
config: I18nOptions<ClientTranslationsObject>
|
||||||
context: 'client'
|
context: 'client'
|
||||||
language: AcceptedLanguages
|
language: AcceptedLanguages
|
||||||
}) => Promise<I18n<ClientTranslationsObject, ClientTranslationKeys>>)
|
}) => Promise<I18n<ClientTranslationsObject, ClientTranslationKeys>>)
|
||||||
| ((args: { config: I18nOptions; context: 'api'; language: AcceptedLanguages }) => Promise<I18n>)
|
|
||||||
|
|
||||||
export type LanguagePreference = {
|
export type LanguagePreference = {
|
||||||
language: AcceptedLanguages
|
language: AcceptedLanguages
|
||||||
|
|||||||
@@ -122,7 +122,7 @@
|
|||||||
"babel-plugin-react-compiler": "0.0.0-experimental-24ec0eb-20240918",
|
"babel-plugin-react-compiler": "0.0.0-experimental-24ec0eb-20240918",
|
||||||
"esbuild": "0.23.1",
|
"esbuild": "0.23.1",
|
||||||
"esbuild-sass-plugin": "3.3.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:*"
|
"payload": "workspace:*"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import LinkImport from 'next/link.js' // TODO: abstract this out to support all routers
|
|
||||||
import type { MouseEvent } from 'react'
|
import type { MouseEvent } from 'react'
|
||||||
|
|
||||||
|
import LinkImport from 'next/link.js' // TODO: abstract this out to support all routers
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|||||||
@@ -26,11 +26,11 @@ export function AddingFilesView() {
|
|||||||
activeIndex,
|
activeIndex,
|
||||||
collectionSlug,
|
collectionSlug,
|
||||||
docPermissions,
|
docPermissions,
|
||||||
|
documentSlots,
|
||||||
forms,
|
forms,
|
||||||
hasPublishPermission,
|
hasPublishPermission,
|
||||||
hasSavePermission,
|
hasSavePermission,
|
||||||
hasSubmitted,
|
hasSubmitted,
|
||||||
documentSlots,
|
|
||||||
} = useFormsManager()
|
} = useFormsManager()
|
||||||
const activeForm = forms[activeIndex]
|
const activeForm = forms[activeIndex]
|
||||||
const { getEntityConfig } = useConfig()
|
const { getEntityConfig } = useConfig()
|
||||||
|
|||||||
@@ -35,18 +35,6 @@ type Action =
|
|||||||
|
|
||||||
export function formsManagementReducer(state: State, action: Action): State {
|
export function formsManagementReducer(state: State, action: Action): State {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'REPLACE': {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
...action.state,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 'SET_ACTIVE_INDEX': {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
activeIndex: action.index,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 'ADD_FORMS': {
|
case 'ADD_FORMS': {
|
||||||
const newForms: State['forms'] = []
|
const newForms: State['forms'] = []
|
||||||
for (let i = 0; i < action.files.length; i++) {
|
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,
|
totalErrorCount: state.totalErrorCount - removedForm.errorCount,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case 'REPLACE': {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
...action.state,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'SET_ACTIVE_INDEX': {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activeIndex: action.index,
|
||||||
|
}
|
||||||
|
}
|
||||||
case 'UPDATE_ERROR_COUNT': {
|
case 'UPDATE_ERROR_COUNT': {
|
||||||
const forms = [...state.forms]
|
const forms = [...state.forms]
|
||||||
forms[action.index].errorCount = action.count
|
forms[action.index].errorCount = action.count
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import { useRouter } from 'next/navigation.js'
|
|||||||
import React, { useCallback, useState } from 'react'
|
import React, { useCallback, useState } from 'react'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
|
|
||||||
|
import type { DocumentDrawerContextType } from '../DocumentDrawer/Provider.js'
|
||||||
|
|
||||||
import { useForm, useFormModified } from '../../forms/Form/context.js'
|
import { useForm, useFormModified } from '../../forms/Form/context.js'
|
||||||
import { useConfig } from '../../providers/Config/index.js'
|
import { useConfig } from '../../providers/Config/index.js'
|
||||||
import { useEditDepth } from '../../providers/EditDepth/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 { Button } from '../Button/index.js'
|
||||||
import { drawerZBase } from '../Drawer/index.js'
|
import { drawerZBase } from '../Drawer/index.js'
|
||||||
import { PopupList } from '../Popup/index.js'
|
import { PopupList } from '../Popup/index.js'
|
||||||
import { DocumentDrawerContextType } from '../DocumentDrawer/Provider.jsx'
|
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
const baseClass = 'duplicate'
|
const baseClass = 'duplicate'
|
||||||
|
|||||||
@@ -119,8 +119,8 @@ export const RelationshipCell: React.FC<RelationshipCellProps> = ({
|
|||||||
if (previewAllowed && document) {
|
if (previewAllowed && document) {
|
||||||
fileField = (
|
fileField = (
|
||||||
<FileCell
|
<FileCell
|
||||||
collectionConfig={relatedCollection}
|
|
||||||
cellData={label}
|
cellData={label}
|
||||||
|
collectionConfig={relatedCollection}
|
||||||
customCellProps={customCellContext}
|
customCellProps={customCellContext}
|
||||||
field={field}
|
field={field}
|
||||||
rowData={document}
|
rowData={document}
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
'use client'
|
'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 type { DefaultCellComponentProps, UploadFieldClient } from 'payload'
|
||||||
|
|
||||||
import { getTranslation } from '@payloadcms/translations'
|
import { getTranslation } from '@payloadcms/translations'
|
||||||
|
import LinkImport from 'next/link.js'
|
||||||
import { fieldAffectsData } from 'payload/shared'
|
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 { useConfig } from '../../../providers/Config/index.js'
|
||||||
import { useTranslation } from '../../../providers/Translation/index.js'
|
import { useTranslation } from '../../../providers/Translation/index.js'
|
||||||
import { formatAdminURL } from '../../../utilities/formatAdminURL.js'
|
import { formatAdminURL } from '../../../utilities/formatAdminURL.js'
|
||||||
import { CodeCell } from './fields/Code/index.js'
|
import { CodeCell } from './fields/Code/index.js'
|
||||||
import { cellComponents } from './fields/index.js'
|
import { cellComponents } from './fields/index.js'
|
||||||
|
|
||||||
const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default
|
const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default
|
||||||
|
|
||||||
export const DefaultCell: React.FC<DefaultCellComponentProps> = (props) => {
|
export const DefaultCell: React.FC<DefaultCellComponentProps> = (props) => {
|
||||||
|
|||||||
@@ -18,19 +18,6 @@ type Action = AddLoadedDocuments | RequestDocuments
|
|||||||
|
|
||||||
export function reducer(state: Documents, action: Action): Documents {
|
export function reducer(state: Documents, action: Action): Documents {
|
||||||
switch (action.type) {
|
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': {
|
case 'ADD_LOADED': {
|
||||||
const newState = { ...state }
|
const newState = { ...state }
|
||||||
if (typeof newState[action.relationTo] !== 'object') {
|
if (typeof newState[action.relationTo] !== 'object') {
|
||||||
@@ -52,6 +39,19 @@ export function reducer(state: Documents, action: Action): Documents {
|
|||||||
return newState
|
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: {
|
default: {
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,9 +43,9 @@ export const DefaultFilter: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (internalField?.field?.type) {
|
switch (internalField?.field?.type) {
|
||||||
case 'number': {
|
case 'date': {
|
||||||
return (
|
return (
|
||||||
<NumberField
|
<DateField
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
field={internalField.field}
|
field={internalField.field}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
@@ -55,9 +55,9 @@ export const DefaultFilter: React.FC<Props> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'date': {
|
case 'number': {
|
||||||
return (
|
return (
|
||||||
<DateField
|
<NumberField
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
field={internalField.field}
|
field={internalField.field}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
|||||||
@@ -14,10 +14,6 @@ const reduceToIDs = (options) =>
|
|||||||
|
|
||||||
const optionsReducer = (state: Option[], action: Action): Option[] => {
|
const optionsReducer = (state: Option[], action: Action): Option[] => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'CLEAR': {
|
|
||||||
return action.required ? [] : [{ label: action.i18n.t('general:none'), value: 'null' }]
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'ADD': {
|
case 'ADD': {
|
||||||
const { collection, data, hasMultipleRelations, i18n, relation } = action
|
const { collection, data, hasMultipleRelations, i18n, relation } = action
|
||||||
|
|
||||||
@@ -79,6 +75,10 @@ const optionsReducer = (state: Option[], action: Action): Option[] => {
|
|||||||
return newOptions
|
return newOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'CLEAR': {
|
||||||
|
return action.required ? [] : [{ label: action.i18n.t('general:none'), value: 'null' }]
|
||||||
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,61 +28,6 @@ const sortOptions = (options: Option[]): Option[] =>
|
|||||||
|
|
||||||
export const optionsReducer = (state: OptionGroup[], action: Action): OptionGroup[] => {
|
export const optionsReducer = (state: OptionGroup[], action: Action): OptionGroup[] => {
|
||||||
switch (action.type) {
|
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': {
|
case 'ADD': {
|
||||||
const { collection, config, docs, i18n, ids = [], sort } = action
|
const { collection, config, docs, i18n, ids = [], sort } = action
|
||||||
const relation = collection.slug
|
const relation = collection.slug
|
||||||
@@ -146,6 +91,35 @@ export const optionsReducer = (state: OptionGroup[], action: Action): OptionGrou
|
|||||||
return newOptions
|
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': {
|
case 'REMOVE': {
|
||||||
const { id, collection } = action
|
const { id, collection } = action
|
||||||
|
|
||||||
@@ -167,6 +141,32 @@ export const optionsReducer = (state: OptionGroup[], action: Action): OptionGrou
|
|||||||
return newOptions
|
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: {
|
default: {
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,36 +17,53 @@ const ObjectId = (ObjectIdImport.default ||
|
|||||||
*/
|
*/
|
||||||
export function fieldReducer(state: FormState, action: FieldAction): FormState {
|
export function fieldReducer(state: FormState, action: FieldAction): FormState {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'REPLACE_STATE': {
|
case 'ADD_ROW': {
|
||||||
if (action.optimize !== false) {
|
const { blockType, path, rowIndex: rowIndexFromArgs, subFieldState = {} } = action
|
||||||
// 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 rowIndex =
|
||||||
const oldField = state[path]
|
typeof rowIndexFromArgs === 'number' ? rowIndexFromArgs : state[path]?.rows?.length || 0
|
||||||
const newField = field
|
|
||||||
|
|
||||||
if (!dequal(oldField, newField)) {
|
const withNewRow = [...(state[path]?.rows || [])]
|
||||||
newState[path] = newField
|
|
||||||
} else if (oldField) {
|
const newRow: Row = {
|
||||||
newState[path] = oldField
|
id: (subFieldState?.id?.value as string) || new ObjectId().toHexString(),
|
||||||
}
|
blockType: blockType || undefined,
|
||||||
})
|
collapsed: false,
|
||||||
return newState
|
|
||||||
}
|
}
|
||||||
// If we're not optimizing, just set the state to the new state
|
|
||||||
return action.state
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'REMOVE': {
|
withNewRow.splice(rowIndex, 0, newRow)
|
||||||
const newState = { ...state }
|
|
||||||
if (newState[action.path]) {
|
if (blockType) {
|
||||||
delete newState[action.path]
|
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
|
return newState
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,160 +129,6 @@ export function fieldReducer(state: FormState, action: FieldAction): FormState {
|
|||||||
return newState
|
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': {
|
case 'DUPLICATE_ROW': {
|
||||||
const { path, rowIndex } = action
|
const { path, rowIndex } = action
|
||||||
const { remainingFields, rows } = separateRows(path, state)
|
const { remainingFields, rows } = separateRows(path, state)
|
||||||
@@ -342,20 +205,100 @@ export function fieldReducer(state: FormState, action: FieldAction): FormState {
|
|||||||
return newState
|
return newState
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'SET_ROW_COLLAPSED': {
|
case 'REMOVE': {
|
||||||
const { path, updatedRows } = action
|
const newState = { ...state }
|
||||||
|
if (newState[action.path]) {
|
||||||
|
delete newState[action.path]
|
||||||
|
}
|
||||||
|
return newState
|
||||||
|
}
|
||||||
|
|
||||||
const newState = {
|
case 'REMOVE_ROW': {
|
||||||
...state,
|
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]: {
|
[path]: {
|
||||||
...state[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
|
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': {
|
case 'SET_ALL_ROWS_COLLAPSED': {
|
||||||
const { path, updatedRows } = action
|
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: {
|
default: {
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,18 +78,18 @@ export function RenderField({
|
|||||||
return <ArrayField {...sharedProps} field={clientFieldConfig} permissions={permissions} />
|
return <ArrayField {...sharedProps} field={clientFieldConfig} permissions={permissions} />
|
||||||
case 'blocks':
|
case 'blocks':
|
||||||
return <BlocksField {...sharedProps} field={clientFieldConfig} permissions={permissions} />
|
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':
|
case 'collapsible':
|
||||||
return (
|
return (
|
||||||
<CollapsibleField {...sharedProps} field={clientFieldConfig} permissions={permissions} />
|
<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:
|
default:
|
||||||
return DefaultField ? <DefaultField field={clientFieldConfig} {...sharedProps} /> : null
|
return DefaultField ? <DefaultField field={clientFieldConfig} {...sharedProps} /> : null
|
||||||
|
|||||||
@@ -473,8 +473,8 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
|||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'upload':
|
case 'relationship':
|
||||||
case 'relationship': {
|
case 'upload': {
|
||||||
if (field.filterOptions) {
|
if (field.filterOptions) {
|
||||||
if (typeof field.filterOptions === 'object') {
|
if (typeof field.filterOptions === 'object') {
|
||||||
if (typeof field.relationTo === 'string') {
|
if (typeof field.relationTo === 'string') {
|
||||||
|
|||||||
@@ -39,25 +39,6 @@ export const defaultValuePromise = async <T>({
|
|||||||
|
|
||||||
// Traverse subfields
|
// Traverse subfields
|
||||||
switch (field.type) {
|
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': {
|
case 'array': {
|
||||||
const rows = siblingData[field.name]
|
const rows = siblingData[field.name]
|
||||||
|
|
||||||
@@ -112,8 +93,9 @@ export const defaultValuePromise = async <T>({
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'row':
|
case 'collapsible':
|
||||||
case 'collapsible': {
|
|
||||||
|
case 'row': {
|
||||||
await iterateFields({
|
await iterateFields({
|
||||||
id,
|
id,
|
||||||
data,
|
data,
|
||||||
@@ -125,6 +107,24 @@ export const defaultValuePromise = async <T>({
|
|||||||
|
|
||||||
break
|
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': {
|
case 'tab': {
|
||||||
let tabSiblingData
|
let tabSiblingData
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ export const traverseFields = ({
|
|||||||
schemaMap.set(schemaPath, field)
|
schemaMap.set(schemaPath, field)
|
||||||
|
|
||||||
switch (field.type) {
|
switch (field.type) {
|
||||||
case 'group':
|
|
||||||
case 'array':
|
case 'array':
|
||||||
|
case 'group':
|
||||||
traverseFields({
|
traverseFields({
|
||||||
config,
|
config,
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
@@ -46,19 +46,6 @@ export const traverseFields = ({
|
|||||||
|
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'collapsible':
|
|
||||||
case 'row':
|
|
||||||
traverseFields({
|
|
||||||
config,
|
|
||||||
fields: field.fields,
|
|
||||||
i18n,
|
|
||||||
parentIndexPath: indexPath,
|
|
||||||
parentSchemaPath,
|
|
||||||
schemaMap,
|
|
||||||
})
|
|
||||||
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'blocks':
|
case 'blocks':
|
||||||
field.blocks.map((block) => {
|
field.blocks.map((block) => {
|
||||||
const blockSchemaPath = `${schemaPath}.${block.slug}`
|
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
|
break
|
||||||
|
|
||||||
case 'richText':
|
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