strapi-relation-names 1.2.1 → 1.3.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/README.md +17 -42
- package/dist/{admin/SettingsPage-BebR4ghK.js → _chunks/SettingsPage-BMwx-BuP.js} +122 -48
- package/dist/{admin/SettingsPage-DFZP5CZs.mjs → _chunks/SettingsPage-D8leItKF.mjs} +122 -48
- package/dist/{admin/index-CiS6cuI9.mjs → _chunks/index-BeLPoAiU.mjs} +1 -1
- package/dist/{admin/index-DfSAe961.js → _chunks/index-COuIED98.js} +1 -1
- package/dist/admin/index.js +2 -3
- package/dist/admin/index.mjs +1 -1
- package/dist/admin/src/index.d.ts +1 -1
- package/dist/admin/src/pages/SettingsPage.d.ts +1 -0
- package/dist/admin/src/utils/template.d.ts +1 -14
- package/dist/server/index.js +279 -130
- package/dist/server/index.mjs +278 -128
- package/dist/server/src/bootstrap.d.ts +1 -1
- package/dist/server/src/config/index.d.ts +2 -0
- package/dist/server/src/controllers/index.d.ts +1 -1
- package/dist/server/src/controllers/settings.d.ts +1 -1
- package/dist/server/src/destroy.d.ts +1 -1
- package/dist/server/src/index.d.ts +20 -11
- package/dist/server/src/register.d.ts +1 -1
- package/dist/server/src/services/index.d.ts +8 -7
- package/dist/server/src/services/relation-labels.d.ts +6 -9
- package/dist/server/src/services/settings.d.ts +2 -2
- package/dist/server/src/services/utils/relation-hydration.d.ts +7 -0
- package/dist/server/src/services/utils/relation-labels.d.ts +4 -22
- package/dist/server/src/services/utils/relation-runtime.d.ts +6 -0
- package/dist/server/src/services/utils/relation-values.d.ts +1 -1
- package/dist/shared/records.d.ts +1 -0
- package/dist/shared/template.d.ts +55 -0
- package/package.json +28 -28
- /package/dist/{admin → _chunks}/de-Bc81LkbY.mjs +0 -0
- /package/dist/{admin → _chunks}/de-CsBvOsWR.js +0 -0
- /package/dist/{admin → _chunks}/en-Bk-HIXNd.mjs +0 -0
- /package/dist/{admin → _chunks}/en-DT4g6X2H.js +0 -0
package/dist/server/index.mjs
CHANGED
|
@@ -2,9 +2,9 @@ const bootstrap = ({ strapi }) => {
|
|
|
2
2
|
};
|
|
3
3
|
const destroy = ({ strapi }) => {
|
|
4
4
|
};
|
|
5
|
-
const PLUGIN_ID
|
|
5
|
+
const PLUGIN_ID = "strapi-relation-names";
|
|
6
6
|
const register = ({ strapi }) => {
|
|
7
|
-
const relationLabels2 = strapi.plugin(PLUGIN_ID
|
|
7
|
+
const relationLabels2 = strapi.plugin(PLUGIN_ID).service("relation-labels");
|
|
8
8
|
const controllers2 = strapi.get("controllers");
|
|
9
9
|
controllers2.extend("plugin::content-manager.relations", (controller) => ({
|
|
10
10
|
...controller,
|
|
@@ -56,6 +56,7 @@ const register = ({ strapi }) => {
|
|
|
56
56
|
await decorateConfiguration(ctx, ctx.params.uid);
|
|
57
57
|
},
|
|
58
58
|
async updateContentTypeConfiguration(ctx, next) {
|
|
59
|
+
ctx.request.body = relationLabels2.sanitizeConfigurationUpdate(ctx.request.body);
|
|
59
60
|
await controller.updateContentTypeConfiguration(ctx, next);
|
|
60
61
|
await decorateConfiguration(ctx, ctx.params.uid);
|
|
61
62
|
}
|
|
@@ -72,12 +73,28 @@ const register = ({ strapi }) => {
|
|
|
72
73
|
}
|
|
73
74
|
}));
|
|
74
75
|
};
|
|
76
|
+
const isOptionalString = (value) => value === void 0 || typeof value === "string";
|
|
75
77
|
const validateConfig = (config2) => {
|
|
76
|
-
if (!config2 || typeof config2 !== "object" || Array.isArray(config2)
|
|
77
|
-
(
|
|
78
|
-
|
|
78
|
+
if (!config2 || typeof config2 !== "object" || Array.isArray(config2)) {
|
|
79
|
+
throw new Error("The collections config must be an array of strings");
|
|
80
|
+
}
|
|
81
|
+
const typedConfig = config2;
|
|
82
|
+
if (!Array.isArray(typedConfig.collections) || !typedConfig.collections.every((collection) => typeof collection === "string")) {
|
|
79
83
|
throw new Error("The collections config must be an array of strings");
|
|
80
84
|
}
|
|
85
|
+
if (!isOptionalString(typedConfig.locale)) {
|
|
86
|
+
throw new Error("The locale config must be a string");
|
|
87
|
+
}
|
|
88
|
+
if (!isOptionalString(typedConfig.timeZone)) {
|
|
89
|
+
throw new Error("The timeZone config must be a string");
|
|
90
|
+
}
|
|
91
|
+
if (typedConfig.timeZone) {
|
|
92
|
+
try {
|
|
93
|
+
new Intl.DateTimeFormat(void 0, { timeZone: typedConfig.timeZone });
|
|
94
|
+
} catch {
|
|
95
|
+
throw new Error("The timeZone config must be a valid IANA timezone");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
81
98
|
};
|
|
82
99
|
const config = {
|
|
83
100
|
default: {
|
|
@@ -88,7 +105,7 @@ const config = {
|
|
|
88
105
|
const contentTypes = {};
|
|
89
106
|
const settings$1 = ({ strapi }) => ({
|
|
90
107
|
async find(ctx) {
|
|
91
|
-
const data = await strapi.plugin(
|
|
108
|
+
const data = await strapi.plugin(PLUGIN_ID).service("settings").get();
|
|
92
109
|
ctx.body = { data };
|
|
93
110
|
},
|
|
94
111
|
async update(ctx) {
|
|
@@ -96,7 +113,7 @@ const settings$1 = ({ strapi }) => ({
|
|
|
96
113
|
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
97
114
|
return ctx.badRequest("Settings must be an object");
|
|
98
115
|
}
|
|
99
|
-
const data = await strapi.plugin(
|
|
116
|
+
const data = await strapi.plugin(PLUGIN_ID).service("settings").set(body);
|
|
100
117
|
ctx.body = { data };
|
|
101
118
|
}
|
|
102
119
|
});
|
|
@@ -134,7 +151,8 @@ const routes = {
|
|
|
134
151
|
"content-api": contentAPIRoutes,
|
|
135
152
|
admin: adminAPIRoutes
|
|
136
153
|
};
|
|
137
|
-
const
|
|
154
|
+
const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
155
|
+
const FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
138
156
|
"string",
|
|
139
157
|
"text",
|
|
140
158
|
"email",
|
|
@@ -149,30 +167,81 @@ const SCALAR_TYPES = /* @__PURE__ */ new Set([
|
|
|
149
167
|
"time",
|
|
150
168
|
"boolean"
|
|
151
169
|
]);
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
"
|
|
155
|
-
"
|
|
156
|
-
"
|
|
157
|
-
"
|
|
158
|
-
"
|
|
159
|
-
"
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const
|
|
170
|
+
const DATE_TYPES = /* @__PURE__ */ new Set(["date", "datetime"]);
|
|
171
|
+
const SYSTEM_SCALAR_TYPES = {
|
|
172
|
+
id: "integer",
|
|
173
|
+
documentId: "string",
|
|
174
|
+
createdAt: "datetime",
|
|
175
|
+
updatedAt: "datetime",
|
|
176
|
+
publishedAt: "datetime",
|
|
177
|
+
locale: "string",
|
|
178
|
+
status: "string"
|
|
179
|
+
};
|
|
180
|
+
const IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
181
|
+
const PATH_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/;
|
|
182
|
+
const getNestedSchemaUid = (attribute) => {
|
|
183
|
+
if (attribute.type === "component") {
|
|
184
|
+
return attribute.component;
|
|
185
|
+
}
|
|
186
|
+
if (attribute.type === "relation") {
|
|
187
|
+
return attribute.targetModel ?? attribute.target;
|
|
188
|
+
}
|
|
189
|
+
return void 0;
|
|
190
|
+
};
|
|
191
|
+
const getDate = (value) => {
|
|
192
|
+
if (value instanceof Date) {
|
|
193
|
+
return Number.isNaN(value.getTime()) ? void 0 : value;
|
|
194
|
+
}
|
|
195
|
+
if (typeof value !== "string" && typeof value !== "number") {
|
|
196
|
+
return void 0;
|
|
197
|
+
}
|
|
198
|
+
const date = new Date(value);
|
|
199
|
+
return Number.isNaN(date.getTime()) ? void 0 : date;
|
|
200
|
+
};
|
|
201
|
+
const isDateOnly = (value) => typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value);
|
|
202
|
+
const formatDate = (value, options, context, preserveDateOnly) => {
|
|
203
|
+
const date = getDate(value);
|
|
204
|
+
if (!date) {
|
|
205
|
+
return "";
|
|
206
|
+
}
|
|
207
|
+
const timeZone = preserveDateOnly && isDateOnly(value) ? "UTC" : context.timeZone;
|
|
208
|
+
return new Intl.DateTimeFormat(context.locale, {
|
|
209
|
+
...options,
|
|
210
|
+
...timeZone ? { timeZone } : {}
|
|
211
|
+
}).format(date);
|
|
212
|
+
};
|
|
213
|
+
const TRANSFORMS = {
|
|
214
|
+
toLocalDate: {
|
|
215
|
+
accepts: (fieldType) => DATE_TYPES.has(fieldType ?? ""),
|
|
216
|
+
apply: (value, context) => formatDate(value, { dateStyle: "short" }, context, true)
|
|
217
|
+
},
|
|
218
|
+
toLocalDateTime: {
|
|
219
|
+
accepts: (fieldType) => fieldType === "datetime",
|
|
220
|
+
apply: (value, context) => formatDate(value, { dateStyle: "short", timeStyle: "short" }, context, false)
|
|
221
|
+
}
|
|
222
|
+
};
|
|
163
223
|
const getPathValue = (value, path) => {
|
|
164
224
|
return path.split(".").reduce((current, segment) => {
|
|
165
225
|
if (Array.isArray(current)) {
|
|
166
|
-
return current.map((item) => isRecord
|
|
226
|
+
return current.map((item) => isRecord(item) ? item[segment] : void 0).filter((item) => item !== void 0);
|
|
167
227
|
}
|
|
168
|
-
return isRecord
|
|
228
|
+
return isRecord(current) ? current[segment] : void 0;
|
|
169
229
|
}, value);
|
|
170
230
|
};
|
|
171
|
-
const
|
|
231
|
+
const parseExpression = (value) => {
|
|
232
|
+
const parts = value.split("|").map((part) => part.trim());
|
|
233
|
+
const [path, ...transforms] = parts;
|
|
234
|
+
if (!path || !PATH_PATTERN.test(path) || transforms.some((name) => !IDENTIFIER_PATTERN.test(name))) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
return { path, transforms };
|
|
238
|
+
};
|
|
239
|
+
const parseTemplateAst = (template) => {
|
|
172
240
|
if (!template.trim()) {
|
|
173
241
|
return null;
|
|
174
242
|
}
|
|
175
|
-
const
|
|
243
|
+
const tokens = [];
|
|
244
|
+
const expressions = [];
|
|
176
245
|
let cursor = 0;
|
|
177
246
|
while (cursor < template.length) {
|
|
178
247
|
const openingBrace = template.indexOf("{", cursor);
|
|
@@ -181,69 +250,98 @@ const parseTemplate = (template) => {
|
|
|
181
250
|
return null;
|
|
182
251
|
}
|
|
183
252
|
if (openingBrace === -1) {
|
|
253
|
+
tokens.push({ type: "text", value: template.slice(cursor) });
|
|
184
254
|
break;
|
|
185
255
|
}
|
|
256
|
+
if (openingBrace > cursor) {
|
|
257
|
+
tokens.push({ type: "text", value: template.slice(cursor, openingBrace) });
|
|
258
|
+
}
|
|
186
259
|
const end = template.indexOf("}", openingBrace + 1);
|
|
187
260
|
if (end === -1) {
|
|
188
261
|
return null;
|
|
189
262
|
}
|
|
190
|
-
const
|
|
191
|
-
if (!
|
|
263
|
+
const expression = parseExpression(template.slice(openingBrace + 1, end));
|
|
264
|
+
if (!expression) {
|
|
192
265
|
return null;
|
|
193
266
|
}
|
|
194
|
-
|
|
267
|
+
tokens.push({ type: "expression", expression });
|
|
268
|
+
expressions.push(expression);
|
|
195
269
|
cursor = end + 1;
|
|
196
270
|
}
|
|
197
|
-
return
|
|
271
|
+
return expressions.length > 0 ? { tokens, expressions } : null;
|
|
272
|
+
};
|
|
273
|
+
const parseTemplate = (template) => {
|
|
274
|
+
const ast = parseTemplateAst(template);
|
|
275
|
+
return ast?.expressions.map(({ path }) => path) ?? null;
|
|
276
|
+
};
|
|
277
|
+
const resolvePathType = (path, targetSchema, resolveSchema) => {
|
|
278
|
+
const segments = path.split(".");
|
|
279
|
+
let schema = targetSchema;
|
|
280
|
+
for (const [index2, segment] of segments.entries()) {
|
|
281
|
+
const isLastSegment = index2 === segments.length - 1;
|
|
282
|
+
if (index2 === 0 && isLastSegment && SYSTEM_SCALAR_TYPES[segment]) {
|
|
283
|
+
return SYSTEM_SCALAR_TYPES[segment];
|
|
284
|
+
}
|
|
285
|
+
const attribute = schema.attributes?.[segment];
|
|
286
|
+
if (!attribute || attribute.private === true) {
|
|
287
|
+
return void 0;
|
|
288
|
+
}
|
|
289
|
+
if (isLastSegment) {
|
|
290
|
+
return FIELD_TYPES.has(attribute.type ?? "") ? attribute.type : void 0;
|
|
291
|
+
}
|
|
292
|
+
const nestedSchemaUid = getNestedSchemaUid(attribute);
|
|
293
|
+
if (!nestedSchemaUid || !resolveSchema) {
|
|
294
|
+
return void 0;
|
|
295
|
+
}
|
|
296
|
+
const nestedSchema = resolveSchema(nestedSchemaUid);
|
|
297
|
+
if (!nestedSchema) {
|
|
298
|
+
return void 0;
|
|
299
|
+
}
|
|
300
|
+
schema = nestedSchema;
|
|
301
|
+
}
|
|
302
|
+
return void 0;
|
|
198
303
|
};
|
|
199
304
|
const compileTemplate = (template, targetSchema, resolveSchema) => {
|
|
200
|
-
const
|
|
201
|
-
if (!
|
|
305
|
+
const ast = parseTemplateAst(template);
|
|
306
|
+
if (!ast || !targetSchema) {
|
|
202
307
|
return null;
|
|
203
308
|
}
|
|
204
|
-
const valid =
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
for (const [index2, segment] of segments.entries()) {
|
|
208
|
-
const attribute = schema.attributes?.[segment];
|
|
209
|
-
const isLastSegment = index2 === segments.length - 1;
|
|
210
|
-
if (index2 === 0 && isLastSegment && SYSTEM_SCALAR_FIELDS.has(segment)) {
|
|
211
|
-
return true;
|
|
212
|
-
}
|
|
213
|
-
if (!attribute || attribute.private === true) {
|
|
214
|
-
return false;
|
|
215
|
-
}
|
|
216
|
-
if (isLastSegment) {
|
|
217
|
-
return SCALAR_TYPES.has(attribute.type ?? "");
|
|
218
|
-
}
|
|
219
|
-
const nestedSchemaUid = attribute.type === "component" ? attribute.component : attribute.type === "relation" ? attribute.target : void 0;
|
|
220
|
-
if (!nestedSchemaUid || !resolveSchema) {
|
|
221
|
-
return false;
|
|
222
|
-
}
|
|
223
|
-
schema = resolveSchema(nestedSchemaUid);
|
|
224
|
-
if (!schema) {
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
return false;
|
|
309
|
+
const valid = ast.expressions.every(({ path, transforms }) => {
|
|
310
|
+
const fieldType = resolvePathType(path, targetSchema, resolveSchema);
|
|
311
|
+
return Boolean(fieldType && transforms.every((name) => TRANSFORMS[name]?.accepts(fieldType)));
|
|
229
312
|
});
|
|
230
313
|
if (!valid) {
|
|
231
314
|
return null;
|
|
232
315
|
}
|
|
233
316
|
return {
|
|
317
|
+
...ast,
|
|
234
318
|
template,
|
|
235
|
-
placeholders,
|
|
236
|
-
displayField:
|
|
319
|
+
placeholders: ast.expressions.map(({ path }) => path),
|
|
320
|
+
displayField: ast.expressions[0].path.split(".").slice(-1)[0] ?? ast.expressions[0].path
|
|
237
321
|
};
|
|
238
322
|
};
|
|
239
|
-
const
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
323
|
+
const applyTransform = (value, name, context) => {
|
|
324
|
+
const transform = TRANSFORMS[name];
|
|
325
|
+
if (!transform) {
|
|
326
|
+
return "";
|
|
327
|
+
}
|
|
328
|
+
if (Array.isArray(value)) {
|
|
329
|
+
return value.map((item) => applyTransform(item, name, context));
|
|
330
|
+
}
|
|
331
|
+
return transform.apply(value, context);
|
|
332
|
+
};
|
|
333
|
+
const renderTemplate = (compiled, values, context = compiled.transformContext ?? {}) => {
|
|
334
|
+
const tokens = compiled.tokens ?? parseTemplateAst(compiled.template)?.tokens ?? [];
|
|
335
|
+
const rendered = tokens.map((token) => {
|
|
336
|
+
if (token.type === "text") {
|
|
337
|
+
return token.value;
|
|
338
|
+
}
|
|
339
|
+
const expressionValue = token.expression.transforms.reduce(
|
|
340
|
+
(value, name) => applyTransform(value, name, context),
|
|
341
|
+
getPathValue(values, token.expression.path)
|
|
342
|
+
);
|
|
343
|
+
return expressionValue === null || expressionValue === void 0 ? "" : String(expressionValue);
|
|
344
|
+
}).join("");
|
|
247
345
|
return rendered.trim();
|
|
248
346
|
};
|
|
249
347
|
const getIdentity = (value, modelType) => modelType === "component" ? value.id : value.documentId ?? value.id;
|
|
@@ -265,6 +363,11 @@ const matchesRelation = (source, candidate, targetModelType) => {
|
|
|
265
363
|
const applyLabel = (value, hydrated, runtime) => {
|
|
266
364
|
const label = renderTemplate(runtime, hydrated);
|
|
267
365
|
if (label) {
|
|
366
|
+
const isNestedTemplate = runtime.placeholders.some((placeholder) => placeholder.includes("."));
|
|
367
|
+
const isIdentityField = runtime.displayField === "id" || runtime.displayField === "documentId";
|
|
368
|
+
if (isNestedTemplate && isIdentityField) {
|
|
369
|
+
return { ...value, label };
|
|
370
|
+
}
|
|
268
371
|
return { ...value, [runtime.displayField]: label };
|
|
269
372
|
}
|
|
270
373
|
if (runtime.displayField === runtime.originalMainField) {
|
|
@@ -276,60 +379,15 @@ const applyLabel = (value, hydrated, runtime) => {
|
|
|
276
379
|
};
|
|
277
380
|
const relationValues = (value) => {
|
|
278
381
|
if (Array.isArray(value)) {
|
|
279
|
-
return value.filter(isRecord
|
|
382
|
+
return value.filter(isRecord);
|
|
280
383
|
}
|
|
281
|
-
return isRecord
|
|
384
|
+
return isRecord(value) ? [value] : [];
|
|
282
385
|
};
|
|
283
386
|
const replaceRelationValue = (original, values, hydrated, runtime) => {
|
|
284
387
|
const decorated = values.map((value) => applyLabel(value, hydrated.get(value) ?? value, runtime));
|
|
285
388
|
return Array.isArray(original) ? decorated : decorated[0] ?? original;
|
|
286
389
|
};
|
|
287
390
|
const isCollectionEnabled = (collections, uid) => collections.length === 0 || collections.includes(uid);
|
|
288
|
-
const getOriginalMainField = async (strapi, sourceSchema, targetSchema, targetField, userAbility) => {
|
|
289
|
-
const serviceName = sourceSchema.modelType === "component" ? "components" : "content-types";
|
|
290
|
-
const configuration = await strapi.plugin("content-manager").service(serviceName).findConfiguration(sourceSchema);
|
|
291
|
-
const configuredMainField = configuration?.metadatas?.[targetField]?.edit?.mainField;
|
|
292
|
-
if (typeof configuredMainField !== "string" || configuredMainField === "documentId" || configuredMainField === "id") {
|
|
293
|
-
return configuredMainField === "documentId" ? "documentId" : "id";
|
|
294
|
-
}
|
|
295
|
-
const permissionChecker = strapi.plugin("content-manager").service("permission-checker").create({ userAbility, model: targetSchema.uid });
|
|
296
|
-
const attribute = targetSchema.attributes?.[configuredMainField];
|
|
297
|
-
if (!attribute || attribute.private === true || permissionChecker.cannot.read(null, configuredMainField)) {
|
|
298
|
-
return "documentId";
|
|
299
|
-
}
|
|
300
|
-
return configuredMainField;
|
|
301
|
-
};
|
|
302
|
-
const getRuntime = async (strapi, settings2, sourceUid, targetField, userAbility) => {
|
|
303
|
-
const sourceSchema = strapi.getModel(sourceUid);
|
|
304
|
-
const attribute = sourceSchema?.attributes?.[targetField];
|
|
305
|
-
if (!sourceSchema || !attribute || attribute.type !== "relation" || !attribute.target) {
|
|
306
|
-
return null;
|
|
307
|
-
}
|
|
308
|
-
const template = settings2.relations[sourceUid]?.[targetField];
|
|
309
|
-
const targetSchema = strapi.getModel(attribute.target);
|
|
310
|
-
const compiled = typeof template === "string" ? compileTemplate(
|
|
311
|
-
template,
|
|
312
|
-
targetSchema,
|
|
313
|
-
(uid) => strapi.getModel(uid)
|
|
314
|
-
) : null;
|
|
315
|
-
if (!compiled || !targetSchema) {
|
|
316
|
-
return null;
|
|
317
|
-
}
|
|
318
|
-
const originalMainField = await getOriginalMainField(
|
|
319
|
-
strapi,
|
|
320
|
-
sourceSchema,
|
|
321
|
-
targetSchema,
|
|
322
|
-
targetField,
|
|
323
|
-
userAbility
|
|
324
|
-
);
|
|
325
|
-
return {
|
|
326
|
-
...compiled,
|
|
327
|
-
displayField: compiled.placeholders.some((placeholder) => placeholder.includes(".")) ? originalMainField : compiled.displayField,
|
|
328
|
-
sourceUid,
|
|
329
|
-
targetUid: targetSchema.uid,
|
|
330
|
-
originalMainField
|
|
331
|
-
};
|
|
332
|
-
};
|
|
333
391
|
const buildPopulate = (placeholders) => {
|
|
334
392
|
const populate = {};
|
|
335
393
|
for (const placeholder of placeholders) {
|
|
@@ -402,8 +460,61 @@ const hydrateValues = async (strapi, ctx, runtime, values) => {
|
|
|
402
460
|
return new Map(values.map((value) => [value, value]));
|
|
403
461
|
}
|
|
404
462
|
};
|
|
463
|
+
const getOriginalMainField = async (strapi, sourceSchema, targetSchema, targetField, userAbility) => {
|
|
464
|
+
const serviceName = sourceSchema.modelType === "component" ? "components" : "content-types";
|
|
465
|
+
const configuration = await strapi.plugin("content-manager").service(serviceName).findConfiguration(sourceSchema);
|
|
466
|
+
const configuredMainField = configuration?.metadatas?.[targetField]?.edit?.mainField;
|
|
467
|
+
if (typeof configuredMainField !== "string" || configuredMainField === "documentId" || configuredMainField === "id") {
|
|
468
|
+
return configuredMainField === "documentId" ? "documentId" : "id";
|
|
469
|
+
}
|
|
470
|
+
const permissionChecker = strapi.plugin("content-manager").service("permission-checker").create({ userAbility, model: targetSchema.uid });
|
|
471
|
+
const attribute = targetSchema.attributes?.[configuredMainField];
|
|
472
|
+
if (!attribute || attribute.private === true || permissionChecker.cannot.read(null, configuredMainField)) {
|
|
473
|
+
return "documentId";
|
|
474
|
+
}
|
|
475
|
+
return configuredMainField;
|
|
476
|
+
};
|
|
477
|
+
const getRuntime = async (strapi, settings2, sourceUid, targetField, userAbility, transformContext) => {
|
|
478
|
+
const sourceSchema = strapi.getModel(sourceUid);
|
|
479
|
+
const attribute = sourceSchema?.attributes?.[targetField];
|
|
480
|
+
if (!sourceSchema || !attribute || attribute.type !== "relation" || !attribute.target) {
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
const template = settings2.relations[sourceUid]?.[targetField];
|
|
484
|
+
const targetSchema = strapi.getModel(attribute.target);
|
|
485
|
+
const compiled = typeof template === "string" ? compileTemplate(
|
|
486
|
+
template,
|
|
487
|
+
targetSchema,
|
|
488
|
+
(uid) => strapi.getModel(uid)
|
|
489
|
+
) : null;
|
|
490
|
+
if (!compiled || !targetSchema) {
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
const originalMainField = await getOriginalMainField(
|
|
494
|
+
strapi,
|
|
495
|
+
sourceSchema,
|
|
496
|
+
targetSchema,
|
|
497
|
+
targetField,
|
|
498
|
+
userAbility
|
|
499
|
+
);
|
|
500
|
+
return {
|
|
501
|
+
...compiled,
|
|
502
|
+
transformContext,
|
|
503
|
+
displayField: compiled.placeholders.some((placeholder) => placeholder.includes(".")) ? originalMainField : compiled.displayField,
|
|
504
|
+
sourceUid,
|
|
505
|
+
targetUid: targetSchema.uid,
|
|
506
|
+
originalMainField
|
|
507
|
+
};
|
|
508
|
+
};
|
|
509
|
+
const getTransformContext = (strapi) => {
|
|
510
|
+
const config2 = strapi.config.get("plugin::strapi-relation-names");
|
|
511
|
+
return {
|
|
512
|
+
locale: typeof config2?.locale === "string" ? config2.locale : void 0,
|
|
513
|
+
timeZone: typeof config2?.timeZone === "string" ? config2.timeZone : void 0
|
|
514
|
+
};
|
|
515
|
+
};
|
|
405
516
|
const relationLabels = ({ strapi }) => {
|
|
406
|
-
const getSettings = () => strapi.plugin(
|
|
517
|
+
const getSettings = () => strapi.plugin(PLUGIN_ID).service("settings").get();
|
|
407
518
|
const decorateRelationResults = async (ctx, sourceUid, targetField, results) => {
|
|
408
519
|
const settings2 = await getSettings();
|
|
409
520
|
if (!isCollectionEnabled(settings2.collections, sourceUid)) {
|
|
@@ -414,26 +525,27 @@ const relationLabels = ({ strapi }) => {
|
|
|
414
525
|
settings2,
|
|
415
526
|
sourceUid,
|
|
416
527
|
targetField,
|
|
417
|
-
ctx.state.userAbility
|
|
528
|
+
ctx.state.userAbility,
|
|
529
|
+
getTransformContext(strapi)
|
|
418
530
|
);
|
|
419
531
|
if (!runtime) {
|
|
420
532
|
return results;
|
|
421
533
|
}
|
|
422
|
-
const values = results.filter(isRecord
|
|
534
|
+
const values = results.filter(isRecord);
|
|
423
535
|
const hydrated = await hydrateValues(strapi, ctx, runtime, values);
|
|
424
536
|
return results.map(
|
|
425
|
-
(result) => isRecord
|
|
537
|
+
(result) => isRecord(result) ? applyLabel(result, hydrated.get(result) ?? result, runtime) : result
|
|
426
538
|
);
|
|
427
539
|
};
|
|
428
540
|
const decorateConfiguration = async (configuration, sourceUid) => {
|
|
429
|
-
if (!configuration || !isRecord
|
|
541
|
+
if (!configuration || !isRecord(configuration.metadatas)) {
|
|
430
542
|
return configuration;
|
|
431
543
|
}
|
|
432
544
|
const settings2 = await getSettings();
|
|
433
545
|
const metadatas = { ...configuration.metadatas };
|
|
434
546
|
const relationNames = {};
|
|
547
|
+
const sourceSchema = strapi.getModel(sourceUid);
|
|
435
548
|
for (const [fieldName, metadata] of Object.entries(metadatas)) {
|
|
436
|
-
const sourceSchema = strapi.getModel(sourceUid);
|
|
437
549
|
const attribute = sourceSchema?.attributes?.[fieldName];
|
|
438
550
|
if (!attribute || attribute.type !== "relation" || !attribute.target) {
|
|
439
551
|
continue;
|
|
@@ -444,12 +556,16 @@ const relationLabels = ({ strapi }) => {
|
|
|
444
556
|
targetSchema,
|
|
445
557
|
(uid) => strapi.getModel(uid)
|
|
446
558
|
);
|
|
447
|
-
if (!compiled || !isRecord
|
|
559
|
+
if (!compiled || !isRecord(metadata)) {
|
|
448
560
|
continue;
|
|
449
561
|
}
|
|
450
|
-
const editMetadata = isRecord
|
|
562
|
+
const editMetadata = isRecord(metadata.edit) ? metadata.edit : {};
|
|
451
563
|
const configuredMainField = typeof editMetadata.mainField === "string" ? editMetadata.mainField : "id";
|
|
452
|
-
const
|
|
564
|
+
const isNestedTemplate = compiled.placeholders.some(
|
|
565
|
+
(placeholder) => placeholder.includes(".")
|
|
566
|
+
);
|
|
567
|
+
const isIdentityField = configuredMainField === "id" || configuredMainField === "documentId";
|
|
568
|
+
const displayField = isNestedTemplate && isIdentityField ? "label" : isNestedTemplate ? configuredMainField : compiled.displayField;
|
|
453
569
|
metadatas[fieldName] = {
|
|
454
570
|
...metadata,
|
|
455
571
|
edit: {
|
|
@@ -457,7 +573,7 @@ const relationLabels = ({ strapi }) => {
|
|
|
457
573
|
mainField: displayField
|
|
458
574
|
},
|
|
459
575
|
list: {
|
|
460
|
-
...isRecord
|
|
576
|
+
...isRecord(metadata.list) ? metadata.list : {},
|
|
461
577
|
mainField: displayField
|
|
462
578
|
}
|
|
463
579
|
};
|
|
@@ -467,13 +583,13 @@ const relationLabels = ({ strapi }) => {
|
|
|
467
583
|
...configuration,
|
|
468
584
|
metadatas,
|
|
469
585
|
settings: {
|
|
470
|
-
...isRecord
|
|
586
|
+
...isRecord(configuration.settings) ? configuration.settings : {},
|
|
471
587
|
relationNames
|
|
472
588
|
}
|
|
473
589
|
};
|
|
474
590
|
};
|
|
475
591
|
const decorateContentManagerConfiguration = async (data, sourceUid) => {
|
|
476
|
-
if (!isRecord
|
|
592
|
+
if (!isRecord(data)) {
|
|
477
593
|
return data;
|
|
478
594
|
}
|
|
479
595
|
const settings2 = await getSettings();
|
|
@@ -487,7 +603,7 @@ const relationLabels = ({ strapi }) => {
|
|
|
487
603
|
if (next.component) {
|
|
488
604
|
next.component = await decorateConfiguration(next.component, sourceUid);
|
|
489
605
|
}
|
|
490
|
-
if (isRecord
|
|
606
|
+
if (isRecord(next.components)) {
|
|
491
607
|
const components = { ...next.components };
|
|
492
608
|
for (const [uid, configuration] of Object.entries(components)) {
|
|
493
609
|
components[uid] = await decorateConfiguration(configuration, uid);
|
|
@@ -496,6 +612,33 @@ const relationLabels = ({ strapi }) => {
|
|
|
496
612
|
}
|
|
497
613
|
return next;
|
|
498
614
|
};
|
|
615
|
+
const sanitizeConfigurationUpdate = (configuration) => {
|
|
616
|
+
if (!isRecord(configuration) || !isRecord(configuration.metadatas)) {
|
|
617
|
+
return configuration;
|
|
618
|
+
}
|
|
619
|
+
const configurationSettings = isRecord(configuration.settings) ? configuration.settings : {};
|
|
620
|
+
const relationNames = isRecord(configurationSettings.relationNames) ? configurationSettings.relationNames : {};
|
|
621
|
+
const { relationNames: _relationNames, ...sanitizedSettings } = configurationSettings;
|
|
622
|
+
const metadatas = { ...configuration.metadatas };
|
|
623
|
+
for (const [fieldName, metadata] of Object.entries(metadatas)) {
|
|
624
|
+
const relationName = relationNames[fieldName];
|
|
625
|
+
if (!isRecord(relationName) || relationName.mainField !== "label" || !isRecord(metadata) || !isRecord(metadata.edit) || metadata.edit.mainField !== "label") {
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
metadatas[fieldName] = {
|
|
629
|
+
...metadata,
|
|
630
|
+
edit: {
|
|
631
|
+
...metadata.edit,
|
|
632
|
+
mainField: "id"
|
|
633
|
+
},
|
|
634
|
+
list: {
|
|
635
|
+
...isRecord(metadata.list) ? metadata.list : {},
|
|
636
|
+
mainField: "id"
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
return { ...configuration, settings: sanitizedSettings, metadatas };
|
|
641
|
+
};
|
|
499
642
|
const decorateCollectionResults = async (ctx, sourceUid, results) => {
|
|
500
643
|
const settings2 = await getSettings();
|
|
501
644
|
if (!isCollectionEnabled(settings2.collections, sourceUid)) {
|
|
@@ -503,6 +646,7 @@ const relationLabels = ({ strapi }) => {
|
|
|
503
646
|
}
|
|
504
647
|
const runtimeCache = /* @__PURE__ */ new Map();
|
|
505
648
|
const sourceSchemas = /* @__PURE__ */ new Map();
|
|
649
|
+
const transformContext = getTransformContext(strapi);
|
|
506
650
|
const getSchema = (uid) => {
|
|
507
651
|
if (!sourceSchemas.has(uid)) {
|
|
508
652
|
sourceSchemas.set(uid, strapi.getModel(uid));
|
|
@@ -516,7 +660,7 @@ const relationLabels = ({ strapi }) => {
|
|
|
516
660
|
}
|
|
517
661
|
return;
|
|
518
662
|
}
|
|
519
|
-
if (!isRecord
|
|
663
|
+
if (!isRecord(value)) {
|
|
520
664
|
return;
|
|
521
665
|
}
|
|
522
666
|
const schema = getSchema(uid);
|
|
@@ -526,7 +670,14 @@ const relationLabels = ({ strapi }) => {
|
|
|
526
670
|
if (!runtimeCache.has(cacheKey)) {
|
|
527
671
|
runtimeCache.set(
|
|
528
672
|
cacheKey,
|
|
529
|
-
await getRuntime(
|
|
673
|
+
await getRuntime(
|
|
674
|
+
strapi,
|
|
675
|
+
settings2,
|
|
676
|
+
uid,
|
|
677
|
+
fieldName,
|
|
678
|
+
ctx.state.userAbility,
|
|
679
|
+
transformContext
|
|
680
|
+
)
|
|
530
681
|
);
|
|
531
682
|
}
|
|
532
683
|
const runtime = runtimeCache.get(cacheKey);
|
|
@@ -562,6 +713,7 @@ const relationLabels = ({ strapi }) => {
|
|
|
562
713
|
return {
|
|
563
714
|
decorateCollectionResults,
|
|
564
715
|
decorateConfiguration: decorateContentManagerConfiguration,
|
|
716
|
+
sanitizeConfigurationUpdate,
|
|
565
717
|
decorateRelationResults,
|
|
566
718
|
compileTemplate,
|
|
567
719
|
parseTemplate,
|
|
@@ -572,9 +724,7 @@ const EMPTY_SETTINGS = {
|
|
|
572
724
|
collections: [],
|
|
573
725
|
relations: {}
|
|
574
726
|
};
|
|
575
|
-
const PLUGIN_ID = "strapi-relation-names";
|
|
576
727
|
const STORE_KEY = "settings";
|
|
577
|
-
const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
578
728
|
const normalizeSettings = (value, collections = []) => {
|
|
579
729
|
if (!isRecord(value) || !isRecord(value.relations)) {
|
|
580
730
|
return { collections, relations: {} };
|