strapi-relation-names 1.2.2 → 1.3.1

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.
Files changed (33) hide show
  1. package/README.md +17 -42
  2. package/dist/{admin/SettingsPage-BebR4ghK.js → _chunks/SettingsPage-BMwx-BuP.js} +122 -48
  3. package/dist/{admin/SettingsPage-DFZP5CZs.mjs → _chunks/SettingsPage-D8leItKF.mjs} +122 -48
  4. package/dist/{admin/index-CiS6cuI9.mjs → _chunks/index-BeLPoAiU.mjs} +1 -1
  5. package/dist/{admin/index-DfSAe961.js → _chunks/index-COuIED98.js} +1 -1
  6. package/dist/admin/index.js +2 -3
  7. package/dist/admin/index.mjs +1 -1
  8. package/dist/admin/src/index.d.ts +1 -1
  9. package/dist/admin/src/pages/SettingsPage.d.ts +1 -0
  10. package/dist/admin/src/utils/template.d.ts +1 -14
  11. package/dist/server/index.js +267 -129
  12. package/dist/server/index.mjs +266 -127
  13. package/dist/server/src/bootstrap.d.ts +1 -1
  14. package/dist/server/src/config/index.d.ts +2 -0
  15. package/dist/server/src/controllers/index.d.ts +1 -1
  16. package/dist/server/src/controllers/settings.d.ts +1 -1
  17. package/dist/server/src/destroy.d.ts +1 -1
  18. package/dist/server/src/index.d.ts +20 -11
  19. package/dist/server/src/register.d.ts +1 -1
  20. package/dist/server/src/services/index.d.ts +8 -7
  21. package/dist/server/src/services/relation-labels.d.ts +6 -9
  22. package/dist/server/src/services/settings.d.ts +2 -2
  23. package/dist/server/src/services/utils/relation-hydration.d.ts +7 -0
  24. package/dist/server/src/services/utils/relation-labels.d.ts +4 -22
  25. package/dist/server/src/services/utils/relation-runtime.d.ts +6 -0
  26. package/dist/server/src/services/utils/relation-values.d.ts +1 -1
  27. package/dist/shared/records.d.ts +1 -0
  28. package/dist/shared/template.d.ts +55 -0
  29. package/package.json +28 -28
  30. /package/dist/{admin → _chunks}/de-Bc81LkbY.mjs +0 -0
  31. /package/dist/{admin → _chunks}/de-CsBvOsWR.js +0 -0
  32. /package/dist/{admin → _chunks}/en-Bk-HIXNd.mjs +0 -0
  33. /package/dist/{admin → _chunks}/en-DT4g6X2H.js +0 -0
package/README.md CHANGED
@@ -11,7 +11,7 @@ Display Strapi relations using readable labels built from your content fields in
11
11
 
12
12
  Ever wanted to display a relation as custom text or with nested field values? This plugin solves exactly that:
13
13
 
14
- ![Relation labels example](assets/example.png)
14
+ ![Relation labels example](https://raw.githubusercontent.com/TupiC/strapi-relation-names/main/assets/example.png)
15
15
 
16
16
  ---
17
17
 
@@ -67,32 +67,25 @@ After installing and enabling the plugin, open the Strapi Admin settings panel:
67
67
 
68
68
  Each relation field is displayed together with the scalar fields available on its related content type or component.
69
69
 
70
- Create a label using placeholders:
70
+ Create a label using placeholders e.g.:
71
71
 
72
72
  ```text
73
73
  {firstName} {lastName}
74
74
  ```
75
75
 
76
- For example, given:
77
-
78
- ```text
79
- firstName = John
80
- lastName = Doe
81
- ```
82
-
83
- the relation will be displayed as:
76
+ You can also mix placeholders with static text:
84
77
 
85
78
  ```text
86
- John Doe
79
+ Name: {firstName} {lastName}
87
80
  ```
88
81
 
89
- You can also mix placeholders with static text:
82
+ It is also possible to transform a label's value using pipe syntax:
83
+ {createdAt | toLocalDateTime}
90
84
 
91
- ```text
92
- {firstName} {lastName} ({customerNumber})
93
- ```
85
+ Currently the following transformations exist:
94
86
 
95
- After saving the settings, the Strapi Admin reloads automatically so open Content Manager views use the updated labels.
87
+ - `toLocalDate` formats date values using the configured locale and time zone.
88
+ - `toLocalDateTime` — formats date-time values using the configured locale and time zone.
96
89
 
97
90
  ---
98
91
 
@@ -100,9 +93,11 @@ After saving the settings, the Strapi Admin reloads automatically so open Conten
100
93
 
101
94
  Possible configuration keys are listed below; omitted keys keep the plugin defaults.
102
95
 
103
- | Key | Description | Possible values |
104
- | ------------- | ----------------------------------------------------------------------------------------------- | --------------- |
105
- | `collections` | Limit relation names to the listed collection and single type UIDs. Leave empty to include all. | `string[]` |
96
+ | Key | Description | Possible values |
97
+ | ------------- | ----------------------------------------------------------------------------------------------- | ------------------------------------ |
98
+ | `collections` | Limit relation names to the listed collection and single type UIDs. Leave empty to include all. | `string[]` (default: `[]`) |
99
+ | `locale` | Locale used by date transformations. | `string` (default: runtime locale) |
100
+ | `timeZone` | IANA timezone used by date transformations. | `string` (default: runtime timezone) |
106
101
 
107
102
  Example:
108
103
 
@@ -112,34 +107,14 @@ Example:
112
107
  enabled: true,
113
108
  config: {
114
109
  collections: ['api::article.article', 'api::author.author'],
110
+ locale: 'de-AT',
111
+ timeZone: 'Europe/Vienna',
115
112
  },
116
113
  },
117
114
  ```
118
115
 
119
116
  ---
120
117
 
121
- ## ⚙️ Templates
122
-
123
- Multiple placeholders can be combined:
124
-
125
- ```text
126
- {firstName} {lastName}
127
- ```
128
-
129
- Placeholders can also be repeated:
130
-
131
- ```text
132
- {name} - {name}
133
- ```
134
-
135
- Static text can be included anywhere:
136
-
137
- ```text
138
- Customer: {firstName} {lastName}
139
- ```
140
-
141
- ---
142
-
143
118
  ## 🗂️ How It Works
144
119
 
145
120
  At runtime, the server-side plugin decorates Strapi Content Manager relation responses and field metadata. It reads the configured template, validates each placeholder against the related content type schema, and renders the label from the returned relation data. If a value is not present, it performs a permission-aware hydration query; nested component paths are loaded by populating their component root. The rendered label is then exposed through Strapi's normal relation display mechanism. Missing field values are replaced with an empty string.
@@ -174,7 +149,7 @@ The default label is also used when:
174
149
  - [x] Support nested relation fields in templates
175
150
  - [ ] Import and export relation-label settings
176
151
  - [ ] Conditional labels and configurable fallback rules
177
- - [ ] Support label transformation functions (e.g., `toUpperCase`, `toLowerCase`, `capitalize`, etc.)
152
+ - [x] Support label transformation functions
178
153
 
179
154
  If you have any feature requests or suggestions, please open a dedicated issue.
180
155
 
@@ -6,7 +6,7 @@ const styled = require("styled-components");
6
6
  const react = require("react");
7
7
  const reactIntl = require("react-intl");
8
8
  const admin = require("@strapi/strapi/admin");
9
- const index = require("./index-DfSAe961.js");
9
+ const index = require("./index-COuIED98.js");
10
10
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
11
11
  const styled__default = /* @__PURE__ */ _interopDefault(styled);
12
12
  const FIELD_TYPES = /* @__PURE__ */ new Set([
@@ -24,16 +24,19 @@ const FIELD_TYPES = /* @__PURE__ */ new Set([
24
24
  "time",
25
25
  "boolean"
26
26
  ]);
27
- const SYSTEM_SCALAR_FIELDS = [
28
- "id",
29
- "documentId",
30
- "createdAt",
31
- "updatedAt",
32
- "publishedAt",
33
- "locale",
34
- "status"
35
- ];
36
- const PLACEHOLDER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/;
27
+ const DATE_TYPES = /* @__PURE__ */ new Set(["date", "datetime"]);
28
+ const SYSTEM_SCALAR_TYPES = {
29
+ id: "integer",
30
+ documentId: "string",
31
+ createdAt: "datetime",
32
+ updatedAt: "datetime",
33
+ publishedAt: "datetime",
34
+ locale: "string",
35
+ status: "string"
36
+ };
37
+ const SYSTEM_SCALAR_FIELDS = Object.keys(SYSTEM_SCALAR_TYPES);
38
+ const IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
39
+ const PATH_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/;
37
40
  const getNestedSchemaUid = (attribute) => {
38
41
  if (attribute.type === "component") {
39
42
  return attribute.component;
@@ -43,58 +46,129 @@ const getNestedSchemaUid = (attribute) => {
43
46
  }
44
47
  return void 0;
45
48
  };
46
- const getPlaceholders = (template) => {
49
+ const getDate = (value) => {
50
+ if (value instanceof Date) {
51
+ return Number.isNaN(value.getTime()) ? void 0 : value;
52
+ }
53
+ if (typeof value !== "string" && typeof value !== "number") {
54
+ return void 0;
55
+ }
56
+ const date = new Date(value);
57
+ return Number.isNaN(date.getTime()) ? void 0 : date;
58
+ };
59
+ const isDateOnly = (value) => typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value);
60
+ const formatDate = (value, options, context, preserveDateOnly) => {
61
+ const date = getDate(value);
62
+ if (!date) {
63
+ return "";
64
+ }
65
+ const timeZone = preserveDateOnly && isDateOnly(value) ? "UTC" : context.timeZone;
66
+ return new Intl.DateTimeFormat(context.locale, {
67
+ ...options,
68
+ ...timeZone ? { timeZone } : {}
69
+ }).format(date);
70
+ };
71
+ const TRANSFORMS = {
72
+ toLocalDate: {
73
+ accepts: (fieldType) => DATE_TYPES.has(fieldType ?? ""),
74
+ apply: (value, context) => formatDate(value, { dateStyle: "short" }, context, true)
75
+ },
76
+ toLocalDateTime: {
77
+ accepts: (fieldType) => fieldType === "datetime",
78
+ apply: (value, context) => formatDate(value, { dateStyle: "short", timeStyle: "short" }, context, false)
79
+ }
80
+ };
81
+ const parseExpression = (value) => {
82
+ const parts = value.split("|").map((part) => part.trim());
83
+ const [path, ...transforms] = parts;
84
+ if (!path || !PATH_PATTERN.test(path) || transforms.some((name) => !IDENTIFIER_PATTERN.test(name))) {
85
+ return null;
86
+ }
87
+ return { path, transforms };
88
+ };
89
+ const parseTemplateAst = (template) => {
47
90
  if (!template.trim()) {
48
91
  return null;
49
92
  }
50
- const placeholders = [];
51
- const matches = template.matchAll(/\{([^{}]*)\}/g);
93
+ const tokens = [];
94
+ const expressions = [];
52
95
  let cursor = 0;
53
- for (const match of matches) {
54
- if (match.index !== cursor && template.slice(cursor, match.index).includes("{")) {
96
+ while (cursor < template.length) {
97
+ const openingBrace = template.indexOf("{", cursor);
98
+ const closingBrace = template.indexOf("}", cursor);
99
+ if (closingBrace !== -1 && (openingBrace === -1 || closingBrace < openingBrace)) {
100
+ return null;
101
+ }
102
+ if (openingBrace === -1) {
103
+ tokens.push({ type: "text", value: template.slice(cursor) });
104
+ break;
105
+ }
106
+ if (openingBrace > cursor) {
107
+ tokens.push({ type: "text", value: template.slice(cursor, openingBrace) });
108
+ }
109
+ const end = template.indexOf("}", openingBrace + 1);
110
+ if (end === -1) {
55
111
  return null;
56
112
  }
57
- const name = match[1];
58
- if (!PLACEHOLDER_PATTERN.test(name)) {
113
+ const expression = parseExpression(template.slice(openingBrace + 1, end));
114
+ if (!expression) {
59
115
  return null;
60
116
  }
61
- placeholders.push(name);
62
- cursor = match.index + match[0].length;
117
+ tokens.push({ type: "expression", expression });
118
+ expressions.push(expression);
119
+ cursor = end + 1;
63
120
  }
64
- if (template.slice(cursor).includes("{") || template.slice(cursor).includes("}")) {
65
- return null;
121
+ return expressions.length > 0 ? { tokens, expressions } : null;
122
+ };
123
+ const resolvePathType = (path, targetSchema, resolveSchema) => {
124
+ const segments = path.split(".");
125
+ let schema = targetSchema;
126
+ for (const [index2, segment] of segments.entries()) {
127
+ const isLastSegment = index2 === segments.length - 1;
128
+ if (index2 === 0 && isLastSegment && SYSTEM_SCALAR_TYPES[segment]) {
129
+ return SYSTEM_SCALAR_TYPES[segment];
130
+ }
131
+ const attribute = schema.attributes?.[segment];
132
+ if (!attribute || attribute.private === true) {
133
+ return void 0;
134
+ }
135
+ if (isLastSegment) {
136
+ return FIELD_TYPES.has(attribute.type ?? "") ? attribute.type : void 0;
137
+ }
138
+ const nestedSchemaUid = getNestedSchemaUid(attribute);
139
+ if (!nestedSchemaUid || !resolveSchema) {
140
+ return void 0;
141
+ }
142
+ const nestedSchema = resolveSchema(nestedSchemaUid);
143
+ if (!nestedSchema) {
144
+ return void 0;
145
+ }
146
+ schema = nestedSchema;
66
147
  }
67
- return placeholders.length > 0 ? placeholders : null;
148
+ return void 0;
68
149
  };
69
150
  const validateTemplate = (template, attributes, schemas) => {
70
- const placeholders = getPlaceholders(template);
71
- if (!placeholders) {
151
+ const ast = parseTemplateAst(template);
152
+ if (!ast) {
72
153
  return "Use at least one valid {fieldName} placeholder.";
73
154
  }
74
- const invalidField = placeholders.find((path) => {
75
- const segments = path.split(".");
76
- let currentAttributes = attributes;
77
- for (const [index2, segment] of segments.entries()) {
78
- if (index2 === 0 && segments.length === 1 && SYSTEM_SCALAR_FIELDS.includes(segment)) {
79
- return false;
80
- }
81
- const attribute = currentAttributes?.[segment];
82
- const isLastSegment = index2 === segments.length - 1;
83
- if (!attribute || attribute.private === true) {
84
- return true;
85
- }
86
- if (isLastSegment) {
87
- return !FIELD_TYPES.has(attribute.type ?? "");
88
- }
89
- const nestedSchemaUid = getNestedSchemaUid(attribute);
90
- if (!nestedSchemaUid) {
91
- return true;
92
- }
93
- currentAttributes = schemas?.get(nestedSchemaUid)?.attributes;
155
+ const targetSchema = { attributes };
156
+ const invalidExpression = ast.expressions.find(({ path, transforms }) => {
157
+ const fieldType = resolvePathType(path, targetSchema, (uid) => schemas?.get(uid));
158
+ if (!fieldType) {
159
+ return true;
94
160
  }
95
- return true;
161
+ return transforms.some((name) => !TRANSFORMS[name] || !TRANSFORMS[name].accepts(fieldType));
96
162
  });
97
- return invalidField ? `“${invalidField}” is not an available scalar field.` : null;
163
+ if (!invalidExpression) {
164
+ return null;
165
+ }
166
+ const invalidTransform = invalidExpression.transforms.find(
167
+ (name) => !TRANSFORMS[name] || !TRANSFORMS[name].accepts(
168
+ resolvePathType(invalidExpression.path, targetSchema, (uid) => schemas?.get(uid))
169
+ )
170
+ );
171
+ return invalidTransform ? `“${invalidTransform}” is not a valid transformation for “${invalidExpression.path}”.` : `“${invalidExpression.path}” is not an available scalar field.`;
98
172
  };
99
173
  const getAvailableFields = (schema, schemas, prefix = "", ancestors = /* @__PURE__ */ new Set()) => {
100
174
  if (!schema) {
@@ -191,7 +265,7 @@ const SettingsPage = () => {
191
265
  );
192
266
  const updateTemplate = (sourceUid, fieldName, template) => {
193
267
  setSettings((current) => {
194
- const sourceRelations = { ...current.relations[sourceUid] ?? {} };
268
+ const sourceRelations = { ...current.relations[sourceUid] };
195
269
  if (template.length === 0) {
196
270
  delete sourceRelations[fieldName];
197
271
  } else {
@@ -4,7 +4,7 @@ import styled from "styled-components";
4
4
  import { useState, useCallback, useEffect, useMemo, Fragment } from "react";
5
5
  import { useIntl } from "react-intl";
6
6
  import { useFetchClient } from "@strapi/strapi/admin";
7
- import { g as getTranslation } from "./index-CiS6cuI9.mjs";
7
+ import { g as getTranslation } from "./index-BeLPoAiU.mjs";
8
8
  const FIELD_TYPES = /* @__PURE__ */ new Set([
9
9
  "string",
10
10
  "text",
@@ -20,16 +20,19 @@ const FIELD_TYPES = /* @__PURE__ */ new Set([
20
20
  "time",
21
21
  "boolean"
22
22
  ]);
23
- const SYSTEM_SCALAR_FIELDS = [
24
- "id",
25
- "documentId",
26
- "createdAt",
27
- "updatedAt",
28
- "publishedAt",
29
- "locale",
30
- "status"
31
- ];
32
- const PLACEHOLDER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/;
23
+ const DATE_TYPES = /* @__PURE__ */ new Set(["date", "datetime"]);
24
+ const SYSTEM_SCALAR_TYPES = {
25
+ id: "integer",
26
+ documentId: "string",
27
+ createdAt: "datetime",
28
+ updatedAt: "datetime",
29
+ publishedAt: "datetime",
30
+ locale: "string",
31
+ status: "string"
32
+ };
33
+ const SYSTEM_SCALAR_FIELDS = Object.keys(SYSTEM_SCALAR_TYPES);
34
+ const IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
35
+ const PATH_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/;
33
36
  const getNestedSchemaUid = (attribute) => {
34
37
  if (attribute.type === "component") {
35
38
  return attribute.component;
@@ -39,58 +42,129 @@ const getNestedSchemaUid = (attribute) => {
39
42
  }
40
43
  return void 0;
41
44
  };
42
- const getPlaceholders = (template) => {
45
+ const getDate = (value) => {
46
+ if (value instanceof Date) {
47
+ return Number.isNaN(value.getTime()) ? void 0 : value;
48
+ }
49
+ if (typeof value !== "string" && typeof value !== "number") {
50
+ return void 0;
51
+ }
52
+ const date = new Date(value);
53
+ return Number.isNaN(date.getTime()) ? void 0 : date;
54
+ };
55
+ const isDateOnly = (value) => typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value);
56
+ const formatDate = (value, options, context, preserveDateOnly) => {
57
+ const date = getDate(value);
58
+ if (!date) {
59
+ return "";
60
+ }
61
+ const timeZone = preserveDateOnly && isDateOnly(value) ? "UTC" : context.timeZone;
62
+ return new Intl.DateTimeFormat(context.locale, {
63
+ ...options,
64
+ ...timeZone ? { timeZone } : {}
65
+ }).format(date);
66
+ };
67
+ const TRANSFORMS = {
68
+ toLocalDate: {
69
+ accepts: (fieldType) => DATE_TYPES.has(fieldType ?? ""),
70
+ apply: (value, context) => formatDate(value, { dateStyle: "short" }, context, true)
71
+ },
72
+ toLocalDateTime: {
73
+ accepts: (fieldType) => fieldType === "datetime",
74
+ apply: (value, context) => formatDate(value, { dateStyle: "short", timeStyle: "short" }, context, false)
75
+ }
76
+ };
77
+ const parseExpression = (value) => {
78
+ const parts = value.split("|").map((part) => part.trim());
79
+ const [path, ...transforms] = parts;
80
+ if (!path || !PATH_PATTERN.test(path) || transforms.some((name) => !IDENTIFIER_PATTERN.test(name))) {
81
+ return null;
82
+ }
83
+ return { path, transforms };
84
+ };
85
+ const parseTemplateAst = (template) => {
43
86
  if (!template.trim()) {
44
87
  return null;
45
88
  }
46
- const placeholders = [];
47
- const matches = template.matchAll(/\{([^{}]*)\}/g);
89
+ const tokens = [];
90
+ const expressions = [];
48
91
  let cursor = 0;
49
- for (const match of matches) {
50
- if (match.index !== cursor && template.slice(cursor, match.index).includes("{")) {
92
+ while (cursor < template.length) {
93
+ const openingBrace = template.indexOf("{", cursor);
94
+ const closingBrace = template.indexOf("}", cursor);
95
+ if (closingBrace !== -1 && (openingBrace === -1 || closingBrace < openingBrace)) {
96
+ return null;
97
+ }
98
+ if (openingBrace === -1) {
99
+ tokens.push({ type: "text", value: template.slice(cursor) });
100
+ break;
101
+ }
102
+ if (openingBrace > cursor) {
103
+ tokens.push({ type: "text", value: template.slice(cursor, openingBrace) });
104
+ }
105
+ const end = template.indexOf("}", openingBrace + 1);
106
+ if (end === -1) {
51
107
  return null;
52
108
  }
53
- const name = match[1];
54
- if (!PLACEHOLDER_PATTERN.test(name)) {
109
+ const expression = parseExpression(template.slice(openingBrace + 1, end));
110
+ if (!expression) {
55
111
  return null;
56
112
  }
57
- placeholders.push(name);
58
- cursor = match.index + match[0].length;
113
+ tokens.push({ type: "expression", expression });
114
+ expressions.push(expression);
115
+ cursor = end + 1;
59
116
  }
60
- if (template.slice(cursor).includes("{") || template.slice(cursor).includes("}")) {
61
- return null;
117
+ return expressions.length > 0 ? { tokens, expressions } : null;
118
+ };
119
+ const resolvePathType = (path, targetSchema, resolveSchema) => {
120
+ const segments = path.split(".");
121
+ let schema = targetSchema;
122
+ for (const [index, segment] of segments.entries()) {
123
+ const isLastSegment = index === segments.length - 1;
124
+ if (index === 0 && isLastSegment && SYSTEM_SCALAR_TYPES[segment]) {
125
+ return SYSTEM_SCALAR_TYPES[segment];
126
+ }
127
+ const attribute = schema.attributes?.[segment];
128
+ if (!attribute || attribute.private === true) {
129
+ return void 0;
130
+ }
131
+ if (isLastSegment) {
132
+ return FIELD_TYPES.has(attribute.type ?? "") ? attribute.type : void 0;
133
+ }
134
+ const nestedSchemaUid = getNestedSchemaUid(attribute);
135
+ if (!nestedSchemaUid || !resolveSchema) {
136
+ return void 0;
137
+ }
138
+ const nestedSchema = resolveSchema(nestedSchemaUid);
139
+ if (!nestedSchema) {
140
+ return void 0;
141
+ }
142
+ schema = nestedSchema;
62
143
  }
63
- return placeholders.length > 0 ? placeholders : null;
144
+ return void 0;
64
145
  };
65
146
  const validateTemplate = (template, attributes, schemas) => {
66
- const placeholders = getPlaceholders(template);
67
- if (!placeholders) {
147
+ const ast = parseTemplateAst(template);
148
+ if (!ast) {
68
149
  return "Use at least one valid {fieldName} placeholder.";
69
150
  }
70
- const invalidField = placeholders.find((path) => {
71
- const segments = path.split(".");
72
- let currentAttributes = attributes;
73
- for (const [index, segment] of segments.entries()) {
74
- if (index === 0 && segments.length === 1 && SYSTEM_SCALAR_FIELDS.includes(segment)) {
75
- return false;
76
- }
77
- const attribute = currentAttributes?.[segment];
78
- const isLastSegment = index === segments.length - 1;
79
- if (!attribute || attribute.private === true) {
80
- return true;
81
- }
82
- if (isLastSegment) {
83
- return !FIELD_TYPES.has(attribute.type ?? "");
84
- }
85
- const nestedSchemaUid = getNestedSchemaUid(attribute);
86
- if (!nestedSchemaUid) {
87
- return true;
88
- }
89
- currentAttributes = schemas?.get(nestedSchemaUid)?.attributes;
151
+ const targetSchema = { attributes };
152
+ const invalidExpression = ast.expressions.find(({ path, transforms }) => {
153
+ const fieldType = resolvePathType(path, targetSchema, (uid) => schemas?.get(uid));
154
+ if (!fieldType) {
155
+ return true;
90
156
  }
91
- return true;
157
+ return transforms.some((name) => !TRANSFORMS[name] || !TRANSFORMS[name].accepts(fieldType));
92
158
  });
93
- return invalidField ? `“${invalidField}” is not an available scalar field.` : null;
159
+ if (!invalidExpression) {
160
+ return null;
161
+ }
162
+ const invalidTransform = invalidExpression.transforms.find(
163
+ (name) => !TRANSFORMS[name] || !TRANSFORMS[name].accepts(
164
+ resolvePathType(invalidExpression.path, targetSchema, (uid) => schemas?.get(uid))
165
+ )
166
+ );
167
+ return invalidTransform ? `“${invalidTransform}” is not a valid transformation for “${invalidExpression.path}”.` : `“${invalidExpression.path}” is not an available scalar field.`;
94
168
  };
95
169
  const getAvailableFields = (schema, schemas, prefix = "", ancestors = /* @__PURE__ */ new Set()) => {
96
170
  if (!schema) {
@@ -187,7 +261,7 @@ const SettingsPage = () => {
187
261
  );
188
262
  const updateTemplate = (sourceUid, fieldName, template) => {
189
263
  setSettings((current) => {
190
- const sourceRelations = { ...current.relations[sourceUid] ?? {} };
264
+ const sourceRelations = { ...current.relations[sourceUid] };
191
265
  if (template.length === 0) {
192
266
  delete sourceRelations[fieldName];
193
267
  } else {
@@ -88,7 +88,7 @@ const plugin = {
88
88
  id: `${PLUGIN_ID}.plugin.name`,
89
89
  defaultMessage: "Relation Names"
90
90
  },
91
- Component: () => import("./SettingsPage-DFZP5CZs.mjs").then((module) => ({ default: module.SettingsPage })),
91
+ Component: () => import("./SettingsPage-D8leItKF.mjs").then((module) => ({ default: module.SettingsPage })),
92
92
  permissions: []
93
93
  });
94
94
  app.registerPlugin({
@@ -89,7 +89,7 @@ const plugin = {
89
89
  id: `${PLUGIN_ID}.plugin.name`,
90
90
  defaultMessage: "Relation Names"
91
91
  },
92
- Component: () => Promise.resolve().then(() => require("./SettingsPage-BebR4ghK.js")).then((module2) => ({ default: module2.SettingsPage })),
92
+ Component: () => Promise.resolve().then(() => require("./SettingsPage-BMwx-BuP.js")).then((module2) => ({ default: module2.SettingsPage })),
93
93
  permissions: []
94
94
  });
95
95
  app.registerPlugin({
@@ -1,4 +1,3 @@
1
1
  "use strict";
2
- Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3
- const index = require("./index-DfSAe961.js");
4
- exports.default = index.plugin;
2
+ const index = require("../_chunks/index-COuIED98.js");
3
+ module.exports = index.plugin;
@@ -1,4 +1,4 @@
1
- import { p } from "./index-CiS6cuI9.mjs";
1
+ import { p } from "../_chunks/index-BeLPoAiU.mjs";
2
2
  export {
3
3
  p as default
4
4
  };
@@ -1,3 +1,3 @@
1
- import { StrapiApp } from '@strapi/strapi/admin';
1
+ import type { StrapiApp } from '@strapi/strapi/admin';
2
2
  declare const plugin: StrapiApp['appPlugins'][string];
3
3
  export default plugin;
@@ -1,2 +1,3 @@
1
+ /// <reference types="react" />
1
2
  declare const SettingsPage: () => import("react").JSX.Element;
2
3
  export { SettingsPage };
@@ -1,14 +1 @@
1
- type TemplateAttribute = {
2
- type?: string;
3
- component?: string;
4
- target?: string;
5
- targetModel?: string;
6
- private?: boolean;
7
- };
8
- type TemplateSchema = {
9
- attributes?: Record<string, TemplateAttribute>;
10
- };
11
- declare const getPlaceholders: (template: string) => string[] | null;
12
- declare const validateTemplate: (template: string, attributes: Record<string, TemplateAttribute> | undefined, schemas?: ReadonlyMap<string, TemplateSchema>) => string | null;
13
- declare const getAvailableFields: (schema: TemplateSchema | undefined, schemas: ReadonlyMap<string, TemplateSchema>, prefix?: string, ancestors?: Set<string>) => string[];
14
- export { getAvailableFields, getPlaceholders, validateTemplate };
1
+ export { getAvailableFields, getPlaceholders, validateTemplate } from '../../../shared/template';