payload-authjs
Version:
A Payload CMS 3 plugin for Auth.js 5
142 lines (141 loc) • 6.18 kB
JavaScript
import { deepCopyObjectSimple, deepMerge } from "payload";
import { fieldAffectsData, tabHasName } from "payload/shared";
/**
* Merge fields deeply
* - overriding fields with the same name
* - returning merged field and the rest of the fields that were not merged
*
* @param path - The path of the field (starting from the collection slug)
* @param baseFields - The base fields
* @param patchFields - The fields that should merge with the base fields (but base fields have priority)
*/ export const mergeFields = ({ path, baseFields, patchFields })=>{
let fields = deepCopyObjectSimple(baseFields);
const toHandleFields = deepCopyObjectSimple(patchFields);
for(let i = 0; i < toHandleFields.length; i++){
const field = toHandleFields[i];
if (fieldAffectsData(field)) {
const existingField = findFieldByName(fields, field.name);
if (existingField) {
// Check if field type matches
if (field.type !== existingField.type) {
throw new Error(`Field type mismatch for '${path}.${field.name}': Got '${field.type}', expected '${existingField.type}'`);
}
// Merge subfields if both have subfields
if ("fields" in field && "fields" in existingField) {
// Merge the field and subfields (existing field has always priority)
const result = mergeFields({
path: `${path}.${field.name}`,
baseFields: existingField.fields,
patchFields: field.fields
});
existingField.fields = [
...result.mergedFields,
...result.restFields
];
const { fields: _, ...restField } = field;
Object.assign(existingField, deepMerge(restField, existingField));
} else {
// Merge the field (existing field has always priority)
Object.assign(existingField, deepMerge(field, existingField));
}
// Remove from toHandleFields / mark as done
toHandleFields.splice(i, 1);
i--;
}
} else if ("fields" in field) {
// Merge subfields that do not affect data (e.g. row)
const result = mergeFields({
path,
baseFields: fields,
patchFields: field.fields
});
fields = result.mergedFields;
// If no inner fields left, remove the field / mark as done
if (result.restFields.length === 0) {
toHandleFields.splice(i, 1);
i--;
}
} else if (field.type === "tabs") {
// Merge tabs if tabs field exists
const tabsField = fields.find((f)=>f.type === "tabs");
if (tabsField) {
for(let t = 0; t < field.tabs.length; t++){
const tab = field.tabs[t];
const tabType = tabHasName(tab) ? "named" : "unnamed";
const existingTab = findTabField(tabsField.tabs, tabType, // If tab is named, use the name
tabType === "named" ? tab.name : tab.custom?.originalTabLabel ? tab.custom.originalTabLabel : typeof tab.label === "string" ? tab.label : "");
if (existingTab) {
// Merge the tab (existing tab has always priority)
const result = mergeFields({
path: tabType ? `${path}.${tab.name}` : path,
baseFields: existingTab.fields,
patchFields: tab.fields
});
existingTab.fields = [
...result.mergedFields,
...result.restFields
];
const { fields: _, ...restTab } = tab;
if (tab.custom?.originalTabLabel && tab.label) {
delete existingTab.label;
}
Object.assign(existingTab, deepMerge(restTab, existingTab));
// Remove tab
field.tabs.splice(t, 1);
t--;
}
}
// Add the rest tabs
tabsField.tabs.push(...field.tabs);
// Remove from toHandleFields / mark as done
toHandleFields.splice(i, 1);
i--;
}
}
}
return {
mergedFields: fields,
restFields: toHandleFields
};
};
/**
* Find a field by name in a list of fields (including subfields and tabs)
*
* @param fields The fields list
* @param name The field name
*/ const findFieldByName = (fields, name)=>{
for (const field of fields){
if ("fields" in field && !fieldAffectsData(field)) {
// Find in subfields if field not affecting data (e.g. row)
const found = findFieldByName(field.fields, name);
if (found) {
return found;
}
} else if (field.type === "tabs") {
// For each tab, find the field
for (const tab of field.tabs){
const found = findFieldByName(tab.fields, name);
if (found) {
return found;
}
}
} else if (field.name === name) {
return field; // Found
}
}
return undefined;
};
/**
* Find a tab field by name or label in a list of tabs
*
* @param tabs The tabs list
* @param type The tab type (named or unnamed)
* @param nameOrLabel The tab name or label
*/ const findTabField = (tabs, type, nameOrLabel)=>{
for (const tab of tabs){
if (type === "unnamed" && !tabHasName(tab) && tab.label === nameOrLabel || type === "named" && tabHasName(tab) && tab.name === nameOrLabel) {
return tab;
}
}
};
//# sourceMappingURL=mergeFields.js.map