strapi-relation-names 1.1.0 → 1.2.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.
package/README.md CHANGED
@@ -171,6 +171,7 @@ The default label is also used when:
171
171
  - [ ] Live label previews while editing templates
172
172
  - [ ] Define default relation templates in plugin configuration
173
173
  - [x] Support nested field paths in templates
174
+ - [x] Support nested relation fields in templates
174
175
  - [ ] Import and export relation-label settings
175
176
  - [ ] Conditional labels and configurable fallback rules
176
177
  - [ ] Support label transformation functions (e.g., `toUpperCase`, `toLowerCase`, `capitalize`, etc.)
@@ -2,10 +2,13 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const jsxRuntime = require("react/jsx-runtime");
4
4
  const designSystem = require("@strapi/design-system");
5
+ const styled = require("styled-components");
5
6
  const react = require("react");
6
7
  const reactIntl = require("react-intl");
7
8
  const admin = require("@strapi/strapi/admin");
8
- const index = require("./index-CKaZcnCM.js");
9
+ const index = require("./index-DfSAe961.js");
10
+ const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
11
+ const styled__default = /* @__PURE__ */ _interopDefault(styled);
9
12
  const FIELD_TYPES = /* @__PURE__ */ new Set([
10
13
  "string",
11
14
  "text",
@@ -31,6 +34,15 @@ const SYSTEM_SCALAR_FIELDS = [
31
34
  "status"
32
35
  ];
33
36
  const PLACEHOLDER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/;
37
+ const getNestedSchemaUid = (attribute) => {
38
+ if (attribute.type === "component") {
39
+ return attribute.component;
40
+ }
41
+ if (attribute.type === "relation") {
42
+ return attribute.targetModel ?? attribute.target;
43
+ }
44
+ return void 0;
45
+ };
34
46
  const getPlaceholders = (template) => {
35
47
  if (!template.trim()) {
36
48
  return null;
@@ -74,10 +86,11 @@ const validateTemplate = (template, attributes, schemas) => {
74
86
  if (isLastSegment) {
75
87
  return !FIELD_TYPES.has(attribute.type ?? "");
76
88
  }
77
- if (attribute.type !== "component" || !attribute.component) {
89
+ const nestedSchemaUid = getNestedSchemaUid(attribute);
90
+ if (!nestedSchemaUid) {
78
91
  return true;
79
92
  }
80
- currentAttributes = schemas?.get(attribute.component)?.attributes;
93
+ currentAttributes = schemas?.get(nestedSchemaUid)?.attributes;
81
94
  }
82
95
  return true;
83
96
  });
@@ -95,20 +108,36 @@ const getAvailableFields = (schema, schemas, prefix = "", ancestors = /* @__PURE
95
108
  if (FIELD_TYPES.has(attribute.type ?? "")) {
96
109
  return [path];
97
110
  }
98
- if (attribute.type !== "component" || !attribute.component || ancestors.has(attribute.component)) {
111
+ const nestedSchemaUid = getNestedSchemaUid(attribute);
112
+ if (!nestedSchemaUid || ancestors.has(nestedSchemaUid)) {
99
113
  return [];
100
114
  }
101
115
  return getAvailableFields(
102
- schemas.get(attribute.component),
116
+ schemas.get(nestedSchemaUid),
103
117
  schemas,
104
118
  `${path}.`,
105
- /* @__PURE__ */ new Set([...ancestors, attribute.component])
119
+ /* @__PURE__ */ new Set([...ancestors, nestedSchemaUid])
106
120
  );
107
121
  });
108
122
  const systemFields = prefix === "" ? SYSTEM_SCALAR_FIELDS : [];
109
123
  return [...systemFields, ...fields.filter((field) => !systemFields.includes(field))];
110
124
  };
111
125
  const PLUGIN_SETTINGS_PATH = "/strapi-relation-names/settings";
126
+ const AvailableField = styled__default.default(designSystem.Typography)`
127
+ display: inline-block;
128
+ padding: 1px 4px;
129
+ border: 1px solid transparent;
130
+ border-radius: 4px;
131
+ background: transparent;
132
+ color: inherit;
133
+ cursor: pointer;
134
+ font: inherit;
135
+
136
+ &:hover,
137
+ &[aria-pressed='true'] {
138
+ border-color: currentColor;
139
+ }
140
+ `;
112
141
  const getRelationTarget = (attribute) => attribute.targetModel ?? attribute.target;
113
142
  const SettingsPage = () => {
114
143
  const { formatMessage } = reactIntl.useIntl();
@@ -190,7 +219,24 @@ const SettingsPage = () => {
190
219
  };
191
220
  return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { "aria-labelledby": "relation-names-title", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 8, children: [
192
221
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", tag: "h1", id: "relation-names-title", children: formatMessage({ id: index.getTranslation("settings.title") }) }),
193
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 2, paddingBottom: 6, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: formatMessage({ id: index.getTranslation("settings.description") }) }) }),
222
+ /* @__PURE__ */ jsxRuntime.jsxs(
223
+ designSystem.Flex,
224
+ {
225
+ gap: 2,
226
+ width: "100%",
227
+ paddingBottom: 6,
228
+ style: {
229
+ "justify-content": "space-between",
230
+ position: "sticky",
231
+ top: ".5rem",
232
+ zIndex: 1
233
+ },
234
+ children: [
235
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: formatMessage({ id: index.getTranslation("settings.description") }) }),
236
+ !isLoading && rows.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { loading: isSaving, disabled: isSaving, onClick: () => void save(), children: formatMessage({ id: index.getTranslation("settings.save") }) }) : null
237
+ ]
238
+ }
239
+ ),
194
240
  isLoading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage({ id: index.getTranslation("settings.loading") }) }) : null,
195
241
  error ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "danger600", tag: "p", children: error }) : null,
196
242
  !isLoading && rows.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage({ id: index.getTranslation("settings.noRelations") }) }) : null,
@@ -198,6 +244,12 @@ const SettingsPage = () => {
198
244
  const targetAttributes = target?.attributes ?? {};
199
245
  const template = settings.relations[source.uid]?.[fieldName] ?? "";
200
246
  const validationError = template ? validateTemplate(template, targetAttributes, schemaMap) : null;
247
+ const availableFields = getAvailableFields(target, schemaMap);
248
+ const toggleField = (field) => {
249
+ const placeholder = `{${field}}`;
250
+ const nextTemplate = template.includes(placeholder) ? template.replaceAll(placeholder, "").replace(/\s{2,}/g, " ").trim() : `${template.trim()}${template.trim() ? " " : ""}${placeholder}`;
251
+ updateTemplate(source.uid, fieldName, nextTemplate);
252
+ };
201
253
  return /* @__PURE__ */ jsxRuntime.jsxs(
202
254
  designSystem.Box,
203
255
  {
@@ -222,7 +274,9 @@ const SettingsPage = () => {
222
274
  error: validationError ?? void 0,
223
275
  name: `${source.uid}.${fieldName}`,
224
276
  children: [
225
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({ id: index.getTranslation("settings.template") }) }),
277
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({
278
+ id: index.getTranslation("settings.template")
279
+ }) }),
226
280
  /* @__PURE__ */ jsxRuntime.jsx(
227
281
  designSystem.TextInput,
228
282
  {
@@ -236,18 +290,34 @@ const SettingsPage = () => {
236
290
  ]
237
291
  }
238
292
  ),
239
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 2, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: [
240
- formatMessage({ id: index.getTranslation("settings.availableFields") }),
241
- ":",
242
- " ",
243
- getAvailableFields(target, schemaMap).join(", ")
244
- ] }) })
293
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 2, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Root, { collapsible: true, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Accordion.Item, { value: "available-fields", children: [
294
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Trigger, { children: formatMessage({
295
+ id: index.getTranslation("settings.availableFields")
296
+ }) }) }),
297
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Content, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: availableFields.map((field, index2) => {
298
+ const placeholder = `{${field}}`;
299
+ return /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
300
+ index2 > 0 ? ", " : null,
301
+ /* @__PURE__ */ jsxRuntime.jsx(
302
+ AvailableField,
303
+ {
304
+ as: "button",
305
+ type: "button",
306
+ variant: "pi",
307
+ textColor: "neutral600",
308
+ "aria-pressed": template.includes(placeholder),
309
+ onClick: () => toggleField(field),
310
+ children: field
311
+ }
312
+ )
313
+ ] }, field);
314
+ }) }) })
315
+ ] }) }) })
245
316
  ]
246
317
  },
247
318
  `${source.uid}.${fieldName}`
248
319
  );
249
- }) : null,
250
- !isLoading && rows.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "flex-end", paddingTop: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { loading: isSaving, disabled: isSaving, onClick: () => void save(), children: formatMessage({ id: index.getTranslation("settings.save") }) }) }) : null
320
+ }) : null
251
321
  ] }) });
252
322
  };
253
323
  exports.SettingsPage = SettingsPage;
@@ -1,9 +1,10 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { Main, Box, Typography, Field, TextInput, Flex, Button } from "@strapi/design-system";
3
- import { useState, useCallback, useEffect, useMemo } from "react";
2
+ import { Main, Box, Typography, Flex, Button, Field, TextInput, Accordion } from "@strapi/design-system";
3
+ import styled from "styled-components";
4
+ import { useState, useCallback, useEffect, useMemo, Fragment } from "react";
4
5
  import { useIntl } from "react-intl";
5
6
  import { useFetchClient } from "@strapi/strapi/admin";
6
- import { g as getTranslation } from "./index-DGXo1dxk.mjs";
7
+ import { g as getTranslation } from "./index-CiS6cuI9.mjs";
7
8
  const FIELD_TYPES = /* @__PURE__ */ new Set([
8
9
  "string",
9
10
  "text",
@@ -29,6 +30,15 @@ const SYSTEM_SCALAR_FIELDS = [
29
30
  "status"
30
31
  ];
31
32
  const PLACEHOLDER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/;
33
+ const getNestedSchemaUid = (attribute) => {
34
+ if (attribute.type === "component") {
35
+ return attribute.component;
36
+ }
37
+ if (attribute.type === "relation") {
38
+ return attribute.targetModel ?? attribute.target;
39
+ }
40
+ return void 0;
41
+ };
32
42
  const getPlaceholders = (template) => {
33
43
  if (!template.trim()) {
34
44
  return null;
@@ -72,10 +82,11 @@ const validateTemplate = (template, attributes, schemas) => {
72
82
  if (isLastSegment) {
73
83
  return !FIELD_TYPES.has(attribute.type ?? "");
74
84
  }
75
- if (attribute.type !== "component" || !attribute.component) {
85
+ const nestedSchemaUid = getNestedSchemaUid(attribute);
86
+ if (!nestedSchemaUid) {
76
87
  return true;
77
88
  }
78
- currentAttributes = schemas?.get(attribute.component)?.attributes;
89
+ currentAttributes = schemas?.get(nestedSchemaUid)?.attributes;
79
90
  }
80
91
  return true;
81
92
  });
@@ -93,20 +104,36 @@ const getAvailableFields = (schema, schemas, prefix = "", ancestors = /* @__PURE
93
104
  if (FIELD_TYPES.has(attribute.type ?? "")) {
94
105
  return [path];
95
106
  }
96
- if (attribute.type !== "component" || !attribute.component || ancestors.has(attribute.component)) {
107
+ const nestedSchemaUid = getNestedSchemaUid(attribute);
108
+ if (!nestedSchemaUid || ancestors.has(nestedSchemaUid)) {
97
109
  return [];
98
110
  }
99
111
  return getAvailableFields(
100
- schemas.get(attribute.component),
112
+ schemas.get(nestedSchemaUid),
101
113
  schemas,
102
114
  `${path}.`,
103
- /* @__PURE__ */ new Set([...ancestors, attribute.component])
115
+ /* @__PURE__ */ new Set([...ancestors, nestedSchemaUid])
104
116
  );
105
117
  });
106
118
  const systemFields = prefix === "" ? SYSTEM_SCALAR_FIELDS : [];
107
119
  return [...systemFields, ...fields.filter((field) => !systemFields.includes(field))];
108
120
  };
109
121
  const PLUGIN_SETTINGS_PATH = "/strapi-relation-names/settings";
122
+ const AvailableField = styled(Typography)`
123
+ display: inline-block;
124
+ padding: 1px 4px;
125
+ border: 1px solid transparent;
126
+ border-radius: 4px;
127
+ background: transparent;
128
+ color: inherit;
129
+ cursor: pointer;
130
+ font: inherit;
131
+
132
+ &:hover,
133
+ &[aria-pressed='true'] {
134
+ border-color: currentColor;
135
+ }
136
+ `;
110
137
  const getRelationTarget = (attribute) => attribute.targetModel ?? attribute.target;
111
138
  const SettingsPage = () => {
112
139
  const { formatMessage } = useIntl();
@@ -188,7 +215,24 @@ const SettingsPage = () => {
188
215
  };
189
216
  return /* @__PURE__ */ jsx(Main, { "aria-labelledby": "relation-names-title", children: /* @__PURE__ */ jsxs(Box, { padding: 8, children: [
190
217
  /* @__PURE__ */ jsx(Typography, { variant: "alpha", tag: "h1", id: "relation-names-title", children: formatMessage({ id: getTranslation("settings.title") }) }),
191
- /* @__PURE__ */ jsx(Box, { paddingTop: 2, paddingBottom: 6, children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: formatMessage({ id: getTranslation("settings.description") }) }) }),
218
+ /* @__PURE__ */ jsxs(
219
+ Flex,
220
+ {
221
+ gap: 2,
222
+ width: "100%",
223
+ paddingBottom: 6,
224
+ style: {
225
+ "justify-content": "space-between",
226
+ position: "sticky",
227
+ top: ".5rem",
228
+ zIndex: 1
229
+ },
230
+ children: [
231
+ /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: formatMessage({ id: getTranslation("settings.description") }) }),
232
+ !isLoading && rows.length > 0 ? /* @__PURE__ */ jsx(Button, { loading: isSaving, disabled: isSaving, onClick: () => void save(), children: formatMessage({ id: getTranslation("settings.save") }) }) : null
233
+ ]
234
+ }
235
+ ),
192
236
  isLoading ? /* @__PURE__ */ jsx(Typography, { children: formatMessage({ id: getTranslation("settings.loading") }) }) : null,
193
237
  error ? /* @__PURE__ */ jsx(Typography, { textColor: "danger600", tag: "p", children: error }) : null,
194
238
  !isLoading && rows.length === 0 ? /* @__PURE__ */ jsx(Typography, { children: formatMessage({ id: getTranslation("settings.noRelations") }) }) : null,
@@ -196,6 +240,12 @@ const SettingsPage = () => {
196
240
  const targetAttributes = target?.attributes ?? {};
197
241
  const template = settings.relations[source.uid]?.[fieldName] ?? "";
198
242
  const validationError = template ? validateTemplate(template, targetAttributes, schemaMap) : null;
243
+ const availableFields = getAvailableFields(target, schemaMap);
244
+ const toggleField = (field) => {
245
+ const placeholder = `{${field}}`;
246
+ const nextTemplate = template.includes(placeholder) ? template.replaceAll(placeholder, "").replace(/\s{2,}/g, " ").trim() : `${template.trim()}${template.trim() ? " " : ""}${placeholder}`;
247
+ updateTemplate(source.uid, fieldName, nextTemplate);
248
+ };
199
249
  return /* @__PURE__ */ jsxs(
200
250
  Box,
201
251
  {
@@ -220,7 +270,9 @@ const SettingsPage = () => {
220
270
  error: validationError ?? void 0,
221
271
  name: `${source.uid}.${fieldName}`,
222
272
  children: [
223
- /* @__PURE__ */ jsx(Field.Label, { children: formatMessage({ id: getTranslation("settings.template") }) }),
273
+ /* @__PURE__ */ jsx(Field.Label, { children: formatMessage({
274
+ id: getTranslation("settings.template")
275
+ }) }),
224
276
  /* @__PURE__ */ jsx(
225
277
  TextInput,
226
278
  {
@@ -234,18 +286,34 @@ const SettingsPage = () => {
234
286
  ]
235
287
  }
236
288
  ),
237
- /* @__PURE__ */ jsx(Box, { paddingTop: 2, children: /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
238
- formatMessage({ id: getTranslation("settings.availableFields") }),
239
- ":",
240
- " ",
241
- getAvailableFields(target, schemaMap).join(", ")
242
- ] }) })
289
+ /* @__PURE__ */ jsx(Box, { paddingTop: 2, children: /* @__PURE__ */ jsx(Accordion.Root, { collapsible: true, children: /* @__PURE__ */ jsxs(Accordion.Item, { value: "available-fields", children: [
290
+ /* @__PURE__ */ jsx(Accordion.Header, { children: /* @__PURE__ */ jsx(Accordion.Trigger, { children: formatMessage({
291
+ id: getTranslation("settings.availableFields")
292
+ }) }) }),
293
+ /* @__PURE__ */ jsx(Accordion.Content, { children: /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: availableFields.map((field, index) => {
294
+ const placeholder = `{${field}}`;
295
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
296
+ index > 0 ? ", " : null,
297
+ /* @__PURE__ */ jsx(
298
+ AvailableField,
299
+ {
300
+ as: "button",
301
+ type: "button",
302
+ variant: "pi",
303
+ textColor: "neutral600",
304
+ "aria-pressed": template.includes(placeholder),
305
+ onClick: () => toggleField(field),
306
+ children: field
307
+ }
308
+ )
309
+ ] }, field);
310
+ }) }) })
311
+ ] }) }) })
243
312
  ]
244
313
  },
245
314
  `${source.uid}.${fieldName}`
246
315
  );
247
- }) : null,
248
- !isLoading && rows.length > 0 ? /* @__PURE__ */ jsx(Flex, { justifyContent: "flex-end", paddingTop: 4, children: /* @__PURE__ */ jsx(Button, { loading: isSaving, disabled: isSaving, onClick: () => void save(), children: formatMessage({ id: getTranslation("settings.save") }) }) }) : null
316
+ }) : null
249
317
  ] }) });
250
318
  };
251
319
  export {
@@ -1,7 +1,7 @@
1
1
  const de = {
2
2
  "plugin.name": "Relationsnamen",
3
3
  "settings.title": "Relationsnamen",
4
- "settings.description": "Legen Sie fest, wie verknüpfte Einträge im Content Manager angezeigt werden. Verwenden Sie ein oder mehrere skalare Felder des verknüpften Inhaltstyps, zum Beispiel Vor- und Nachname.",
4
+ "settings.description": "Legen Sie fest, wie verknüpfte Einträge beschriftet werden, z. B. mit Vor- und Nachname.",
5
5
  "settings.target": "Verknüpfter Inhalt: {target}",
6
6
  "settings.template": "Bezeichnungsvorlage",
7
7
  "settings.availableFields": "Verfügbare Felder",
@@ -3,7 +3,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const de = {
4
4
  "plugin.name": "Relationsnamen",
5
5
  "settings.title": "Relationsnamen",
6
- "settings.description": "Legen Sie fest, wie verknüpfte Einträge im Content Manager angezeigt werden. Verwenden Sie ein oder mehrere skalare Felder des verknüpften Inhaltstyps, zum Beispiel Vor- und Nachname.",
6
+ "settings.description": "Legen Sie fest, wie verknüpfte Einträge beschriftet werden, z. B. mit Vor- und Nachname.",
7
7
  "settings.target": "Verknüpfter Inhalt: {target}",
8
8
  "settings.template": "Bezeichnungsvorlage",
9
9
  "settings.availableFields": "Verfügbare Felder",
@@ -1,7 +1,7 @@
1
1
  const en = {
2
2
  "plugin.name": "Relation Names",
3
3
  "settings.title": "Relation Names",
4
- "settings.description": "Choose how related entries are displayed in the Content Manager. Use one or more scalar fields from the related content type, such as a first and last name.",
4
+ "settings.description": "Choose how related entries are labeled, using fields such as first and last name.",
5
5
  "settings.target": "Related content: {target}",
6
6
  "settings.template": "Label template",
7
7
  "settings.availableFields": "Available fields",
@@ -3,7 +3,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const en = {
4
4
  "plugin.name": "Relation Names",
5
5
  "settings.title": "Relation Names",
6
- "settings.description": "Choose how related entries are displayed in the Content Manager. Use one or more scalar fields from the related content type, such as a first and last name.",
6
+ "settings.description": "Choose how related entries are labeled, using fields such as first and last name.",
7
7
  "settings.target": "Related content: {target}",
8
8
  "settings.template": "Label template",
9
9
  "settings.availableFields": "Available fields",
@@ -88,7 +88,7 @@ const plugin = {
88
88
  id: `${PLUGIN_ID}.plugin.name`,
89
89
  defaultMessage: "Relation Names"
90
90
  },
91
- Component: () => import("./SettingsPage-CpTyZSvB.mjs").then((module) => ({ default: module.SettingsPage })),
91
+ Component: () => import("./SettingsPage-DFZP5CZs.mjs").then((module) => ({ default: module.SettingsPage })),
92
92
  permissions: []
93
93
  });
94
94
  app.registerPlugin({
@@ -105,7 +105,7 @@ const plugin = {
105
105
  return Promise.all(
106
106
  locales.map(async (locale) => {
107
107
  try {
108
- const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/de.json": () => import("./de-a3KtzZ7N.mjs"), "./translations/en.json": () => import("./en-8w3y1FSw.mjs") }), `./translations/${locale}.json`, 3);
108
+ const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/de.json": () => import("./de-Bc81LkbY.mjs"), "./translations/en.json": () => import("./en-Bk-HIXNd.mjs") }), `./translations/${locale}.json`, 3);
109
109
  const newData = {};
110
110
  const keys = Object.keys(data);
111
111
  for (const key of keys) {
@@ -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-CaBXOirN.js")).then((module2) => ({ default: module2.SettingsPage })),
92
+ Component: () => Promise.resolve().then(() => require("./SettingsPage-BebR4ghK.js")).then((module2) => ({ default: module2.SettingsPage })),
93
93
  permissions: []
94
94
  });
95
95
  app.registerPlugin({
@@ -106,7 +106,7 @@ const plugin = {
106
106
  return Promise.all(
107
107
  locales.map(async (locale) => {
108
108
  try {
109
- const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/de.json": () => Promise.resolve().then(() => require("./de-BBrO1tf0.js")), "./translations/en.json": () => Promise.resolve().then(() => require("./en-DlY6ZGdo.js")) }), `./translations/${locale}.json`, 3);
109
+ const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/de.json": () => Promise.resolve().then(() => require("./de-CsBvOsWR.js")), "./translations/en.json": () => Promise.resolve().then(() => require("./en-DT4g6X2H.js")) }), `./translations/${locale}.json`, 3);
110
110
  const newData = {};
111
111
  const keys = Object.keys(data);
112
112
  for (const key of keys) {
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3
- const index = require("./index-CKaZcnCM.js");
3
+ const index = require("./index-DfSAe961.js");
4
4
  exports.default = index.plugin;
@@ -1,4 +1,4 @@
1
- import { p } from "./index-DGXo1dxk.mjs";
1
+ import { p } from "./index-CiS6cuI9.mjs";
2
2
  export {
3
3
  p as default
4
4
  };
@@ -1,6 +1,8 @@
1
1
  type TemplateAttribute = {
2
2
  type?: string;
3
3
  component?: string;
4
+ target?: string;
5
+ targetModel?: string;
4
6
  private?: boolean;
5
7
  };
6
8
  type TemplateSchema = {
@@ -164,6 +164,9 @@ const PLACEHOLDER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)
164
164
  const isRecord$1 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
165
165
  const getPathValue = (value, path) => {
166
166
  return path.split(".").reduce((current, segment) => {
167
+ if (Array.isArray(current)) {
168
+ return current.map((item) => isRecord$1(item) ? item[segment] : void 0).filter((item) => item !== void 0);
169
+ }
167
170
  return isRecord$1(current) ? current[segment] : void 0;
168
171
  }, value);
169
172
  };
@@ -215,10 +218,11 @@ const compileTemplate = (template, targetSchema, resolveSchema) => {
215
218
  if (isLastSegment) {
216
219
  return SCALAR_TYPES.has(attribute.type ?? "");
217
220
  }
218
- if (attribute.type !== "component" || !attribute.component || !resolveSchema) {
221
+ const nestedSchemaUid = attribute.type === "component" ? attribute.component : attribute.type === "relation" ? attribute.target : void 0;
222
+ if (!nestedSchemaUid || !resolveSchema) {
219
223
  return false;
220
224
  }
221
- schema = resolveSchema(attribute.component);
225
+ schema = resolveSchema(nestedSchemaUid);
222
226
  if (!schema) {
223
227
  return false;
224
228
  }
@@ -329,10 +333,32 @@ const getRuntime = async (strapi, settings2, sourceUid, targetField, userAbility
329
333
  };
330
334
  };
331
335
  const buildPopulate = (placeholders) => {
332
- const roots = new Set(
333
- placeholders.filter((placeholder) => placeholder.includes(".")).map((placeholder) => placeholder.split(".")[0])
334
- );
335
- return roots.size > 0 ? Object.fromEntries(Array.from(roots, (root) => [root, true])) : void 0;
336
+ const populate = {};
337
+ for (const placeholder of placeholders) {
338
+ const segments = placeholder.split(".");
339
+ if (segments.length < 2) {
340
+ continue;
341
+ }
342
+ let current = populate;
343
+ for (const [index2, segment] of segments.slice(0, -1).entries()) {
344
+ const existing = current[segment];
345
+ if (existing === true) {
346
+ break;
347
+ }
348
+ if (index2 === segments.length - 2) {
349
+ current[segment] = existing ?? true;
350
+ break;
351
+ }
352
+ if (!existing) {
353
+ current[segment] = { populate: {} };
354
+ }
355
+ const next = current[segment];
356
+ if (next !== true) {
357
+ current = next.populate;
358
+ }
359
+ }
360
+ }
361
+ return Object.keys(populate).length > 0 ? populate : void 0;
336
362
  };
337
363
  const hydrateValues = async (strapi, ctx, runtime, values) => {
338
364
  const targetSchema = strapi.getModel(runtime.targetUid);
@@ -162,6 +162,9 @@ const PLACEHOLDER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)
162
162
  const isRecord$1 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
163
163
  const getPathValue = (value, path) => {
164
164
  return path.split(".").reduce((current, segment) => {
165
+ if (Array.isArray(current)) {
166
+ return current.map((item) => isRecord$1(item) ? item[segment] : void 0).filter((item) => item !== void 0);
167
+ }
165
168
  return isRecord$1(current) ? current[segment] : void 0;
166
169
  }, value);
167
170
  };
@@ -213,10 +216,11 @@ const compileTemplate = (template, targetSchema, resolveSchema) => {
213
216
  if (isLastSegment) {
214
217
  return SCALAR_TYPES.has(attribute.type ?? "");
215
218
  }
216
- if (attribute.type !== "component" || !attribute.component || !resolveSchema) {
219
+ const nestedSchemaUid = attribute.type === "component" ? attribute.component : attribute.type === "relation" ? attribute.target : void 0;
220
+ if (!nestedSchemaUid || !resolveSchema) {
217
221
  return false;
218
222
  }
219
- schema = resolveSchema(attribute.component);
223
+ schema = resolveSchema(nestedSchemaUid);
220
224
  if (!schema) {
221
225
  return false;
222
226
  }
@@ -327,10 +331,32 @@ const getRuntime = async (strapi, settings2, sourceUid, targetField, userAbility
327
331
  };
328
332
  };
329
333
  const buildPopulate = (placeholders) => {
330
- const roots = new Set(
331
- placeholders.filter((placeholder) => placeholder.includes(".")).map((placeholder) => placeholder.split(".")[0])
332
- );
333
- return roots.size > 0 ? Object.fromEntries(Array.from(roots, (root) => [root, true])) : void 0;
334
+ const populate = {};
335
+ for (const placeholder of placeholders) {
336
+ const segments = placeholder.split(".");
337
+ if (segments.length < 2) {
338
+ continue;
339
+ }
340
+ let current = populate;
341
+ for (const [index2, segment] of segments.slice(0, -1).entries()) {
342
+ const existing = current[segment];
343
+ if (existing === true) {
344
+ break;
345
+ }
346
+ if (index2 === segments.length - 2) {
347
+ current[segment] = existing ?? true;
348
+ break;
349
+ }
350
+ if (!existing) {
351
+ current[segment] = { populate: {} };
352
+ }
353
+ const next = current[segment];
354
+ if (next !== true) {
355
+ current = next.populate;
356
+ }
357
+ }
358
+ }
359
+ return Object.keys(populate).length > 0 ? populate : void 0;
334
360
  };
335
361
  const hydrateValues = async (strapi, ctx, runtime, values) => {
336
362
  const targetSchema = strapi.getModel(runtime.targetUid);
@@ -1,6 +1,9 @@
1
1
  import { Core } from '@strapi/strapi';
2
2
  import { SchemaLike } from './utils/relation-labels';
3
- declare const buildPopulate: (placeholders: string[]) => Record<string, true> | undefined;
3
+ type Populate = Record<string, true | {
4
+ populate: Populate;
5
+ }>;
6
+ declare const buildPopulate: (placeholders: string[]) => Populate | undefined;
4
7
  declare const relationLabels: ({ strapi }: {
5
8
  strapi: Core.Strapi;
6
9
  }) => {
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "author": "Christoph Tupi <christoph.tupi@gmail.com>",
6
6
  "keywords": [],
7
7
  "type": "commonjs",
8
- "version": "1.1.0",
8
+ "version": "1.2.1",
9
9
  "strapi": {
10
10
  "kind": "plugin",
11
11
  "name": "strapi-relation-names",