chore: merge master

This commit is contained in:
James
2022-09-11 18:07:05 -07:00
245 changed files with 4708 additions and 2347 deletions

View File

@@ -37,11 +37,11 @@ const sanitizeFields = (fields: Field[], validRelationships: string[]): Field[]
});
}
if (field.type === 'blocks') {
if (field.type === 'blocks' && field.blocks) {
field.blocks = field.blocks.map((block) => ({ ...block, fields: block.fields.concat(baseBlockFields) }));
}
if (field.type === 'array') {
if (field.type === 'array' && field.fields) {
field.fields.push(baseIDField);
}

View File

@@ -131,13 +131,15 @@ export const code = baseField.keys({
export const select = baseField.keys({
type: joi.string().valid('select').required(),
name: joi.string().required(),
options: joi.array().items(joi.alternatives().try(
joi.string(),
joi.object({
value: joi.string().required().allow(''),
label: joi.string().required(),
}),
)).required(),
options: joi.array().min(1).items(
joi.alternatives().try(
joi.string(),
joi.object({
value: joi.string().required().allow(''),
label: joi.string().required(),
}),
),
).required(),
hasMany: joi.boolean().default(false),
defaultValue: joi.alternatives().try(
joi.string().allow(''),
@@ -146,19 +148,22 @@ export const select = baseField.keys({
),
admin: baseAdminFields.keys({
isClearable: joi.boolean().default(false),
isSortable: joi.boolean().default(false),
}),
});
export const radio = baseField.keys({
type: joi.string().valid('radio').required(),
name: joi.string().required(),
options: joi.array().items(joi.alternatives().try(
joi.string(),
joi.object({
value: joi.string().required().allow(''),
label: joi.string().required(),
}),
)).required(),
options: joi.array().min(1).items(
joi.alternatives().try(
joi.string(),
joi.object({
value: joi.string().required().allow(''),
label: joi.string().required(),
}),
),
).required(),
defaultValue: joi.alternatives().try(
joi.string().allow(''),
joi.func(),
@@ -221,7 +226,7 @@ export const array = baseField.keys({
name: joi.string().required(),
minRows: joi.number(),
maxRows: joi.number(),
fields: joi.array().items(joi.link('#field')),
fields: joi.array().items(joi.link('#field')).required(),
labels: joi.object({
singular: joi.string(),
plural: joi.string(),
@@ -277,6 +282,9 @@ export const relationship = baseField.keys({
defaultValue: joi.alternatives().try(
joi.func(),
),
admin: baseAdminFields.keys({
isSortable: joi.boolean().default(false),
}),
});
export const blocks = baseField.keys({
@@ -299,7 +307,7 @@ export const blocks = baseField.keys({
}),
fields: joi.array().items(joi.link('#field')),
}),
),
).required(),
defaultValue: joi.alternatives().try(
joi.array().items(joi.object()),
joi.func(),
@@ -343,6 +351,9 @@ export const richText = baseField.keys({
fields: joi.array().items(joi.link('#field')),
})),
}),
link: joi.object({
fields: joi.array().items(joi.link('#field')),
}),
}),
});

View File

@@ -51,9 +51,9 @@ type Admin = {
condition?: Condition;
description?: Description;
components?: {
Filter?: React.ComponentType;
Cell?: React.ComponentType;
Field?: React.ComponentType;
Filter?: React.ComponentType<any>;
Cell?: React.ComponentType<any>;
Field?: React.ComponentType<any>;
}
hidden?: boolean
}
@@ -245,6 +245,7 @@ export type SelectField = FieldBase & {
hasMany?: boolean
admin?: Admin & {
isClearable?: boolean;
isSortable?: boolean;
}
}
@@ -254,6 +255,9 @@ export type RelationshipField = FieldBase & {
hasMany?: boolean;
maxDepth?: number;
filterOptions?: FilterOptions;
admin?: Admin & {
isSortable?: boolean;
}
}
export type ValueWithRelation = {
@@ -274,15 +278,15 @@ type RichTextPlugin = (editor: Editor) => Editor;
export type RichTextCustomElement = {
name: string
Button: React.ComponentType
Element: React.ComponentType
Button: React.ComponentType<any>
Element: React.ComponentType<any>
plugins?: RichTextPlugin[]
}
export type RichTextCustomLeaf = {
name: string
Button: React.ComponentType
Leaf: React.ComponentType
Button: React.ComponentType<any>
Leaf: React.ComponentType<any>
plugins?: RichTextPlugin[]
}
@@ -303,6 +307,9 @@ export type RichTextField = FieldBase & {
}
}
}
link?: {
fields?: Field[];
}
}
}
@@ -311,7 +318,7 @@ export type ArrayField = FieldBase & {
minRows?: number;
maxRows?: number;
labels?: Labels;
fields?: Field[];
fields: Field[];
}
export type RadioField = FieldBase & {
@@ -334,7 +341,7 @@ export type BlockField = FieldBase & {
type: 'blocks';
minRows?: number;
maxRows?: number;
blocks?: Block[];
blocks: Block[];
defaultValue?: unknown
labels?: Labels
}
@@ -384,7 +391,8 @@ export type FieldAffectingData =
| CodeField
| PointField
export type NonPresentationalField = TextField
export type NonPresentationalField =
TextField
| NumberField
| EmailField
| TextareaField

View File

@@ -15,9 +15,8 @@ const getValueWithDefault = async ({ value, defaultValue, locale, user }: Args):
if (defaultValue && typeof defaultValue === 'function') {
return defaultValue({ locale, user });
}
return defaultValue;
return undefined;
return defaultValue;
};
export default getValueWithDefault;

View File

@@ -2,7 +2,7 @@
import { Field, fieldAffectsData, tabHasName } from '../../config/types';
import { PayloadRequest } from '../../../express/types';
import { traverseFields } from './traverseFields';
import richTextRelationshipPromise from '../../richText/relationshipPromise';
import richTextRelationshipPromise from '../../richText/richTextRelationshipPromise';
import relationshipPopulationPromise from './relationshipPopulationPromise';
type Args = {
@@ -47,11 +47,11 @@ export const promise = async ({
}
const hasLocalizedValue = flattenLocales
&& fieldAffectsData(field)
&& (typeof siblingDoc[field.name] === 'object' && siblingDoc[field.name] !== null)
&& field.name
&& field.localized
&& req.locale !== 'all';
&& fieldAffectsData(field)
&& (typeof siblingDoc[field.name] === 'object' && siblingDoc[field.name] !== null)
&& field.name
&& field.localized
&& req.locale !== 'all';
if (hasLocalizedValue) {
let localizedValue = siblingDoc[field.name][req.locale];
@@ -119,8 +119,8 @@ export const promise = async ({
await priorHook;
const shouldRunHookOnAllLocales = field.localized
&& (req.locale === 'all' || !flattenLocales)
&& typeof siblingDoc[field.name] === 'object';
&& (req.locale === 'all' || !flattenLocales)
&& typeof siblingDoc[field.name] === 'object';
if (shouldRunHookOnAllLocales) {
const hookPromises = Object.entries(siblingDoc[field.name]).map(([locale, value]) => (async () => {

View File

@@ -2,7 +2,7 @@
import { Field, fieldHasSubFields, fieldIsArrayType, fieldAffectsData } from '../config/types';
import { PayloadRequest } from '../../express/types';
import { populate } from './populate';
import { recurseRichText } from './relationshipPromise';
import { recurseRichText } from './richTextRelationshipPromise';
type NestedRichTextFieldsArgs = {
promises: Promise<void>[]

View File

@@ -1,112 +0,0 @@
import { RichTextField } from '../config/types';
import { PayloadRequest } from '../../express/types';
import { recurseNestedFields } from './recurseNestedFields';
import { populate } from './populate';
type Args = {
currentDepth?: number
depth: number
field: RichTextField
overrideAccess?: boolean
req: PayloadRequest
siblingDoc: Record<string, unknown>
showHiddenFields: boolean
}
type RecurseRichTextArgs = {
children: unknown[]
overrideAccess: boolean
depth: number
currentDepth: number
field: RichTextField
req: PayloadRequest
promises: Promise<void>[]
showHiddenFields: boolean
}
export const recurseRichText = ({
req,
children,
overrideAccess = false,
depth,
currentDepth = 0,
field,
promises,
showHiddenFields,
}: RecurseRichTextArgs): void => {
if (Array.isArray(children)) {
(children as any[]).forEach((element) => {
const collection = req.payload.collections[element?.relationTo];
if ((element.type === 'relationship' || element.type === 'upload')
&& element?.value?.id
&& collection
&& (depth && currentDepth <= depth)) {
if (element.type === 'upload' && Array.isArray(field.admin?.upload?.collections?.[element?.relationTo]?.fields)) {
recurseNestedFields({
promises,
data: element.fields || {},
fields: field.admin.upload.collections[element.relationTo].fields,
req,
overrideAccess,
depth,
currentDepth,
showHiddenFields,
});
}
promises.push(populate({
req,
id: element.value.id,
data: element,
key: 'value',
overrideAccess,
depth,
currentDepth,
field,
collection,
showHiddenFields,
}));
}
if (element?.children) {
recurseRichText({
children: element.children,
currentDepth,
depth,
field,
overrideAccess,
promises,
req,
showHiddenFields,
});
}
});
}
};
const richTextRelationshipPromise = async ({
currentDepth,
depth,
field,
overrideAccess,
req,
siblingDoc,
showHiddenFields,
}: Args): Promise<void> => {
const promises = [];
recurseRichText({
children: siblingDoc[field.name] as unknown[],
currentDepth,
depth,
field,
overrideAccess,
promises,
req,
showHiddenFields,
});
await Promise.all(promises);
};
export default richTextRelationshipPromise;

View File

@@ -0,0 +1,149 @@
import { RichTextField } from '../config/types';
import { PayloadRequest } from '../../express/types';
import { recurseNestedFields } from './recurseNestedFields';
import { populate } from './populate';
type Args = {
currentDepth?: number
depth: number
field: RichTextField
overrideAccess?: boolean
req: PayloadRequest
siblingDoc: Record<string, unknown>
showHiddenFields: boolean
}
type RecurseRichTextArgs = {
children: unknown[]
overrideAccess: boolean
depth: number
currentDepth: number
field: RichTextField
req: PayloadRequest
promises: Promise<void>[]
showHiddenFields: boolean
}
export const recurseRichText = ({
req,
children,
overrideAccess = false,
depth,
currentDepth = 0,
field,
promises,
showHiddenFields,
}: RecurseRichTextArgs): void => {
if (Array.isArray(children)) {
(children as any[]).forEach((element) => {
if ((depth && currentDepth <= depth)) {
if ((element.type === 'relationship' || element.type === 'upload')
&& element?.value?.id) {
const collection = req.payload.collections[element?.relationTo];
if (collection) {
promises.push(populate({
req,
id: element.value.id,
data: element,
key: 'value',
overrideAccess,
depth,
currentDepth,
field,
collection,
showHiddenFields,
}));
}
if (element.type === 'upload' && Array.isArray(field.admin?.upload?.collections?.[element?.relationTo]?.fields)) {
recurseNestedFields({
promises,
data: element.fields || {},
fields: field.admin.upload.collections[element.relationTo].fields,
req,
overrideAccess,
depth,
currentDepth,
showHiddenFields,
});
}
}
if (element.type === 'link') {
if (element?.doc?.value && element?.doc?.relationTo) {
const collection = req.payload.collections[element?.doc?.relationTo];
if (collection) {
promises.push(populate({
req,
id: element.doc.value,
data: element.doc,
key: 'value',
overrideAccess,
depth,
currentDepth,
field,
collection,
showHiddenFields,
}));
}
}
if (Array.isArray(field.admin?.link?.fields)) {
recurseNestedFields({
promises,
data: element.fields || {},
fields: field.admin?.link?.fields,
req,
overrideAccess,
depth,
currentDepth,
showHiddenFields,
});
}
}
}
if (element?.children) {
recurseRichText({
children: element.children,
currentDepth,
depth,
field,
overrideAccess,
promises,
req,
showHiddenFields,
});
}
});
}
};
const richTextRelationshipPromise = async ({
currentDepth,
depth,
field,
overrideAccess,
req,
siblingDoc,
showHiddenFields,
}: Args): Promise<void> => {
const promises = [];
recurseRichText({
children: siblingDoc[field.name] as unknown[],
currentDepth,
depth,
field,
overrideAccess,
promises,
req,
showHiddenFields,
});
await Promise.all(promises);
};
export default richTextRelationshipPromise;

View File

@@ -17,9 +17,12 @@ import {
TextField,
UploadField,
Validate,
fieldAffectsData,
} from './config/types';
import { TypeWithID } from '../collections/config/types';
import canUseDOM from '../utilities/canUseDOM';
import { isValidID } from '../utilities/isValidID';
import { getIDType } from '../utilities/getIDType';
const defaultMessage = 'This field is required.';
@@ -232,6 +235,15 @@ export const upload: Validate<unknown, unknown, UploadField> = (value: string, o
return defaultMessage;
}
if (!canUseDOM && typeof value !== 'undefined' && value !== null) {
const idField = options.payload.collections[options.relationTo].config.fields.find((field) => fieldAffectsData(field) && field.name === 'id');
const type = getIDType(idField);
if (!isValidID(value, type)) {
return 'This field is not a valid upload ID';
}
}
return validateFilterOptions(value, options);
};
@@ -240,6 +252,45 @@ export const relationship: Validate<unknown, unknown, RelationshipField> = async
return defaultMessage;
}
if (!canUseDOM && typeof value !== 'undefined' && value !== null) {
const values = Array.isArray(value) ? value : [value];
const invalidRelationships = values.filter((val) => {
let collection: string;
let requestedID: string | number;
if (typeof options.relationTo === 'string') {
collection = options.relationTo;
// custom id
if (typeof val === 'string' || typeof val === 'number') {
requestedID = val;
}
}
if (Array.isArray(options.relationTo) && typeof val === 'object' && val?.relationTo) {
collection = val.relationTo;
requestedID = val.value;
}
const idField = options.payload.collections[collection].config.fields.find((field) => fieldAffectsData(field) && field.name === 'id');
let type;
if (idField) {
type = idField.type === 'number' ? 'number' : 'text';
} else {
type = 'ObjectID';
}
return !isValidID(requestedID, type);
});
if (invalidRelationships.length > 0) {
return `This field has the following invalid selections: ${invalidRelationships.map((err, invalid) => {
return `${err} ${JSON.stringify(invalid)}`;
}).join(', ')}` as string;
}
}
return validateFilterOptions(value, options);
};