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:
@@ -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')),
|
||||
})),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -223,6 +223,13 @@ export type RichTextField = FieldBase & {
|
||||
elements?: RichTextElement[];
|
||||
leaves?: RichTextLeaf[];
|
||||
hideGutter?: boolean;
|
||||
upload?: {
|
||||
collections: {
|
||||
[collection: string]: {
|
||||
fields: Field[];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
54
src/fields/richText/populate.ts
Normal file
54
src/fields/richText/populate.ts
Normal 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;
|
||||
}
|
||||
};
|
||||
183
src/fields/richText/recurseNestedFields.ts
Normal file
183
src/fields/richText/recurseNestedFields.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -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,
|
||||
@@ -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') {
|
||||
|
||||
Reference in New Issue
Block a user