Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 20s
Build & Deploy / 🧪 QA (push) Failing after 34s
Build & Deploy / 🏗️ Build (push) Has started running
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Smoke Test (push) Has been cancelled
Build & Deploy / ⚡ Lighthouse (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
170 lines
7.3 KiB
Plaintext
170 lines
7.3 KiB
Plaintext
'use client';
|
|
|
|
import { getTranslation } from '@payloadcms/translations';
|
|
import { fieldAffectsData, fieldIsHiddenOrDisabled, fieldIsID, tabHasName } from 'payload/shared';
|
|
import { fieldTypeConditions, getValidFieldOperators } from '../elements/WhereBuilder/field-types.js';
|
|
import { createNestedClientFieldPath } from '../forms/Form/createNestedClientFieldPath.js';
|
|
import { combineFieldLabel } from './combineFieldLabel.js';
|
|
/**
|
|
* Transforms a fields schema into a flattened array of fields with labels and values.
|
|
* Used in the `WhereBuilder` component to render the fields in the dropdown.
|
|
*/
|
|
export const reduceFieldsToOptions = ({
|
|
fieldPermissions,
|
|
fields,
|
|
i18n,
|
|
labelPrefix,
|
|
pathPrefix: pathPrefixFromArgs
|
|
}) => {
|
|
return fields.reduce((reduced, field) => {
|
|
let pathPrefix = pathPrefixFromArgs;
|
|
// Do not filter out `field.admin.disableListFilter` fields here, as these should still render as disabled if they appear in the URL query
|
|
// Filter out `virtual: true` fields since they are regular virtuals and not backed by a DB field
|
|
if (fieldIsHiddenOrDisabled(field) && !fieldIsID(field) || 'virtual' in field && field.virtual === true) {
|
|
return reduced;
|
|
}
|
|
// IMPORTANT: We DON'T mutate field.name here because the field object is shared across
|
|
// multiple components (WhereBuilder, GroupByBuilder, etc.). Mutating it would break
|
|
// permission checks and cause issues in other components that need the field name.
|
|
// Instead, we use a flag to determine whether to include the field name in the path.
|
|
let shouldIgnoreFieldName = false;
|
|
// Handle virtual:string fields (virtual relationships, e.g. "post.title")
|
|
if ('virtual' in field && typeof field.virtual === 'string') {
|
|
pathPrefix = pathPrefix ? pathPrefix + '.' + field.virtual : field.virtual;
|
|
if (fieldAffectsData(field)) {
|
|
// Mark that we should ignore the field name when constructing the field path
|
|
shouldIgnoreFieldName = true;
|
|
}
|
|
}
|
|
if (field.type === 'tabs' && 'tabs' in field) {
|
|
const tabs = field.tabs;
|
|
tabs.forEach(tab => {
|
|
if (typeof tab.label !== 'boolean') {
|
|
const localizedTabLabel = getTranslation(tab.label, i18n);
|
|
const labelWithPrefix = labelPrefix ? labelPrefix + ' > ' + localizedTabLabel : localizedTabLabel;
|
|
// Make sure we handle nested tabs
|
|
const tabPathPrefix = tabHasName(tab) && tab.name ? pathPrefix ? pathPrefix + '.' + tab.name : tab.name : pathPrefix;
|
|
if (typeof localizedTabLabel === 'string') {
|
|
reduced.push(...reduceFieldsToOptions({
|
|
fieldPermissions: typeof fieldPermissions === 'boolean' ? fieldPermissions : tabHasName(tab) && tab.name ? fieldPermissions?.[tab.name]?.fields || fieldPermissions?.[tab.name] : fieldPermissions,
|
|
fields: tab.fields,
|
|
i18n,
|
|
labelPrefix: labelWithPrefix,
|
|
pathPrefix: tabPathPrefix
|
|
}));
|
|
}
|
|
}
|
|
});
|
|
return reduced;
|
|
}
|
|
// Rows cant have labels, so we need to handle them differently
|
|
if (field.type === 'row' && 'fields' in field) {
|
|
reduced.push(...reduceFieldsToOptions({
|
|
fieldPermissions,
|
|
fields: field.fields,
|
|
i18n,
|
|
labelPrefix,
|
|
pathPrefix
|
|
}));
|
|
return reduced;
|
|
}
|
|
if (field.type === 'collapsible' && 'fields' in field) {
|
|
const localizedTabLabel = getTranslation(field.label || '', i18n);
|
|
const labelWithPrefix = labelPrefix ? labelPrefix + ' > ' + localizedTabLabel : localizedTabLabel;
|
|
reduced.push(...reduceFieldsToOptions({
|
|
fieldPermissions,
|
|
fields: field.fields,
|
|
i18n,
|
|
labelPrefix: labelWithPrefix,
|
|
pathPrefix
|
|
}));
|
|
return reduced;
|
|
}
|
|
if (field.type === 'group' && 'fields' in field) {
|
|
const translatedLabel = getTranslation(field.label || '', i18n);
|
|
const labelWithPrefix = labelPrefix ? translatedLabel ? labelPrefix + ' > ' + translatedLabel : labelPrefix : translatedLabel;
|
|
if (fieldAffectsData(field)) {
|
|
// Make sure we handle deeply nested groups
|
|
const pathWithPrefix = field.name ? pathPrefix ? pathPrefix + '.' + field.name : field.name : pathPrefix;
|
|
reduced.push(...reduceFieldsToOptions({
|
|
fieldPermissions: typeof fieldPermissions === 'boolean' ? fieldPermissions : fieldPermissions?.[field.name]?.fields || fieldPermissions?.[field.name],
|
|
fields: field.fields,
|
|
i18n,
|
|
labelPrefix: labelWithPrefix,
|
|
pathPrefix: pathWithPrefix
|
|
}));
|
|
} else {
|
|
reduced.push(...reduceFieldsToOptions({
|
|
fieldPermissions,
|
|
fields: field.fields,
|
|
i18n,
|
|
labelPrefix: labelWithPrefix,
|
|
pathPrefix
|
|
}));
|
|
}
|
|
return reduced;
|
|
}
|
|
if (field.type === 'array' && 'fields' in field) {
|
|
const translatedLabel = getTranslation(field.label || '', i18n);
|
|
const labelWithPrefix = labelPrefix ? translatedLabel ? labelPrefix + ' > ' + translatedLabel : labelPrefix : translatedLabel;
|
|
// Make sure we handle deeply nested groups
|
|
const pathWithPrefix = field.name ? pathPrefix ? pathPrefix + '.' + field.name : field.name : pathPrefix;
|
|
reduced.push(...reduceFieldsToOptions({
|
|
fieldPermissions: typeof fieldPermissions === 'boolean' ? fieldPermissions : fieldPermissions?.[field.name]?.fields || fieldPermissions?.[field.name],
|
|
fields: field.fields,
|
|
i18n,
|
|
labelPrefix: labelWithPrefix,
|
|
pathPrefix: pathWithPrefix
|
|
}));
|
|
return reduced;
|
|
}
|
|
if (typeof fieldTypeConditions[field.type] === 'object') {
|
|
if (fieldIsID(field) || fieldPermissions === true || fieldPermissions?.[field.name] === true || fieldPermissions?.[field.name]?.read === true) {
|
|
const operatorKeys = new Set();
|
|
const {
|
|
validOperators
|
|
} = getValidFieldOperators({
|
|
field
|
|
});
|
|
const operators = validOperators.reduce((acc, operator) => {
|
|
if (!operatorKeys.has(operator.value)) {
|
|
operatorKeys.add(operator.value);
|
|
const operatorKey = `operators:${operator.label}`;
|
|
acc.push({
|
|
...operator,
|
|
label: i18n.t(operatorKey)
|
|
});
|
|
}
|
|
return acc;
|
|
}, []);
|
|
const localizedLabel = getTranslation(field.label || '', i18n);
|
|
const formattedLabel = labelPrefix ? combineFieldLabel({
|
|
field,
|
|
prefix: labelPrefix
|
|
}) : localizedLabel;
|
|
// For virtual fields, we use just the pathPrefix (the virtual path) without appending the field name
|
|
// For regular fields, we use createNestedClientFieldPath which appends the field name to the path
|
|
let fieldPath;
|
|
if (shouldIgnoreFieldName) {
|
|
fieldPath = pathPrefix;
|
|
} else if (pathPrefix) {
|
|
fieldPath = createNestedClientFieldPath(pathPrefix, field);
|
|
} else {
|
|
fieldPath = field.name;
|
|
}
|
|
const formattedField = {
|
|
label: formattedLabel,
|
|
plainTextLabel: `${labelPrefix ? labelPrefix + ' > ' : ''}${localizedLabel}`,
|
|
value: fieldPath,
|
|
...fieldTypeConditions[field.type],
|
|
field,
|
|
operators
|
|
};
|
|
reduced.push(formattedField);
|
|
return reduced;
|
|
}
|
|
}
|
|
return reduced;
|
|
}, []);
|
|
};
|
|
//# sourceMappingURL=reduceFieldsToOptions.js.map |