@mvriu5/payload-ai 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/AIInput.js +155 -6
- package/dist/components/AIInput.module.css +11 -2
- package/dist/components/ActionToast.d.ts +2 -1
- package/dist/components/ActionToast.js +3 -2
- package/dist/handlers/applyActionHandler.js +316 -107
- package/dist/handlers/chatHandler.js +660 -48
- package/dist/handlers/proposalDiffHandler.js +81 -26
- package/dist/payload/logging.d.ts +9 -0
- package/dist/payload/logging.js +14 -0
- package/dist/payload/normalizeData.d.ts +17 -7
- package/dist/payload/normalizeData.js +22 -1
- package/dist/payload/proposalData.d.ts +31 -0
- package/dist/payload/proposalData.js +575 -0
- package/dist/payload/schemaContext.d.ts +3 -7
- package/dist/payload/schemaContext.js +49 -23
- package/package.json +1 -1
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
import { getSafeProposalLabel, getOptionValue, isRecord } from "./shared.js";
|
|
2
|
+
import { createLexicalText, getSchemaFields, normalizeAuthData, normalizeDataForFields } from "./normalizeData.js";
|
|
3
|
+
const isTitleLikeField = (field, titleFieldName)=>{
|
|
4
|
+
const normalizedName = field.name?.toLowerCase();
|
|
5
|
+
return Boolean(field.name && (field.name === titleFieldName || normalizedName === "title" || normalizedName === "name" || normalizedName === "label" || normalizedName === "headline"));
|
|
6
|
+
};
|
|
7
|
+
const getFieldOptionValues = (field)=>{
|
|
8
|
+
return (field.options || []).map((option)=>getOptionValue(option)).filter((option)=>Boolean(option));
|
|
9
|
+
};
|
|
10
|
+
const escapeRegExp = (value)=>value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
11
|
+
const getFieldNameVariants = (fieldName)=>{
|
|
12
|
+
if (!fieldName) return [];
|
|
13
|
+
const spacedName = fieldName.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").toLowerCase();
|
|
14
|
+
return Array.from(new Set([
|
|
15
|
+
fieldName.toLowerCase(),
|
|
16
|
+
spacedName
|
|
17
|
+
])).filter(Boolean);
|
|
18
|
+
};
|
|
19
|
+
const inferOptionValueFromText = ({ field, requireFieldMention = false, text })=>{
|
|
20
|
+
const normalizedText = text?.trim().toLowerCase();
|
|
21
|
+
if (!normalizedText) return undefined;
|
|
22
|
+
if (requireFieldMention) {
|
|
23
|
+
const fieldMentioned = getFieldNameVariants(field.name).some((variant)=>{
|
|
24
|
+
const pattern = new RegExp(`\\b${escapeRegExp(variant)}\\b`, "i");
|
|
25
|
+
return pattern.test(normalizedText);
|
|
26
|
+
});
|
|
27
|
+
if (!fieldMentioned) return undefined;
|
|
28
|
+
}
|
|
29
|
+
const optionValues = getFieldOptionValues(field).sort((left, right)=>right.length - left.length);
|
|
30
|
+
return optionValues.find((optionValue)=>{
|
|
31
|
+
const pattern = new RegExp(`\\b${escapeRegExp(optionValue.toLowerCase())}\\b`, "i");
|
|
32
|
+
return pattern.test(normalizedText);
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
const getDefaultFieldValue = ({ field, inferenceText, label, titleFieldName })=>{
|
|
36
|
+
if (field.name === "_status") return "draft";
|
|
37
|
+
if (field.type === "checkbox") return false;
|
|
38
|
+
if (field.type === "select" || field.type === "radio") {
|
|
39
|
+
const inferredOptionValue = inferOptionValueFromText({
|
|
40
|
+
field,
|
|
41
|
+
requireFieldMention: true,
|
|
42
|
+
text: inferenceText || label
|
|
43
|
+
});
|
|
44
|
+
if (inferredOptionValue !== undefined) {
|
|
45
|
+
return inferredOptionValue;
|
|
46
|
+
}
|
|
47
|
+
if (field.defaultValue !== undefined) {
|
|
48
|
+
return field.defaultValue;
|
|
49
|
+
}
|
|
50
|
+
return getOptionValue(field.options?.[0]);
|
|
51
|
+
}
|
|
52
|
+
if (field.defaultValue !== undefined) return field.defaultValue;
|
|
53
|
+
if ([
|
|
54
|
+
"email",
|
|
55
|
+
"text",
|
|
56
|
+
"textarea"
|
|
57
|
+
].includes(field.type || "") && isTitleLikeField(field, titleFieldName)) {
|
|
58
|
+
return getSafeProposalLabel(label);
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
};
|
|
62
|
+
const addIssue = (issues, issue)=>{
|
|
63
|
+
issues.push(issue);
|
|
64
|
+
};
|
|
65
|
+
const normalizeRelationshipScalar = (value)=>{
|
|
66
|
+
if (typeof value === "number") {
|
|
67
|
+
if (!Number.isInteger(value) || value <= 0) return undefined;
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
const trimmedValue = value.trim();
|
|
71
|
+
if (!trimmedValue || /\s/.test(trimmedValue)) return undefined;
|
|
72
|
+
if (/^\d+$/.test(trimmedValue) && Number(trimmedValue) <= 0) return undefined;
|
|
73
|
+
return trimmedValue;
|
|
74
|
+
};
|
|
75
|
+
const normalizeRelationshipValue = ({ field, issues, path, value })=>{
|
|
76
|
+
const allowedRelationTargets = Array.isArray(field.relationTo) ? field.relationTo.filter((item)=>typeof item === "string") : typeof field.relationTo === "string" ? [
|
|
77
|
+
field.relationTo
|
|
78
|
+
] : [];
|
|
79
|
+
const normalizeSingle = (item, itemPath)=>{
|
|
80
|
+
if (typeof item === "number") {
|
|
81
|
+
const normalizedValue = normalizeRelationshipScalar(item);
|
|
82
|
+
if (normalizedValue !== undefined) {
|
|
83
|
+
return normalizedValue;
|
|
84
|
+
}
|
|
85
|
+
addIssue(issues, {
|
|
86
|
+
code: "invalid_relationship",
|
|
87
|
+
message: "Relationship and upload fields must use a valid document ID.",
|
|
88
|
+
path: itemPath
|
|
89
|
+
});
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
if (typeof item === "string") {
|
|
93
|
+
const normalizedValue = normalizeRelationshipScalar(item);
|
|
94
|
+
if (normalizedValue !== undefined) {
|
|
95
|
+
return normalizedValue;
|
|
96
|
+
}
|
|
97
|
+
addIssue(issues, {
|
|
98
|
+
code: "invalid_relationship",
|
|
99
|
+
message: "Relationship and upload fields must use a valid document ID, not free text.",
|
|
100
|
+
path: itemPath
|
|
101
|
+
});
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
if (!isRecord(item)) {
|
|
105
|
+
addIssue(issues, {
|
|
106
|
+
code: "invalid_relationship",
|
|
107
|
+
message: "Relationship and upload fields must use a document ID or an object with id/value.",
|
|
108
|
+
path: itemPath
|
|
109
|
+
});
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
if (typeof item.id === "string" || typeof item.id === "number") {
|
|
113
|
+
const normalizedValue = normalizeRelationshipScalar(item.id);
|
|
114
|
+
if (normalizedValue !== undefined) {
|
|
115
|
+
return normalizedValue;
|
|
116
|
+
}
|
|
117
|
+
addIssue(issues, {
|
|
118
|
+
code: "invalid_relationship",
|
|
119
|
+
message: "Relationship and upload fields must use a valid document ID.",
|
|
120
|
+
path: itemPath
|
|
121
|
+
});
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
if (typeof item.value === "string" || typeof item.value === "number") {
|
|
125
|
+
const normalizedValue = normalizeRelationshipScalar(item.value);
|
|
126
|
+
if (normalizedValue === undefined) {
|
|
127
|
+
addIssue(issues, {
|
|
128
|
+
code: "invalid_relationship",
|
|
129
|
+
message: "Relationship and upload fields must use a valid document ID.",
|
|
130
|
+
path: itemPath
|
|
131
|
+
});
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
if (allowedRelationTargets.length > 1) {
|
|
135
|
+
const relationTo = typeof item.relationTo === "string" ? item.relationTo : null;
|
|
136
|
+
if (!relationTo || !allowedRelationTargets.includes(relationTo)) {
|
|
137
|
+
addIssue(issues, {
|
|
138
|
+
code: "invalid_relationship",
|
|
139
|
+
message: `Relationship must include one of: ${allowedRelationTargets.join(", ")}.`,
|
|
140
|
+
path: itemPath
|
|
141
|
+
});
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
relationTo,
|
|
146
|
+
value: normalizedValue
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return normalizedValue;
|
|
150
|
+
}
|
|
151
|
+
addIssue(issues, {
|
|
152
|
+
code: "invalid_relationship",
|
|
153
|
+
message: "Relationship and upload fields must use a document ID or an object with id/value.",
|
|
154
|
+
path: itemPath
|
|
155
|
+
});
|
|
156
|
+
return undefined;
|
|
157
|
+
};
|
|
158
|
+
if (field.hasMany) {
|
|
159
|
+
if (!Array.isArray(value)) {
|
|
160
|
+
addIssue(issues, {
|
|
161
|
+
code: "invalid_relationship",
|
|
162
|
+
message: "Relationship field expects an array of document IDs.",
|
|
163
|
+
path
|
|
164
|
+
});
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
const normalizedValues = value.map((item, index)=>normalizeSingle(item, `${path}.${index}`)).filter((item)=>item !== undefined);
|
|
168
|
+
return normalizedValues;
|
|
169
|
+
}
|
|
170
|
+
return normalizeSingle(value, path);
|
|
171
|
+
};
|
|
172
|
+
const normalizeFieldValue = ({ coercedFields, enforceRequiredChildren, field, inferenceText, issues, label, mode, path, titleFieldName, value })=>{
|
|
173
|
+
if (field.type === "group") {
|
|
174
|
+
if (!isRecord(value)) {
|
|
175
|
+
addIssue(issues, {
|
|
176
|
+
code: "invalid_container",
|
|
177
|
+
message: "Group fields must be objects.",
|
|
178
|
+
path
|
|
179
|
+
});
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
return normalizeRecordForFields({
|
|
183
|
+
allowSafeFallback: false,
|
|
184
|
+
coercedFields,
|
|
185
|
+
enforceRequiredChildren: mode === "create" || enforceRequiredChildren,
|
|
186
|
+
fields: field.fields || [],
|
|
187
|
+
inferenceText,
|
|
188
|
+
issues,
|
|
189
|
+
label,
|
|
190
|
+
mode,
|
|
191
|
+
path,
|
|
192
|
+
titleFieldName,
|
|
193
|
+
value
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
if (field.type === "array") {
|
|
197
|
+
if (!Array.isArray(value)) {
|
|
198
|
+
addIssue(issues, {
|
|
199
|
+
code: "invalid_array",
|
|
200
|
+
message: "Array fields must use an array value.",
|
|
201
|
+
path
|
|
202
|
+
});
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
const childFields = (field.fields || []).filter((childField)=>Boolean(childField.name));
|
|
206
|
+
const itemLabelField = childFields.find((childField)=>childField.name === "label") || childFields.find((childField)=>childField.name === "title") || childFields.find((childField)=>childField.name === "name") || childFields.find((childField)=>childField.name === "value") || childFields[0];
|
|
207
|
+
return value.map((item, index)=>{
|
|
208
|
+
const itemPath = `${path}.${index}`;
|
|
209
|
+
if (!isRecord(item)) {
|
|
210
|
+
if (!itemLabelField) {
|
|
211
|
+
addIssue(issues, {
|
|
212
|
+
code: "invalid_array",
|
|
213
|
+
message: "Array items must be objects for this field.",
|
|
214
|
+
path: itemPath
|
|
215
|
+
});
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
const wrappedItem = {
|
|
219
|
+
[itemLabelField.name]: item
|
|
220
|
+
};
|
|
221
|
+
return normalizeRecordForFields({
|
|
222
|
+
allowSafeFallback: false,
|
|
223
|
+
coercedFields,
|
|
224
|
+
enforceRequiredChildren: true,
|
|
225
|
+
fields: field.fields || [],
|
|
226
|
+
inferenceText,
|
|
227
|
+
issues,
|
|
228
|
+
label,
|
|
229
|
+
mode,
|
|
230
|
+
path: itemPath,
|
|
231
|
+
titleFieldName,
|
|
232
|
+
value: wrappedItem
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
return normalizeRecordForFields({
|
|
236
|
+
allowSafeFallback: false,
|
|
237
|
+
coercedFields,
|
|
238
|
+
enforceRequiredChildren: true,
|
|
239
|
+
fields: field.fields || [],
|
|
240
|
+
inferenceText,
|
|
241
|
+
issues,
|
|
242
|
+
label,
|
|
243
|
+
mode,
|
|
244
|
+
path: itemPath,
|
|
245
|
+
titleFieldName,
|
|
246
|
+
value: item
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
if (field.type === "blocks") {
|
|
251
|
+
if (!Array.isArray(value)) {
|
|
252
|
+
addIssue(issues, {
|
|
253
|
+
code: "invalid_blocks",
|
|
254
|
+
message: "Blocks fields must use an array value.",
|
|
255
|
+
path
|
|
256
|
+
});
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
259
|
+
return value.map((item, index)=>{
|
|
260
|
+
const itemPath = `${path}.${index}`;
|
|
261
|
+
if (!isRecord(item)) {
|
|
262
|
+
addIssue(issues, {
|
|
263
|
+
code: "invalid_blocks",
|
|
264
|
+
message: "Each block entry must be an object.",
|
|
265
|
+
path: itemPath
|
|
266
|
+
});
|
|
267
|
+
return undefined;
|
|
268
|
+
}
|
|
269
|
+
const blockType = typeof item.blockType === "string" ? item.blockType : typeof item.type === "string" ? item.type : typeof item.slug === "string" ? item.slug : null;
|
|
270
|
+
if (!blockType) {
|
|
271
|
+
addIssue(issues, {
|
|
272
|
+
code: "invalid_block_type",
|
|
273
|
+
message: "Each block entry must include blockType.",
|
|
274
|
+
path: itemPath
|
|
275
|
+
});
|
|
276
|
+
return undefined;
|
|
277
|
+
}
|
|
278
|
+
const block = field.blocks?.find((candidate)=>candidate.slug === blockType);
|
|
279
|
+
if (!block) {
|
|
280
|
+
addIssue(issues, {
|
|
281
|
+
code: "invalid_block_type",
|
|
282
|
+
message: `Unknown block type "${blockType}".`,
|
|
283
|
+
path: `${itemPath}.blockType`
|
|
284
|
+
});
|
|
285
|
+
return undefined;
|
|
286
|
+
}
|
|
287
|
+
const { blockType: _blockType, slug: _slug, type: _type, ...blockData } = item;
|
|
288
|
+
const normalizedBlock = normalizeRecordForFields({
|
|
289
|
+
allowSafeFallback: false,
|
|
290
|
+
coercedFields,
|
|
291
|
+
enforceRequiredChildren: true,
|
|
292
|
+
fields: block.fields || [],
|
|
293
|
+
inferenceText,
|
|
294
|
+
issues,
|
|
295
|
+
label,
|
|
296
|
+
mode,
|
|
297
|
+
path: itemPath,
|
|
298
|
+
titleFieldName,
|
|
299
|
+
value: blockData
|
|
300
|
+
});
|
|
301
|
+
return {
|
|
302
|
+
...normalizedBlock,
|
|
303
|
+
blockType
|
|
304
|
+
};
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
if (field.type === "checkbox") {
|
|
308
|
+
if (typeof value === "boolean") return value;
|
|
309
|
+
if (value === "true") return true;
|
|
310
|
+
if (value === "false") return false;
|
|
311
|
+
addIssue(issues, {
|
|
312
|
+
code: "invalid_checkbox",
|
|
313
|
+
message: "Checkbox fields must use true or false.",
|
|
314
|
+
path
|
|
315
|
+
});
|
|
316
|
+
return undefined;
|
|
317
|
+
}
|
|
318
|
+
if (field.type === "relationship" || field.type === "upload") {
|
|
319
|
+
return normalizeRelationshipValue({
|
|
320
|
+
field,
|
|
321
|
+
issues,
|
|
322
|
+
path,
|
|
323
|
+
value
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
if (field.type === "richText") return createLexicalText(value);
|
|
327
|
+
if (field.type === "select" || field.type === "radio") {
|
|
328
|
+
const optionValues = getFieldOptionValues(field);
|
|
329
|
+
const stringValue = value === null ? "" : String(value);
|
|
330
|
+
const inferredOptionValue = inferOptionValueFromText({
|
|
331
|
+
field,
|
|
332
|
+
requireFieldMention: true,
|
|
333
|
+
text: inferenceText || label
|
|
334
|
+
});
|
|
335
|
+
if (inferredOptionValue && optionValues.includes(inferredOptionValue)) {
|
|
336
|
+
if (stringValue !== inferredOptionValue) {
|
|
337
|
+
coercedFields.push(path);
|
|
338
|
+
}
|
|
339
|
+
return inferredOptionValue;
|
|
340
|
+
}
|
|
341
|
+
if (stringValue && optionValues.includes(stringValue)) return stringValue;
|
|
342
|
+
addIssue(issues, {
|
|
343
|
+
code: "invalid_option",
|
|
344
|
+
message: `Field must use one of: ${optionValues.join(", ")}.`,
|
|
345
|
+
path
|
|
346
|
+
});
|
|
347
|
+
return undefined;
|
|
348
|
+
}
|
|
349
|
+
if ([
|
|
350
|
+
"email",
|
|
351
|
+
"text",
|
|
352
|
+
"textarea"
|
|
353
|
+
].includes(field.type || "")) {
|
|
354
|
+
return value === null ? value : String(value);
|
|
355
|
+
}
|
|
356
|
+
if (field.type === "date") {
|
|
357
|
+
const date = new Date(String(value));
|
|
358
|
+
if (Number.isNaN(date.getTime())) {
|
|
359
|
+
addIssue(issues, {
|
|
360
|
+
code: "invalid_date",
|
|
361
|
+
message: "Date fields must use a valid date value.",
|
|
362
|
+
path
|
|
363
|
+
});
|
|
364
|
+
return undefined;
|
|
365
|
+
}
|
|
366
|
+
return date.toISOString();
|
|
367
|
+
}
|
|
368
|
+
return value;
|
|
369
|
+
};
|
|
370
|
+
const normalizeRecordForFields = ({ allowSafeFallback, coercedFields, enforceRequiredChildren, fields, inferenceText, issues, label, mode, path, titleFieldName, value })=>{
|
|
371
|
+
const normalizedData = {};
|
|
372
|
+
const fieldsByName = new Map(fields.filter((field)=>Boolean(field.name)).map((field)=>[
|
|
373
|
+
field.name,
|
|
374
|
+
field
|
|
375
|
+
]));
|
|
376
|
+
for (const [key, fieldValue] of Object.entries(value)){
|
|
377
|
+
const field = fieldsByName.get(key);
|
|
378
|
+
const fieldPath = path ? `${path}.${key}` : key;
|
|
379
|
+
if (!field) {
|
|
380
|
+
addIssue(issues, {
|
|
381
|
+
code: "unknown_field",
|
|
382
|
+
message: "Field does not exist in the schema.",
|
|
383
|
+
path: fieldPath
|
|
384
|
+
});
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
const normalizedValue = normalizeFieldValue({
|
|
388
|
+
coercedFields,
|
|
389
|
+
enforceRequiredChildren,
|
|
390
|
+
field,
|
|
391
|
+
inferenceText,
|
|
392
|
+
issues,
|
|
393
|
+
label,
|
|
394
|
+
mode,
|
|
395
|
+
path: fieldPath,
|
|
396
|
+
titleFieldName,
|
|
397
|
+
value: fieldValue
|
|
398
|
+
});
|
|
399
|
+
if (normalizedValue !== undefined) {
|
|
400
|
+
normalizedData[key] = normalizedValue;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (mode === "create" || enforceRequiredChildren) {
|
|
404
|
+
for (const field of fields.filter((candidate)=>Boolean(candidate.name) && candidate.required === true)){
|
|
405
|
+
const fieldPath = path ? `${path}.${field.name}` : field.name;
|
|
406
|
+
if (normalizedData[field.name] === undefined) {
|
|
407
|
+
if (allowSafeFallback) {
|
|
408
|
+
const fallbackValue = getDefaultFieldValue({
|
|
409
|
+
field,
|
|
410
|
+
inferenceText,
|
|
411
|
+
label,
|
|
412
|
+
titleFieldName
|
|
413
|
+
});
|
|
414
|
+
if (fallbackValue !== undefined) {
|
|
415
|
+
normalizedData[field.name] = fallbackValue;
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
addIssue(issues, {
|
|
420
|
+
code: "missing_required_field",
|
|
421
|
+
message: "Required field is missing.",
|
|
422
|
+
path: fieldPath
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
for (const field of fields.filter((candidate)=>Boolean(candidate.name))){
|
|
428
|
+
if (normalizedData[field.name] !== undefined) continue;
|
|
429
|
+
if (field.type !== "select" && field.type !== "radio") continue;
|
|
430
|
+
const inferredOptionValue = inferOptionValueFromText({
|
|
431
|
+
field,
|
|
432
|
+
requireFieldMention: true,
|
|
433
|
+
text: inferenceText || label
|
|
434
|
+
});
|
|
435
|
+
if (inferredOptionValue !== undefined) {
|
|
436
|
+
normalizedData[field.name] = inferredOptionValue;
|
|
437
|
+
coercedFields.push(path ? `${path}.${field.name}` : field.name);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return normalizedData;
|
|
441
|
+
};
|
|
442
|
+
const prepareSingleLocaleData = ({ coercedFields, data, fields, inferenceText, issues, label, mode, titleFieldName })=>{
|
|
443
|
+
const normalizedData = normalizeRecordForFields({
|
|
444
|
+
allowSafeFallback: true,
|
|
445
|
+
coercedFields,
|
|
446
|
+
enforceRequiredChildren: false,
|
|
447
|
+
fields,
|
|
448
|
+
inferenceText,
|
|
449
|
+
issues,
|
|
450
|
+
label,
|
|
451
|
+
mode,
|
|
452
|
+
path: "",
|
|
453
|
+
titleFieldName,
|
|
454
|
+
value: data
|
|
455
|
+
});
|
|
456
|
+
return normalizeDataForFields(fields, normalizedData).data;
|
|
457
|
+
};
|
|
458
|
+
export const prepareProposalWriteData = ({ collectionConfig, data, inferenceText, label, localizedData, mode })=>{
|
|
459
|
+
const schemaFields = getSchemaFields(collectionConfig);
|
|
460
|
+
const titleFieldName = typeof collectionConfig?.admin?.useAsTitle === "string" ? collectionConfig.admin.useAsTitle : undefined;
|
|
461
|
+
const issues = [];
|
|
462
|
+
const coercedFields = [];
|
|
463
|
+
if (localizedData) {
|
|
464
|
+
const localeEntries = Object.entries(localizedData);
|
|
465
|
+
if (localeEntries.length === 0) {
|
|
466
|
+
addIssue(issues, {
|
|
467
|
+
code: "empty_localized_data",
|
|
468
|
+
message: "localizedData must contain at least one locale entry.",
|
|
469
|
+
path: "localizedData"
|
|
470
|
+
});
|
|
471
|
+
return {
|
|
472
|
+
coercedFields,
|
|
473
|
+
issues,
|
|
474
|
+
localizedData: {}
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
const normalizedLocalizedData = {};
|
|
478
|
+
for (const [locale, localeValue] of localeEntries){
|
|
479
|
+
if (!isRecord(localeValue)) {
|
|
480
|
+
addIssue(issues, {
|
|
481
|
+
code: "invalid_container",
|
|
482
|
+
message: "Each locale entry must be an object.",
|
|
483
|
+
path: `localizedData.${locale}`
|
|
484
|
+
});
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
const normalizedLocaleData = prepareSingleLocaleData({
|
|
488
|
+
coercedFields,
|
|
489
|
+
data: localeValue,
|
|
490
|
+
fields: schemaFields,
|
|
491
|
+
inferenceText,
|
|
492
|
+
issues,
|
|
493
|
+
label,
|
|
494
|
+
mode,
|
|
495
|
+
titleFieldName
|
|
496
|
+
});
|
|
497
|
+
normalizedLocalizedData[locale] = normalizedLocaleData;
|
|
498
|
+
}
|
|
499
|
+
const [firstLocale] = localeEntries[0] || [];
|
|
500
|
+
if (firstLocale) {
|
|
501
|
+
for (const field of schemaFields.filter((candidate)=>Boolean(candidate.name))){
|
|
502
|
+
if (!field.localized) {
|
|
503
|
+
for (const [locale, localeValue] of Object.entries(normalizedLocalizedData)){
|
|
504
|
+
if (locale === firstLocale) continue;
|
|
505
|
+
if (localeValue[field.name] !== undefined) {
|
|
506
|
+
addIssue(issues, {
|
|
507
|
+
code: "non_localized_field_in_secondary_locale",
|
|
508
|
+
message: "Non-localized fields may only be set in the first locale entry.",
|
|
509
|
+
path: `localizedData.${locale}.${field.name}`
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
const normalizedResult = {};
|
|
517
|
+
for (const [locale, localeValue] of Object.entries(normalizedLocalizedData)){
|
|
518
|
+
normalizedResult[locale] = normalizeAuthData(collectionConfig, {
|
|
519
|
+
coercedFields: [],
|
|
520
|
+
data: localeValue,
|
|
521
|
+
droppedFields: []
|
|
522
|
+
}).data;
|
|
523
|
+
}
|
|
524
|
+
return {
|
|
525
|
+
coercedFields,
|
|
526
|
+
issues,
|
|
527
|
+
localizedData: normalizedResult
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
const sourceData = data || {};
|
|
531
|
+
const normalizedData = prepareSingleLocaleData({
|
|
532
|
+
coercedFields,
|
|
533
|
+
data: sourceData,
|
|
534
|
+
fields: schemaFields,
|
|
535
|
+
inferenceText,
|
|
536
|
+
issues,
|
|
537
|
+
label,
|
|
538
|
+
mode,
|
|
539
|
+
titleFieldName
|
|
540
|
+
});
|
|
541
|
+
const authNormalized = normalizeAuthData(collectionConfig, {
|
|
542
|
+
coercedFields: [],
|
|
543
|
+
data: normalizedData,
|
|
544
|
+
droppedFields: []
|
|
545
|
+
});
|
|
546
|
+
coercedFields.push(...authNormalized.coercedFields);
|
|
547
|
+
return {
|
|
548
|
+
coercedFields,
|
|
549
|
+
data: authNormalized.data,
|
|
550
|
+
issues
|
|
551
|
+
};
|
|
552
|
+
};
|
|
553
|
+
export const applyLocalizedRequiredFallbackToPreparedData = ({ fallbackSource, fields, preparedData })=>{
|
|
554
|
+
const mergedData = {
|
|
555
|
+
...preparedData
|
|
556
|
+
};
|
|
557
|
+
for (const field of fields.filter((candidate)=>Boolean(candidate.name))){
|
|
558
|
+
if (field.localized && field.required && mergedData[field.name] === undefined && fallbackSource[field.name] !== undefined) {
|
|
559
|
+
mergedData[field.name] = fallbackSource[field.name];
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
if (field.type === "group" && field.fields?.length && isRecord(fallbackSource[field.name])) {
|
|
563
|
+
const currentValue = isRecord(mergedData[field.name]) ? mergedData[field.name] : {};
|
|
564
|
+
const nestedValue = applyLocalizedRequiredFallbackToPreparedData({
|
|
565
|
+
fallbackSource: fallbackSource[field.name],
|
|
566
|
+
fields: field.fields,
|
|
567
|
+
preparedData: currentValue
|
|
568
|
+
});
|
|
569
|
+
if (Object.keys(nestedValue).length > 0) {
|
|
570
|
+
mergedData[field.name] = nestedValue;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return mergedData;
|
|
575
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { PayloadHandler } from "payload";
|
|
2
2
|
import { type ResolvedCollectionPermissionMap } from "./collectionPermissions.js";
|
|
3
|
+
import { type CollectionConfig as NormalizeCollectionConfig } from "./normalizeData.js";
|
|
3
4
|
export type ChatMention = {
|
|
4
5
|
collection?: string;
|
|
5
6
|
id?: string;
|
|
@@ -9,9 +10,7 @@ export type ChatMention = {
|
|
|
9
10
|
type?: "block" | "collection" | "doc" | "global" | "locale";
|
|
10
11
|
};
|
|
11
12
|
export type FieldConfig = {
|
|
12
|
-
admin?:
|
|
13
|
-
condition?: unknown;
|
|
14
|
-
};
|
|
13
|
+
admin?: Record<string, unknown>;
|
|
15
14
|
blocks?: BlockConfig[];
|
|
16
15
|
defaultValue?: unknown;
|
|
17
16
|
fields?: FieldConfig[];
|
|
@@ -35,9 +34,6 @@ type BlockConfig = {
|
|
|
35
34
|
};
|
|
36
35
|
slug: string;
|
|
37
36
|
};
|
|
38
|
-
type VersionConfig = boolean | {
|
|
39
|
-
drafts?: boolean | Record<string, unknown>;
|
|
40
|
-
};
|
|
41
37
|
type CollectionLikeConfig = {
|
|
42
38
|
admin?: {
|
|
43
39
|
useAsTitle?: string;
|
|
@@ -51,7 +47,7 @@ type CollectionLikeConfig = {
|
|
|
51
47
|
singular?: unknown;
|
|
52
48
|
};
|
|
53
49
|
slug: string;
|
|
54
|
-
versions?:
|
|
50
|
+
versions?: NormalizeCollectionConfig["versions"];
|
|
55
51
|
};
|
|
56
52
|
type MentionContext = {
|
|
57
53
|
blockContexts: (Record<string, unknown> & {
|