UNPKG

payload

Version:

Node, React, Headless CMS and Application Framework built on Next.js

425 lines (424 loc) • 18.9 kB
// @ts-strict-ignore import { MissingEditorProp } from '../../../errors/index.js'; import { deepMergeWithSourceArrays } from '../../../utilities/deepMerge.js'; import { getTranslatedLabel } from '../../../utilities/getTranslatedLabel.js'; import { fieldAffectsData, fieldShouldBeLocalized, tabHasName } from '../../config/types.js'; import { getFieldPathsModified as getFieldPaths } from '../../getFieldPaths.js'; import { getExistingRowDoc } from './getExistingRowDoc.js'; import { traverseFields } from './traverseFields.js'; function buildFieldLabel(parentLabel, label) { const capitalizedLabel = label.charAt(0).toUpperCase() + label.slice(1); return parentLabel && capitalizedLabel ? `${parentLabel} > ${capitalizedLabel}` : capitalizedLabel || parentLabel; } // This function is responsible for the following actions, in order: // - Run condition // - Execute field hooks // - Validate data // - Transform data for storage // - beforeDuplicate hooks (if duplicate) // - Unflatten locales export const promise = async ({ id, blockData, collection, context, data, doc, docWithLocales, errors, field, fieldIndex, fieldLabelPath, global, mergeLocaleActions, operation, parentIndexPath, parentIsLocalized, parentPath, parentSchemaPath, req, siblingData, siblingDoc, siblingDocWithLocales, siblingFields, skipValidation })=>{ const { indexPath, path, schemaPath } = getFieldPaths({ field, index: fieldIndex, parentIndexPath, parentPath, parentSchemaPath }); const { localization } = req.payload.config; const defaultLocale = localization ? localization?.defaultLocale : 'en'; const operationLocale = req.locale || defaultLocale; const pathSegments = path ? path.split('.') : []; const schemaPathSegments = schemaPath ? schemaPath.split('.') : []; const indexPathSegments = indexPath ? indexPath.split('-').filter(Boolean)?.map(Number) : []; const passesCondition = field.admin?.condition ? Boolean(field.admin.condition(data, siblingData, { blockData, path: pathSegments, user: req.user })) : true; let skipValidationFromHere = skipValidation || !passesCondition; if (fieldAffectsData(field)) { // skip validation if the field is localized and the incoming data is null if (fieldShouldBeLocalized({ field, parentIsLocalized }) && operationLocale !== defaultLocale) { if ([ 'array', 'blocks' ].includes(field.type) && siblingData[field.name] === null) { skipValidationFromHere = true; } } // Execute hooks if (field.hooks?.beforeChange) { for (const hook of field.hooks.beforeChange){ const hookedValue = await hook({ blockData, collection, context, data, field, global, indexPath: indexPathSegments, operation, originalDoc: doc, path: pathSegments, previousSiblingDoc: siblingDoc, previousValue: siblingDoc[field.name], req, schemaPath: schemaPathSegments, siblingData, siblingDocWithLocales, siblingFields, value: siblingData[field.name] }); if (hookedValue !== undefined) { siblingData[field.name] = hookedValue; } } } // Validate if (!skipValidationFromHere && 'validate' in field && field.validate) { const valueToValidate = siblingData[field.name]; let jsonError; if (field.type === 'json' && typeof siblingData[field.name] === 'string') { try { JSON.parse(siblingData[field.name]); } catch (e) { jsonError = e; } } const validateFn = field.validate; const validationResult = await validateFn(valueToValidate, { ...field, id, blockData, collectionSlug: collection?.slug, data: deepMergeWithSourceArrays(doc, data), event: 'submit', // @ts-expect-error jsonError, operation, path: pathSegments, preferences: { fields: {} }, previousValue: siblingDoc[field.name], req, siblingData: deepMergeWithSourceArrays(siblingDoc, siblingData) }); if (typeof validationResult === 'string') { const fieldLabel = buildFieldLabel(fieldLabelPath, getTranslatedLabel(field?.label || field?.name, req.i18n)); errors.push({ label: fieldLabel, message: validationResult, path }); } } // Push merge locale action if applicable if (localization && fieldShouldBeLocalized({ field, parentIsLocalized })) { mergeLocaleActions.push(()=>{ const localeData = {}; for (const locale of localization.localeCodes){ const fieldValue = locale === req.locale ? siblingData[field.name] : siblingDocWithLocales?.[field.name]?.[locale]; // update locale value if it's not undefined if (typeof fieldValue !== 'undefined') { localeData[locale] = fieldValue; } } // If there are locales with data, set the data if (Object.keys(localeData).length > 0) { siblingData[field.name] = localeData; } }); } } switch(field.type){ case 'array': { const rows = siblingData[field.name]; if (Array.isArray(rows)) { const promises = []; rows.forEach((row, rowIndex)=>{ promises.push(traverseFields({ id, blockData, collection, context, data, doc, docWithLocales, errors, fieldLabelPath: field?.label === false ? fieldLabelPath : buildFieldLabel(fieldLabelPath, `${getTranslatedLabel(field?.label || field?.name, req.i18n)} ${rowIndex + 1}`), fields: field.fields, global, mergeLocaleActions, operation, parentIndexPath: '', parentIsLocalized: parentIsLocalized || field.localized, parentPath: path + '.' + rowIndex, parentSchemaPath: schemaPath, req, siblingData: row, siblingDoc: getExistingRowDoc(row, siblingDoc[field.name]), siblingDocWithLocales: getExistingRowDoc(row, siblingDocWithLocales[field.name]), skipValidation: skipValidationFromHere })); }); await Promise.all(promises); } break; } case 'blocks': { const rows = siblingData[field.name]; if (Array.isArray(rows)) { const promises = []; rows.forEach((row, rowIndex)=>{ const rowSiblingDoc = getExistingRowDoc(row, siblingDoc[field.name]); const rowSiblingDocWithLocales = getExistingRowDoc(row, siblingDocWithLocales ? siblingDocWithLocales[field.name] : {}); const blockTypeToMatch = row.blockType || rowSiblingDoc.blockType; const block = req.payload.blocks[blockTypeToMatch] ?? (field.blockReferences ?? field.blocks).find((curBlock)=>typeof curBlock !== 'string' && curBlock.slug === blockTypeToMatch); if (block) { promises.push(traverseFields({ id, blockData: row, collection, context, data, doc, docWithLocales, errors, fieldLabelPath: field?.label === false ? fieldLabelPath : buildFieldLabel(fieldLabelPath, `${getTranslatedLabel(field?.label || field?.name, req.i18n)} ${rowIndex + 1}`), fields: block.fields, global, mergeLocaleActions, operation, parentIndexPath: '', parentIsLocalized: parentIsLocalized || field.localized, parentPath: path + '.' + rowIndex, parentSchemaPath: schemaPath + '.' + block.slug, req, siblingData: row, siblingDoc: rowSiblingDoc, siblingDocWithLocales: rowSiblingDocWithLocales, skipValidation: skipValidationFromHere })); } }); await Promise.all(promises); } break; } case 'collapsible': case 'row': { await traverseFields({ id, blockData, collection, context, data, doc, docWithLocales, errors, fieldLabelPath: field.type === 'row' || field?.label === false ? fieldLabelPath : buildFieldLabel(fieldLabelPath, getTranslatedLabel(field?.label || field?.type, req.i18n)), fields: field.fields, global, mergeLocaleActions, operation, parentIndexPath: indexPath, parentIsLocalized, parentPath, parentSchemaPath: schemaPath, req, siblingData, siblingDoc, siblingDocWithLocales, skipValidation: skipValidationFromHere }); break; } case 'group': { if (typeof siblingData[field.name] !== 'object') { siblingData[field.name] = {}; } if (typeof siblingDoc[field.name] !== 'object') { siblingDoc[field.name] = {}; } if (typeof siblingDocWithLocales[field.name] !== 'object') { siblingDocWithLocales[field.name] = {}; } await traverseFields({ id, blockData, collection, context, data, doc, docWithLocales, errors, fieldLabelPath: field?.label === false ? fieldLabelPath : buildFieldLabel(fieldLabelPath, getTranslatedLabel(field?.label || field?.name, req.i18n)), fields: field.fields, global, mergeLocaleActions, operation, parentIndexPath: '', parentIsLocalized: parentIsLocalized || field.localized, parentPath: path, parentSchemaPath: schemaPath, req, siblingData: siblingData[field.name], siblingDoc: siblingDoc[field.name], siblingDocWithLocales: siblingDocWithLocales[field.name], skipValidation: skipValidationFromHere }); break; } case 'point': { // Transform point data for storage if (Array.isArray(siblingData[field.name]) && siblingData[field.name][0] !== null && siblingData[field.name][1] !== null) { siblingData[field.name] = { type: 'Point', coordinates: [ parseFloat(siblingData[field.name][0]), parseFloat(siblingData[field.name][1]) ] }; } break; } case 'richText': { if (!field?.editor) { throw new MissingEditorProp(field) // while we allow disabling editor functionality, you should not have any richText fields defined if you do not have an editor ; } if (typeof field?.editor === 'function') { throw new Error('Attempted to access unsanitized rich text editor.'); } const editor = field?.editor; if (editor?.hooks?.beforeChange?.length) { for (const hook of editor.hooks.beforeChange){ const hookedValue = await hook({ collection, context, data, docWithLocales, errors, field, fieldLabelPath: field?.label === false ? fieldLabelPath : buildFieldLabel(fieldLabelPath, getTranslatedLabel(field?.label || field?.name, req.i18n)), global, indexPath: indexPathSegments, mergeLocaleActions, operation, originalDoc: doc, parentIsLocalized, path: pathSegments, previousSiblingDoc: siblingDoc, previousValue: siblingDoc[field.name], req, schemaPath: schemaPathSegments, siblingData, siblingDocWithLocales, skipValidation, value: siblingData[field.name] }); if (hookedValue !== undefined) { siblingData[field.name] = hookedValue; } } } break; } case 'tab': { let tabSiblingData = siblingData; let tabSiblingDoc = siblingDoc; let tabSiblingDocWithLocales = siblingDocWithLocales; const isNamedTab = tabHasName(field); if (isNamedTab) { if (typeof siblingData[field.name] !== 'object') { siblingData[field.name] = {}; } if (typeof siblingDoc[field.name] !== 'object') { siblingDoc[field.name] = {}; } if (typeof siblingDocWithLocales[field.name] !== 'object') { siblingDocWithLocales[field.name] = {}; } tabSiblingData = siblingData[field.name]; tabSiblingDoc = siblingDoc[field.name]; tabSiblingDocWithLocales = siblingDocWithLocales[field.name]; } await traverseFields({ id, blockData, collection, context, data, doc, docWithLocales, errors, fieldLabelPath: field?.label === false ? fieldLabelPath : buildFieldLabel(fieldLabelPath, getTranslatedLabel(field?.label || field?.name, req.i18n)), fields: field.fields, global, mergeLocaleActions, operation, parentIndexPath: isNamedTab ? '' : indexPath, parentIsLocalized: parentIsLocalized || field.localized, parentPath: isNamedTab ? path : parentPath, parentSchemaPath: schemaPath, req, siblingData: tabSiblingData, siblingDoc: tabSiblingDoc, siblingDocWithLocales: tabSiblingDocWithLocales, skipValidation: skipValidationFromHere }); break; } case 'tabs': { await traverseFields({ id, blockData, collection, context, data, doc, docWithLocales, errors, fieldLabelPath: field?.label === false ? fieldLabelPath : buildFieldLabel(fieldLabelPath, getTranslatedLabel(field?.label || '', req.i18n)), fields: field.tabs.map((tab)=>({ ...tab, type: 'tab' })), global, mergeLocaleActions, operation, parentIndexPath: indexPath, parentIsLocalized, parentPath: path, parentSchemaPath: schemaPath, req, siblingData, siblingDoc, siblingDocWithLocales, skipValidation: skipValidationFromHere }); break; } default: { break; } } }; //# sourceMappingURL=promise.js.map