chore: merge master
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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')),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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>[]
|
||||
|
||||
@@ -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;
|
||||
149
src/fields/richText/richTextRelationshipPromise.ts
Normal file
149
src/fields/richText/richTextRelationshipPromise.ts
Normal 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;
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user