diff --git a/.eslintrc.js b/.eslintrc.js index 7371db358a..d5bc22b7d6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -28,6 +28,8 @@ module.exports = { 'plugin:@typescript-eslint/recommended', ], rules: { + "no-shadow": "off", + "@typescript-eslint/no-shadow": ["error"], 'import/no-unresolved': [ 2, { diff --git a/demo/collections/Admin.ts b/demo/collections/Admin.ts index 31e73c98d4..2ac83b1628 100644 --- a/demo/collections/Admin.ts +++ b/demo/collections/Admin.ts @@ -1,3 +1,4 @@ +import { PayloadCollectionConfig } from '../../src/collections/config/types'; import roles from '../access/roles'; import checkRole from '../access/checkRole'; @@ -6,7 +7,7 @@ const access = ({ req: { user } }) => { return result; }; -export default { +const Admin: PayloadCollectionConfig = { slug: 'admins', labels: { singular: 'Admin', @@ -68,3 +69,5 @@ export default { useAsTitle: 'email', }, }; + +export default Admin; diff --git a/demo/collections/AllFields.ts b/demo/collections/AllFields.ts index feaeffaa6b..0029593e0c 100644 --- a/demo/collections/AllFields.ts +++ b/demo/collections/AllFields.ts @@ -1,10 +1,11 @@ +import { PayloadCollectionConfig } from '../../src/collections/config/types'; import checkRole from '../access/checkRole'; import Email from '../blocks/Email'; import Quote from '../blocks/Quote'; import NumberBlock from '../blocks/Number'; import CallToAction from '../blocks/CallToAction'; -const AllFields = { +const AllFields: PayloadCollectionConfig = { slug: 'all-fields', // labels: { // singular: 'All Fields', diff --git a/demo/collections/AutoLabel.ts b/demo/collections/AutoLabel.ts index 3c52f3cfd0..67452083f7 100644 --- a/demo/collections/AutoLabel.ts +++ b/demo/collections/AutoLabel.ts @@ -1,6 +1,6 @@ -// No labels necessary +import { PayloadCollectionConfig } from '../../src/collections/config/types'; -const AutoLabel = { +const AutoLabel: PayloadCollectionConfig = { slug: 'auto-label', fields: [{ name: 'text', diff --git a/demo/collections/Blocks.ts b/demo/collections/Blocks.ts index 4b737542ee..7c9a27ddd2 100644 --- a/demo/collections/Blocks.ts +++ b/demo/collections/Blocks.ts @@ -1,9 +1,10 @@ +import { PayloadCollectionConfig } from '../../src/collections/config/types'; import Email from '../blocks/Email'; import Quote from '../blocks/Quote'; import NumberBlock from '../blocks/Number'; import CallToAction from '../blocks/CallToAction'; -export default { +const Blocks: PayloadCollectionConfig = { slug: 'blocks', labels: { singular: 'Blocks', @@ -38,3 +39,5 @@ export default { }, ], }; + +export default Blocks; diff --git a/demo/collections/Code.ts b/demo/collections/Code.ts index a4530efa94..3f473ba879 100644 --- a/demo/collections/Code.ts +++ b/demo/collections/Code.ts @@ -1,4 +1,6 @@ -const Code = { +import { PayloadCollectionConfig } from '../../src/collections/config/types'; + +const Code: PayloadCollectionConfig = { slug: 'code', labels: { singular: 'Code', diff --git a/demo/collections/Conditions.ts b/demo/collections/Conditions.ts index 9b6f56b9c9..b8b6ac5bdf 100644 --- a/demo/collections/Conditions.ts +++ b/demo/collections/Conditions.ts @@ -1,4 +1,6 @@ -const Conditions = { +import { PayloadCollectionConfig } from '../../src/collections/config/types'; + +const Conditions: PayloadCollectionConfig = { slug: 'conditions', labels: { singular: 'Conditions', diff --git a/demo/collections/CustomComponents/index.ts b/demo/collections/CustomComponents/index.ts index 4cbd904852..e2515346c4 100644 --- a/demo/collections/CustomComponents/index.ts +++ b/demo/collections/CustomComponents/index.ts @@ -1,4 +1,4 @@ - +import { PayloadCollectionConfig } from '../../../src/collections/config/types'; import DescriptionField from './components/fields/Description/Field'; import DescriptionCell from './components/fields/Description/Cell'; import DescriptionFilter from './components/fields/Description/Filter'; @@ -7,9 +7,8 @@ import GroupField from './components/fields/Group/Field'; import NestedGroupField from './components/fields/NestedGroupCustomField/Field'; import NestedText1Field from './components/fields/NestedText1/Field'; import ListView from './components/views/List'; -import buildCollection from '../../../src/collections/config/build'; -export default buildCollection({ +const CustomComponents: PayloadCollectionConfig = { slug: 'custom-components', labels: { singular: 'Custom Component', @@ -105,4 +104,6 @@ export default buildCollection({ List: ListView, }, }, -}); +}; + +export default CustomComponents; diff --git a/demo/collections/DefaultValues.ts b/demo/collections/DefaultValues.ts index 93074b177b..bec032e31f 100644 --- a/demo/collections/DefaultValues.ts +++ b/demo/collections/DefaultValues.ts @@ -1,10 +1,11 @@ +import { PayloadCollectionConfig } from '../../src/collections/config/types'; import checkRole from '../access/checkRole'; import Email from '../blocks/Email'; import Quote from '../blocks/Quote'; import NumberBlock from '../blocks/Number'; import CallToAction from '../blocks/CallToAction'; -const DefaultValues = { +const DefaultValues: PayloadCollectionConfig = { slug: 'default-values', labels: { singular: 'Default Value Test', diff --git a/demo/collections/File.ts b/demo/collections/File.ts index b1f7825c72..be27ed319d 100644 --- a/demo/collections/File.ts +++ b/demo/collections/File.ts @@ -1,3 +1,4 @@ +import { PayloadCollectionConfig } from '../../src/collections/config/types'; import checkRole from '../access/checkRole'; const access = ({ req: { user } }) => { @@ -18,7 +19,7 @@ const access = ({ req: { user } }) => { return false; }; -export default { +const Files: PayloadCollectionConfig = { slug: 'files', labels: { singular: 'File', @@ -65,3 +66,5 @@ export default { useAsTitle: 'filename', }, }; + +export default Files; diff --git a/demo/collections/HiddenFields.ts b/demo/collections/HiddenFields.ts index 27f1012229..7f71314157 100644 --- a/demo/collections/HiddenFields.ts +++ b/demo/collections/HiddenFields.ts @@ -1,4 +1,6 @@ -const HiddenFields = { +import { PayloadCollectionConfig } from '../../src/collections/config/types'; + +const HiddenFields: PayloadCollectionConfig = { slug: 'hidden-fields', labels: { singular: 'Hidden Fields', diff --git a/demo/collections/Hooks.ts b/demo/collections/Hooks.ts index 30f69e5301..67c93e0cfc 100644 --- a/demo/collections/Hooks.ts +++ b/demo/collections/Hooks.ts @@ -1,5 +1,8 @@ /* eslint-disable no-param-reassign */ -export default { + +import { PayloadCollectionConfig } from '../../src/collections/config/types'; + +const Hooks: PayloadCollectionConfig = { slug: 'hooks', labels: { singular: 'Hook', @@ -88,3 +91,5 @@ export default { ], timestamps: true, }; + +export default Hooks; diff --git a/demo/collections/LocalOperations.ts b/demo/collections/LocalOperations.ts index 621bd665d9..b8570baea9 100644 --- a/demo/collections/LocalOperations.ts +++ b/demo/collections/LocalOperations.ts @@ -1,4 +1,6 @@ -const LocalOperations = { +import { PayloadCollectionConfig } from '../../src/collections/config/types'; + +const LocalOperations: PayloadCollectionConfig = { slug: 'local-operations', labels: { singular: 'Local Operation', diff --git a/demo/collections/Localized.ts b/demo/collections/Localized.ts index d79ead5b09..b27aaa315a 100644 --- a/demo/collections/Localized.ts +++ b/demo/collections/Localized.ts @@ -1,4 +1,6 @@ -export default { +import { PayloadCollectionConfig } from '../../src/collections/config/types'; + +const LocalizedPosts: PayloadCollectionConfig = { slug: 'localized-posts', labels: { singular: 'Localized Post', @@ -56,3 +58,5 @@ export default { ], timestamps: true, }; + +export default LocalizedPosts; diff --git a/demo/collections/LocalizedArray.ts b/demo/collections/LocalizedArray.ts index b30653fbb3..2059b1ab92 100644 --- a/demo/collections/LocalizedArray.ts +++ b/demo/collections/LocalizedArray.ts @@ -1,4 +1,6 @@ -const LocalizedArrays = { +import { PayloadCollectionConfig } from '../../src/collections/config/types'; + +const LocalizedArrays: PayloadCollectionConfig = { slug: 'localized-arrays', labels: { singular: 'Localized Array', diff --git a/demo/collections/Media.ts b/demo/collections/Media.ts index 4a289e42a9..46987d3827 100644 --- a/demo/collections/Media.ts +++ b/demo/collections/Media.ts @@ -1,6 +1,6 @@ -import checkRole from '../access/checkRole'; +import { PayloadCollectionConfig } from '../../src/collections/config/types'; -export default { +const Media: PayloadCollectionConfig = { slug: 'media', labels: { singular: 'Media', @@ -47,3 +47,5 @@ export default { ], timestamps: true, }; + +export default Media; diff --git a/demo/collections/NestedArrays.ts b/demo/collections/NestedArrays.ts index 35baa4fb6e..020921189f 100644 --- a/demo/collections/NestedArrays.ts +++ b/demo/collections/NestedArrays.ts @@ -1,4 +1,6 @@ -const NestedArray = { +import { PayloadCollectionConfig } from '../../src/collections/config/types'; + +const NestedArray: PayloadCollectionConfig = { slug: 'nested-arrays', labels: { singular: 'Nested Array', diff --git a/demo/collections/Preview.ts b/demo/collections/Preview.ts index 5ad345dba2..0f93981ae2 100644 --- a/demo/collections/Preview.ts +++ b/demo/collections/Preview.ts @@ -1,4 +1,6 @@ -export default { +import { PayloadCollectionConfig } from '../../src/collections/config/types'; + +const Preview: PayloadCollectionConfig = { slug: 'previewable-post', labels: { singular: 'Previewable Post', @@ -27,3 +29,5 @@ export default { ], timestamps: true, }; + +export default Preview; diff --git a/demo/collections/PublicUsers.ts b/demo/collections/PublicUsers.ts index a188fdf271..0c2271be2a 100644 --- a/demo/collections/PublicUsers.ts +++ b/demo/collections/PublicUsers.ts @@ -1,8 +1,9 @@ import checkRole from '../access/checkRole'; +import { PayloadCollectionConfig } from '../../src/collections/config/types'; const access = ({ req: { user } }) => checkRole(['admin'], user); -export default { +const PublicUsers: PayloadCollectionConfig = { slug: 'public-users', labels: { singular: 'Public User', @@ -56,3 +57,5 @@ export default { ], timestamps: true, }; + +export default PublicUsers; diff --git a/demo/collections/RelationshipA.ts b/demo/collections/RelationshipA.ts index c1794474ae..29929537bd 100644 --- a/demo/collections/RelationshipA.ts +++ b/demo/collections/RelationshipA.ts @@ -1,4 +1,6 @@ -export default { +import { PayloadCollectionConfig } from '../../src/collections/config/types'; + +const RelationshipA: PayloadCollectionConfig = { slug: 'relationship-a', access: { read: () => true, @@ -34,3 +36,5 @@ export default { ], timestamps: true, }; + +export default RelationshipA; diff --git a/demo/collections/RelationshipB.ts b/demo/collections/RelationshipB.ts index f6f0499bb2..566bf883c1 100644 --- a/demo/collections/RelationshipB.ts +++ b/demo/collections/RelationshipB.ts @@ -1,4 +1,6 @@ -export default { +import { PayloadCollectionConfig } from '../../src/collections/config/types'; + +const RelationshipB: PayloadCollectionConfig = { slug: 'relationship-b', access: { read: () => true, @@ -19,3 +21,5 @@ export default { ], timestamps: true, }; + +export default RelationshipB; diff --git a/demo/collections/RichText.ts b/demo/collections/RichText.ts index 2f6c827b29..1405bd7d87 100644 --- a/demo/collections/RichText.ts +++ b/demo/collections/RichText.ts @@ -1,7 +1,8 @@ import Button from '../client/components/richText/elements/Button'; import PurpleBackground from '../client/components/richText/leaves/PurpleBackground'; +import { PayloadCollectionConfig } from '../../src/collections/config/types'; -const RichText = { +const RichText: PayloadCollectionConfig = { slug: 'rich-text', labels: { singular: 'Rich Text', diff --git a/demo/collections/Select.ts b/demo/collections/Select.ts index 1bdb60c4b4..87b5c7a33b 100644 --- a/demo/collections/Select.ts +++ b/demo/collections/Select.ts @@ -1,4 +1,6 @@ -const Select = { +import { PayloadCollectionConfig } from '../../src/collections/config/types'; + +const Select: PayloadCollectionConfig = { slug: 'select', labels: { singular: 'Select', diff --git a/demo/collections/StrictPolicies.ts b/demo/collections/StrictPolicies.ts index c9f9fd998c..86ff0adc25 100644 --- a/demo/collections/StrictPolicies.ts +++ b/demo/collections/StrictPolicies.ts @@ -1,6 +1,7 @@ +import { PayloadCollectionConfig } from '../../src/collections/config/types'; import checkRole from '../access/checkRole'; -export default { +const StrictAccess: PayloadCollectionConfig = { slug: 'strict-access', labels: { singular: 'Strict Access', @@ -71,3 +72,5 @@ export default { ], timestamps: true, }; + +export default StrictAccess; diff --git a/demo/collections/Validations.ts b/demo/collections/Validations.ts index e26540f96b..c2982b7572 100644 --- a/demo/collections/Validations.ts +++ b/demo/collections/Validations.ts @@ -1,4 +1,6 @@ -export default { +import { PayloadCollectionConfig } from '../../src/collections/config/types'; + +const Validations: PayloadCollectionConfig = { slug: 'validations', labels: { singular: 'Validation', @@ -106,5 +108,6 @@ export default { ], }, ], - timestamps: true, }; + +export default Validations; diff --git a/src/admin/components/forms/Form/buildStateFromSchema.ts b/src/admin/components/forms/Form/buildStateFromSchema.ts index 20864d3c09..bbef51134c 100644 --- a/src/admin/components/forms/Form/buildStateFromSchema.ts +++ b/src/admin/components/forms/Form/buildStateFromSchema.ts @@ -44,10 +44,12 @@ const buildStateFromSchema = async (fieldSchema: FieldSchema[], fullData: Data = if (field.type === 'array' || field.type === 'blocks') { if (Array.isArray(initialData?.[field.name])) { + const rows = initialData[field.name] as Data[]; + if (field.type === 'array') { return { ...state, - ...initialData[field.name].reduce((rowState, row, i) => ({ + ...rows.reduce((rowState, row, i) => ({ ...rowState, ...iterateFields(field.fields, row, `${path}${field.name}.${i}.`), }), {}), @@ -57,7 +59,7 @@ const buildStateFromSchema = async (fieldSchema: FieldSchema[], fullData: Data = if (field.type === 'blocks') { return { ...state, - ...initialData[field.name].reduce((rowState, row, i) => { + ...rows.reduce((rowState, row, i) => { const block = field.blocks.find((blockType) => blockType.slug === row.blockType); const rowPath = `${path}${field.name}.${i}.`; @@ -84,7 +86,7 @@ const buildStateFromSchema = async (fieldSchema: FieldSchema[], fullData: Data = } // Handle non-array-based nested fields (group, etc) - if (field.fields) { + if (Array.isArray(field.fields)) { return { ...state, ...iterateFields(field.fields, initialData?.[field.name], `${path}${field.name}.`), diff --git a/src/auth/types.ts b/src/auth/types.ts index e767b6f60a..5b575fc574 100644 --- a/src/auth/types.ts +++ b/src/auth/types.ts @@ -74,6 +74,7 @@ export interface IncomingAuthType { maxLoginAttempts?: number; lockTime?: number; useAPIKey?: boolean; + depth?: number cookies?: { secure?: boolean; sameSite?: string; diff --git a/src/collections/config/types.ts b/src/collections/config/types.ts index 78c19b3e33..e93f95a7f9 100644 --- a/src/collections/config/types.ts +++ b/src/collections/config/types.ts @@ -1,13 +1,14 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { DeepRequired } from 'ts-essentials'; -import { PaginateModel, Document, PassportLocalModel } from 'mongoose'; +import { PaginateModel, Document as MongooseDocument, PassportLocalModel } from 'mongoose'; import { Access } from '../../config/types'; import { Field } from '../../fields/config/types'; +import { Document } from '../../types'; import { PayloadRequest } from '../../express/types/payloadRequest'; import { IncomingAuthType, Auth } from '../../auth/types'; import { IncomingUploadType, Upload } from '../../uploads/types'; -interface CollectionModel extends PaginateModel, PassportLocalModel{} +interface CollectionModel extends PaginateModel, PassportLocalModel{} export type HookOperationType = | 'create' @@ -91,7 +92,9 @@ export type PayloadCollectionConfig = { useAsTitle?: string; defaultColumns?: string[]; components?: any; + enableRichTextRelationship?: boolean }; + preview?: (doc: Document, token: string) => string hooks?: { beforeOperation?: BeforeOperationHook[]; beforeValidate?: BeforeValidateHook[]; diff --git a/src/fields/config/types.ts b/src/fields/config/types.ts index 3539e93c79..3d705f4022 100644 --- a/src/fields/config/types.ts +++ b/src/fields/config/types.ts @@ -4,7 +4,6 @@ import { PayloadRequest } from '../../express/types/payloadRequest'; import { Access } from '../../config/types'; import { Document } from '../../types'; -// TODO: add generic type and use mongoose types for originalDoc & data export type FieldHook = (args: { value?: unknown, originalDoc?: Document, @@ -15,7 +14,23 @@ export type FieldHook = (args: { req: PayloadRequest }) => Promise | unknown; -type FieldBase = { +type Admin = { + position?: string; + width?: string; + style?: CSSProperties; + readOnly?: boolean; + disabled?: boolean; + condition?: (...args: any[]) => any | void; + components?: { [key: string]: React.ComponentType }; + hidden?: boolean +} + +type Labels = { + singular: string; + plural: string; +}; + +interface FieldBase { name?: string; label?: string; slug?: string; @@ -24,96 +39,112 @@ type FieldBase = { index?: boolean; defaultValue?: any; hidden?: boolean; + saveToJWT?: boolean localized?: boolean; - maxLength?: number; validate?: (value: any, field: Field) => any; - // eslint-disable-next-line no-use-before-define hooks?: { beforeValidate?: FieldHook[]; beforeChange?: FieldHook[]; afterChange?: FieldHook[]; afterRead?: FieldHook[]; } - admin?: { - position?: string; - width?: string; - style?: CSSProperties; - readOnly?: boolean; - disabled?: boolean; - condition?: (...args: any[]) => any | void; - components?: { [key: string]: React.ComponentType }; - }; + admin?: Admin; access?: { create?: Access; read?: Access; update?: Access; delete?: Access; - admin?: Access; unlock?: Access; }; } -export type StandardField = FieldBase & { - type: string; - fields?: Field[]; +export type NumberField = FieldBase & { + type: 'number'; + min?: number + max?: number } -export type NumberField = StandardField & { type: 'number'; }; -export type TextField = StandardField & { type: 'text'; }; -export type EmailField = StandardField & { type: 'email'; }; -export type TextareaField = StandardField & { type: 'textarea'; }; -export type CodeField = StandardField & { type: 'code'; }; -export type CheckboxField = StandardField & { type: 'checkbox'; }; -export type DateField = StandardField & { type: 'date'; }; -export type GroupField = StandardField & { type: 'group'; }; -export type RowField = StandardField & { type: 'row'; }; +export type TextField = FieldBase & { + type: 'text'; + maxLength?: number + minLength?: number +} + +export type EmailField = FieldBase & { + type: 'email'; +} + +export type TextareaField = FieldBase & { + type: 'textarea'; + maxLength?: number + minLength?: number +} + +export type CheckboxField = FieldBase & { + type: 'checkbox'; +} + +export type DateField = FieldBase & { + type: 'date'; +} + +export type GroupField = FieldBase & { + type: 'group'; + fields: Field[]; +} + +export type RowField = FieldBase & { + type: 'row'; + fields: Field[]; +} export type UploadField = FieldBase & { type: 'upload'; relationTo: string; } +type CodeAdmin = Admin & { + language?: string; +} + +export type CodeField = Omit & { + admin?: CodeAdmin + type: 'code'; +} + export type SelectField = FieldBase & { type: 'select'; options: { value: string; label: string; - }[]; + }[] | string[]; hasMany?: boolean; } -export type SelectManyField = SelectField & { - hasMany: true; -} - -export type RelationshipSingleField = FieldBase & { +export type RelationshipField = FieldBase & { type: 'relationship'; - relationTo: string; - hasMany?: false; + relationTo: string | string[]; + hasMany?: boolean; } -export type RelationshipManyField = FieldBase & { - type: 'relationship'; - relationTo: string[]; - hasMany: true; -} - -export type RelationshipField = RelationshipSingleField | RelationshipManyField; - type RichTextElements = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'blockquote' | 'ul' | 'ol' | 'link'; type RichTextLeaves = 'bold' | 'italic' | 'underline' | 'strikethrough'; -export type RichTextField = FieldBase & { + +type RichTextAdmin = Admin & { + elements?: RichTextElements[]; + leaves?: RichTextLeaves[]; +} + +export type RichTextField = Omit & { type: 'richText'; - admin?: { - elements?: RichTextElements[]; - leaves?: RichTextLeaves[]; - } + admin?: RichTextAdmin } export type ArrayField = FieldBase & { type: 'array'; minRows?: number; maxRows?: number; + labels?: Labels; fields?: Field[]; } @@ -122,16 +153,13 @@ export type RadioField = FieldBase & { options: { value: string; label: string; - }[]; + }[] | string[]; hasMany?: boolean; } export type Block = { slug: string, - labels: { - singular: string; - plural: string; - }; + labels: Labels fields: Field[], } @@ -139,16 +167,25 @@ export type BlockField = FieldBase & { type: 'blocks'; minRows?: number; maxRows?: number; - blocks?: Field[]; -}; + blocks?: Block[]; + defaultValue?: unknown + labels?: Labels +} export type Field = - | StandardField + TextField + | NumberField + | EmailField + | TextareaField + | CheckboxField + | DateField | BlockField + | GroupField | RadioField | RelationshipField | ArrayField | RichTextField | SelectField - | SelectManyField - | UploadField; + | UploadField + | CodeField + | RowField; diff --git a/src/uploads/types.ts b/src/uploads/types.ts index 3c0c706d32..97be10b6b7 100644 --- a/src/uploads/types.ts +++ b/src/uploads/types.ts @@ -23,7 +23,7 @@ export type ImageSize = { name: string, width: number, height: number, - crop: string, // comes from sharp package + crop?: string, // comes from sharp package }; export type IncomingUploadType = {