feat: enhances rich text upload with custom field API

* feat: adds admin.upload.collections[collection-name].fields to the RTE to save specific data on upload elements

* chore: renames flatten to unflatten in reduceFieldsToValues, disables automatic arrow function return in eslint

* docs: adds documentation for upload.collections[collection-name].fields feature

* feat: adds recursion to richText field to populate relationship and upload nested fields

* chore: removes unused css

* fix: import path for createRichTextRelationshipPromise

* docs: updates docs to include images for the RTE upload docs
This commit is contained in:
Jarrod Flesch
2022-01-21 10:15:51 -05:00
committed by GitHub
parent d07bb932ca
commit 0e4eb906f2
23 changed files with 885 additions and 259 deletions

View File

@@ -263,6 +263,11 @@ export const richText = baseField.keys({
),
),
hideGutter: joi.boolean().default(false),
upload: joi.object({
collections: joi.object().pattern(joi.string(), joi.object().keys({
fields: joi.array().items(joi.link('#field')),
})),
}),
}),
});

View File

@@ -223,6 +223,13 @@ export type RichTextField = FieldBase & {
elements?: RichTextElement[];
leaves?: RichTextLeaf[];
hideGutter?: boolean;
upload?: {
collections: {
[collection: string]: {
fields: Field[];
}
}
}
}
}

View File

@@ -0,0 +1,54 @@
/* eslint-disable @typescript-eslint/no-use-before-define */
import { Collection } from '../../collections/config/types';
import { Payload } from '../..';
import { RichTextField, Field } from '../config/types';
import { PayloadRequest } from '../../express/types';
type Arguments = {
data: unknown
overrideAccess?: boolean
depth: number
currentDepth?: number
payload: Payload
field: RichTextField
req: PayloadRequest
showHiddenFields: boolean
}
export const populate = async ({
id,
collection,
data,
overrideAccess,
depth,
currentDepth,
payload,
req,
showHiddenFields,
}: Omit<Arguments, 'field'> & {
id: string,
field: Field
collection: Collection
}): Promise<void> => {
let dataRef = data as Record<string, unknown>;
const doc = await payload.operations.collections.findByID({
req: {
...req,
payloadAPI: 'local',
},
collection,
id,
currentDepth: currentDepth + 1,
overrideAccess,
disableErrors: true,
depth,
showHiddenFields,
});
if (doc) {
dataRef = doc;
} else {
dataRef = null;
}
};

View File

@@ -0,0 +1,183 @@
/* eslint-disable @typescript-eslint/no-use-before-define */
import { Payload } from '../..';
import { Field, fieldHasSubFields, fieldIsArrayType, fieldAffectsData } from '../config/types';
import { PayloadRequest } from '../../express/types';
import { populate } from './populate';
import { recurseRichText } from './relationshipPromise';
type NestedRichTextFieldsArgs = {
promises: Promise<void>[]
data: unknown
fields: Field[]
req: PayloadRequest
payload: Payload
overrideAccess: boolean
depth: number
currentDepth?: number
showHiddenFields: boolean
}
export const recurseNestedFields = ({
promises,
data,
fields,
req,
payload,
overrideAccess = false,
depth,
currentDepth = 0,
showHiddenFields,
}: NestedRichTextFieldsArgs): void => {
fields.forEach((field) => {
if (field.type === 'relationship' || field.type === 'upload') {
if (field.type === 'relationship') {
if (field.hasMany && Array.isArray(data[field.name])) {
if (Array.isArray(field.relationTo)) {
data[field.name].forEach(({ relationTo, value }, i) => {
const collection = payload.collections[relationTo];
if (collection) {
promises.push(populate({
id: value,
field,
collection,
data: data[field.name][i],
overrideAccess,
depth,
currentDepth,
payload,
req,
showHiddenFields,
}));
}
});
} else {
data[field.name].forEach((id, i) => {
const collection = payload.collections[field.relationTo as string];
if (collection) {
promises.push(populate({
id,
field,
collection,
data: data[field.name][i],
overrideAccess,
depth,
currentDepth,
payload,
req,
showHiddenFields,
}));
}
});
}
} else if (Array.isArray(field.relationTo) && data[field.name]?.value && data[field.name]?.relationTo) {
const collection = payload.collections[data[field.name].relationTo];
promises.push(populate({
id: data[field.name].value,
field,
collection,
data: data[field.name].value,
overrideAccess,
depth,
currentDepth,
payload,
req,
showHiddenFields,
}));
}
} else if (typeof data[field.name] !== undefined) {
const collection = payload.collections[field.relationTo];
promises.push(populate({
id: data[field.name],
field,
collection,
data: data[field.name],
overrideAccess,
depth,
currentDepth,
payload,
req,
showHiddenFields,
}));
}
} else if (fieldHasSubFields(field) && !fieldIsArrayType(field)) {
if (fieldAffectsData(field) && typeof data[field.name] === 'object') {
recurseNestedFields({
promises,
data: data[field.name],
fields: field.fields,
req,
payload,
overrideAccess,
depth,
currentDepth,
showHiddenFields,
});
} else {
recurseNestedFields({
promises,
data,
fields: field.fields,
req,
payload,
overrideAccess,
depth,
currentDepth,
showHiddenFields,
});
}
} else if (Array.isArray(data[field.name])) {
if (field.type === 'blocks') {
data[field.name].forEach((row, i) => {
const block = field.blocks.find(({ slug }) => slug === row?.blockType);
if (block) {
recurseNestedFields({
promises,
data: data[field.name][i],
fields: block.fields,
req,
payload,
overrideAccess,
depth,
currentDepth,
showHiddenFields,
});
}
});
}
if (field.type === 'array') {
data[field.name].forEach((_, i) => {
recurseNestedFields({
promises,
data: data[field.name][i],
fields: field.fields,
req,
payload,
overrideAccess,
depth,
currentDepth,
showHiddenFields,
});
});
}
}
if (field.type === 'richText' && Array.isArray(data[field.name])) {
data[field.name].forEach((node) => {
if (Array.isArray(node.children)) {
recurseRichText({
req,
children: node.children,
payload,
overrideAccess,
depth,
currentDepth,
field,
promises,
showHiddenFields,
});
}
});
}
});
};

View File

@@ -1,7 +1,8 @@
import { Collection } from '../collections/config/types';
import { Payload } from '..';
import { RichTextField } from './config/types';
import { PayloadRequest } from '../express/types';
import { Payload } from '../..';
import { RichTextField } from '../config/types';
import { PayloadRequest } from '../../express/types';
import { recurseNestedFields } from './recurseNestedFields';
import { populate } from './populate';
type Arguments = {
data: unknown
@@ -26,44 +27,7 @@ type RecurseRichTextArgs = {
showHiddenFields: boolean
}
const populate = async ({
id,
collection,
data,
overrideAccess,
depth,
currentDepth,
payload,
req,
showHiddenFields,
}: Arguments & {
id: string,
collection: Collection
}) => {
const dataRef = data as Record<string, unknown>;
const doc = await payload.operations.collections.findByID({
req: {
...req,
payloadAPI: 'local',
},
collection,
id,
currentDepth: currentDepth + 1,
overrideAccess,
disableErrors: true,
depth,
showHiddenFields,
});
if (doc) {
dataRef.value = doc;
} else {
dataRef.value = null;
}
};
const recurseRichText = ({
export const recurseRichText = ({
req,
children,
payload,
@@ -73,7 +37,7 @@ const recurseRichText = ({
field,
promises,
showHiddenFields,
}: RecurseRichTextArgs) => {
}: RecurseRichTextArgs): void => {
if (Array.isArray(children)) {
(children as any[]).forEach((element) => {
const collection = payload.collections[element?.relationTo];
@@ -82,10 +46,23 @@ const recurseRichText = ({
&& 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,
payload,
overrideAccess,
depth,
currentDepth,
showHiddenFields,
});
}
promises.push(populate({
req,
id: element.value.id,
data: element,
data: element.value,
overrideAccess,
depth,
currentDepth,

View File

@@ -5,7 +5,7 @@ import { Field, fieldHasSubFields, fieldIsArrayType, fieldIsBlockType, fieldAffe
import { Operation } from '../types';
import { PayloadRequest } from '../express/types';
import { Payload } from '..';
import richTextRelationshipPromise from './richTextRelationshipPromise';
import richTextRelationshipPromise from './richText/relationshipPromise';
type Arguments = {
fields: Field[]
@@ -28,7 +28,7 @@ type Arguments = {
fullOriginalDoc: Record<string, any>
fullData: Record<string, any>
validationPromises: (() => Promise<string | boolean>)[]
errors: {message: string, field: string}[]
errors: { message: string, field: string }[]
payload: Payload
showHiddenFields: boolean
unflattenLocales: boolean
@@ -96,7 +96,7 @@ const traverseFields = (args: Arguments): void => {
}
if ((field.type === 'upload' || field.type === 'relationship')
&& (data[field.name] === '' || data[field.name] === 'none' || data[field.name] === 'null')) {
&& (data[field.name] === '' || data[field.name] === 'none' || data[field.name] === 'null')) {
if (field.type === 'relationship' && field.hasMany === true) {
dataCopy[field.name] = [];
} else {
@@ -304,7 +304,7 @@ const traverseFields = (args: Arguments): void => {
if (field.type === 'relationship' || field.type === 'upload') {
if (Array.isArray(field.relationTo)) {
if (Array.isArray(dataCopy[field.name])) {
dataCopy[field.name].forEach((relatedDoc: {value: unknown, relationTo: string}, i) => {
dataCopy[field.name].forEach((relatedDoc: { value: unknown, relationTo: string }, i) => {
const relatedCollection = payload.config.collections.find((collection) => collection.slug === relatedDoc.relationTo);
const relationshipIDField = relatedCollection.fields.find((collectionField) => fieldAffectsData(collectionField) && collectionField.name === 'id');
if (relationshipIDField?.type === 'number') {