chore!: admin now takes a client side custom property and custom is server only (#5926)

This commit is contained in:
Paul
2024-04-22 12:22:32 -03:00
committed by GitHub
parent 102feb9576
commit 594f319fc6
25 changed files with 93 additions and 165 deletions

View File

@@ -2,7 +2,7 @@ import type { LivePreviewConfig, ServerOnlyLivePreviewProperties } from '../../c
export type ServerOnlyCollectionProperties = keyof Pick<
SanitizedCollectionConfig,
'access' | 'endpoints' | 'hooks'
'access' | 'custom' | 'endpoints' | 'hooks'
>
export type ServerOnlyCollectionAdminProperties = keyof Pick<
@@ -44,6 +44,7 @@ export const createClientCollectionConfig = ({
'hooks',
'access',
'endpoints',
'custom',
// `upload`
// `admin`
// are all handled separately
@@ -77,12 +78,6 @@ export const createClientCollectionConfig = ({
})
}
if ('custom' in sanitized && sanitized.custom) {
if ('server' in sanitized.custom && sanitized.custom.server) {
delete sanitized.custom.server
}
}
if ('admin' in sanitized) {
sanitized.admin = { ...sanitized.admin }

View File

@@ -10,6 +10,7 @@ export const defaults = {
},
admin: {
components: {},
custom: {},
enableRichTextLink: true,
enableRichTextRelationship: true,
pagination: {

View File

@@ -57,6 +57,7 @@ const collectionSchema = joi.object().keys({
),
}),
}),
custom: joi.object().pattern(joi.string(), joi.any()),
defaultColumns: joi.array().items(joi.string()),
description: joi.alternatives().try(joi.string(), componentSchema),
enableRichTextLink: joi.boolean(),
@@ -107,10 +108,7 @@ const collectionSchema = joi.object().keys({
}),
joi.boolean(),
),
custom: joi.object().keys({
client: joi.object().pattern(joi.string(), joi.any()),
server: joi.object().pattern(joi.string(), joi.any()),
}),
custom: joi.object().pattern(joi.string(), joi.any()),
dbName: joi.alternatives().try(joi.string(), joi.func()),
defaultSort: joi.string(),
disableDuplicate: joi.bool(),

View File

@@ -246,6 +246,8 @@ export type CollectionAdminOptions = {
| React.ComponentType<any>
}
}
/** Extension point to add your custom data. Available in server and client. */
custom?: Record<string, any>
/**
* Default columns to show in list view
*/
@@ -314,17 +316,8 @@ export type CollectionConfig = {
* Use `true` to enable with default options
*/
auth?: IncomingAuthType | boolean
/** Extension point to add your custom data. */
custom?: {
/**
* Available in client bundle.
*/
client?: Record<string, any>
/**
* Server only.
*/
server?: Record<string, any>
}
/** Extension point to add your custom data. Server only. */
custom?: Record<string, any>
/**
* Default field to sort by in collection list view
*/

View File

@@ -18,6 +18,7 @@ export type ServerOnlyRootProperties = keyof Pick<
| 'bin'
| 'cors'
| 'csrf'
| 'custom'
| 'db'
| 'editor'
| 'email'
@@ -40,6 +41,7 @@ export type ClientConfig = Omit<
livePreview?: Omit<LivePreviewConfig, ServerOnlyLivePreviewProperties>
}
collections: ClientCollectionConfig[]
custom?: Record<string, any>
globals: ClientGlobalConfig[]
}
@@ -67,6 +69,7 @@ export const createClientConfig = async ({
'cors',
'csrf',
'email',
'custom',
// `admin`, `onInit`, `localization`, `collections`, and `globals` are all handled separately
]
@@ -84,12 +87,6 @@ export const createClientConfig = async ({
})
}
if ('custom' in clientConfig && clientConfig.custom) {
if ('server' in clientConfig.custom && clientConfig.custom.server) {
delete clientConfig.custom.server
}
}
if ('admin' in clientConfig) {
clientConfig.admin = { ...clientConfig.admin }

View File

@@ -4,6 +4,7 @@ export const defaults: Omit<Config, 'db' | 'editor' | 'secret'> = {
admin: {
avatar: 'default',
components: {},
custom: {},
dateFormat: 'MMMM do yyyy, h:mm a',
disable: false,
inactivityRoute: '/logout-inactivity',

View File

@@ -57,6 +57,7 @@ export default joi.object({
joi.object().pattern(joi.string(), component),
),
}),
custom: joi.object().pattern(joi.string(), joi.any()),
dateFormat: joi.string(),
disable: joi.bool(),
inactivityRoute: joi.string(),
@@ -83,10 +84,7 @@ export default joi.object({
cookiePrefix: joi.string(),
cors: [joi.string().valid('*'), joi.array().items(joi.string())],
csrf: joi.array().items(joi.string().allow('')).sparse(),
custom: joi.object().keys({
client: joi.object().pattern(joi.string(), joi.any()),
server: joi.object().pattern(joi.string(), joi.any()),
}),
custom: joi.object().pattern(joi.string(), joi.any()),
db: joi.any(),
debug: joi.boolean(),
defaultDepth: joi.number().min(0).max(30),

View File

@@ -417,9 +417,9 @@ export type Config = {
prefillOnly?: boolean
}
| false
/** Set account profile picture. Options: gravatar, default or a custom React component. */
avatar?: 'default' | 'gravatar' | React.ComponentType<any>
/**
* Add extra and/or replace built-in components with custom components
*
@@ -489,6 +489,8 @@ export type Config = {
Dashboard?: AdminView
}
}
/** Extension point to add your custom data. Available in server and client. */
custom?: Record<string, any>
/** Global date format that will be used for all dates in the Admin panel. Any valid date-fns format pattern can be used. */
dateFormat?: string
/** If set to true, the entire Admin panel will be disabled. */
@@ -547,17 +549,8 @@ export type Config = {
/** A whitelist array of URLs to allow Payload cookies to be accepted from as a form of CSRF protection. */
csrf?: string[]
/** Extension point to add your custom data. */
custom?: {
/**
* Available in client bundle.
*/
client?: Record<string, any>
/**
* Server only.
*/
server?: Record<string, any>
}
/** Extension point to add your custom data. Server only. */
custom?: Record<string, any>
/** Pass in a database adapter for use on this project. */
db: DatabaseAdapterResult

View File

@@ -8,7 +8,7 @@ export type ServerOnlyFieldProperties =
| 'editor' // This is a `richText` only property
| 'filterOptions' // This is a `relationship` and `upload` only property
| 'label'
| keyof Pick<FieldBase, 'access' | 'defaultValue' | 'hooks' | 'validate'>
| keyof Pick<FieldBase, 'access' | 'custom' | 'defaultValue' | 'hooks' | 'validate'>
export type ServerOnlyFieldAdminProperties = keyof Pick<
FieldBase['admin'],
@@ -32,6 +32,7 @@ export const createClientFieldConfig = ({
'label',
'filterOptions', // This is a `relationship` and `upload` only property
'editor', // This is a `richText` only property
'custom',
// `fields`
// `blocks`
// `tabs`
@@ -45,12 +46,6 @@ export const createClientFieldConfig = ({
}
})
if ('custom' in field && field.custom) {
if ('server' in field.custom && field.custom.server) {
delete field.custom.server
}
}
if ('options' in field && Array.isArray(field.options)) {
field.options = field.options.map((option) => {
if (typeof option === 'object' && typeof option.label === 'function') {

View File

@@ -15,6 +15,7 @@ export const baseAdminFields = joi.object().keys({
className: joi.string(),
components: baseAdminComponentFields,
condition: joi.func(),
custom: joi.object().pattern(joi.string(), joi.any()),
description: joi
.alternatives()
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()]), componentSchema),
@@ -37,10 +38,7 @@ export const baseField = joi
update: joi.func(),
}),
admin: baseAdminFields.default(),
custom: joi.object().keys({
client: joi.object().pattern(joi.string(), joi.any()),
server: joi.object().pattern(joi.string(), joi.any()),
}),
custom: joi.object().pattern(joi.string(), joi.any()),
hidden: joi.boolean().default(false),
hooks: joi
.object()
@@ -422,10 +420,10 @@ export const blocks = baseField.keys({
.items(
joi.object({
slug: joi.string().required(),
custom: joi.object().keys({
client: joi.object().pattern(joi.string(), joi.any()),
server: joi.object().pattern(joi.string(), joi.any()),
admin: joi.object().keys({
custom: joi.object().pattern(joi.string(), joi.any()),
}),
custom: joi.object().pattern(joi.string(), joi.any()),
dbName: joi.alternatives().try(joi.string(), joi.func()),
fields: joi.array().items(joi.link('#field')),
graphQL: joi.object().keys({
@@ -522,14 +520,12 @@ export const ui = joi.object().keys({
})
.default({}),
condition: joi.func(),
custom: joi.object().pattern(joi.string(), joi.any()),
position: joi.string().valid('sidebar'),
width: joi.string(),
})
.default(),
custom: joi.object().keys({
client: joi.object().pattern(joi.string(), joi.any()),
server: joi.object().pattern(joi.string(), joi.any()),
}),
custom: joi.object().pattern(joi.string(), joi.any()),
label: joi.alternatives().try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
})

View File

@@ -124,6 +124,8 @@ type Admin = {
* This is also run on the server, to determine if the field should be validated.
*/
condition?: Condition
/** Extension point to add your custom data. Available in server and client. */
custom?: Record<string, any>
description?: Description
disableBulkEdit?: boolean
disabled?: boolean
@@ -179,17 +181,8 @@ export interface FieldBase {
update?: FieldAccess
}
admin?: Admin
/** Extension point to add your custom data. */
custom?: {
/**
* Available in client bundle.
*/
client?: Record<string, any>
/**
* Server only.
*/
server?: Record<string, any>
}
/** Extension point to add your custom data. Server only. */
custom?: Record<string, any>
defaultValue?: any
hidden?: boolean
hooks?: {
@@ -430,21 +423,14 @@ export type UIField = {
Filter?: React.ComponentType<any>
}
condition?: Condition
/** Extension point to add your custom data. Available in server and client. */
custom?: Record<string, any>
disableBulkEdit?: boolean
position?: string
width?: string
}
/** Extension point to add your custom data. */
custom?: {
/**
* Available in client bundle.
*/
client?: Record<string, any>
/**
* Server only.
*/
server?: Record<string, any>
}
/** Extension point to add your custom data. Server only. */
custom?: Record<string, any>
label?: Record<string, string> | string
name: string
type: 'ui'
@@ -655,17 +641,12 @@ export type RadioField = FieldBase & {
}
export type Block = {
/** Extension point to add your custom data. */
custom?: {
/**
* Available in client bundle.
*/
client?: Record<string, any>
/**
* Server only.
*/
server?: Record<string, any>
admin?: {
/** Extension point to add your custom data. Available in server and client. */
custom?: Record<string, any>
}
/** Extension point to add your custom data. Server only. */
custom?: Record<string, any>
/**
* Customize the SQL table name
*/

View File

@@ -12,7 +12,7 @@ import { createClientFieldConfigs } from '../../fields/config/client.js'
export type ServerOnlyGlobalProperties = keyof Pick<
SanitizedGlobalConfig,
'access' | 'admin' | 'endpoints' | 'fields' | 'hooks'
'access' | 'admin' | 'custom' | 'endpoints' | 'fields' | 'hooks'
>
export type ServerOnlyGlobalAdminProperties = keyof Pick<
SanitizedGlobalConfig['admin'],
@@ -46,6 +46,7 @@ export const createClientGlobalConfig = ({
'hooks',
'access',
'endpoints',
'custom',
// `admin` is handled separately
]
@@ -55,12 +56,6 @@ export const createClientGlobalConfig = ({
}
})
if ('custom' in sanitized && sanitized.custom) {
if ('server' in sanitized.custom && sanitized.custom.server) {
delete sanitized.custom.server
}
}
if ('admin' in sanitized) {
sanitized.admin = { ...sanitized.admin }

View File

@@ -39,6 +39,7 @@ const globalSchema = joi
),
}),
}),
custom: joi.object().pattern(joi.string(), joi.any()),
description: joi.alternatives().try(joi.string(), componentSchema),
group: joi
.alternatives()
@@ -48,10 +49,7 @@ const globalSchema = joi
livePreview: joi.object(livePreviewSchema),
preview: joi.func(),
}),
custom: joi.object().keys({
client: joi.object().pattern(joi.string(), joi.any()),
server: joi.object().pattern(joi.string(), joi.any()),
}),
custom: joi.object().pattern(joi.string(), joi.any()),
dbName: joi.alternatives().try(joi.string(), joi.func()),
endpoints: endpointsSchema,
fields: joi.array(),

View File

@@ -106,6 +106,8 @@ export type GlobalAdminOptions = {
Edit?: EditConfig
}
}
/** Extension point to add your custom data. Available in server and client. */
custom?: Record<string, any>
/**
* Custom description for collection
*/
@@ -140,17 +142,8 @@ export type GlobalConfig = {
update?: Access
}
admin?: GlobalAdminOptions
/** Extension point to add your custom data. */
custom?: {
/**
* Available in client bundle.
*/
client?: Record<string, any>
/**
* Server only.
*/
server?: Record<string, any>
}
/** Extension point to add your custom data. Server only. */
custom?: Record<string, any>
/**
* Customize the SQL table name
*/

View File

@@ -17,9 +17,7 @@ export type FormFieldBase = {
CustomError?: React.ReactNode
CustomLabel?: React.ReactNode
className?: string
custom?: {
client?: Record<string, any>
}
custom?: Record<string, any>
descriptionProps?: FieldDescriptionProps
disabled?: boolean
docPreferences?: DocumentPreferences

View File

@@ -5,9 +5,7 @@ import type { FieldPermissions } from 'payload/types'
import React from 'react'
export type FieldPropsContextType = {
custom?: {
client?: Record<string, any>
}
custom?: Record<any, string>
indexPath?: string
path: string
permissions?: FieldPermissions
@@ -21,6 +19,7 @@ export type FieldPropsContextType = {
const FieldPropsContext = React.createContext<FieldPropsContextType>({
type: '' as keyof FieldTypes,
custom: {},
indexPath: '',
path: '',
permissions: {} as FieldPermissions,
@@ -31,9 +30,7 @@ const FieldPropsContext = React.createContext<FieldPropsContextType>({
export type Props = {
children: React.ReactNode
custom?: {
client?: Record<string, any>
}
custom?: Record<any, string>
indexPath?: string
path: string
permissions?: FieldPermissions

View File

@@ -17,9 +17,7 @@ import { FieldPropsProvider, useFieldProps } from '../FieldPropsProvider/index.j
type Props = {
CustomField: MappedField['CustomField']
custom?: {
client?: Record<string, any>
}
custom?: Record<any, string>
disabled: boolean
fieldComponentProps?: FieldComponentProps
indexPath?: string

View File

@@ -60,6 +60,7 @@ export const RenderFields: React.FC<Props> = (props) => {
const {
type,
CustomField,
custom,
disabled,
fieldComponentProps,
fieldComponentProps: { readOnly },
@@ -71,7 +72,7 @@ export const RenderFields: React.FC<Props> = (props) => {
return (
<RenderField
CustomField={CustomField}
custom={f.custom}
custom={custom}
disabled={disabled}
fieldComponentProps={fieldComponentProps}
indexPath={indexPath !== undefined ? `${indexPath}.${fieldIndex}` : `${fieldIndex}`}

View File

@@ -200,7 +200,7 @@ export const mapFields = (args: {
CustomDescription,
CustomError,
CustomLabel,
custom: field.custom,
custom: 'admin' in field && 'custom' in field.admin ? field.admin?.custom : undefined,
descriptionProps,
disabled: 'admin' in field && 'disabled' in field.admin ? field.admin?.disabled : false,
errorProps,
@@ -296,6 +296,7 @@ export const mapFields = (args: {
const reducedBlock: ReducedBlock = {
slug: block.slug,
custom: block.admin?.custom,
fieldMap: blockFieldMap,
imageAltText: block.imageAltText,
imageURL: block.imageURL,
@@ -756,7 +757,7 @@ export const mapFields = (args: {
<WithServerSideProps Component={CustomFieldComponent} {...fieldComponentProps} />
) : undefined,
cellComponentProps,
custom: field.custom,
custom: field?.admin?.custom,
disableBulkEdit:
'admin' in field && 'disableBulkEdit' in field.admin && field.admin.disableBulkEdit,
fieldComponentProps,

View File

@@ -35,6 +35,7 @@ export type MappedTab = {
}
export type ReducedBlock = {
custom?: Record<any, string>
fieldMap: FieldMap
imageAltText?: string
imageURL?: string
@@ -68,9 +69,7 @@ export type MappedField = {
CustomCell?: React.ReactNode
CustomField?: React.ReactNode
cellComponentProps: CellComponentProps
custom?: {
client?: Record<string, any>
}
custom?: Record<any, string>
disableBulkEdit?: boolean
disabled?: boolean
fieldComponentProps: FieldComponentProps

View File

@@ -26,8 +26,7 @@ export default buildConfigWithDefaults({
name: 'title',
type: 'text',
custom: {
client: { description: 'The title of this page' },
server: { description: 'The title of this page' },
description: 'The title of this page',
},
},
{
@@ -47,20 +46,17 @@ export default buildConfigWithDefaults({
},
],
custom: {
client: { description: 'The blockOne of this page' },
server: { description: 'The blockOne of this page' },
description: 'The blockOne of this page',
},
},
],
custom: {
client: { description: 'The blocks of this page' },
server: { description: 'The blocks of this page' },
description: 'The blocks of this page',
},
},
],
custom: {
client: { externalLink: 'https://foo.bar' },
server: { externalLink: 'https://foo.bar' },
externalLink: 'https://foo.bar',
},
},
],
@@ -83,12 +79,11 @@ export default buildConfigWithDefaults({
name: 'title',
type: 'text',
custom: {
client: { description: 'The title of my global' },
server: { description: 'The title of my global' },
description: 'The title of my global',
},
},
],
custom: { client: { foo: 'bar' }, server: { foo: 'bar' } },
custom: { foo: 'bar' },
},
],
endpoints: [
@@ -101,7 +96,7 @@ export default buildConfigWithDefaults({
custom: { description: 'Get the sanitized payload config' },
},
],
custom: { client: { name: 'Customer portal' }, server: { name: 'Customer portal' } },
custom: { name: 'Customer portal' },
onInit: async (payload) => {
await payload.create({
collection: 'users',

View File

@@ -21,8 +21,7 @@ describe('Config', () => {
it('allows a custom field at the config root', () => {
const { config } = payload
expect(config.custom).toEqual({
client: { name: 'Customer portal' },
server: { name: 'Customer portal' },
name: 'Customer portal',
})
})
@@ -39,8 +38,7 @@ describe('Config', () => {
it('allows a custom field in collections', () => {
const [collection] = payload.config.collections
expect(collection.custom).toEqual({
client: { externalLink: 'https://foo.bar' },
server: { externalLink: 'https://foo.bar' },
externalLink: 'https://foo.bar',
})
})
@@ -57,11 +55,8 @@ describe('Config', () => {
const [collection] = payload.config.collections
const [field] = collection.fields
console.log({ custom: field.custom })
expect(field.custom).toEqual({
client: { description: 'The title of this page' },
server: { description: 'The title of this page' },
description: 'The title of this page',
})
})
@@ -70,8 +65,7 @@ describe('Config', () => {
const [, blocksField] = collection.fields
expect((blocksField as BlockField).blocks[0].custom).toEqual({
server: { description: 'The blockOne of this page' },
client: { description: 'The blockOne of this page' },
description: 'The blockOne of this page',
})
})
})
@@ -79,7 +73,7 @@ describe('Config', () => {
describe('global config', () => {
it('allows a custom field in globals', () => {
const [global] = payload.config.globals
expect(global.custom).toEqual({ client: { foo: 'bar' }, server: { foo: 'bar' } })
expect(global.custom).toEqual({ foo: 'bar' })
})
it('allows a custom field in global endpoints', () => {
@@ -94,8 +88,7 @@ describe('Config', () => {
const [field] = global.fields
expect(field.custom).toEqual({
client: { description: 'The title of my global' },
server: { description: 'The title of my global' },
description: 'The title of my global',
})
})
})

View File

@@ -4,7 +4,6 @@ import React from 'react'
export const UICustomClient: React.FC = () => {
const { custom, path } = useFieldProps()
const client = custom?.client
return <div id={path}>{client?.customValue}</div>
return <div id={path}>{custom?.customValue}</div>
}

View File

@@ -7,6 +7,12 @@ const UIFields: CollectionConfig = {
slug: uiFieldsSlug,
admin: {
useAsTitle: 'text',
custom: {
'new-value': 'client available',
},
},
custom: {
'new-server-value': 'only available on server',
},
defaultSort: 'id',
fields: [
@@ -22,13 +28,13 @@ const UIFields: CollectionConfig = {
components: {
Field: UICustomClient,
},
},
custom: {
client: {
customValue: `client-side-configuration`,
},
},
custom: {
server: {
'new-server-value': 'only available on server',
serverOnly: 'string',
},
},
},

View File

@@ -82,6 +82,13 @@ export default buildConfigWithDefaults({
'new-server-value': 'only available on server',
},
},
admin: {
custom: {
client: {
'new-value': 'client available',
},
},
},
localization: {
defaultLocale: 'en',
fallback: true,