@strapi/content-manager 0.0.0-experimental.abc → 0.0.0-experimental.ae69a6ec6a65b1061cd6a00b2608abeeff436042

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 (136) hide show
  1. package/dist/_chunks/{ComponentConfigurationPage-B3yDbeU1.mjs → ComponentConfigurationPage-DH3rgf1K.mjs} +4 -4
  2. package/dist/_chunks/{ComponentConfigurationPage-B3yDbeU1.mjs.map → ComponentConfigurationPage-DH3rgf1K.mjs.map} +1 -1
  3. package/dist/_chunks/{ComponentConfigurationPage-KXSuLnQD.js → ComponentConfigurationPage-DRh2GoZx.js} +4 -4
  4. package/dist/_chunks/{ComponentConfigurationPage-KXSuLnQD.js.map → ComponentConfigurationPage-DRh2GoZx.js.map} +1 -1
  5. package/dist/_chunks/{EditConfigurationPage-BQ17--5R.js → EditConfigurationPage-BOAqRJUV.js} +4 -4
  6. package/dist/_chunks/{EditConfigurationPage-BQ17--5R.js.map → EditConfigurationPage-BOAqRJUV.js.map} +1 -1
  7. package/dist/_chunks/{EditConfigurationPage-D7PrLO8j.mjs → EditConfigurationPage-C1HHivAz.mjs} +4 -4
  8. package/dist/_chunks/{EditConfigurationPage-D7PrLO8j.mjs.map → EditConfigurationPage-C1HHivAz.mjs.map} +1 -1
  9. package/dist/_chunks/{EditViewPage-BgjdnGz2.js → EditViewPage-CgfL33yw.js} +15 -5
  10. package/dist/_chunks/EditViewPage-CgfL33yw.js.map +1 -0
  11. package/dist/_chunks/{EditViewPage-B7VgwJaG.mjs → EditViewPage-DBV7CZVf.mjs} +15 -5
  12. package/dist/_chunks/EditViewPage-DBV7CZVf.mjs.map +1 -0
  13. package/dist/_chunks/{Field-tHCw4lGA.mjs → Field-BQFK_H87.mjs} +98 -85
  14. package/dist/_chunks/Field-BQFK_H87.mjs.map +1 -0
  15. package/dist/_chunks/{Field-CdK7ZLmv.js → Field-C647fIuP.js} +101 -88
  16. package/dist/_chunks/Field-C647fIuP.js.map +1 -0
  17. package/dist/_chunks/{Form-BJxdTv3Q.mjs → Form--31reETM.mjs} +16 -8
  18. package/dist/_chunks/Form--31reETM.mjs.map +1 -0
  19. package/dist/_chunks/{Form-C_0KTVvV.js → Form-CsYUrVEA.js} +16 -8
  20. package/dist/_chunks/Form-CsYUrVEA.js.map +1 -0
  21. package/dist/_chunks/{History-DR2txJLE.mjs → History-8tQaor_-.mjs} +25 -12
  22. package/dist/_chunks/History-8tQaor_-.mjs.map +1 -0
  23. package/dist/_chunks/{History-nuEzM5qm.js → History-Dl6wOm0V.js} +24 -11
  24. package/dist/_chunks/History-Dl6wOm0V.js.map +1 -0
  25. package/dist/_chunks/{ListConfigurationPage-CnB86Psm.js → ListConfigurationPage-BedUJnmo.js} +3 -3
  26. package/dist/_chunks/{ListConfigurationPage-CnB86Psm.js.map → ListConfigurationPage-BedUJnmo.js.map} +1 -1
  27. package/dist/_chunks/{ListConfigurationPage-voFVtXu6.mjs → ListConfigurationPage-_WWIGpVT.mjs} +3 -3
  28. package/dist/_chunks/{ListConfigurationPage-voFVtXu6.mjs.map → ListConfigurationPage-_WWIGpVT.mjs.map} +1 -1
  29. package/dist/_chunks/{ListViewPage-SXIXm-RM.js → ListViewPage-5QzoAtAo.js} +55 -40
  30. package/dist/_chunks/ListViewPage-5QzoAtAo.js.map +1 -0
  31. package/dist/_chunks/{ListViewPage-B_GaWgRH.mjs → ListViewPage-CZfw9OW9.mjs} +52 -37
  32. package/dist/_chunks/ListViewPage-CZfw9OW9.mjs.map +1 -0
  33. package/dist/_chunks/{NoContentTypePage-CYiGpsbj.mjs → NoContentTypePage-D-RqshUI.mjs} +2 -2
  34. package/dist/_chunks/{NoContentTypePage-CYiGpsbj.mjs.map → NoContentTypePage-D-RqshUI.mjs.map} +1 -1
  35. package/dist/_chunks/{NoContentTypePage-BzsQ3hLZ.js → NoContentTypePage-DBIyA7hd.js} +2 -2
  36. package/dist/_chunks/{NoContentTypePage-BzsQ3hLZ.js.map → NoContentTypePage-DBIyA7hd.js.map} +1 -1
  37. package/dist/_chunks/{NoPermissionsPage-IGkId4C5.js → NoPermissionsPage-CCAreseM.js} +2 -2
  38. package/dist/_chunks/{NoPermissionsPage-IGkId4C5.js.map → NoPermissionsPage-CCAreseM.js.map} +1 -1
  39. package/dist/_chunks/{NoPermissionsPage-B5baIHal.mjs → NoPermissionsPage-m7GAd26r.mjs} +2 -2
  40. package/dist/_chunks/{NoPermissionsPage-B5baIHal.mjs.map → NoPermissionsPage-m7GAd26r.mjs.map} +1 -1
  41. package/dist/_chunks/{Relations-CIYDdKU-.mjs → Relations-Dq52hb_u.mjs} +69 -36
  42. package/dist/_chunks/Relations-Dq52hb_u.mjs.map +1 -0
  43. package/dist/_chunks/{Relations-Dhuurpx2.js → Relations-SRwPhmo7.js} +68 -35
  44. package/dist/_chunks/Relations-SRwPhmo7.js.map +1 -0
  45. package/dist/_chunks/{en-uOUIxfcQ.js → en-Bm0D0IWz.js} +13 -12
  46. package/dist/_chunks/{en-uOUIxfcQ.js.map → en-Bm0D0IWz.js.map} +1 -1
  47. package/dist/_chunks/{en-BrCTWlZv.mjs → en-DKV44jRb.mjs} +13 -12
  48. package/dist/_chunks/{en-BrCTWlZv.mjs.map → en-DKV44jRb.mjs.map} +1 -1
  49. package/dist/_chunks/{index-C9TJPyni.mjs → index-1Bdj-6ls.mjs} +2067 -1914
  50. package/dist/_chunks/index-1Bdj-6ls.mjs.map +1 -0
  51. package/dist/_chunks/{index-CdT0kHZ8.js → index-CsrCnNa3.js} +2048 -1894
  52. package/dist/_chunks/index-CsrCnNa3.js.map +1 -0
  53. package/dist/_chunks/{layout-C6dxWYT7.js → layout-B33V9Tdu.js} +5 -4
  54. package/dist/_chunks/{layout-C6dxWYT7.js.map → layout-B33V9Tdu.js.map} +1 -1
  55. package/dist/_chunks/{layout-BNqvLR_b.mjs → layout-ByD1cQSW.mjs} +5 -4
  56. package/dist/_chunks/{layout-BNqvLR_b.mjs.map → layout-ByD1cQSW.mjs.map} +1 -1
  57. package/dist/_chunks/{objects-gigeqt7s.js → objects-BcXOv6_9.js} +2 -4
  58. package/dist/_chunks/{objects-gigeqt7s.js.map → objects-BcXOv6_9.js.map} +1 -1
  59. package/dist/_chunks/{objects-mKMAmfec.mjs → objects-D6yBsdmx.mjs} +2 -4
  60. package/dist/_chunks/{objects-mKMAmfec.mjs.map → objects-D6yBsdmx.mjs.map} +1 -1
  61. package/dist/_chunks/{relations-DtFaDnP1.js → relations-BBHlo3qQ.js} +3 -7
  62. package/dist/_chunks/relations-BBHlo3qQ.js.map +1 -0
  63. package/dist/_chunks/{relations-CkKqKw65.mjs → relations-DFwbNLZ0.mjs} +3 -7
  64. package/dist/_chunks/relations-DFwbNLZ0.mjs.map +1 -0
  65. package/dist/_chunks/{usePrev-B9w_-eYc.js → useDebounce-CtcjDB3L.js} +14 -1
  66. package/dist/_chunks/useDebounce-CtcjDB3L.js.map +1 -0
  67. package/dist/_chunks/useDebounce-DmuSJIF3.mjs +29 -0
  68. package/dist/_chunks/useDebounce-DmuSJIF3.mjs.map +1 -0
  69. package/dist/admin/index.js +2 -1
  70. package/dist/admin/index.js.map +1 -1
  71. package/dist/admin/index.mjs +5 -4
  72. package/dist/admin/src/exports.d.ts +1 -1
  73. package/dist/admin/src/history/services/historyVersion.d.ts +1 -1
  74. package/dist/admin/src/hooks/useDocument.d.ts +32 -1
  75. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/WysiwygStyles.d.ts +0 -32
  76. package/dist/admin/src/pages/EditView/components/Header.d.ts +11 -11
  77. package/dist/admin/src/preview/constants.d.ts +1 -0
  78. package/dist/admin/src/preview/index.d.ts +4 -0
  79. package/dist/admin/src/services/api.d.ts +1 -1
  80. package/dist/admin/src/services/components.d.ts +2 -2
  81. package/dist/admin/src/services/contentTypes.d.ts +3 -3
  82. package/dist/admin/src/services/documents.d.ts +19 -17
  83. package/dist/admin/src/services/init.d.ts +1 -1
  84. package/dist/admin/src/services/relations.d.ts +2 -2
  85. package/dist/admin/src/services/uid.d.ts +3 -3
  86. package/dist/admin/src/utils/validation.d.ts +4 -1
  87. package/dist/server/index.js +132 -55
  88. package/dist/server/index.js.map +1 -1
  89. package/dist/server/index.mjs +132 -55
  90. package/dist/server/index.mjs.map +1 -1
  91. package/dist/server/src/bootstrap.d.ts.map +1 -1
  92. package/dist/server/src/controllers/collection-types.d.ts.map +1 -1
  93. package/dist/server/src/controllers/relations.d.ts.map +1 -1
  94. package/dist/server/src/controllers/utils/metadata.d.ts +15 -1
  95. package/dist/server/src/controllers/utils/metadata.d.ts.map +1 -1
  96. package/dist/server/src/history/services/history.d.ts.map +1 -1
  97. package/dist/server/src/history/services/lifecycles.d.ts.map +1 -1
  98. package/dist/server/src/history/services/utils.d.ts +1 -0
  99. package/dist/server/src/history/services/utils.d.ts.map +1 -1
  100. package/dist/server/src/index.d.ts +4 -4
  101. package/dist/server/src/policies/hasPermissions.d.ts.map +1 -1
  102. package/dist/server/src/preview/constants.d.ts +2 -0
  103. package/dist/server/src/preview/constants.d.ts.map +1 -0
  104. package/dist/server/src/preview/index.d.ts +4 -0
  105. package/dist/server/src/preview/index.d.ts.map +1 -0
  106. package/dist/server/src/services/document-metadata.d.ts +8 -8
  107. package/dist/server/src/services/document-metadata.d.ts.map +1 -1
  108. package/dist/server/src/services/index.d.ts +4 -4
  109. package/dist/server/src/services/permission-checker.d.ts.map +1 -1
  110. package/dist/server/src/services/utils/configuration/index.d.ts +2 -2
  111. package/dist/server/src/services/utils/configuration/layouts.d.ts +2 -2
  112. package/dist/server/src/utils/index.d.ts +2 -0
  113. package/dist/server/src/utils/index.d.ts.map +1 -1
  114. package/dist/shared/contracts/collection-types.d.ts +3 -1
  115. package/dist/shared/contracts/collection-types.d.ts.map +1 -1
  116. package/package.json +13 -13
  117. package/dist/_chunks/EditViewPage-B7VgwJaG.mjs.map +0 -1
  118. package/dist/_chunks/EditViewPage-BgjdnGz2.js.map +0 -1
  119. package/dist/_chunks/Field-CdK7ZLmv.js.map +0 -1
  120. package/dist/_chunks/Field-tHCw4lGA.mjs.map +0 -1
  121. package/dist/_chunks/Form-BJxdTv3Q.mjs.map +0 -1
  122. package/dist/_chunks/Form-C_0KTVvV.js.map +0 -1
  123. package/dist/_chunks/History-DR2txJLE.mjs.map +0 -1
  124. package/dist/_chunks/History-nuEzM5qm.js.map +0 -1
  125. package/dist/_chunks/ListViewPage-B_GaWgRH.mjs.map +0 -1
  126. package/dist/_chunks/ListViewPage-SXIXm-RM.js.map +0 -1
  127. package/dist/_chunks/Relations-CIYDdKU-.mjs.map +0 -1
  128. package/dist/_chunks/Relations-Dhuurpx2.js.map +0 -1
  129. package/dist/_chunks/index-C9TJPyni.mjs.map +0 -1
  130. package/dist/_chunks/index-CdT0kHZ8.js.map +0 -1
  131. package/dist/_chunks/relations-CkKqKw65.mjs.map +0 -1
  132. package/dist/_chunks/relations-DtFaDnP1.js.map +0 -1
  133. package/dist/_chunks/usePrev-B9w_-eYc.js.map +0 -1
  134. package/dist/_chunks/usePrev-DH6iah0A.mjs +0 -16
  135. package/dist/_chunks/usePrev-DH6iah0A.mjs.map +0 -1
  136. package/strapi-server.js +0 -3
@@ -4,12 +4,13 @@ const jsxRuntime = require("react/jsx-runtime");
4
4
  const strapiAdmin = require("@strapi/admin/strapi-admin");
5
5
  const React = require("react");
6
6
  const designSystem = require("@strapi/design-system");
7
+ const mapValues = require("lodash/fp/mapValues");
7
8
  const reactIntl = require("react-intl");
8
9
  const reactRouterDom = require("react-router-dom");
9
- const styledComponents = require("styled-components");
10
10
  const yup = require("yup");
11
11
  const pipe = require("lodash/fp/pipe");
12
12
  const dateFns = require("date-fns");
13
+ const styledComponents = require("styled-components");
13
14
  const qs = require("qs");
14
15
  const toolkit = require("@reduxjs/toolkit");
15
16
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
@@ -32,6 +33,7 @@ function _interopNamespace(e) {
32
33
  return Object.freeze(n);
33
34
  }
34
35
  const React__namespace = /* @__PURE__ */ _interopNamespace(React);
36
+ const mapValues__default = /* @__PURE__ */ _interopDefault(mapValues);
35
37
  const yup__namespace = /* @__PURE__ */ _interopNamespace(yup);
36
38
  const pipe__default = /* @__PURE__ */ _interopDefault(pipe);
37
39
  const __variableDynamicImportRuntimeHelper = (glob, path) => {
@@ -121,6 +123,7 @@ const DocumentRBAC = ({ children, permissions }) => {
121
123
  if (!slug) {
122
124
  throw new Error("Cannot find the slug param in the URL");
123
125
  }
126
+ const [{ rawQuery }] = strapiAdmin.useQueryParams();
124
127
  const userPermissions = strapiAdmin.useAuth("DocumentRBAC", (state) => state.permissions);
125
128
  const contentTypePermissions = React__namespace.useMemo(() => {
126
129
  const contentTypePermissions2 = userPermissions.filter(
@@ -131,7 +134,14 @@ const DocumentRBAC = ({ children, permissions }) => {
131
134
  return { ...acc, [action]: [permission] };
132
135
  }, {});
133
136
  }, [slug, userPermissions]);
134
- const { isLoading, allowedActions } = strapiAdmin.useRBAC(contentTypePermissions, permissions ?? void 0);
137
+ const { isLoading, allowedActions } = strapiAdmin.useRBAC(
138
+ contentTypePermissions,
139
+ permissions ?? void 0,
140
+ // TODO: useRBAC context should be typed and built differently
141
+ // We are passing raw query as context to the hook so that it can
142
+ // rely on the locale provided from DocumentRBAC for its permission calculations.
143
+ rawQuery
144
+ );
135
145
  const canCreateFields = !isLoading && allowedActions.canCreate ? extractAndDedupeFields(contentTypePermissions.create) : [];
136
146
  const canReadFields = !isLoading && allowedActions.canRead ? extractAndDedupeFields(contentTypePermissions.read) : [];
137
147
  const canUpdateFields = !isLoading && allowedActions.canUpdate ? extractAndDedupeFields(contentTypePermissions.update) : [];
@@ -179,7 +189,8 @@ const contentManagerApi = strapiAdmin.adminApi.enhanceEndpoints({
179
189
  "Document",
180
190
  "InitialData",
181
191
  "HistoryVersion",
182
- "Relations"
192
+ "Relations",
193
+ "UidAvailability"
183
194
  ]
184
195
  });
185
196
  const documentApi = contentManagerApi.injectEndpoints({
@@ -209,7 +220,10 @@ const documentApi = contentManagerApi.injectEndpoints({
209
220
  params
210
221
  }
211
222
  }),
212
- invalidatesTags: (_result, _error, { model }) => [{ type: "Document", id: `${model}_LIST` }]
223
+ invalidatesTags: (_result, _error, { model }) => [
224
+ { type: "Document", id: `${model}_LIST` },
225
+ { type: "UidAvailability", id: model }
226
+ ]
213
227
  }),
214
228
  /**
215
229
  * Creates a new collection-type document. This should ONLY be used for collection-types.
@@ -226,7 +240,8 @@ const documentApi = contentManagerApi.injectEndpoints({
226
240
  }),
227
241
  invalidatesTags: (result, _error, { model }) => [
228
242
  { type: "Document", id: `${model}_LIST` },
229
- "Relations"
243
+ "Relations",
244
+ { type: "UidAvailability", id: model }
230
245
  ]
231
246
  }),
232
247
  deleteDocument: builder.mutation({
@@ -267,7 +282,8 @@ const documentApi = contentManagerApi.injectEndpoints({
267
282
  id: collectionType !== SINGLE_TYPES ? `${model}_${documentId}` : model
268
283
  },
269
284
  { type: "Document", id: `${model}_LIST` },
270
- "Relations"
285
+ "Relations",
286
+ { type: "UidAvailability", id: model }
271
287
  ];
272
288
  }
273
289
  }),
@@ -277,7 +293,7 @@ const documentApi = contentManagerApi.injectEndpoints({
277
293
  */
278
294
  getAllDocuments: builder.query({
279
295
  query: ({ model, params }) => ({
280
- url: `/content-manager/collection-types/${model}`,
296
+ url: `/content-manager/collection-types/${model}${params ? `?${params}` : ""}`,
281
297
  method: "GET",
282
298
  config: {
283
299
  params
@@ -392,7 +408,8 @@ const documentApi = contentManagerApi.injectEndpoints({
392
408
  type: "Document",
393
409
  id: collectionType !== SINGLE_TYPES ? `${model}_${documentId}` : model
394
410
  },
395
- "Relations"
411
+ "Relations",
412
+ { type: "UidAvailability", id: model }
396
413
  ];
397
414
  },
398
415
  async onQueryStarted({ data, ...patch }, { dispatch, queryFulfilled }) {
@@ -475,20 +492,39 @@ const buildValidParams = (query) => {
475
492
  const isBaseQueryError = (error) => {
476
493
  return error.name !== void 0;
477
494
  };
478
- const createYupSchema = (attributes = {}, components = {}) => {
495
+ const arrayValidator = (attribute, options) => ({
496
+ message: strapiAdmin.translatedErrors.required,
497
+ test(value) {
498
+ if (options.status === "draft") {
499
+ return true;
500
+ }
501
+ if (!attribute.required) {
502
+ return true;
503
+ }
504
+ if (!value) {
505
+ return false;
506
+ }
507
+ if (Array.isArray(value) && value.length === 0) {
508
+ return false;
509
+ }
510
+ return true;
511
+ }
512
+ });
513
+ const createYupSchema = (attributes = {}, components = {}, options = { status: null }) => {
479
514
  const createModelSchema = (attributes2) => yup__namespace.object().shape(
480
515
  Object.entries(attributes2).reduce((acc, [name, attribute]) => {
481
516
  if (DOCUMENT_META_FIELDS.includes(name)) {
482
517
  return acc;
483
518
  }
484
519
  const validations = [
520
+ addNullableValidation,
485
521
  addRequiredValidation,
486
522
  addMinLengthValidation,
487
523
  addMaxLengthValidation,
488
524
  addMinValidation,
489
525
  addMaxValidation,
490
526
  addRegexValidation
491
- ].map((fn) => fn(attribute));
527
+ ].map((fn) => fn(attribute, options));
492
528
  const transformSchema = pipe__default.default(...validations);
493
529
  switch (attribute.type) {
494
530
  case "component": {
@@ -498,12 +534,12 @@ const createYupSchema = (attributes = {}, components = {}) => {
498
534
  ...acc,
499
535
  [name]: transformSchema(
500
536
  yup__namespace.array().of(createModelSchema(attributes3).nullable(false))
501
- )
537
+ ).test(arrayValidator(attribute, options))
502
538
  };
503
539
  } else {
504
540
  return {
505
541
  ...acc,
506
- [name]: transformSchema(createModelSchema(attributes3))
542
+ [name]: transformSchema(createModelSchema(attributes3).nullable())
507
543
  };
508
544
  }
509
545
  }
@@ -525,7 +561,7 @@ const createYupSchema = (attributes = {}, components = {}) => {
525
561
  }
526
562
  )
527
563
  )
528
- )
564
+ ).test(arrayValidator(attribute, options))
529
565
  };
530
566
  case "relation":
531
567
  return {
@@ -537,7 +573,7 @@ const createYupSchema = (attributes = {}, components = {}) => {
537
573
  } else if (Array.isArray(value)) {
538
574
  return yup__namespace.array().of(
539
575
  yup__namespace.object().shape({
540
- id: yup__namespace.string().required()
576
+ id: yup__namespace.number().required()
541
577
  })
542
578
  );
543
579
  } else if (typeof value === "object") {
@@ -615,13 +651,7 @@ const createAttributeSchema = (attribute) => {
615
651
  return yup__namespace.mixed();
616
652
  }
617
653
  };
618
- const addRequiredValidation = (attribute) => (schema) => {
619
- if ((attribute.type === "component" && attribute.repeatable || attribute.type === "dynamiczone") && attribute.required && "min" in schema) {
620
- return schema.min(1, strapiAdmin.translatedErrors.required);
621
- }
622
- if (attribute.required && attribute.type !== "relation") {
623
- return schema.required(strapiAdmin.translatedErrors.required);
624
- }
654
+ const nullableSchema = (schema) => {
625
655
  return schema?.nullable ? schema.nullable() : (
626
656
  // In some cases '.nullable' will not be available on the schema.
627
657
  // e.g. when the schema has been built using yup.lazy (e.g. for relations).
@@ -629,7 +659,22 @@ const addRequiredValidation = (attribute) => (schema) => {
629
659
  schema
630
660
  );
631
661
  };
632
- const addMinLengthValidation = (attribute) => (schema) => {
662
+ const addNullableValidation = () => (schema) => {
663
+ return nullableSchema(schema);
664
+ };
665
+ const addRequiredValidation = (attribute, options) => (schema) => {
666
+ if (options.status === "draft" || !attribute.required) {
667
+ return schema;
668
+ }
669
+ if (attribute.required && "required" in schema) {
670
+ return schema.required(strapiAdmin.translatedErrors.required);
671
+ }
672
+ return schema;
673
+ };
674
+ const addMinLengthValidation = (attribute, options) => (schema) => {
675
+ if (options.status === "draft") {
676
+ return schema;
677
+ }
633
678
  if ("minLength" in attribute && attribute.minLength && Number.isInteger(attribute.minLength) && "min" in schema) {
634
679
  return schema.min(attribute.minLength, {
635
680
  ...strapiAdmin.translatedErrors.minLength,
@@ -651,32 +696,13 @@ const addMaxLengthValidation = (attribute) => (schema) => {
651
696
  }
652
697
  return schema;
653
698
  };
654
- const addMinValidation = (attribute) => (schema) => {
655
- if ("min" in attribute) {
699
+ const addMinValidation = (attribute, options) => (schema) => {
700
+ if (options.status === "draft") {
701
+ return schema;
702
+ }
703
+ if ("min" in attribute && "min" in schema) {
656
704
  const min = toInteger(attribute.min);
657
- if (attribute.type === "component" && attribute.repeatable || attribute.type === "dynamiczone") {
658
- if (!attribute.required && "test" in schema && min) {
659
- return schema.test(
660
- "custom-min",
661
- {
662
- ...strapiAdmin.translatedErrors.min,
663
- values: {
664
- min: attribute.min
665
- }
666
- },
667
- (value) => {
668
- if (!value) {
669
- return true;
670
- }
671
- if (Array.isArray(value) && value.length === 0) {
672
- return true;
673
- }
674
- return value.length >= min;
675
- }
676
- );
677
- }
678
- }
679
- if ("min" in schema && min) {
705
+ if (min) {
680
706
  return schema.min(min, {
681
707
  ...strapiAdmin.translatedErrors.min,
682
708
  values: {
@@ -794,19 +820,115 @@ const extractContentTypeComponents = (attributes = {}, allComponents = {}) => {
794
820
  }, {});
795
821
  return componentsByKey;
796
822
  };
797
- const useDocument = (args, opts) => {
823
+ const HOOKS = {
824
+ /**
825
+ * Hook that allows to mutate the displayed headers of the list view table
826
+ * @constant
827
+ * @type {string}
828
+ */
829
+ INJECT_COLUMN_IN_TABLE: "Admin/CM/pages/ListView/inject-column-in-table",
830
+ /**
831
+ * Hook that allows to mutate the CM's collection types links pre-set filters
832
+ * @constant
833
+ * @type {string}
834
+ */
835
+ MUTATE_COLLECTION_TYPES_LINKS: "Admin/CM/pages/App/mutate-collection-types-links",
836
+ /**
837
+ * Hook that allows to mutate the CM's edit view layout
838
+ * @constant
839
+ * @type {string}
840
+ */
841
+ MUTATE_EDIT_VIEW_LAYOUT: "Admin/CM/pages/EditView/mutate-edit-view-layout",
842
+ /**
843
+ * Hook that allows to mutate the CM's single types links pre-set filters
844
+ * @constant
845
+ * @type {string}
846
+ */
847
+ MUTATE_SINGLE_TYPES_LINKS: "Admin/CM/pages/App/mutate-single-types-links"
848
+ };
849
+ const contentTypesApi = contentManagerApi.injectEndpoints({
850
+ endpoints: (builder) => ({
851
+ getContentTypeConfiguration: builder.query({
852
+ query: (uid) => ({
853
+ url: `/content-manager/content-types/${uid}/configuration`,
854
+ method: "GET"
855
+ }),
856
+ transformResponse: (response) => response.data,
857
+ providesTags: (_result, _error, uid) => [
858
+ { type: "ContentTypesConfiguration", id: uid },
859
+ { type: "ContentTypeSettings", id: "LIST" }
860
+ ]
861
+ }),
862
+ getAllContentTypeSettings: builder.query({
863
+ query: () => "/content-manager/content-types-settings",
864
+ transformResponse: (response) => response.data,
865
+ providesTags: [{ type: "ContentTypeSettings", id: "LIST" }]
866
+ }),
867
+ updateContentTypeConfiguration: builder.mutation({
868
+ query: ({ uid, ...body }) => ({
869
+ url: `/content-manager/content-types/${uid}/configuration`,
870
+ method: "PUT",
871
+ data: body
872
+ }),
873
+ transformResponse: (response) => response.data,
874
+ invalidatesTags: (_result, _error, { uid }) => [
875
+ { type: "ContentTypesConfiguration", id: uid },
876
+ { type: "ContentTypeSettings", id: "LIST" },
877
+ // Is this necessary?
878
+ { type: "InitialData" }
879
+ ]
880
+ })
881
+ })
882
+ });
883
+ const {
884
+ useGetContentTypeConfigurationQuery,
885
+ useGetAllContentTypeSettingsQuery,
886
+ useUpdateContentTypeConfigurationMutation
887
+ } = contentTypesApi;
888
+ const checkIfAttributeIsDisplayable = (attribute) => {
889
+ const { type } = attribute;
890
+ if (type === "relation") {
891
+ return !attribute.relation.toLowerCase().includes("morph");
892
+ }
893
+ return !["json", "dynamiczone", "richtext", "password", "blocks"].includes(type) && !!type;
894
+ };
895
+ const getMainField = (attribute, mainFieldName, { schemas, components }) => {
896
+ if (!mainFieldName) {
897
+ return void 0;
898
+ }
899
+ const mainFieldType = attribute.type === "component" ? components[attribute.component].attributes[mainFieldName].type : (
900
+ // @ts-expect-error – `targetModel` does exist on the attribute for a relation.
901
+ schemas.find((schema) => schema.uid === attribute.targetModel)?.attributes[mainFieldName].type
902
+ );
903
+ return {
904
+ name: mainFieldName,
905
+ type: mainFieldType ?? "string"
906
+ };
907
+ };
908
+ const DEFAULT_SETTINGS = {
909
+ bulkable: false,
910
+ filterable: false,
911
+ searchable: false,
912
+ pagination: false,
913
+ defaultSortBy: "",
914
+ defaultSortOrder: "asc",
915
+ mainField: "id",
916
+ pageSize: 10
917
+ };
918
+ const useDocumentLayout = (model) => {
919
+ const { schema, components } = useDocument({ model, collectionType: "" }, { skip: true });
920
+ const [{ query }] = strapiAdmin.useQueryParams();
921
+ const runHookWaterfall = strapiAdmin.useStrapiApp("useDocumentLayout", (state) => state.runHookWaterfall);
798
922
  const { toggleNotification } = strapiAdmin.useNotification();
799
923
  const { _unstableFormatAPIError: formatAPIError } = strapiAdmin.useAPIErrorHandler();
924
+ const { isLoading: isLoadingSchemas, schemas } = useContentTypeSchema();
800
925
  const {
801
- currentData: data,
802
- isLoading: isLoadingDocument,
803
- isFetching: isFetchingDocument,
804
- error
805
- } = useGetDocumentQuery(args, {
806
- ...opts,
807
- skip: !args.documentId && args.collectionType !== SINGLE_TYPES || opts?.skip
808
- });
809
- const { components, schema, isLoading: isLoadingSchema } = useContentTypeSchema(args.model);
926
+ data,
927
+ isLoading: isLoadingConfigs,
928
+ error,
929
+ isFetching: isFetchingConfigs
930
+ } = useGetContentTypeConfigurationQuery(model);
931
+ const isLoading = isLoadingSchemas || isFetchingConfigs || isLoadingConfigs;
810
932
  React__namespace.useEffect(() => {
811
933
  if (error) {
812
934
  toggleNotification({
@@ -814,395 +936,436 @@ const useDocument = (args, opts) => {
814
936
  message: formatAPIError(error)
815
937
  });
816
938
  }
817
- }, [toggleNotification, error, formatAPIError, args.collectionType]);
818
- const validationSchema = React__namespace.useMemo(() => {
819
- if (!schema) {
820
- return null;
821
- }
822
- return createYupSchema(schema.attributes, components);
823
- }, [schema, components]);
824
- const validate = React__namespace.useCallback(
825
- (document) => {
826
- if (!validationSchema) {
827
- throw new Error(
828
- "There is no validation schema generated, this is likely due to the schema not being loaded yet."
829
- );
830
- }
831
- try {
832
- validationSchema.validateSync(document, { abortEarly: false, strict: true });
833
- return null;
834
- } catch (error2) {
835
- if (error2 instanceof yup.ValidationError) {
836
- return strapiAdmin.getYupValidationErrors(error2);
837
- }
838
- throw error2;
839
- }
939
+ }, [error, formatAPIError, toggleNotification]);
940
+ const editLayout = React__namespace.useMemo(
941
+ () => data && !isLoading ? formatEditLayout(data, { schemas, schema, components }) : {
942
+ layout: [],
943
+ components: {},
944
+ metadatas: {},
945
+ options: {},
946
+ settings: DEFAULT_SETTINGS
840
947
  },
841
- [validationSchema]
948
+ [data, isLoading, schemas, schema, components]
949
+ );
950
+ const listLayout = React__namespace.useMemo(() => {
951
+ return data && !isLoading ? formatListLayout(data, { schemas, schema, components }) : {
952
+ layout: [],
953
+ metadatas: {},
954
+ options: {},
955
+ settings: DEFAULT_SETTINGS
956
+ };
957
+ }, [data, isLoading, schemas, schema, components]);
958
+ const { layout: edit } = React__namespace.useMemo(
959
+ () => runHookWaterfall(HOOKS.MUTATE_EDIT_VIEW_LAYOUT, {
960
+ layout: editLayout,
961
+ query
962
+ }),
963
+ [editLayout, query, runHookWaterfall]
842
964
  );
843
- const isLoading = isLoadingDocument || isFetchingDocument || isLoadingSchema;
844
965
  return {
845
- components,
846
- document: data?.data,
847
- meta: data?.meta,
966
+ error,
848
967
  isLoading,
849
- schema,
850
- validate
851
- };
852
- };
853
- const useDoc = () => {
854
- const { id, slug, collectionType, origin } = reactRouterDom.useParams();
855
- const [{ query }] = strapiAdmin.useQueryParams();
856
- const params = React__namespace.useMemo(() => buildValidParams(query), [query]);
857
- if (!collectionType) {
858
- throw new Error("Could not find collectionType in url params");
859
- }
860
- if (!slug) {
861
- throw new Error("Could not find model in url params");
862
- }
863
- return {
864
- collectionType,
865
- model: slug,
866
- id: origin || id === "create" ? void 0 : id,
867
- ...useDocument(
868
- { documentId: origin || id, model: slug, collectionType, params },
869
- {
870
- skip: id === "create" || !origin && !id && collectionType !== SINGLE_TYPES
871
- }
872
- )
968
+ edit,
969
+ list: listLayout
873
970
  };
874
971
  };
875
- const prefixPluginTranslations = (trad, pluginId) => {
876
- if (!pluginId) {
877
- throw new TypeError("pluginId can't be empty");
878
- }
879
- return Object.keys(trad).reduce((acc, current) => {
880
- acc[`${pluginId}.${current}`] = trad[current];
881
- return acc;
882
- }, {});
883
- };
884
- const getTranslation = (id) => `content-manager.${id}`;
885
- const DEFAULT_UNEXPECTED_ERROR_MSG = {
886
- id: "notification.error",
887
- defaultMessage: "An error occurred, please try again"
972
+ const useDocLayout = () => {
973
+ const { model } = useDoc();
974
+ return useDocumentLayout(model);
888
975
  };
889
- const useDocumentActions = () => {
890
- const { toggleNotification } = strapiAdmin.useNotification();
891
- const { formatMessage } = reactIntl.useIntl();
892
- const { trackUsage } = strapiAdmin.useTracking();
893
- const { _unstableFormatAPIError: formatAPIError } = strapiAdmin.useAPIErrorHandler();
894
- const [deleteDocument] = useDeleteDocumentMutation();
895
- const _delete = React__namespace.useCallback(
896
- async ({ collectionType, model, documentId, params }, trackerProperty) => {
897
- try {
898
- trackUsage("willDeleteEntry", trackerProperty);
899
- const res = await deleteDocument({
900
- collectionType,
901
- model,
902
- documentId,
903
- params
904
- });
905
- if ("error" in res) {
906
- toggleNotification({
907
- type: "danger",
908
- message: formatAPIError(res.error)
909
- });
910
- return { error: res.error };
911
- }
912
- toggleNotification({
913
- type: "success",
914
- message: formatMessage({
915
- id: getTranslation("success.record.delete"),
916
- defaultMessage: "Deleted document"
917
- })
918
- });
919
- trackUsage("didDeleteEntry", trackerProperty);
920
- return res.data;
921
- } catch (err) {
922
- toggleNotification({
923
- type: "danger",
924
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
925
- });
926
- trackUsage("didNotDeleteEntry", { error: err, ...trackerProperty });
927
- throw err;
976
+ const formatEditLayout = (data, {
977
+ schemas,
978
+ schema,
979
+ components
980
+ }) => {
981
+ let currentPanelIndex = 0;
982
+ const panelledEditAttributes = convertEditLayoutToFieldLayouts(
983
+ data.contentType.layouts.edit,
984
+ schema?.attributes,
985
+ data.contentType.metadatas,
986
+ { configurations: data.components, schemas: components },
987
+ schemas
988
+ ).reduce((panels, row) => {
989
+ if (row.some((field) => field.type === "dynamiczone")) {
990
+ panels.push([row]);
991
+ currentPanelIndex += 2;
992
+ } else {
993
+ if (!panels[currentPanelIndex]) {
994
+ panels.push([]);
928
995
  }
929
- },
930
- [trackUsage, deleteDocument, toggleNotification, formatMessage, formatAPIError]
931
- );
932
- const [deleteManyDocuments] = useDeleteManyDocumentsMutation();
933
- const deleteMany = React__namespace.useCallback(
934
- async ({ model, documentIds, params }) => {
935
- try {
936
- trackUsage("willBulkDeleteEntries");
937
- const res = await deleteManyDocuments({
938
- model,
939
- documentIds,
940
- params
941
- });
942
- if ("error" in res) {
943
- toggleNotification({
944
- type: "danger",
945
- message: formatAPIError(res.error)
946
- });
947
- return { error: res.error };
996
+ panels[currentPanelIndex].push(row);
997
+ }
998
+ return panels;
999
+ }, []);
1000
+ const componentEditAttributes = Object.entries(data.components).reduce(
1001
+ (acc, [uid, configuration]) => {
1002
+ acc[uid] = {
1003
+ layout: convertEditLayoutToFieldLayouts(
1004
+ configuration.layouts.edit,
1005
+ components[uid].attributes,
1006
+ configuration.metadatas,
1007
+ { configurations: data.components, schemas: components }
1008
+ ),
1009
+ settings: {
1010
+ ...configuration.settings,
1011
+ icon: components[uid].info.icon,
1012
+ displayName: components[uid].info.displayName
948
1013
  }
949
- toggleNotification({
950
- type: "success",
951
- title: formatMessage({
952
- id: getTranslation("success.records.delete"),
953
- defaultMessage: "Successfully deleted."
954
- }),
955
- message: ""
956
- });
957
- trackUsage("didBulkDeleteEntries");
958
- return res.data;
959
- } catch (err) {
960
- toggleNotification({
961
- type: "danger",
962
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
963
- });
964
- trackUsage("didNotBulkDeleteEntries");
965
- throw err;
966
- }
1014
+ };
1015
+ return acc;
967
1016
  },
968
- [trackUsage, deleteManyDocuments, toggleNotification, formatMessage, formatAPIError]
1017
+ {}
969
1018
  );
970
- const [discardDocument] = useDiscardDocumentMutation();
971
- const discard = React__namespace.useCallback(
972
- async ({ collectionType, model, documentId, params }) => {
973
- try {
974
- const res = await discardDocument({
975
- collectionType,
976
- model,
977
- documentId,
978
- params
979
- });
980
- if ("error" in res) {
981
- toggleNotification({
982
- type: "danger",
983
- message: formatAPIError(res.error)
984
- });
985
- return { error: res.error };
986
- }
987
- toggleNotification({
988
- type: "success",
989
- message: formatMessage({
990
- id: "content-manager.success.record.discard",
991
- defaultMessage: "Changes discarded"
992
- })
993
- });
994
- return res.data;
995
- } catch (err) {
996
- toggleNotification({
997
- type: "danger",
998
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
999
- });
1000
- throw err;
1001
- }
1019
+ const editMetadatas = Object.entries(data.contentType.metadatas).reduce(
1020
+ (acc, [attribute, metadata]) => {
1021
+ return {
1022
+ ...acc,
1023
+ [attribute]: metadata.edit
1024
+ };
1002
1025
  },
1003
- [discardDocument, formatAPIError, formatMessage, toggleNotification]
1026
+ {}
1004
1027
  );
1005
- const [publishDocument] = usePublishDocumentMutation();
1006
- const publish = React__namespace.useCallback(
1007
- async ({ collectionType, model, documentId, params }, data) => {
1008
- try {
1009
- trackUsage("willPublishEntry");
1010
- const res = await publishDocument({
1011
- collectionType,
1012
- model,
1013
- documentId,
1014
- data,
1015
- params
1016
- });
1017
- if ("error" in res) {
1018
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1019
- return { error: res.error };
1020
- }
1021
- trackUsage("didPublishEntry");
1022
- toggleNotification({
1023
- type: "success",
1024
- message: formatMessage({
1025
- id: getTranslation("success.record.publish"),
1026
- defaultMessage: "Published document"
1027
- })
1028
- });
1029
- return res.data;
1030
- } catch (err) {
1031
- toggleNotification({
1032
- type: "danger",
1033
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1034
- });
1035
- throw err;
1036
- }
1028
+ return {
1029
+ layout: panelledEditAttributes,
1030
+ components: componentEditAttributes,
1031
+ metadatas: editMetadatas,
1032
+ settings: {
1033
+ ...data.contentType.settings,
1034
+ displayName: schema?.info.displayName
1037
1035
  },
1038
- [trackUsage, publishDocument, toggleNotification, formatMessage, formatAPIError]
1039
- );
1040
- const [publishManyDocuments] = usePublishManyDocumentsMutation();
1041
- const publishMany = React__namespace.useCallback(
1042
- async ({ model, documentIds, params }) => {
1043
- try {
1044
- const res = await publishManyDocuments({
1045
- model,
1046
- documentIds,
1047
- params
1048
- });
1049
- if ("error" in res) {
1050
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1051
- return { error: res.error };
1052
- }
1053
- toggleNotification({
1054
- type: "success",
1055
- message: formatMessage({
1056
- id: getTranslation("success.record.publish"),
1057
- defaultMessage: "Published document"
1058
- })
1059
- });
1060
- return res.data;
1061
- } catch (err) {
1062
- toggleNotification({
1063
- type: "danger",
1064
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1065
- });
1066
- throw err;
1036
+ options: {
1037
+ ...schema?.options,
1038
+ ...schema?.pluginOptions,
1039
+ ...data.contentType.options
1040
+ }
1041
+ };
1042
+ };
1043
+ const convertEditLayoutToFieldLayouts = (rows, attributes = {}, metadatas, components, schemas = []) => {
1044
+ return rows.map(
1045
+ (row) => row.map((field) => {
1046
+ const attribute = attributes[field.name];
1047
+ if (!attribute) {
1048
+ return null;
1067
1049
  }
1068
- },
1069
- [
1070
- // trackUsage,
1071
- publishManyDocuments,
1072
- toggleNotification,
1073
- formatMessage,
1074
- formatAPIError
1075
- ]
1050
+ const { edit: metadata } = metadatas[field.name];
1051
+ const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
1052
+ return {
1053
+ attribute,
1054
+ disabled: !metadata.editable,
1055
+ hint: metadata.description,
1056
+ label: metadata.label ?? "",
1057
+ name: field.name,
1058
+ // @ts-expect-error – mainField does exist on the metadata for a relation.
1059
+ mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
1060
+ schemas,
1061
+ components: components?.schemas ?? {}
1062
+ }),
1063
+ placeholder: metadata.placeholder ?? "",
1064
+ required: attribute.required ?? false,
1065
+ size: field.size,
1066
+ unique: "unique" in attribute ? attribute.unique : false,
1067
+ visible: metadata.visible ?? true,
1068
+ type: attribute.type
1069
+ };
1070
+ }).filter((field) => field !== null)
1076
1071
  );
1077
- const [updateDocument] = useUpdateDocumentMutation();
1078
- const update = React__namespace.useCallback(
1079
- async ({ collectionType, model, documentId, params }, data, trackerProperty) => {
1080
- try {
1081
- trackUsage("willEditEntry", trackerProperty);
1082
- const res = await updateDocument({
1083
- collectionType,
1084
- model,
1085
- documentId,
1086
- data,
1087
- params
1088
- });
1089
- if ("error" in res) {
1090
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1091
- trackUsage("didNotEditEntry", { error: res.error, ...trackerProperty });
1092
- return { error: res.error };
1072
+ };
1073
+ const formatListLayout = (data, {
1074
+ schemas,
1075
+ schema,
1076
+ components
1077
+ }) => {
1078
+ const listMetadatas = Object.entries(data.contentType.metadatas).reduce(
1079
+ (acc, [attribute, metadata]) => {
1080
+ return {
1081
+ ...acc,
1082
+ [attribute]: metadata.list
1083
+ };
1084
+ },
1085
+ {}
1086
+ );
1087
+ const listAttributes = convertListLayoutToFieldLayouts(
1088
+ data.contentType.layouts.list,
1089
+ schema?.attributes,
1090
+ listMetadatas,
1091
+ { configurations: data.components, schemas: components },
1092
+ schemas
1093
+ );
1094
+ return {
1095
+ layout: listAttributes,
1096
+ settings: { ...data.contentType.settings, displayName: schema?.info.displayName },
1097
+ metadatas: listMetadatas,
1098
+ options: {
1099
+ ...schema?.options,
1100
+ ...schema?.pluginOptions,
1101
+ ...data.contentType.options
1102
+ }
1103
+ };
1104
+ };
1105
+ const convertListLayoutToFieldLayouts = (columns, attributes = {}, metadatas, components, schemas = []) => {
1106
+ return columns.map((name) => {
1107
+ const attribute = attributes[name];
1108
+ if (!attribute) {
1109
+ return null;
1110
+ }
1111
+ const metadata = metadatas[name];
1112
+ const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
1113
+ return {
1114
+ attribute,
1115
+ label: metadata.label ?? "",
1116
+ mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
1117
+ schemas,
1118
+ components: components?.schemas ?? {}
1119
+ }),
1120
+ name,
1121
+ searchable: metadata.searchable ?? true,
1122
+ sortable: metadata.sortable ?? true
1123
+ };
1124
+ }).filter((field) => field !== null);
1125
+ };
1126
+ const useDocument = (args, opts) => {
1127
+ const { toggleNotification } = strapiAdmin.useNotification();
1128
+ const { _unstableFormatAPIError: formatAPIError } = strapiAdmin.useAPIErrorHandler();
1129
+ const {
1130
+ currentData: data,
1131
+ isLoading: isLoadingDocument,
1132
+ isFetching: isFetchingDocument,
1133
+ error
1134
+ } = useGetDocumentQuery(args, {
1135
+ ...opts,
1136
+ skip: !args.documentId && args.collectionType !== SINGLE_TYPES || opts?.skip
1137
+ });
1138
+ const {
1139
+ components,
1140
+ schema,
1141
+ schemas,
1142
+ isLoading: isLoadingSchema
1143
+ } = useContentTypeSchema(args.model);
1144
+ React__namespace.useEffect(() => {
1145
+ if (error) {
1146
+ toggleNotification({
1147
+ type: "danger",
1148
+ message: formatAPIError(error)
1149
+ });
1150
+ }
1151
+ }, [toggleNotification, error, formatAPIError, args.collectionType]);
1152
+ const validationSchema = React__namespace.useMemo(() => {
1153
+ if (!schema) {
1154
+ return null;
1155
+ }
1156
+ return createYupSchema(schema.attributes, components);
1157
+ }, [schema, components]);
1158
+ const validate = React__namespace.useCallback(
1159
+ (document) => {
1160
+ if (!validationSchema) {
1161
+ throw new Error(
1162
+ "There is no validation schema generated, this is likely due to the schema not being loaded yet."
1163
+ );
1164
+ }
1165
+ try {
1166
+ validationSchema.validateSync(document, { abortEarly: false, strict: true });
1167
+ return null;
1168
+ } catch (error2) {
1169
+ if (error2 instanceof yup.ValidationError) {
1170
+ return strapiAdmin.getYupValidationErrors(error2);
1093
1171
  }
1094
- trackUsage("didEditEntry", trackerProperty);
1095
- toggleNotification({
1096
- type: "success",
1097
- message: formatMessage({
1098
- id: getTranslation("success.record.save"),
1099
- defaultMessage: "Saved document"
1100
- })
1101
- });
1102
- return res.data;
1103
- } catch (err) {
1104
- trackUsage("didNotEditEntry", { error: err, ...trackerProperty });
1105
- toggleNotification({
1106
- type: "danger",
1107
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1108
- });
1109
- throw err;
1172
+ throw error2;
1110
1173
  }
1111
1174
  },
1112
- [trackUsage, updateDocument, toggleNotification, formatMessage, formatAPIError]
1175
+ [validationSchema]
1113
1176
  );
1114
- const [unpublishDocument] = useUnpublishDocumentMutation();
1115
- const unpublish = React__namespace.useCallback(
1116
- async ({ collectionType, model, documentId, params }, discardDraft = false) => {
1177
+ const isLoading = isLoadingDocument || isFetchingDocument || isLoadingSchema;
1178
+ const hasError = !!error;
1179
+ return {
1180
+ components,
1181
+ document: data?.data,
1182
+ meta: data?.meta,
1183
+ isLoading,
1184
+ hasError,
1185
+ schema,
1186
+ schemas,
1187
+ validate
1188
+ };
1189
+ };
1190
+ const useDoc = () => {
1191
+ const { id, slug, collectionType, origin } = reactRouterDom.useParams();
1192
+ const [{ query }] = strapiAdmin.useQueryParams();
1193
+ const params = React__namespace.useMemo(() => buildValidParams(query), [query]);
1194
+ if (!collectionType) {
1195
+ throw new Error("Could not find collectionType in url params");
1196
+ }
1197
+ if (!slug) {
1198
+ throw new Error("Could not find model in url params");
1199
+ }
1200
+ const document = useDocument(
1201
+ { documentId: origin || id, model: slug, collectionType, params },
1202
+ {
1203
+ skip: id === "create" || !origin && !id && collectionType !== SINGLE_TYPES
1204
+ }
1205
+ );
1206
+ const returnId = origin || id === "create" ? void 0 : id;
1207
+ return {
1208
+ collectionType,
1209
+ model: slug,
1210
+ id: returnId,
1211
+ ...document
1212
+ };
1213
+ };
1214
+ const useContentManagerContext = () => {
1215
+ const {
1216
+ collectionType,
1217
+ model,
1218
+ id,
1219
+ components,
1220
+ isLoading: isLoadingDoc,
1221
+ schema,
1222
+ schemas
1223
+ } = useDoc();
1224
+ const layout = useDocumentLayout(model);
1225
+ const form = strapiAdmin.useForm("useContentManagerContext", (state) => state);
1226
+ const isSingleType = collectionType === SINGLE_TYPES;
1227
+ const slug = model;
1228
+ const isCreatingEntry = id === "create";
1229
+ useContentTypeSchema();
1230
+ const isLoading = isLoadingDoc || layout.isLoading;
1231
+ const error = layout.error;
1232
+ return {
1233
+ error,
1234
+ isLoading,
1235
+ // Base metadata
1236
+ model,
1237
+ collectionType,
1238
+ id,
1239
+ slug,
1240
+ isCreatingEntry,
1241
+ isSingleType,
1242
+ hasDraftAndPublish: schema?.options?.draftAndPublish ?? false,
1243
+ // All schema infos
1244
+ components,
1245
+ contentType: schema,
1246
+ contentTypes: schemas,
1247
+ // Form state
1248
+ form,
1249
+ // layout infos
1250
+ layout
1251
+ };
1252
+ };
1253
+ const prefixPluginTranslations = (trad, pluginId) => {
1254
+ return Object.keys(trad).reduce((acc, current) => {
1255
+ acc[`${pluginId}.${current}`] = trad[current];
1256
+ return acc;
1257
+ }, {});
1258
+ };
1259
+ const getTranslation = (id) => `content-manager.${id}`;
1260
+ const DEFAULT_UNEXPECTED_ERROR_MSG = {
1261
+ id: "notification.error",
1262
+ defaultMessage: "An error occurred, please try again"
1263
+ };
1264
+ const useDocumentActions = () => {
1265
+ const { toggleNotification } = strapiAdmin.useNotification();
1266
+ const { formatMessage } = reactIntl.useIntl();
1267
+ const { trackUsage } = strapiAdmin.useTracking();
1268
+ const { _unstableFormatAPIError: formatAPIError } = strapiAdmin.useAPIErrorHandler();
1269
+ const navigate = reactRouterDom.useNavigate();
1270
+ const setCurrentStep = strapiAdmin.useGuidedTour("useDocumentActions", (state) => state.setCurrentStep);
1271
+ const [deleteDocument] = useDeleteDocumentMutation();
1272
+ const _delete = React__namespace.useCallback(
1273
+ async ({ collectionType, model, documentId, params }, trackerProperty) => {
1117
1274
  try {
1118
- trackUsage("willUnpublishEntry");
1119
- const res = await unpublishDocument({
1275
+ trackUsage("willDeleteEntry", trackerProperty);
1276
+ const res = await deleteDocument({
1120
1277
  collectionType,
1121
1278
  model,
1122
1279
  documentId,
1123
- params,
1124
- data: {
1125
- discardDraft
1126
- }
1280
+ params
1127
1281
  });
1128
1282
  if ("error" in res) {
1129
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1283
+ toggleNotification({
1284
+ type: "danger",
1285
+ message: formatAPIError(res.error)
1286
+ });
1130
1287
  return { error: res.error };
1131
1288
  }
1132
- trackUsage("didUnpublishEntry");
1133
1289
  toggleNotification({
1134
1290
  type: "success",
1135
1291
  message: formatMessage({
1136
- id: getTranslation("success.record.unpublish"),
1137
- defaultMessage: "Unpublished document"
1292
+ id: getTranslation("success.record.delete"),
1293
+ defaultMessage: "Deleted document"
1138
1294
  })
1139
1295
  });
1296
+ trackUsage("didDeleteEntry", trackerProperty);
1140
1297
  return res.data;
1141
1298
  } catch (err) {
1142
1299
  toggleNotification({
1143
1300
  type: "danger",
1144
1301
  message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1145
1302
  });
1303
+ trackUsage("didNotDeleteEntry", { error: err, ...trackerProperty });
1146
1304
  throw err;
1147
1305
  }
1148
1306
  },
1149
- [trackUsage, unpublishDocument, toggleNotification, formatMessage, formatAPIError]
1307
+ [trackUsage, deleteDocument, toggleNotification, formatMessage, formatAPIError]
1150
1308
  );
1151
- const [unpublishManyDocuments] = useUnpublishManyDocumentsMutation();
1152
- const unpublishMany = React__namespace.useCallback(
1309
+ const [deleteManyDocuments] = useDeleteManyDocumentsMutation();
1310
+ const deleteMany = React__namespace.useCallback(
1153
1311
  async ({ model, documentIds, params }) => {
1154
1312
  try {
1155
- trackUsage("willBulkUnpublishEntries");
1156
- const res = await unpublishManyDocuments({
1313
+ trackUsage("willBulkDeleteEntries");
1314
+ const res = await deleteManyDocuments({
1157
1315
  model,
1158
1316
  documentIds,
1159
1317
  params
1160
1318
  });
1161
1319
  if ("error" in res) {
1162
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1320
+ toggleNotification({
1321
+ type: "danger",
1322
+ message: formatAPIError(res.error)
1323
+ });
1163
1324
  return { error: res.error };
1164
1325
  }
1165
- trackUsage("didBulkUnpublishEntries");
1166
1326
  toggleNotification({
1167
1327
  type: "success",
1168
1328
  title: formatMessage({
1169
- id: getTranslation("success.records.unpublish"),
1170
- defaultMessage: "Successfully unpublished."
1329
+ id: getTranslation("success.records.delete"),
1330
+ defaultMessage: "Successfully deleted."
1171
1331
  }),
1172
1332
  message: ""
1173
1333
  });
1334
+ trackUsage("didBulkDeleteEntries");
1174
1335
  return res.data;
1175
1336
  } catch (err) {
1176
1337
  toggleNotification({
1177
1338
  type: "danger",
1178
1339
  message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1179
1340
  });
1180
- trackUsage("didNotBulkUnpublishEntries");
1341
+ trackUsage("didNotBulkDeleteEntries");
1181
1342
  throw err;
1182
1343
  }
1183
1344
  },
1184
- [trackUsage, unpublishManyDocuments, toggleNotification, formatMessage, formatAPIError]
1345
+ [trackUsage, deleteManyDocuments, toggleNotification, formatMessage, formatAPIError]
1185
1346
  );
1186
- const [createDocument] = useCreateDocumentMutation();
1187
- const create = React__namespace.useCallback(
1188
- async ({ model, params }, data, trackerProperty) => {
1347
+ const [discardDocument] = useDiscardDocumentMutation();
1348
+ const discard = React__namespace.useCallback(
1349
+ async ({ collectionType, model, documentId, params }) => {
1189
1350
  try {
1190
- const res = await createDocument({
1351
+ const res = await discardDocument({
1352
+ collectionType,
1191
1353
  model,
1192
- data,
1354
+ documentId,
1193
1355
  params
1194
1356
  });
1195
1357
  if ("error" in res) {
1196
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1197
- trackUsage("didNotCreateEntry", { error: res.error, ...trackerProperty });
1358
+ toggleNotification({
1359
+ type: "danger",
1360
+ message: formatAPIError(res.error)
1361
+ });
1198
1362
  return { error: res.error };
1199
1363
  }
1200
- trackUsage("didCreateEntry", trackerProperty);
1201
1364
  toggleNotification({
1202
1365
  type: "success",
1203
1366
  message: formatMessage({
1204
- id: getTranslation("success.record.save"),
1205
- defaultMessage: "Saved document"
1367
+ id: "content-manager.success.record.discard",
1368
+ defaultMessage: "Changes discarded"
1206
1369
  })
1207
1370
  });
1208
1371
  return res.data;
@@ -1211,28 +1374,33 @@ const useDocumentActions = () => {
1211
1374
  type: "danger",
1212
1375
  message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1213
1376
  });
1214
- trackUsage("didNotCreateEntry", { error: err, ...trackerProperty });
1215
1377
  throw err;
1216
1378
  }
1217
1379
  },
1218
- [createDocument, formatAPIError, formatMessage, toggleNotification, trackUsage]
1380
+ [discardDocument, formatAPIError, formatMessage, toggleNotification]
1219
1381
  );
1220
- const [autoCloneDocument] = useAutoCloneDocumentMutation();
1221
- const autoClone = React__namespace.useCallback(
1222
- async ({ model, sourceId }) => {
1382
+ const [publishDocument] = usePublishDocumentMutation();
1383
+ const publish = React__namespace.useCallback(
1384
+ async ({ collectionType, model, documentId, params }, data) => {
1223
1385
  try {
1224
- const res = await autoCloneDocument({
1386
+ trackUsage("willPublishEntry");
1387
+ const res = await publishDocument({
1388
+ collectionType,
1225
1389
  model,
1226
- sourceId
1390
+ documentId,
1391
+ data,
1392
+ params
1227
1393
  });
1228
1394
  if ("error" in res) {
1395
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1229
1396
  return { error: res.error };
1230
1397
  }
1398
+ trackUsage("didPublishEntry");
1231
1399
  toggleNotification({
1232
1400
  type: "success",
1233
1401
  message: formatMessage({
1234
- id: getTranslation("success.record.clone"),
1235
- defaultMessage: "Cloned document"
1402
+ id: getTranslation("success.record.publish"),
1403
+ defaultMessage: "Published document"
1236
1404
  })
1237
1405
  });
1238
1406
  return res.data;
@@ -1244,30 +1412,26 @@ const useDocumentActions = () => {
1244
1412
  throw err;
1245
1413
  }
1246
1414
  },
1247
- [autoCloneDocument, formatAPIError, formatMessage, toggleNotification]
1415
+ [trackUsage, publishDocument, toggleNotification, formatMessage, formatAPIError]
1248
1416
  );
1249
- const [cloneDocument] = useCloneDocumentMutation();
1250
- const clone = React__namespace.useCallback(
1251
- async ({ model, documentId, params }, body, trackerProperty) => {
1417
+ const [publishManyDocuments] = usePublishManyDocumentsMutation();
1418
+ const publishMany = React__namespace.useCallback(
1419
+ async ({ model, documentIds, params }) => {
1252
1420
  try {
1253
- const { id: _id, ...restBody } = body;
1254
- const res = await cloneDocument({
1421
+ const res = await publishManyDocuments({
1255
1422
  model,
1256
- sourceId: documentId,
1257
- data: restBody,
1423
+ documentIds,
1258
1424
  params
1259
1425
  });
1260
1426
  if ("error" in res) {
1261
1427
  toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1262
- trackUsage("didNotCreateEntry", { error: res.error, ...trackerProperty });
1263
1428
  return { error: res.error };
1264
1429
  }
1265
- trackUsage("didCreateEntry", trackerProperty);
1266
1430
  toggleNotification({
1267
1431
  type: "success",
1268
1432
  message: formatMessage({
1269
- id: getTranslation("success.record.clone"),
1270
- defaultMessage: "Cloned document"
1433
+ id: getTranslation("success.record.publish"),
1434
+ defaultMessage: "Published document"
1271
1435
  })
1272
1436
  });
1273
1437
  return res.data;
@@ -1276,1192 +1440,1085 @@ const useDocumentActions = () => {
1276
1440
  type: "danger",
1277
1441
  message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1278
1442
  });
1279
- trackUsage("didNotCreateEntry", { error: err, ...trackerProperty });
1280
1443
  throw err;
1281
1444
  }
1282
1445
  },
1283
- [cloneDocument, trackUsage, toggleNotification, formatMessage, formatAPIError]
1284
- );
1285
- const [getDoc] = useLazyGetDocumentQuery();
1286
- const getDocument = React__namespace.useCallback(
1287
- async (args) => {
1288
- const { data } = await getDoc(args);
1289
- return data;
1290
- },
1291
- [getDoc]
1446
+ [
1447
+ // trackUsage,
1448
+ publishManyDocuments,
1449
+ toggleNotification,
1450
+ formatMessage,
1451
+ formatAPIError
1452
+ ]
1292
1453
  );
1293
- return {
1294
- autoClone,
1295
- clone,
1296
- create,
1297
- delete: _delete,
1298
- deleteMany,
1299
- discard,
1300
- getDocument,
1301
- publish,
1302
- publishMany,
1303
- unpublish,
1304
- unpublishMany,
1305
- update
1306
- };
1307
- };
1308
- const ProtectedHistoryPage = React.lazy(
1309
- () => Promise.resolve().then(() => require("./History-nuEzM5qm.js")).then((mod) => ({ default: mod.ProtectedHistoryPage }))
1310
- );
1311
- const routes$1 = [
1312
- {
1313
- path: ":collectionType/:slug/:id/history",
1314
- Component: ProtectedHistoryPage
1315
- },
1316
- {
1317
- path: ":collectionType/:slug/history",
1318
- Component: ProtectedHistoryPage
1319
- }
1320
- ];
1321
- const ProtectedEditViewPage = React.lazy(
1322
- () => Promise.resolve().then(() => require("./EditViewPage-BgjdnGz2.js")).then((mod) => ({ default: mod.ProtectedEditViewPage }))
1323
- );
1324
- const ProtectedListViewPage = React.lazy(
1325
- () => Promise.resolve().then(() => require("./ListViewPage-SXIXm-RM.js")).then((mod) => ({ default: mod.ProtectedListViewPage }))
1326
- );
1327
- const ProtectedListConfiguration = React.lazy(
1328
- () => Promise.resolve().then(() => require("./ListConfigurationPage-CnB86Psm.js")).then((mod) => ({
1329
- default: mod.ProtectedListConfiguration
1330
- }))
1331
- );
1332
- const ProtectedEditConfigurationPage = React.lazy(
1333
- () => Promise.resolve().then(() => require("./EditConfigurationPage-BQ17--5R.js")).then((mod) => ({
1334
- default: mod.ProtectedEditConfigurationPage
1335
- }))
1336
- );
1337
- const ProtectedComponentConfigurationPage = React.lazy(
1338
- () => Promise.resolve().then(() => require("./ComponentConfigurationPage-KXSuLnQD.js")).then((mod) => ({
1339
- default: mod.ProtectedComponentConfigurationPage
1340
- }))
1341
- );
1342
- const NoPermissions = React.lazy(
1343
- () => Promise.resolve().then(() => require("./NoPermissionsPage-IGkId4C5.js")).then((mod) => ({ default: mod.NoPermissions }))
1344
- );
1345
- const NoContentType = React.lazy(
1346
- () => Promise.resolve().then(() => require("./NoContentTypePage-BzsQ3hLZ.js")).then((mod) => ({ default: mod.NoContentType }))
1347
- );
1348
- const CollectionTypePages = () => {
1349
- const { collectionType } = reactRouterDom.useParams();
1350
- if (collectionType !== COLLECTION_TYPES && collectionType !== SINGLE_TYPES) {
1351
- return /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Navigate, { to: "/404" });
1352
- }
1353
- return collectionType === COLLECTION_TYPES ? /* @__PURE__ */ jsxRuntime.jsx(ProtectedListViewPage, {}) : /* @__PURE__ */ jsxRuntime.jsx(ProtectedEditViewPage, {});
1354
- };
1355
- const CLONE_RELATIVE_PATH = ":collectionType/:slug/clone/:origin";
1356
- const CLONE_PATH = `/content-manager/${CLONE_RELATIVE_PATH}`;
1357
- const LIST_RELATIVE_PATH = ":collectionType/:slug";
1358
- const LIST_PATH = `/content-manager/${LIST_RELATIVE_PATH}`;
1359
- const routes = [
1360
- {
1361
- path: LIST_RELATIVE_PATH,
1362
- element: /* @__PURE__ */ jsxRuntime.jsx(CollectionTypePages, {})
1363
- },
1364
- {
1365
- path: ":collectionType/:slug/:id",
1366
- Component: ProtectedEditViewPage
1367
- },
1368
- {
1369
- path: CLONE_RELATIVE_PATH,
1370
- Component: ProtectedEditViewPage
1371
- },
1372
- {
1373
- path: ":collectionType/:slug/configurations/list",
1374
- Component: ProtectedListConfiguration
1375
- },
1376
- {
1377
- path: "components/:slug/configurations/edit",
1378
- Component: ProtectedComponentConfigurationPage
1379
- },
1380
- {
1381
- path: ":collectionType/:slug/configurations/edit",
1382
- Component: ProtectedEditConfigurationPage
1383
- },
1384
- {
1385
- path: "403",
1386
- Component: NoPermissions
1387
- },
1388
- {
1389
- path: "no-content-types",
1390
- Component: NoContentType
1391
- },
1392
- ...routes$1
1393
- ];
1394
- const DocumentActions = ({ actions: actions2 }) => {
1395
- const { formatMessage } = reactIntl.useIntl();
1396
- const [primaryAction, secondaryAction, ...restActions] = actions2.filter((action) => {
1397
- if (action.position === void 0) {
1398
- return true;
1399
- }
1400
- const positions = Array.isArray(action.position) ? action.position : [action.position];
1401
- return positions.includes("panel");
1402
- });
1403
- if (!primaryAction) {
1404
- return null;
1405
- }
1406
- return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 2, alignItems: "stretch", width: "100%", children: [
1407
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
1408
- /* @__PURE__ */ jsxRuntime.jsx(DocumentActionButton, { ...primaryAction, variant: primaryAction.variant || "default" }),
1409
- restActions.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
1410
- DocumentActionsMenu,
1411
- {
1412
- actions: restActions,
1413
- label: formatMessage({
1414
- id: "content-manager.containers.edit.panels.default.more-actions",
1415
- defaultMessage: "More document actions"
1416
- })
1454
+ const [updateDocument] = useUpdateDocumentMutation();
1455
+ const update = React__namespace.useCallback(
1456
+ async ({ collectionType, model, documentId, params }, data, trackerProperty) => {
1457
+ try {
1458
+ trackUsage("willEditEntry", trackerProperty);
1459
+ const res = await updateDocument({
1460
+ collectionType,
1461
+ model,
1462
+ documentId,
1463
+ data,
1464
+ params
1465
+ });
1466
+ if ("error" in res) {
1467
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1468
+ trackUsage("didNotEditEntry", { error: res.error, ...trackerProperty });
1469
+ return { error: res.error };
1417
1470
  }
1418
- ) : null
1419
- ] }),
1420
- secondaryAction ? /* @__PURE__ */ jsxRuntime.jsx(
1421
- DocumentActionButton,
1422
- {
1423
- ...secondaryAction,
1424
- variant: secondaryAction.variant || "secondary"
1425
- }
1426
- ) : null
1427
- ] });
1428
- };
1429
- const DocumentActionButton = (action) => {
1430
- const [dialogId, setDialogId] = React__namespace.useState(null);
1431
- const { toggleNotification } = strapiAdmin.useNotification();
1432
- const handleClick = (action2) => async (e) => {
1433
- const { onClick = () => false, dialog, id } = action2;
1434
- const muteDialog = await onClick(e);
1435
- if (dialog && !muteDialog) {
1436
- switch (dialog.type) {
1437
- case "notification":
1438
- toggleNotification({
1439
- title: dialog.title,
1440
- message: dialog.content,
1441
- type: dialog.status,
1442
- timeout: dialog.timeout,
1443
- onClose: dialog.onClose
1444
- });
1445
- break;
1446
- case "dialog":
1447
- case "modal":
1448
- e.preventDefault();
1449
- setDialogId(id);
1471
+ trackUsage("didEditEntry", trackerProperty);
1472
+ toggleNotification({
1473
+ type: "success",
1474
+ message: formatMessage({
1475
+ id: getTranslation("success.record.save"),
1476
+ defaultMessage: "Saved document"
1477
+ })
1478
+ });
1479
+ return res.data;
1480
+ } catch (err) {
1481
+ trackUsage("didNotEditEntry", { error: err, ...trackerProperty });
1482
+ toggleNotification({
1483
+ type: "danger",
1484
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1485
+ });
1486
+ throw err;
1450
1487
  }
1451
- }
1452
- };
1453
- const handleClose = () => {
1454
- setDialogId(null);
1455
- };
1456
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1457
- /* @__PURE__ */ jsxRuntime.jsx(
1458
- designSystem.Button,
1459
- {
1460
- flex: "auto",
1461
- startIcon: action.icon,
1462
- disabled: action.disabled,
1463
- onClick: handleClick(action),
1464
- justifyContent: "center",
1465
- variant: action.variant || "default",
1466
- paddingTop: "7px",
1467
- paddingBottom: "7px",
1468
- children: action.label
1469
- }
1470
- ),
1471
- action.dialog?.type === "dialog" ? /* @__PURE__ */ jsxRuntime.jsx(
1472
- DocumentActionConfirmDialog,
1473
- {
1474
- ...action.dialog,
1475
- variant: action.dialog?.variant ?? action.variant,
1476
- isOpen: dialogId === action.id,
1477
- onClose: handleClose
1478
- }
1479
- ) : null,
1480
- action.dialog?.type === "modal" ? /* @__PURE__ */ jsxRuntime.jsx(
1481
- DocumentActionModal,
1482
- {
1483
- ...action.dialog,
1484
- onModalClose: handleClose,
1485
- isOpen: dialogId === action.id
1486
- }
1487
- ) : null
1488
- ] });
1489
- };
1490
- const DocumentActionsMenu = ({
1491
- actions: actions2,
1492
- children,
1493
- label,
1494
- variant = "tertiary"
1495
- }) => {
1496
- const [isOpen, setIsOpen] = React__namespace.useState(false);
1497
- const [dialogId, setDialogId] = React__namespace.useState(null);
1498
- const { formatMessage } = reactIntl.useIntl();
1499
- const { toggleNotification } = strapiAdmin.useNotification();
1500
- const isDisabled = actions2.every((action) => action.disabled) || actions2.length === 0;
1501
- const handleClick = (action) => async (e) => {
1502
- const { onClick = () => false, dialog, id } = action;
1503
- const muteDialog = await onClick(e);
1504
- if (dialog && !muteDialog) {
1505
- switch (dialog.type) {
1506
- case "notification":
1507
- toggleNotification({
1508
- title: dialog.title,
1509
- message: dialog.content,
1510
- type: dialog.status,
1511
- timeout: dialog.timeout,
1512
- onClose: dialog.onClose
1513
- });
1514
- break;
1515
- case "dialog":
1516
- case "modal":
1517
- setDialogId(id);
1518
- }
1519
- }
1520
- };
1521
- const handleClose = () => {
1522
- setDialogId(null);
1523
- setIsOpen(false);
1524
- };
1525
- return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Menu.Root, { open: isOpen, onOpenChange: setIsOpen, children: [
1526
- /* @__PURE__ */ jsxRuntime.jsxs(
1527
- StyledMoreButton,
1528
- {
1529
- disabled: isDisabled,
1530
- size: "S",
1531
- endIcon: null,
1532
- paddingTop: "4px",
1533
- paddingLeft: "7px",
1534
- paddingRight: "7px",
1535
- variant,
1536
- children: [
1537
- /* @__PURE__ */ jsxRuntime.jsx(Icons.More, { "aria-hidden": true, focusable: false }),
1538
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.VisuallyHidden, { tag: "span", children: label || formatMessage({
1539
- id: "content-manager.containers.edit.panels.default.more-actions",
1540
- defaultMessage: "More document actions"
1541
- }) })
1542
- ]
1543
- }
1544
- ),
1545
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Menu.Content, { top: "4px", maxHeight: void 0, popoverPlacement: "bottom-end", children: [
1546
- actions2.map((action) => {
1547
- return /* @__PURE__ */ jsxRuntime.jsx(
1548
- designSystem.Menu.Item,
1549
- {
1550
- disabled: action.disabled,
1551
- onSelect: handleClick(action),
1552
- display: "block",
1553
- children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", gap: 4, children: [
1554
- /* @__PURE__ */ jsxRuntime.jsxs(
1555
- designSystem.Flex,
1556
- {
1557
- color: !action.disabled ? convertActionVariantToColor(action.variant) : "inherit",
1558
- gap: 2,
1559
- tag: "span",
1560
- children: [
1561
- /* @__PURE__ */ jsxRuntime.jsx(
1562
- designSystem.Flex,
1563
- {
1564
- tag: "span",
1565
- color: !action.disabled ? convertActionVariantToIconColor(action.variant) : "inherit",
1566
- children: action.icon
1567
- }
1568
- ),
1569
- action.label
1570
- ]
1571
- }
1572
- ),
1573
- action.id.startsWith("HistoryAction") && /* @__PURE__ */ jsxRuntime.jsx(
1574
- designSystem.Flex,
1575
- {
1576
- alignItems: "center",
1577
- background: "alternative100",
1578
- borderStyle: "solid",
1579
- borderColor: "alternative200",
1580
- borderWidth: "1px",
1581
- height: 5,
1582
- paddingLeft: 2,
1583
- paddingRight: 2,
1584
- hasRadius: true,
1585
- color: "alternative600",
1586
- children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", fontWeight: "bold", lineHeight: 1, children: formatMessage({ id: "global.new", defaultMessage: "New" }) })
1587
- }
1588
- )
1589
- ] })
1590
- },
1591
- action.id
1592
- );
1593
- }),
1594
- children
1595
- ] }),
1596
- actions2.map((action) => {
1597
- return /* @__PURE__ */ jsxRuntime.jsxs(React__namespace.Fragment, { children: [
1598
- action.dialog?.type === "dialog" ? /* @__PURE__ */ jsxRuntime.jsx(
1599
- DocumentActionConfirmDialog,
1600
- {
1601
- ...action.dialog,
1602
- variant: action.variant,
1603
- isOpen: dialogId === action.id,
1604
- onClose: handleClose
1605
- }
1606
- ) : null,
1607
- action.dialog?.type === "modal" ? /* @__PURE__ */ jsxRuntime.jsx(
1608
- DocumentActionModal,
1609
- {
1610
- ...action.dialog,
1611
- onModalClose: handleClose,
1612
- isOpen: dialogId === action.id
1613
- }
1614
- ) : null
1615
- ] }, action.id);
1616
- })
1617
- ] });
1618
- };
1619
- const convertActionVariantToColor = (variant = "secondary") => {
1620
- switch (variant) {
1621
- case "danger":
1622
- return "danger600";
1623
- case "secondary":
1624
- return void 0;
1625
- case "success":
1626
- return "success600";
1627
- default:
1628
- return "primary600";
1629
- }
1630
- };
1631
- const convertActionVariantToIconColor = (variant = "secondary") => {
1632
- switch (variant) {
1633
- case "danger":
1634
- return "danger600";
1635
- case "secondary":
1636
- return "neutral500";
1637
- case "success":
1638
- return "success600";
1639
- default:
1640
- return "primary600";
1641
- }
1642
- };
1643
- const StyledMoreButton = styledComponents.styled(designSystem.Menu.Trigger)`
1644
- & > span {
1645
- display: flex;
1646
- }
1647
- `;
1648
- const DocumentActionConfirmDialog = ({
1649
- onClose,
1650
- onCancel,
1651
- onConfirm,
1652
- title,
1653
- content,
1654
- isOpen,
1655
- variant = "secondary"
1656
- }) => {
1657
- const { formatMessage } = reactIntl.useIntl();
1658
- const handleClose = async () => {
1659
- if (onCancel) {
1660
- await onCancel();
1661
- }
1662
- onClose();
1663
- };
1664
- const handleConfirm = async () => {
1665
- if (onConfirm) {
1666
- await onConfirm();
1667
- }
1668
- onClose();
1669
- };
1670
- return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Dialog.Content, { children: [
1671
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Header, { children: title }),
1672
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Body, { children: content }),
1673
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Dialog.Footer, { children: [
1674
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Cancel, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", children: formatMessage({
1675
- id: "app.components.Button.cancel",
1676
- defaultMessage: "Cancel"
1677
- }) }) }),
1678
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: handleConfirm, variant, children: formatMessage({
1679
- id: "app.components.Button.confirm",
1680
- defaultMessage: "Confirm"
1681
- }) })
1682
- ] })
1683
- ] }) });
1684
- };
1685
- const DocumentActionModal = ({
1686
- isOpen,
1687
- title,
1688
- onClose,
1689
- footer: Footer,
1690
- content: Content,
1691
- onModalClose
1692
- }) => {
1693
- const handleClose = () => {
1694
- if (onClose) {
1695
- onClose();
1696
- }
1697
- onModalClose();
1698
- };
1699
- return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Content, { children: [
1700
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Title, { children: title }) }),
1701
- typeof Content === "function" ? /* @__PURE__ */ jsxRuntime.jsx(Content, { onClose: handleClose }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Body, { children: Content }),
1702
- typeof Footer === "function" ? /* @__PURE__ */ jsxRuntime.jsx(Footer, { onClose: handleClose }) : Footer
1703
- ] }) });
1704
- };
1705
- const PublishAction$1 = ({
1706
- activeTab,
1707
- documentId,
1708
- model,
1709
- collectionType,
1710
- meta,
1711
- document
1712
- }) => {
1713
- const { schema } = useDoc();
1714
- const navigate = reactRouterDom.useNavigate();
1715
- const { toggleNotification } = strapiAdmin.useNotification();
1716
- const { _unstableFormatValidationErrors: formatValidationErrors } = strapiAdmin.useAPIErrorHandler();
1717
- const isCloning = reactRouterDom.useMatch(CLONE_PATH) !== null;
1718
- const { formatMessage } = reactIntl.useIntl();
1719
- const { canPublish, canCreate, canUpdate } = useDocumentRBAC(
1720
- "PublishAction",
1721
- ({ canPublish: canPublish2, canCreate: canCreate2, canUpdate: canUpdate2 }) => ({ canPublish: canPublish2, canCreate: canCreate2, canUpdate: canUpdate2 })
1488
+ },
1489
+ [trackUsage, updateDocument, toggleNotification, formatMessage, formatAPIError]
1722
1490
  );
1723
- const { publish } = useDocumentActions();
1724
- const [
1725
- countDraftRelations,
1726
- { isLoading: isLoadingDraftRelations, isError: isErrorDraftRelations }
1727
- ] = useLazyGetDraftRelationCountQuery();
1728
- const [localCountOfDraftRelations, setLocalCountOfDraftRelations] = React__namespace.useState(0);
1729
- const [serverCountOfDraftRelations, setServerCountOfDraftRelations] = React__namespace.useState(0);
1730
- const [{ query, rawQuery }] = strapiAdmin.useQueryParams();
1731
- const params = React__namespace.useMemo(() => buildValidParams(query), [query]);
1732
- const modified = strapiAdmin.useForm("PublishAction", ({ modified: modified2 }) => modified2);
1733
- const setSubmitting = strapiAdmin.useForm("PublishAction", ({ setSubmitting: setSubmitting2 }) => setSubmitting2);
1734
- const isSubmitting = strapiAdmin.useForm("PublishAction", ({ isSubmitting: isSubmitting2 }) => isSubmitting2);
1735
- const validate = strapiAdmin.useForm("PublishAction", (state) => state.validate);
1736
- const setErrors = strapiAdmin.useForm("PublishAction", (state) => state.setErrors);
1737
- const formValues = strapiAdmin.useForm("PublishAction", ({ values }) => values);
1738
- React__namespace.useEffect(() => {
1739
- if (isErrorDraftRelations) {
1740
- toggleNotification({
1741
- type: "danger",
1742
- message: formatMessage({
1743
- id: getTranslation("error.records.fetch-draft-relatons"),
1744
- defaultMessage: "An error occurred while fetching draft relations on this document."
1745
- })
1746
- });
1747
- }
1748
- }, [isErrorDraftRelations, toggleNotification, formatMessage]);
1749
- React__namespace.useEffect(() => {
1750
- const localDraftRelations = /* @__PURE__ */ new Set();
1751
- const extractDraftRelations = (data) => {
1752
- const relations = data.connect || [];
1753
- relations.forEach((relation) => {
1754
- if (relation.status === "draft") {
1755
- localDraftRelations.add(relation.id);
1756
- }
1757
- });
1758
- };
1759
- const traverseAndExtract = (data) => {
1760
- Object.entries(data).forEach(([key, value]) => {
1761
- if (key === "connect" && Array.isArray(value)) {
1762
- extractDraftRelations({ connect: value });
1763
- } else if (typeof value === "object" && value !== null) {
1764
- traverseAndExtract(value);
1765
- }
1766
- });
1767
- };
1768
- if (!documentId || modified) {
1769
- traverseAndExtract(formValues);
1770
- setLocalCountOfDraftRelations(localDraftRelations.size);
1771
- }
1772
- }, [documentId, modified, formValues, setLocalCountOfDraftRelations]);
1773
- React__namespace.useEffect(() => {
1774
- if (documentId) {
1775
- const fetchDraftRelationsCount = async () => {
1776
- const { data, error } = await countDraftRelations({
1491
+ const [unpublishDocument] = useUnpublishDocumentMutation();
1492
+ const unpublish = React__namespace.useCallback(
1493
+ async ({ collectionType, model, documentId, params }, discardDraft = false) => {
1494
+ try {
1495
+ trackUsage("willUnpublishEntry");
1496
+ const res = await unpublishDocument({
1777
1497
  collectionType,
1778
1498
  model,
1779
1499
  documentId,
1500
+ params,
1501
+ data: {
1502
+ discardDraft
1503
+ }
1504
+ });
1505
+ if ("error" in res) {
1506
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1507
+ return { error: res.error };
1508
+ }
1509
+ trackUsage("didUnpublishEntry");
1510
+ toggleNotification({
1511
+ type: "success",
1512
+ message: formatMessage({
1513
+ id: getTranslation("success.record.unpublish"),
1514
+ defaultMessage: "Unpublished document"
1515
+ })
1516
+ });
1517
+ return res.data;
1518
+ } catch (err) {
1519
+ toggleNotification({
1520
+ type: "danger",
1521
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1522
+ });
1523
+ throw err;
1524
+ }
1525
+ },
1526
+ [trackUsage, unpublishDocument, toggleNotification, formatMessage, formatAPIError]
1527
+ );
1528
+ const [unpublishManyDocuments] = useUnpublishManyDocumentsMutation();
1529
+ const unpublishMany = React__namespace.useCallback(
1530
+ async ({ model, documentIds, params }) => {
1531
+ try {
1532
+ trackUsage("willBulkUnpublishEntries");
1533
+ const res = await unpublishManyDocuments({
1534
+ model,
1535
+ documentIds,
1780
1536
  params
1781
1537
  });
1782
- if (error) {
1783
- throw error;
1538
+ if ("error" in res) {
1539
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1540
+ return { error: res.error };
1784
1541
  }
1785
- if (data) {
1786
- setServerCountOfDraftRelations(data.data);
1542
+ trackUsage("didBulkUnpublishEntries");
1543
+ toggleNotification({
1544
+ type: "success",
1545
+ title: formatMessage({
1546
+ id: getTranslation("success.records.unpublish"),
1547
+ defaultMessage: "Successfully unpublished."
1548
+ }),
1549
+ message: ""
1550
+ });
1551
+ return res.data;
1552
+ } catch (err) {
1553
+ toggleNotification({
1554
+ type: "danger",
1555
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1556
+ });
1557
+ trackUsage("didNotBulkUnpublishEntries");
1558
+ throw err;
1559
+ }
1560
+ },
1561
+ [trackUsage, unpublishManyDocuments, toggleNotification, formatMessage, formatAPIError]
1562
+ );
1563
+ const [createDocument] = useCreateDocumentMutation();
1564
+ const create = React__namespace.useCallback(
1565
+ async ({ model, params }, data, trackerProperty) => {
1566
+ try {
1567
+ const res = await createDocument({
1568
+ model,
1569
+ data,
1570
+ params
1571
+ });
1572
+ if ("error" in res) {
1573
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1574
+ trackUsage("didNotCreateEntry", { error: res.error, ...trackerProperty });
1575
+ return { error: res.error };
1787
1576
  }
1788
- };
1789
- fetchDraftRelationsCount();
1790
- }
1791
- }, [documentId, countDraftRelations, collectionType, model, params]);
1792
- const isDocumentPublished = (document?.[PUBLISHED_AT_ATTRIBUTE_NAME] || meta?.availableStatus.some((doc) => doc[PUBLISHED_AT_ATTRIBUTE_NAME] !== null)) && document?.status !== "modified";
1793
- if (!schema?.options?.draftAndPublish) {
1794
- return null;
1795
- }
1796
- const performPublish = async () => {
1797
- setSubmitting(true);
1798
- try {
1799
- const { errors } = await validate();
1800
- if (errors) {
1577
+ trackUsage("didCreateEntry", trackerProperty);
1578
+ toggleNotification({
1579
+ type: "success",
1580
+ message: formatMessage({
1581
+ id: getTranslation("success.record.save"),
1582
+ defaultMessage: "Saved document"
1583
+ })
1584
+ });
1585
+ setCurrentStep("contentManager.success");
1586
+ return res.data;
1587
+ } catch (err) {
1801
1588
  toggleNotification({
1802
1589
  type: "danger",
1590
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1591
+ });
1592
+ trackUsage("didNotCreateEntry", { error: err, ...trackerProperty });
1593
+ throw err;
1594
+ }
1595
+ },
1596
+ [createDocument, formatAPIError, formatMessage, toggleNotification, trackUsage]
1597
+ );
1598
+ const [autoCloneDocument] = useAutoCloneDocumentMutation();
1599
+ const autoClone = React__namespace.useCallback(
1600
+ async ({ model, sourceId }) => {
1601
+ try {
1602
+ const res = await autoCloneDocument({
1603
+ model,
1604
+ sourceId
1605
+ });
1606
+ if ("error" in res) {
1607
+ return { error: res.error };
1608
+ }
1609
+ toggleNotification({
1610
+ type: "success",
1803
1611
  message: formatMessage({
1804
- id: "content-manager.validation.error",
1805
- defaultMessage: "There are validation errors in your document. Please fix them before saving."
1612
+ id: getTranslation("success.record.clone"),
1613
+ defaultMessage: "Cloned document"
1806
1614
  })
1807
1615
  });
1808
- return;
1616
+ return res.data;
1617
+ } catch (err) {
1618
+ toggleNotification({
1619
+ type: "danger",
1620
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1621
+ });
1622
+ throw err;
1809
1623
  }
1810
- const res = await publish(
1811
- {
1812
- collectionType,
1624
+ },
1625
+ [autoCloneDocument, formatMessage, toggleNotification]
1626
+ );
1627
+ const [cloneDocument] = useCloneDocumentMutation();
1628
+ const clone = React__namespace.useCallback(
1629
+ async ({ model, documentId, params }, body, trackerProperty) => {
1630
+ try {
1631
+ const { id: _id, ...restBody } = body;
1632
+ const res = await cloneDocument({
1813
1633
  model,
1814
- documentId,
1634
+ sourceId: documentId,
1635
+ data: restBody,
1815
1636
  params
1816
- },
1817
- formValues
1818
- );
1819
- if ("data" in res && collectionType !== SINGLE_TYPES) {
1820
- navigate({
1821
- pathname: `../${collectionType}/${model}/${res.data.documentId}`,
1822
- search: rawQuery
1823
1637
  });
1824
- } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1825
- setErrors(formatValidationErrors(res.error));
1638
+ if ("error" in res) {
1639
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1640
+ trackUsage("didNotCreateEntry", { error: res.error, ...trackerProperty });
1641
+ return { error: res.error };
1642
+ }
1643
+ trackUsage("didCreateEntry", trackerProperty);
1644
+ toggleNotification({
1645
+ type: "success",
1646
+ message: formatMessage({
1647
+ id: getTranslation("success.record.clone"),
1648
+ defaultMessage: "Cloned document"
1649
+ })
1650
+ });
1651
+ navigate(`../../${res.data.data.documentId}`, { relative: "path" });
1652
+ return res.data;
1653
+ } catch (err) {
1654
+ toggleNotification({
1655
+ type: "danger",
1656
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1657
+ });
1658
+ trackUsage("didNotCreateEntry", { error: err, ...trackerProperty });
1659
+ throw err;
1826
1660
  }
1827
- } finally {
1828
- setSubmitting(false);
1661
+ },
1662
+ [cloneDocument, trackUsage, toggleNotification, formatMessage, formatAPIError, navigate]
1663
+ );
1664
+ const [getDoc] = useLazyGetDocumentQuery();
1665
+ const getDocument = React__namespace.useCallback(
1666
+ async (args) => {
1667
+ const { data } = await getDoc(args);
1668
+ return data;
1669
+ },
1670
+ [getDoc]
1671
+ );
1672
+ return {
1673
+ autoClone,
1674
+ clone,
1675
+ create,
1676
+ delete: _delete,
1677
+ deleteMany,
1678
+ discard,
1679
+ getDocument,
1680
+ publish,
1681
+ publishMany,
1682
+ unpublish,
1683
+ unpublishMany,
1684
+ update
1685
+ };
1686
+ };
1687
+ const ProtectedHistoryPage = React.lazy(
1688
+ () => Promise.resolve().then(() => require("./History-Dl6wOm0V.js")).then((mod) => ({ default: mod.ProtectedHistoryPage }))
1689
+ );
1690
+ const routes$1 = [
1691
+ {
1692
+ path: ":collectionType/:slug/:id/history",
1693
+ Component: ProtectedHistoryPage
1694
+ },
1695
+ {
1696
+ path: ":collectionType/:slug/history",
1697
+ Component: ProtectedHistoryPage
1698
+ }
1699
+ ];
1700
+ const ProtectedEditViewPage = React.lazy(
1701
+ () => Promise.resolve().then(() => require("./EditViewPage-CgfL33yw.js")).then((mod) => ({ default: mod.ProtectedEditViewPage }))
1702
+ );
1703
+ const ProtectedListViewPage = React.lazy(
1704
+ () => Promise.resolve().then(() => require("./ListViewPage-5QzoAtAo.js")).then((mod) => ({ default: mod.ProtectedListViewPage }))
1705
+ );
1706
+ const ProtectedListConfiguration = React.lazy(
1707
+ () => Promise.resolve().then(() => require("./ListConfigurationPage-BedUJnmo.js")).then((mod) => ({
1708
+ default: mod.ProtectedListConfiguration
1709
+ }))
1710
+ );
1711
+ const ProtectedEditConfigurationPage = React.lazy(
1712
+ () => Promise.resolve().then(() => require("./EditConfigurationPage-BOAqRJUV.js")).then((mod) => ({
1713
+ default: mod.ProtectedEditConfigurationPage
1714
+ }))
1715
+ );
1716
+ const ProtectedComponentConfigurationPage = React.lazy(
1717
+ () => Promise.resolve().then(() => require("./ComponentConfigurationPage-DRh2GoZx.js")).then((mod) => ({
1718
+ default: mod.ProtectedComponentConfigurationPage
1719
+ }))
1720
+ );
1721
+ const NoPermissions = React.lazy(
1722
+ () => Promise.resolve().then(() => require("./NoPermissionsPage-CCAreseM.js")).then((mod) => ({ default: mod.NoPermissions }))
1723
+ );
1724
+ const NoContentType = React.lazy(
1725
+ () => Promise.resolve().then(() => require("./NoContentTypePage-DBIyA7hd.js")).then((mod) => ({ default: mod.NoContentType }))
1726
+ );
1727
+ const CollectionTypePages = () => {
1728
+ const { collectionType } = reactRouterDom.useParams();
1729
+ if (collectionType !== COLLECTION_TYPES && collectionType !== SINGLE_TYPES) {
1730
+ return /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Navigate, { to: "/404" });
1731
+ }
1732
+ return collectionType === COLLECTION_TYPES ? /* @__PURE__ */ jsxRuntime.jsx(ProtectedListViewPage, {}) : /* @__PURE__ */ jsxRuntime.jsx(ProtectedEditViewPage, {});
1733
+ };
1734
+ const CLONE_RELATIVE_PATH = ":collectionType/:slug/clone/:origin";
1735
+ const CLONE_PATH = `/content-manager/${CLONE_RELATIVE_PATH}`;
1736
+ const LIST_RELATIVE_PATH = ":collectionType/:slug";
1737
+ const LIST_PATH = `/content-manager/${LIST_RELATIVE_PATH}`;
1738
+ const routes = [
1739
+ {
1740
+ path: LIST_RELATIVE_PATH,
1741
+ element: /* @__PURE__ */ jsxRuntime.jsx(CollectionTypePages, {})
1742
+ },
1743
+ {
1744
+ path: ":collectionType/:slug/:id",
1745
+ Component: ProtectedEditViewPage
1746
+ },
1747
+ {
1748
+ path: CLONE_RELATIVE_PATH,
1749
+ Component: ProtectedEditViewPage
1750
+ },
1751
+ {
1752
+ path: ":collectionType/:slug/configurations/list",
1753
+ Component: ProtectedListConfiguration
1754
+ },
1755
+ {
1756
+ path: "components/:slug/configurations/edit",
1757
+ Component: ProtectedComponentConfigurationPage
1758
+ },
1759
+ {
1760
+ path: ":collectionType/:slug/configurations/edit",
1761
+ Component: ProtectedEditConfigurationPage
1762
+ },
1763
+ {
1764
+ path: "403",
1765
+ Component: NoPermissions
1766
+ },
1767
+ {
1768
+ path: "no-content-types",
1769
+ Component: NoContentType
1770
+ },
1771
+ ...routes$1
1772
+ ];
1773
+ const DocumentActions = ({ actions: actions2 }) => {
1774
+ const { formatMessage } = reactIntl.useIntl();
1775
+ const [primaryAction, secondaryAction, ...restActions] = actions2.filter((action) => {
1776
+ if (action.position === void 0) {
1777
+ return true;
1829
1778
  }
1830
- };
1831
- const totalDraftRelations = localCountOfDraftRelations + serverCountOfDraftRelations;
1832
- const hasDraftRelations = totalDraftRelations > 0;
1833
- return {
1834
- /**
1835
- * Disabled when:
1836
- * - currently if you're cloning a document we don't support publish & clone at the same time.
1837
- * - the form is submitting
1838
- * - the active tab is the published tab
1839
- * - the document is already published & not modified
1840
- * - the document is being created & not modified
1841
- * - the user doesn't have the permission to publish
1842
- */
1843
- disabled: isCloning || isSubmitting || isLoadingDraftRelations || activeTab === "published" || !modified && isDocumentPublished || !modified && !document?.documentId || !canPublish,
1844
- label: formatMessage({
1845
- id: "app.utils.publish",
1846
- defaultMessage: "Publish"
1847
- }),
1848
- onClick: async () => {
1849
- if (hasDraftRelations) {
1850
- return;
1851
- }
1852
- await performPublish();
1853
- },
1854
- dialog: hasDraftRelations ? {
1855
- type: "dialog",
1856
- variant: "danger",
1857
- footer: null,
1858
- title: formatMessage({
1859
- id: getTranslation(`popUpwarning.warning.bulk-has-draft-relations.title`),
1860
- defaultMessage: "Confirmation"
1861
- }),
1862
- content: formatMessage(
1863
- {
1864
- id: getTranslation(`popUpwarning.warning.bulk-has-draft-relations.message`),
1865
- defaultMessage: "This entry is related to {count, plural, one {# draft entry} other {# draft entries}}. Publishing it could leave broken links in your app."
1866
- },
1779
+ const positions = Array.isArray(action.position) ? action.position : [action.position];
1780
+ return positions.includes("panel");
1781
+ });
1782
+ if (!primaryAction) {
1783
+ return null;
1784
+ }
1785
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 2, alignItems: "stretch", width: "100%", children: [
1786
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
1787
+ /* @__PURE__ */ jsxRuntime.jsx(DocumentActionButton, { ...primaryAction, variant: primaryAction.variant || "default" }),
1788
+ restActions.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
1789
+ DocumentActionsMenu,
1867
1790
  {
1868
- count: totalDraftRelations
1791
+ actions: restActions,
1792
+ label: formatMessage({
1793
+ id: "content-manager.containers.edit.panels.default.more-actions",
1794
+ defaultMessage: "More document actions"
1795
+ })
1869
1796
  }
1870
- ),
1871
- onConfirm: async () => {
1872
- await performPublish();
1797
+ ) : null
1798
+ ] }),
1799
+ secondaryAction ? /* @__PURE__ */ jsxRuntime.jsx(
1800
+ DocumentActionButton,
1801
+ {
1802
+ ...secondaryAction,
1803
+ variant: secondaryAction.variant || "secondary"
1873
1804
  }
1874
- } : void 0
1875
- };
1805
+ ) : null
1806
+ ] });
1876
1807
  };
1877
- PublishAction$1.type = "publish";
1878
- const UpdateAction = ({
1879
- activeTab,
1880
- documentId,
1881
- model,
1882
- collectionType
1883
- }) => {
1884
- const navigate = reactRouterDom.useNavigate();
1808
+ const DocumentActionButton = (action) => {
1809
+ const [dialogId, setDialogId] = React__namespace.useState(null);
1885
1810
  const { toggleNotification } = strapiAdmin.useNotification();
1886
- const { _unstableFormatValidationErrors: formatValidationErrors } = strapiAdmin.useAPIErrorHandler();
1887
- const cloneMatch = reactRouterDom.useMatch(CLONE_PATH);
1888
- const isCloning = cloneMatch !== null;
1889
- const { formatMessage } = reactIntl.useIntl();
1890
- useDocumentRBAC("UpdateAction", ({ canCreate: canCreate2, canUpdate: canUpdate2 }) => ({
1891
- canCreate: canCreate2,
1892
- canUpdate: canUpdate2
1893
- }));
1894
- const { create, update, clone } = useDocumentActions();
1895
- const [{ query, rawQuery }] = strapiAdmin.useQueryParams();
1896
- const params = React__namespace.useMemo(() => buildValidParams(query), [query]);
1897
- const isSubmitting = strapiAdmin.useForm("UpdateAction", ({ isSubmitting: isSubmitting2 }) => isSubmitting2);
1898
- const modified = strapiAdmin.useForm("UpdateAction", ({ modified: modified2 }) => modified2);
1899
- const setSubmitting = strapiAdmin.useForm("UpdateAction", ({ setSubmitting: setSubmitting2 }) => setSubmitting2);
1900
- const document = strapiAdmin.useForm("UpdateAction", ({ values }) => values);
1901
- const validate = strapiAdmin.useForm("UpdateAction", (state) => state.validate);
1902
- const setErrors = strapiAdmin.useForm("UpdateAction", (state) => state.setErrors);
1903
- const resetForm = strapiAdmin.useForm("PublishAction", ({ resetForm: resetForm2 }) => resetForm2);
1904
- return {
1905
- /**
1906
- * Disabled when:
1907
- * - the form is submitting
1908
- * - the document is not modified & we're not cloning (you can save a clone entity straight away)
1909
- * - the active tab is the published tab
1910
- */
1911
- disabled: isSubmitting || !modified && !isCloning || activeTab === "published",
1912
- label: formatMessage({
1913
- id: "content-manager.containers.Edit.save",
1914
- defaultMessage: "Save"
1915
- }),
1916
- onClick: async () => {
1917
- setSubmitting(true);
1918
- try {
1919
- const { errors } = await validate();
1920
- if (errors) {
1811
+ const handleClick = (action2) => async (e) => {
1812
+ const { onClick = () => false, dialog, id } = action2;
1813
+ const muteDialog = await onClick(e);
1814
+ if (dialog && !muteDialog) {
1815
+ switch (dialog.type) {
1816
+ case "notification":
1921
1817
  toggleNotification({
1922
- type: "danger",
1923
- message: formatMessage({
1924
- id: "content-manager.validation.error",
1925
- defaultMessage: "There are validation errors in your document. Please fix them before saving."
1926
- })
1818
+ title: dialog.title,
1819
+ message: dialog.content,
1820
+ type: dialog.status,
1821
+ timeout: dialog.timeout,
1822
+ onClose: dialog.onClose
1927
1823
  });
1928
- return;
1929
- }
1930
- if (isCloning) {
1931
- const res = await clone(
1932
- {
1933
- model,
1934
- documentId: cloneMatch.params.origin,
1935
- params
1936
- },
1937
- document
1938
- );
1939
- if ("data" in res) {
1940
- navigate(
1941
- {
1942
- pathname: `../${res.data.documentId}`,
1943
- search: rawQuery
1944
- },
1945
- { relative: "path" }
1946
- );
1947
- } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1948
- setErrors(formatValidationErrors(res.error));
1949
- }
1950
- } else if (documentId || collectionType === SINGLE_TYPES) {
1951
- const res = await update(
1952
- {
1953
- collectionType,
1954
- model,
1955
- documentId,
1956
- params
1957
- },
1958
- document
1959
- );
1960
- if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1961
- setErrors(formatValidationErrors(res.error));
1962
- } else {
1963
- resetForm();
1964
- }
1965
- } else {
1966
- const res = await create(
1967
- {
1968
- model,
1969
- params
1970
- },
1971
- document
1972
- );
1973
- if ("data" in res && collectionType !== SINGLE_TYPES) {
1974
- navigate(
1975
- {
1976
- pathname: `../${res.data.documentId}`,
1977
- search: rawQuery
1978
- },
1979
- { replace: true, relative: "path" }
1980
- );
1981
- } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1982
- setErrors(formatValidationErrors(res.error));
1983
- }
1984
- }
1985
- } finally {
1986
- setSubmitting(false);
1824
+ break;
1825
+ case "dialog":
1826
+ case "modal":
1827
+ e.preventDefault();
1828
+ setDialogId(id);
1987
1829
  }
1988
1830
  }
1989
1831
  };
1832
+ const handleClose = () => {
1833
+ setDialogId(null);
1834
+ };
1835
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1836
+ /* @__PURE__ */ jsxRuntime.jsx(
1837
+ designSystem.Button,
1838
+ {
1839
+ flex: "auto",
1840
+ startIcon: action.icon,
1841
+ disabled: action.disabled,
1842
+ onClick: handleClick(action),
1843
+ justifyContent: "center",
1844
+ variant: action.variant || "default",
1845
+ paddingTop: "7px",
1846
+ paddingBottom: "7px",
1847
+ children: action.label
1848
+ }
1849
+ ),
1850
+ action.dialog?.type === "dialog" ? /* @__PURE__ */ jsxRuntime.jsx(
1851
+ DocumentActionConfirmDialog,
1852
+ {
1853
+ ...action.dialog,
1854
+ variant: action.dialog?.variant ?? action.variant,
1855
+ isOpen: dialogId === action.id,
1856
+ onClose: handleClose
1857
+ }
1858
+ ) : null,
1859
+ action.dialog?.type === "modal" ? /* @__PURE__ */ jsxRuntime.jsx(
1860
+ DocumentActionModal,
1861
+ {
1862
+ ...action.dialog,
1863
+ onModalClose: handleClose,
1864
+ isOpen: dialogId === action.id
1865
+ }
1866
+ ) : null
1867
+ ] });
1990
1868
  };
1991
- UpdateAction.type = "update";
1992
- const UNPUBLISH_DRAFT_OPTIONS = {
1993
- KEEP: "keep",
1994
- DISCARD: "discard"
1995
- };
1996
- const UnpublishAction$1 = ({
1997
- activeTab,
1998
- documentId,
1999
- model,
2000
- collectionType,
2001
- document
1869
+ const DocumentActionsMenu = ({
1870
+ actions: actions2,
1871
+ children,
1872
+ label,
1873
+ variant = "tertiary"
2002
1874
  }) => {
1875
+ const [isOpen, setIsOpen] = React__namespace.useState(false);
1876
+ const [dialogId, setDialogId] = React__namespace.useState(null);
2003
1877
  const { formatMessage } = reactIntl.useIntl();
2004
- const { schema } = useDoc();
2005
- const canPublish = useDocumentRBAC("UnpublishAction", ({ canPublish: canPublish2 }) => canPublish2);
2006
- const { unpublish } = useDocumentActions();
2007
- const [{ query }] = strapiAdmin.useQueryParams();
2008
- const params = React__namespace.useMemo(() => buildValidParams(query), [query]);
2009
1878
  const { toggleNotification } = strapiAdmin.useNotification();
2010
- const [shouldKeepDraft, setShouldKeepDraft] = React__namespace.useState(true);
2011
- const isDocumentModified = document?.status === "modified";
2012
- const handleChange = (value) => {
2013
- setShouldKeepDraft(value === UNPUBLISH_DRAFT_OPTIONS.KEEP);
2014
- };
2015
- if (!schema?.options?.draftAndPublish) {
2016
- return null;
2017
- }
2018
- return {
2019
- disabled: !canPublish || activeTab === "published" || document?.status !== "published" && document?.status !== "modified",
2020
- label: formatMessage({
2021
- id: "app.utils.unpublish",
2022
- defaultMessage: "Unpublish"
2023
- }),
2024
- icon: /* @__PURE__ */ jsxRuntime.jsx(StyledCrossCircle, {}),
2025
- onClick: async () => {
2026
- if (!documentId && collectionType !== SINGLE_TYPES || isDocumentModified) {
2027
- if (!documentId) {
2028
- console.error(
2029
- "You're trying to unpublish a document without an id, this is likely a bug with Strapi. Please open an issue."
2030
- );
1879
+ const isDisabled = actions2.every((action) => action.disabled) || actions2.length === 0;
1880
+ const handleClick = (action) => async (e) => {
1881
+ const { onClick = () => false, dialog, id } = action;
1882
+ const muteDialog = await onClick(e);
1883
+ if (dialog && !muteDialog) {
1884
+ switch (dialog.type) {
1885
+ case "notification":
2031
1886
  toggleNotification({
2032
- message: formatMessage({
2033
- id: "content-manager.actions.unpublish.error",
2034
- defaultMessage: "An error occurred while trying to unpublish the document."
2035
- }),
2036
- type: "danger"
1887
+ title: dialog.title,
1888
+ message: dialog.content,
1889
+ type: dialog.status,
1890
+ timeout: dialog.timeout,
1891
+ onClose: dialog.onClose
2037
1892
  });
2038
- }
2039
- return;
1893
+ break;
1894
+ case "dialog":
1895
+ case "modal":
1896
+ setDialogId(id);
2040
1897
  }
2041
- await unpublish({
2042
- collectionType,
2043
- model,
2044
- documentId,
2045
- params
2046
- });
2047
- },
2048
- dialog: isDocumentModified ? {
2049
- type: "dialog",
2050
- title: formatMessage({
2051
- id: "app.components.ConfirmDialog.title",
2052
- defaultMessage: "Confirmation"
2053
- }),
2054
- content: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "flex-start", direction: "column", gap: 6, children: [
2055
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { width: "100%", direction: "column", gap: 2, children: [
2056
- /* @__PURE__ */ jsxRuntime.jsx(Icons.WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
2057
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
2058
- id: "content-manager.actions.unpublish.dialog.body",
2059
- defaultMessage: "Are you sure?"
1898
+ }
1899
+ };
1900
+ const handleClose = () => {
1901
+ setDialogId(null);
1902
+ setIsOpen(false);
1903
+ };
1904
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Menu.Root, { open: isOpen, onOpenChange: setIsOpen, children: [
1905
+ /* @__PURE__ */ jsxRuntime.jsxs(
1906
+ designSystem.Menu.Trigger,
1907
+ {
1908
+ disabled: isDisabled,
1909
+ size: "S",
1910
+ endIcon: null,
1911
+ paddingTop: "4px",
1912
+ paddingLeft: "7px",
1913
+ paddingRight: "7px",
1914
+ variant,
1915
+ children: [
1916
+ /* @__PURE__ */ jsxRuntime.jsx(Icons.More, { "aria-hidden": true, focusable: false }),
1917
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.VisuallyHidden, { tag: "span", children: label || formatMessage({
1918
+ id: "content-manager.containers.edit.panels.default.more-actions",
1919
+ defaultMessage: "More document actions"
2060
1920
  }) })
2061
- ] }),
2062
- /* @__PURE__ */ jsxRuntime.jsxs(
2063
- designSystem.Radio.Group,
2064
- {
2065
- defaultValue: UNPUBLISH_DRAFT_OPTIONS.KEEP,
2066
- name: "discard-options",
2067
- "aria-label": formatMessage({
2068
- id: "content-manager.actions.unpublish.dialog.radio-label",
2069
- defaultMessage: "Choose an option to unpublish the document."
2070
- }),
2071
- onValueChange: handleChange,
2072
- children: [
2073
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Radio.Item, { checked: shouldKeepDraft, value: UNPUBLISH_DRAFT_OPTIONS.KEEP, children: formatMessage({
2074
- id: "content-manager.actions.unpublish.dialog.option.keep-draft",
2075
- defaultMessage: "Keep draft"
2076
- }) }),
2077
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Radio.Item, { checked: !shouldKeepDraft, value: UNPUBLISH_DRAFT_OPTIONS.DISCARD, children: formatMessage({
2078
- id: "content-manager.actions.unpublish.dialog.option.replace-draft",
2079
- defaultMessage: "Replace draft"
2080
- }) })
2081
- ]
2082
- }
2083
- )
2084
- ] }),
2085
- onConfirm: async () => {
2086
- if (!documentId && collectionType !== SINGLE_TYPES) {
2087
- console.error(
2088
- "You're trying to unpublish a document without an id, this is likely a bug with Strapi. Please open an issue."
2089
- );
2090
- toggleNotification({
2091
- message: formatMessage({
2092
- id: "content-manager.actions.unpublish.error",
2093
- defaultMessage: "An error occurred while trying to unpublish the document."
2094
- }),
2095
- type: "danger"
2096
- });
2097
- }
2098
- await unpublish(
1921
+ ]
1922
+ }
1923
+ ),
1924
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Menu.Content, { maxHeight: void 0, popoverPlacement: "bottom-end", children: [
1925
+ actions2.map((action) => {
1926
+ return /* @__PURE__ */ jsxRuntime.jsx(
1927
+ designSystem.Menu.Item,
2099
1928
  {
2100
- collectionType,
2101
- model,
2102
- documentId,
2103
- params
1929
+ disabled: action.disabled,
1930
+ onSelect: handleClick(action),
1931
+ display: "block",
1932
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", gap: 4, children: [
1933
+ /* @__PURE__ */ jsxRuntime.jsxs(
1934
+ designSystem.Flex,
1935
+ {
1936
+ color: !action.disabled ? convertActionVariantToColor(action.variant) : "inherit",
1937
+ gap: 2,
1938
+ tag: "span",
1939
+ children: [
1940
+ /* @__PURE__ */ jsxRuntime.jsx(
1941
+ designSystem.Flex,
1942
+ {
1943
+ tag: "span",
1944
+ color: !action.disabled ? convertActionVariantToIconColor(action.variant) : "inherit",
1945
+ children: action.icon
1946
+ }
1947
+ ),
1948
+ action.label
1949
+ ]
1950
+ }
1951
+ ),
1952
+ action.id.startsWith("HistoryAction") && /* @__PURE__ */ jsxRuntime.jsx(
1953
+ designSystem.Flex,
1954
+ {
1955
+ alignItems: "center",
1956
+ background: "alternative100",
1957
+ borderStyle: "solid",
1958
+ borderColor: "alternative200",
1959
+ borderWidth: "1px",
1960
+ height: 5,
1961
+ paddingLeft: 2,
1962
+ paddingRight: 2,
1963
+ hasRadius: true,
1964
+ color: "alternative600",
1965
+ children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", fontWeight: "bold", lineHeight: 1, children: formatMessage({ id: "global.new", defaultMessage: "New" }) })
1966
+ }
1967
+ )
1968
+ ] })
2104
1969
  },
2105
- !shouldKeepDraft
1970
+ action.id
2106
1971
  );
2107
- }
2108
- } : void 0,
2109
- variant: "danger",
2110
- position: ["panel", "table-row"]
1972
+ }),
1973
+ children
1974
+ ] }),
1975
+ actions2.map((action) => {
1976
+ return /* @__PURE__ */ jsxRuntime.jsxs(React__namespace.Fragment, { children: [
1977
+ action.dialog?.type === "dialog" ? /* @__PURE__ */ jsxRuntime.jsx(
1978
+ DocumentActionConfirmDialog,
1979
+ {
1980
+ ...action.dialog,
1981
+ variant: action.variant,
1982
+ isOpen: dialogId === action.id,
1983
+ onClose: handleClose
1984
+ }
1985
+ ) : null,
1986
+ action.dialog?.type === "modal" ? /* @__PURE__ */ jsxRuntime.jsx(
1987
+ DocumentActionModal,
1988
+ {
1989
+ ...action.dialog,
1990
+ onModalClose: handleClose,
1991
+ isOpen: dialogId === action.id
1992
+ }
1993
+ ) : null
1994
+ ] }, action.id);
1995
+ })
1996
+ ] });
1997
+ };
1998
+ const convertActionVariantToColor = (variant = "secondary") => {
1999
+ switch (variant) {
2000
+ case "danger":
2001
+ return "danger600";
2002
+ case "secondary":
2003
+ return void 0;
2004
+ case "success":
2005
+ return "success600";
2006
+ default:
2007
+ return "primary600";
2008
+ }
2009
+ };
2010
+ const convertActionVariantToIconColor = (variant = "secondary") => {
2011
+ switch (variant) {
2012
+ case "danger":
2013
+ return "danger600";
2014
+ case "secondary":
2015
+ return "neutral500";
2016
+ case "success":
2017
+ return "success600";
2018
+ default:
2019
+ return "primary600";
2020
+ }
2021
+ };
2022
+ const DocumentActionConfirmDialog = ({
2023
+ onClose,
2024
+ onCancel,
2025
+ onConfirm,
2026
+ title,
2027
+ content,
2028
+ isOpen,
2029
+ variant = "secondary"
2030
+ }) => {
2031
+ const { formatMessage } = reactIntl.useIntl();
2032
+ const handleClose = async () => {
2033
+ if (onCancel) {
2034
+ await onCancel();
2035
+ }
2036
+ onClose();
2037
+ };
2038
+ const handleConfirm = async () => {
2039
+ if (onConfirm) {
2040
+ await onConfirm();
2041
+ }
2042
+ onClose();
2111
2043
  };
2044
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Dialog.Content, { children: [
2045
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Header, { children: title }),
2046
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Body, { children: content }),
2047
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Dialog.Footer, { children: [
2048
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Cancel, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", fullWidth: true, children: formatMessage({
2049
+ id: "app.components.Button.cancel",
2050
+ defaultMessage: "Cancel"
2051
+ }) }) }),
2052
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: handleConfirm, variant, fullWidth: true, children: formatMessage({
2053
+ id: "app.components.Button.confirm",
2054
+ defaultMessage: "Confirm"
2055
+ }) })
2056
+ ] })
2057
+ ] }) });
2112
2058
  };
2113
- UnpublishAction$1.type = "unpublish";
2114
- const DiscardAction = ({
2115
- activeTab,
2116
- documentId,
2117
- model,
2118
- collectionType,
2119
- document
2059
+ const DocumentActionModal = ({
2060
+ isOpen,
2061
+ title,
2062
+ onClose,
2063
+ footer: Footer,
2064
+ content: Content,
2065
+ onModalClose
2120
2066
  }) => {
2121
- const { formatMessage } = reactIntl.useIntl();
2122
- const { schema } = useDoc();
2123
- const canUpdate = useDocumentRBAC("DiscardAction", ({ canUpdate: canUpdate2 }) => canUpdate2);
2124
- const { discard } = useDocumentActions();
2125
- const [{ query }] = strapiAdmin.useQueryParams();
2126
- const params = React__namespace.useMemo(() => buildValidParams(query), [query]);
2127
- if (!schema?.options?.draftAndPublish) {
2128
- return null;
2129
- }
2130
- return {
2131
- disabled: !canUpdate || activeTab === "published" || document?.status !== "modified",
2132
- label: formatMessage({
2133
- id: "content-manager.actions.discard.label",
2134
- defaultMessage: "Discard changes"
2135
- }),
2136
- icon: /* @__PURE__ */ jsxRuntime.jsx(StyledCrossCircle, {}),
2137
- position: ["panel", "table-row"],
2138
- variant: "danger",
2139
- dialog: {
2140
- type: "dialog",
2141
- title: formatMessage({
2142
- id: "app.components.ConfirmDialog.title",
2143
- defaultMessage: "Confirmation"
2144
- }),
2145
- content: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 2, children: [
2146
- /* @__PURE__ */ jsxRuntime.jsx(Icons.WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
2147
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
2148
- id: "content-manager.actions.discard.dialog.body",
2149
- defaultMessage: "Are you sure?"
2150
- }) })
2151
- ] }),
2152
- onConfirm: async () => {
2153
- await discard({
2154
- collectionType,
2155
- model,
2156
- documentId,
2157
- params
2158
- });
2159
- }
2067
+ const handleClose = () => {
2068
+ if (onClose) {
2069
+ onClose();
2160
2070
  }
2071
+ onModalClose();
2161
2072
  };
2073
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Content, { children: [
2074
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Title, { children: title }) }),
2075
+ typeof Content === "function" ? /* @__PURE__ */ jsxRuntime.jsx(Content, { onClose: handleClose }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Body, { children: Content }),
2076
+ typeof Footer === "function" ? /* @__PURE__ */ jsxRuntime.jsx(Footer, { onClose: handleClose }) : Footer
2077
+ ] }) });
2162
2078
  };
2163
- DiscardAction.type = "discard";
2164
- const StyledCrossCircle = styledComponents.styled(Icons.CrossCircle)`
2165
- path {
2166
- fill: currentColor;
2167
- }
2168
- `;
2169
- const DEFAULT_ACTIONS = [PublishAction$1, UpdateAction, UnpublishAction$1, DiscardAction];
2170
- const intervals = ["years", "months", "days", "hours", "minutes", "seconds"];
2171
- const RelativeTime = React__namespace.forwardRef(
2172
- ({ timestamp, customIntervals = [], ...restProps }, forwardedRef) => {
2173
- const { formatRelativeTime, formatDate, formatTime } = reactIntl.useIntl();
2174
- const interval = dateFns.intervalToDuration({
2175
- start: timestamp,
2176
- end: Date.now()
2177
- // see https://github.com/date-fns/date-fns/issues/2891 – No idea why it's all partial it returns it every time.
2178
- });
2179
- const unit = intervals.find((intervalUnit) => {
2180
- return interval[intervalUnit] > 0 && Object.keys(interval).includes(intervalUnit);
2181
- });
2182
- const relativeTime = dateFns.isPast(timestamp) ? -interval[unit] : interval[unit];
2183
- const customInterval = customIntervals.find(
2184
- (custom) => interval[custom.unit] < custom.threshold
2185
- );
2186
- const displayText = customInterval ? customInterval.text : formatRelativeTime(relativeTime, unit, { numeric: "auto" });
2187
- return /* @__PURE__ */ jsxRuntime.jsx(
2188
- "time",
2189
- {
2190
- ref: forwardedRef,
2191
- dateTime: timestamp.toISOString(),
2192
- role: "time",
2193
- title: `${formatDate(timestamp)} ${formatTime(timestamp)}`,
2194
- ...restProps,
2195
- children: displayText
2196
- }
2197
- );
2198
- }
2199
- );
2200
- const getDisplayName = ({
2201
- firstname,
2202
- lastname,
2203
- username,
2204
- email
2205
- } = {}) => {
2206
- if (username) {
2207
- return username;
2079
+ const transformData = (data) => {
2080
+ if (Array.isArray(data)) {
2081
+ return data.map(transformData);
2208
2082
  }
2209
- if (firstname) {
2210
- return `${firstname} ${lastname ?? ""}`.trim();
2083
+ if (typeof data === "object" && data !== null) {
2084
+ if ("apiData" in data) {
2085
+ return data.apiData;
2086
+ }
2087
+ return mapValues__default.default(transformData)(data);
2211
2088
  }
2212
- return email ?? "";
2213
- };
2214
- const capitalise = (str) => str.charAt(0).toUpperCase() + str.slice(1);
2215
- const DocumentStatus = ({ status = "draft", ...restProps }) => {
2216
- const statusVariant = status === "draft" ? "primary" : status === "published" ? "success" : "alternative";
2217
- return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Status, { ...restProps, showBullet: false, size: "S", variant: statusVariant, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "span", variant: "omega", fontWeight: "bold", children: capitalise(status) }) });
2089
+ return data;
2218
2090
  };
2219
- const Header = ({ isCreating, status, title: documentTitle = "Untitled" }) => {
2220
- const { formatMessage } = reactIntl.useIntl();
2091
+ const PublishAction$1 = ({
2092
+ activeTab,
2093
+ documentId,
2094
+ model,
2095
+ collectionType,
2096
+ meta,
2097
+ document
2098
+ }) => {
2099
+ const { schema } = useDoc();
2100
+ const navigate = reactRouterDom.useNavigate();
2101
+ const { toggleNotification } = strapiAdmin.useNotification();
2102
+ const { _unstableFormatValidationErrors: formatValidationErrors } = strapiAdmin.useAPIErrorHandler();
2103
+ const isListView = reactRouterDom.useMatch(LIST_PATH) !== null;
2221
2104
  const isCloning = reactRouterDom.useMatch(CLONE_PATH) !== null;
2222
- const title = isCreating ? formatMessage({
2223
- id: "content-manager.containers.edit.title.new",
2224
- defaultMessage: "Create an entry"
2225
- }) : documentTitle;
2226
- return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "flex-start", paddingTop: 6, paddingBottom: 4, gap: 2, children: [
2227
- /* @__PURE__ */ jsxRuntime.jsx(strapiAdmin.BackButton, {}),
2228
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { width: "100%", justifyContent: "space-between", gap: "80px", alignItems: "flex-start", children: [
2229
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", tag: "h1", children: title }),
2230
- /* @__PURE__ */ jsxRuntime.jsx(HeaderToolbar, {})
2231
- ] }),
2232
- status ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 1, children: /* @__PURE__ */ jsxRuntime.jsx(DocumentStatus, { status: isCloning ? "draft" : status }) }) : null
2233
- ] });
2234
- };
2235
- const HeaderToolbar = () => {
2236
2105
  const { formatMessage } = reactIntl.useIntl();
2237
- const isCloning = reactRouterDom.useMatch(CLONE_PATH) !== null;
2106
+ const canPublish = useDocumentRBAC("PublishAction", ({ canPublish: canPublish2 }) => canPublish2);
2107
+ const { publish } = useDocumentActions();
2238
2108
  const [
2239
- {
2240
- query: { status = "draft" }
2109
+ countDraftRelations,
2110
+ { isLoading: isLoadingDraftRelations, isError: isErrorDraftRelations }
2111
+ ] = useLazyGetDraftRelationCountQuery();
2112
+ const [localCountOfDraftRelations, setLocalCountOfDraftRelations] = React__namespace.useState(0);
2113
+ const [serverCountOfDraftRelations, setServerCountOfDraftRelations] = React__namespace.useState(0);
2114
+ const [{ query, rawQuery }] = strapiAdmin.useQueryParams();
2115
+ const params = React__namespace.useMemo(() => buildValidParams(query), [query]);
2116
+ const modified = strapiAdmin.useForm("PublishAction", ({ modified: modified2 }) => modified2);
2117
+ const setSubmitting = strapiAdmin.useForm("PublishAction", ({ setSubmitting: setSubmitting2 }) => setSubmitting2);
2118
+ const isSubmitting = strapiAdmin.useForm("PublishAction", ({ isSubmitting: isSubmitting2 }) => isSubmitting2);
2119
+ const validate = strapiAdmin.useForm("PublishAction", (state) => state.validate);
2120
+ const setErrors = strapiAdmin.useForm("PublishAction", (state) => state.setErrors);
2121
+ const formValues = strapiAdmin.useForm("PublishAction", ({ values }) => values);
2122
+ React__namespace.useEffect(() => {
2123
+ if (isErrorDraftRelations) {
2124
+ toggleNotification({
2125
+ type: "danger",
2126
+ message: formatMessage({
2127
+ id: getTranslation("error.records.fetch-draft-relatons"),
2128
+ defaultMessage: "An error occurred while fetching draft relations on this document."
2129
+ })
2130
+ });
2241
2131
  }
2242
- ] = strapiAdmin.useQueryParams();
2243
- const { model, id, document, meta, collectionType } = useDoc();
2244
- const plugins = strapiAdmin.useStrapiApp("HeaderToolbar", (state) => state.plugins);
2245
- return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
2246
- /* @__PURE__ */ jsxRuntime.jsx(
2247
- strapiAdmin.DescriptionComponentRenderer,
2248
- {
2249
- props: {
2250
- activeTab: status,
2251
- model,
2252
- documentId: id,
2253
- document: isCloning ? void 0 : document,
2254
- meta: isCloning ? void 0 : meta,
2255
- collectionType
2256
- },
2257
- descriptions: plugins["content-manager"].apis.getHeaderActions(),
2258
- children: (actions2) => {
2259
- if (actions2.length > 0) {
2260
- return /* @__PURE__ */ jsxRuntime.jsx(HeaderActions, { actions: actions2 });
2261
- } else {
2262
- return null;
2263
- }
2132
+ }, [isErrorDraftRelations, toggleNotification, formatMessage]);
2133
+ React__namespace.useEffect(() => {
2134
+ const localDraftRelations = /* @__PURE__ */ new Set();
2135
+ const extractDraftRelations = (data) => {
2136
+ const relations = data.connect || [];
2137
+ relations.forEach((relation) => {
2138
+ if (relation.status === "draft") {
2139
+ localDraftRelations.add(relation.id);
2264
2140
  }
2265
- }
2266
- ),
2267
- /* @__PURE__ */ jsxRuntime.jsx(
2268
- strapiAdmin.DescriptionComponentRenderer,
2269
- {
2270
- props: {
2271
- activeTab: status,
2272
- model,
2273
- documentId: id,
2274
- document: isCloning ? void 0 : document,
2275
- meta: isCloning ? void 0 : meta,
2276
- collectionType
2277
- },
2278
- descriptions: plugins["content-manager"].apis.getDocumentActions(),
2279
- children: (actions2) => {
2280
- const headerActions = actions2.filter((action) => {
2281
- const positions = Array.isArray(action.position) ? action.position : [action.position];
2282
- return positions.includes("header");
2283
- });
2284
- return /* @__PURE__ */ jsxRuntime.jsx(
2285
- DocumentActionsMenu,
2286
- {
2287
- actions: headerActions,
2288
- label: formatMessage({
2289
- id: "content-manager.containers.edit.header.more-actions",
2290
- defaultMessage: "More actions"
2291
- }),
2292
- children: /* @__PURE__ */ jsxRuntime.jsx(Information, { activeTab: status })
2293
- }
2294
- );
2141
+ });
2142
+ };
2143
+ const traverseAndExtract = (data) => {
2144
+ Object.entries(data).forEach(([key, value]) => {
2145
+ if (key === "connect" && Array.isArray(value)) {
2146
+ extractDraftRelations({ connect: value });
2147
+ } else if (typeof value === "object" && value !== null) {
2148
+ traverseAndExtract(value);
2295
2149
  }
2150
+ });
2151
+ };
2152
+ if (!documentId || modified) {
2153
+ traverseAndExtract(formValues);
2154
+ setLocalCountOfDraftRelations(localDraftRelations.size);
2155
+ }
2156
+ }, [documentId, modified, formValues, setLocalCountOfDraftRelations]);
2157
+ React__namespace.useEffect(() => {
2158
+ if (!document || !document.documentId || isListView) {
2159
+ return;
2160
+ }
2161
+ const fetchDraftRelationsCount = async () => {
2162
+ const { data, error } = await countDraftRelations({
2163
+ collectionType,
2164
+ model,
2165
+ documentId,
2166
+ params
2167
+ });
2168
+ if (error) {
2169
+ throw error;
2296
2170
  }
2297
- )
2298
- ] });
2299
- };
2300
- const Information = ({ activeTab }) => {
2301
- const { formatMessage } = reactIntl.useIntl();
2302
- const { document, meta } = useDoc();
2303
- if (!document || !document.id) {
2171
+ if (data) {
2172
+ setServerCountOfDraftRelations(data.data);
2173
+ }
2174
+ };
2175
+ fetchDraftRelationsCount();
2176
+ }, [isListView, document, documentId, countDraftRelations, collectionType, model, params]);
2177
+ const isDocumentPublished = (document?.[PUBLISHED_AT_ATTRIBUTE_NAME] || meta?.availableStatus.some((doc) => doc[PUBLISHED_AT_ATTRIBUTE_NAME] !== null)) && document?.status !== "modified";
2178
+ if (!schema?.options?.draftAndPublish) {
2304
2179
  return null;
2305
2180
  }
2306
- const createAndUpdateDocument = activeTab === "draft" ? document : meta?.availableStatus.find((status) => status.publishedAt === null);
2307
- const publishDocument = activeTab === "published" ? document : meta?.availableStatus.find((status) => status.publishedAt !== null);
2308
- const creator = createAndUpdateDocument?.[CREATED_BY_ATTRIBUTE_NAME] ? getDisplayName(createAndUpdateDocument[CREATED_BY_ATTRIBUTE_NAME]) : null;
2309
- const updator = createAndUpdateDocument?.[UPDATED_BY_ATTRIBUTE_NAME] ? getDisplayName(createAndUpdateDocument[UPDATED_BY_ATTRIBUTE_NAME]) : null;
2310
- const information = [
2311
- {
2312
- isDisplayed: !!publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME],
2313
- label: formatMessage({
2314
- id: "content-manager.containers.edit.information.last-published.label",
2315
- defaultMessage: "Last published"
2316
- }),
2317
- value: formatMessage(
2181
+ const performPublish = async () => {
2182
+ setSubmitting(true);
2183
+ try {
2184
+ const { errors } = await validate(true, {
2185
+ status: "published"
2186
+ });
2187
+ if (errors) {
2188
+ toggleNotification({
2189
+ type: "danger",
2190
+ message: formatMessage({
2191
+ id: "content-manager.validation.error",
2192
+ defaultMessage: "There are validation errors in your document. Please fix them before saving."
2193
+ })
2194
+ });
2195
+ return;
2196
+ }
2197
+ const res = await publish(
2318
2198
  {
2319
- id: "content-manager.containers.edit.information.last-published.value",
2320
- defaultMessage: `Published {time}{isAnonymous, select, true {} other { by {author}}}`
2199
+ collectionType,
2200
+ model,
2201
+ documentId,
2202
+ params
2321
2203
  },
2322
- {
2323
- time: /* @__PURE__ */ jsxRuntime.jsx(RelativeTime, { timestamp: new Date(publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME]) }),
2324
- isAnonymous: !publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME],
2325
- author: publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME] ? getDisplayName(publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME]) : null
2326
- }
2327
- )
2204
+ transformData(formValues)
2205
+ );
2206
+ if ("data" in res && collectionType !== SINGLE_TYPES) {
2207
+ navigate({
2208
+ pathname: `../${collectionType}/${model}/${res.data.documentId}`,
2209
+ search: rawQuery
2210
+ });
2211
+ } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
2212
+ setErrors(formatValidationErrors(res.error));
2213
+ }
2214
+ } finally {
2215
+ setSubmitting(false);
2216
+ }
2217
+ };
2218
+ const totalDraftRelations = localCountOfDraftRelations + serverCountOfDraftRelations;
2219
+ const enableDraftRelationsCount = false;
2220
+ const hasDraftRelations = enableDraftRelationsCount;
2221
+ return {
2222
+ /**
2223
+ * Disabled when:
2224
+ * - currently if you're cloning a document we don't support publish & clone at the same time.
2225
+ * - the form is submitting
2226
+ * - the active tab is the published tab
2227
+ * - the document is already published & not modified
2228
+ * - the document is being created & not modified
2229
+ * - the user doesn't have the permission to publish
2230
+ */
2231
+ disabled: isCloning || isSubmitting || isLoadingDraftRelations || activeTab === "published" || !modified && isDocumentPublished || !modified && !document?.documentId || !canPublish,
2232
+ label: formatMessage({
2233
+ id: "app.utils.publish",
2234
+ defaultMessage: "Publish"
2235
+ }),
2236
+ onClick: async () => {
2237
+ await performPublish();
2328
2238
  },
2329
- {
2330
- isDisplayed: !!createAndUpdateDocument?.[UPDATED_AT_ATTRIBUTE_NAME],
2331
- label: formatMessage({
2332
- id: "content-manager.containers.edit.information.last-draft.label",
2333
- defaultMessage: "Last draft"
2239
+ dialog: hasDraftRelations ? {
2240
+ type: "dialog",
2241
+ variant: "danger",
2242
+ footer: null,
2243
+ title: formatMessage({
2244
+ id: getTranslation(`popUpwarning.warning.bulk-has-draft-relations.title`),
2245
+ defaultMessage: "Confirmation"
2334
2246
  }),
2335
- value: formatMessage(
2247
+ content: formatMessage(
2336
2248
  {
2337
- id: "content-manager.containers.edit.information.last-draft.value",
2338
- defaultMessage: `Modified {time}{isAnonymous, select, true {} other { by {author}}}`
2249
+ id: getTranslation(`popUpwarning.warning.bulk-has-draft-relations.message`),
2250
+ defaultMessage: "This entry is related to {count, plural, one {# draft entry} other {# draft entries}}. Publishing it could leave broken links in your app."
2339
2251
  },
2340
2252
  {
2341
- time: /* @__PURE__ */ jsxRuntime.jsx(
2342
- RelativeTime,
2343
- {
2344
- timestamp: new Date(createAndUpdateDocument?.[UPDATED_AT_ATTRIBUTE_NAME])
2345
- }
2346
- ),
2347
- isAnonymous: !updator,
2348
- author: updator
2253
+ count: totalDraftRelations
2349
2254
  }
2350
- )
2351
- },
2352
- {
2353
- isDisplayed: !!createAndUpdateDocument?.[CREATED_AT_ATTRIBUTE_NAME],
2354
- label: formatMessage({
2355
- id: "content-manager.containers.edit.information.document.label",
2356
- defaultMessage: "Document"
2357
- }),
2358
- value: formatMessage(
2359
- {
2360
- id: "content-manager.containers.edit.information.document.value",
2361
- defaultMessage: `Created {time}{isAnonymous, select, true {} other { by {author}}}`
2362
- },
2363
- {
2364
- time: /* @__PURE__ */ jsxRuntime.jsx(
2365
- RelativeTime,
2366
- {
2367
- timestamp: new Date(createAndUpdateDocument?.[CREATED_AT_ATTRIBUTE_NAME])
2368
- }
2369
- ),
2370
- isAnonymous: !creator,
2371
- author: creator
2255
+ ),
2256
+ onConfirm: async () => {
2257
+ await performPublish();
2258
+ }
2259
+ } : void 0
2260
+ };
2261
+ };
2262
+ PublishAction$1.type = "publish";
2263
+ const UpdateAction = ({
2264
+ activeTab,
2265
+ documentId,
2266
+ model,
2267
+ collectionType
2268
+ }) => {
2269
+ const navigate = reactRouterDom.useNavigate();
2270
+ const { toggleNotification } = strapiAdmin.useNotification();
2271
+ const { _unstableFormatValidationErrors: formatValidationErrors } = strapiAdmin.useAPIErrorHandler();
2272
+ const cloneMatch = reactRouterDom.useMatch(CLONE_PATH);
2273
+ const isCloning = cloneMatch !== null;
2274
+ const { formatMessage } = reactIntl.useIntl();
2275
+ const { create, update, clone } = useDocumentActions();
2276
+ const [{ query, rawQuery }] = strapiAdmin.useQueryParams();
2277
+ const params = React__namespace.useMemo(() => buildValidParams(query), [query]);
2278
+ const isSubmitting = strapiAdmin.useForm("UpdateAction", ({ isSubmitting: isSubmitting2 }) => isSubmitting2);
2279
+ const modified = strapiAdmin.useForm("UpdateAction", ({ modified: modified2 }) => modified2);
2280
+ const setSubmitting = strapiAdmin.useForm("UpdateAction", ({ setSubmitting: setSubmitting2 }) => setSubmitting2);
2281
+ const document = strapiAdmin.useForm("UpdateAction", ({ values }) => values);
2282
+ const validate = strapiAdmin.useForm("UpdateAction", (state) => state.validate);
2283
+ const setErrors = strapiAdmin.useForm("UpdateAction", (state) => state.setErrors);
2284
+ const resetForm = strapiAdmin.useForm("PublishAction", ({ resetForm: resetForm2 }) => resetForm2);
2285
+ return {
2286
+ /**
2287
+ * Disabled when:
2288
+ * - the form is submitting
2289
+ * - the document is not modified & we're not cloning (you can save a clone entity straight away)
2290
+ * - the active tab is the published tab
2291
+ */
2292
+ disabled: isSubmitting || !modified && !isCloning || activeTab === "published",
2293
+ label: formatMessage({
2294
+ id: "content-manager.containers.Edit.save",
2295
+ defaultMessage: "Save"
2296
+ }),
2297
+ onClick: async () => {
2298
+ setSubmitting(true);
2299
+ try {
2300
+ const { errors } = await validate(true, {
2301
+ status: "draft"
2302
+ });
2303
+ if (errors) {
2304
+ toggleNotification({
2305
+ type: "danger",
2306
+ message: formatMessage({
2307
+ id: "content-manager.validation.error",
2308
+ defaultMessage: "There are validation errors in your document. Please fix them before saving."
2309
+ })
2310
+ });
2311
+ return;
2372
2312
  }
2373
- )
2374
- }
2375
- ].filter((info) => info.isDisplayed);
2376
- return /* @__PURE__ */ jsxRuntime.jsx(
2377
- designSystem.Flex,
2378
- {
2379
- borderWidth: "1px 0 0 0",
2380
- borderStyle: "solid",
2381
- borderColor: "neutral150",
2382
- direction: "column",
2383
- marginTop: 2,
2384
- tag: "dl",
2385
- padding: 5,
2386
- gap: 3,
2387
- alignItems: "flex-start",
2388
- marginLeft: "-0.4rem",
2389
- marginRight: "-0.4rem",
2390
- width: "calc(100% + 8px)",
2391
- children: information.map((info) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, direction: "column", alignItems: "flex-start", children: [
2392
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "dt", variant: "pi", fontWeight: "bold", children: info.label }),
2393
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "dd", variant: "pi", textColor: "neutral600", children: info.value })
2394
- ] }, info.label))
2313
+ if (isCloning) {
2314
+ const res = await clone(
2315
+ {
2316
+ model,
2317
+ documentId: cloneMatch.params.origin,
2318
+ params
2319
+ },
2320
+ transformData(document)
2321
+ );
2322
+ if ("data" in res) {
2323
+ navigate(
2324
+ {
2325
+ pathname: `../${res.data.documentId}`,
2326
+ search: rawQuery
2327
+ },
2328
+ { relative: "path" }
2329
+ );
2330
+ } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
2331
+ setErrors(formatValidationErrors(res.error));
2332
+ }
2333
+ } else if (documentId || collectionType === SINGLE_TYPES) {
2334
+ const res = await update(
2335
+ {
2336
+ collectionType,
2337
+ model,
2338
+ documentId,
2339
+ params
2340
+ },
2341
+ transformData(document)
2342
+ );
2343
+ if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
2344
+ setErrors(formatValidationErrors(res.error));
2345
+ } else {
2346
+ resetForm();
2347
+ }
2348
+ } else {
2349
+ const res = await create(
2350
+ {
2351
+ model,
2352
+ params
2353
+ },
2354
+ transformData(document)
2355
+ );
2356
+ if ("data" in res && collectionType !== SINGLE_TYPES) {
2357
+ navigate(
2358
+ {
2359
+ pathname: `../${res.data.documentId}`,
2360
+ search: rawQuery
2361
+ },
2362
+ { replace: true, relative: "path" }
2363
+ );
2364
+ } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
2365
+ setErrors(formatValidationErrors(res.error));
2366
+ }
2367
+ }
2368
+ } finally {
2369
+ setSubmitting(false);
2370
+ }
2395
2371
  }
2396
- );
2372
+ };
2397
2373
  };
2398
- const HeaderActions = ({ actions: actions2 }) => {
2399
- return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { children: actions2.map((action) => {
2400
- if ("options" in action) {
2401
- return /* @__PURE__ */ jsxRuntime.jsx(
2402
- designSystem.SingleSelect,
2403
- {
2404
- size: "S",
2405
- disabled: action.disabled,
2406
- "aria-label": action.label,
2407
- onChange: action.onSelect,
2408
- value: action.value,
2409
- children: action.options.map(({ label, ...option }) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { ...option, children: label }, option.value))
2410
- },
2411
- action.id
2412
- );
2413
- } else {
2414
- return null;
2415
- }
2416
- }) });
2374
+ UpdateAction.type = "update";
2375
+ const UNPUBLISH_DRAFT_OPTIONS = {
2376
+ KEEP: "keep",
2377
+ DISCARD: "discard"
2417
2378
  };
2418
- const ConfigureTheViewAction = ({ collectionType, model }) => {
2419
- const navigate = reactRouterDom.useNavigate();
2379
+ const UnpublishAction$1 = ({
2380
+ activeTab,
2381
+ documentId,
2382
+ model,
2383
+ collectionType,
2384
+ document
2385
+ }) => {
2420
2386
  const { formatMessage } = reactIntl.useIntl();
2421
- return {
2422
- label: formatMessage({
2423
- id: "app.links.configure-view",
2424
- defaultMessage: "Configure the view"
2425
- }),
2426
- icon: /* @__PURE__ */ jsxRuntime.jsx(Icons.ListPlus, {}),
2427
- onClick: () => {
2428
- navigate(`../${collectionType}/${model}/configurations/edit`);
2429
- },
2430
- position: "header"
2387
+ const { schema } = useDoc();
2388
+ const canPublish = useDocumentRBAC("UnpublishAction", ({ canPublish: canPublish2 }) => canPublish2);
2389
+ const { unpublish } = useDocumentActions();
2390
+ const [{ query }] = strapiAdmin.useQueryParams();
2391
+ const params = React__namespace.useMemo(() => buildValidParams(query), [query]);
2392
+ const { toggleNotification } = strapiAdmin.useNotification();
2393
+ const [shouldKeepDraft, setShouldKeepDraft] = React__namespace.useState(true);
2394
+ const isDocumentModified = document?.status === "modified";
2395
+ const handleChange = (value) => {
2396
+ setShouldKeepDraft(value === UNPUBLISH_DRAFT_OPTIONS.KEEP);
2431
2397
  };
2432
- };
2433
- ConfigureTheViewAction.type = "configure-the-view";
2434
- const EditTheModelAction = ({ model }) => {
2435
- const navigate = reactRouterDom.useNavigate();
2436
- const { formatMessage } = reactIntl.useIntl();
2398
+ if (!schema?.options?.draftAndPublish) {
2399
+ return null;
2400
+ }
2437
2401
  return {
2402
+ disabled: !canPublish || activeTab === "published" || document?.status !== "published" && document?.status !== "modified",
2438
2403
  label: formatMessage({
2439
- id: "content-manager.link-to-ctb",
2440
- defaultMessage: "Edit the model"
2404
+ id: "app.utils.unpublish",
2405
+ defaultMessage: "Unpublish"
2441
2406
  }),
2442
- icon: /* @__PURE__ */ jsxRuntime.jsx(Icons.Pencil, {}),
2443
- onClick: () => {
2444
- navigate(`/plugins/content-type-builder/content-types/${model}`);
2407
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Icons.Cross, {}),
2408
+ onClick: async () => {
2409
+ if (!documentId && collectionType !== SINGLE_TYPES || isDocumentModified) {
2410
+ if (!documentId) {
2411
+ console.error(
2412
+ "You're trying to unpublish a document without an id, this is likely a bug with Strapi. Please open an issue."
2413
+ );
2414
+ toggleNotification({
2415
+ message: formatMessage({
2416
+ id: "content-manager.actions.unpublish.error",
2417
+ defaultMessage: "An error occurred while trying to unpublish the document."
2418
+ }),
2419
+ type: "danger"
2420
+ });
2421
+ }
2422
+ return;
2423
+ }
2424
+ await unpublish({
2425
+ collectionType,
2426
+ model,
2427
+ documentId,
2428
+ params
2429
+ });
2445
2430
  },
2446
- position: "header"
2431
+ dialog: isDocumentModified ? {
2432
+ type: "dialog",
2433
+ title: formatMessage({
2434
+ id: "app.components.ConfirmDialog.title",
2435
+ defaultMessage: "Confirmation"
2436
+ }),
2437
+ content: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "flex-start", direction: "column", gap: 6, children: [
2438
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { width: "100%", direction: "column", gap: 2, children: [
2439
+ /* @__PURE__ */ jsxRuntime.jsx(Icons.WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
2440
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
2441
+ id: "content-manager.actions.unpublish.dialog.body",
2442
+ defaultMessage: "Are you sure?"
2443
+ }) })
2444
+ ] }),
2445
+ /* @__PURE__ */ jsxRuntime.jsxs(
2446
+ designSystem.Radio.Group,
2447
+ {
2448
+ defaultValue: UNPUBLISH_DRAFT_OPTIONS.KEEP,
2449
+ name: "discard-options",
2450
+ "aria-label": formatMessage({
2451
+ id: "content-manager.actions.unpublish.dialog.radio-label",
2452
+ defaultMessage: "Choose an option to unpublish the document."
2453
+ }),
2454
+ onValueChange: handleChange,
2455
+ children: [
2456
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Radio.Item, { checked: shouldKeepDraft, value: UNPUBLISH_DRAFT_OPTIONS.KEEP, children: formatMessage({
2457
+ id: "content-manager.actions.unpublish.dialog.option.keep-draft",
2458
+ defaultMessage: "Keep draft"
2459
+ }) }),
2460
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Radio.Item, { checked: !shouldKeepDraft, value: UNPUBLISH_DRAFT_OPTIONS.DISCARD, children: formatMessage({
2461
+ id: "content-manager.actions.unpublish.dialog.option.replace-draft",
2462
+ defaultMessage: "Replace draft"
2463
+ }) })
2464
+ ]
2465
+ }
2466
+ )
2467
+ ] }),
2468
+ onConfirm: async () => {
2469
+ if (!documentId && collectionType !== SINGLE_TYPES) {
2470
+ console.error(
2471
+ "You're trying to unpublish a document without an id, this is likely a bug with Strapi. Please open an issue."
2472
+ );
2473
+ toggleNotification({
2474
+ message: formatMessage({
2475
+ id: "content-manager.actions.unpublish.error",
2476
+ defaultMessage: "An error occurred while trying to unpublish the document."
2477
+ }),
2478
+ type: "danger"
2479
+ });
2480
+ }
2481
+ await unpublish(
2482
+ {
2483
+ collectionType,
2484
+ model,
2485
+ documentId,
2486
+ params
2487
+ },
2488
+ !shouldKeepDraft
2489
+ );
2490
+ }
2491
+ } : void 0,
2492
+ variant: "danger",
2493
+ position: ["panel", "table-row"]
2447
2494
  };
2448
2495
  };
2449
- EditTheModelAction.type = "edit-the-model";
2450
- const DeleteAction$1 = ({ documentId, model, collectionType, document }) => {
2451
- const navigate = reactRouterDom.useNavigate();
2496
+ UnpublishAction$1.type = "unpublish";
2497
+ const DiscardAction = ({
2498
+ activeTab,
2499
+ documentId,
2500
+ model,
2501
+ collectionType,
2502
+ document
2503
+ }) => {
2452
2504
  const { formatMessage } = reactIntl.useIntl();
2453
- const listViewPathMatch = reactRouterDom.useMatch(LIST_PATH);
2454
- const canDelete = useDocumentRBAC("DeleteAction", (state) => state.canDelete);
2455
- const { delete: deleteAction } = useDocumentActions();
2456
- const { toggleNotification } = strapiAdmin.useNotification();
2457
- const setSubmitting = strapiAdmin.useForm("DeleteAction", (state) => state.setSubmitting);
2505
+ const { schema } = useDoc();
2506
+ const canUpdate = useDocumentRBAC("DiscardAction", ({ canUpdate: canUpdate2 }) => canUpdate2);
2507
+ const { discard } = useDocumentActions();
2508
+ const [{ query }] = strapiAdmin.useQueryParams();
2509
+ const params = React__namespace.useMemo(() => buildValidParams(query), [query]);
2510
+ if (!schema?.options?.draftAndPublish) {
2511
+ return null;
2512
+ }
2458
2513
  return {
2459
- disabled: !canDelete || !document,
2514
+ disabled: !canUpdate || activeTab === "published" || document?.status !== "modified",
2460
2515
  label: formatMessage({
2461
- id: "content-manager.actions.delete.label",
2462
- defaultMessage: "Delete document"
2516
+ id: "content-manager.actions.discard.label",
2517
+ defaultMessage: "Discard changes"
2463
2518
  }),
2464
- icon: /* @__PURE__ */ jsxRuntime.jsx(Icons.Trash, {}),
2519
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Icons.Cross, {}),
2520
+ position: ["panel", "table-row"],
2521
+ variant: "danger",
2465
2522
  dialog: {
2466
2523
  type: "dialog",
2467
2524
  title: formatMessage({
@@ -2471,92 +2528,90 @@ const DeleteAction$1 = ({ documentId, model, collectionType, document }) => {
2471
2528
  content: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 2, children: [
2472
2529
  /* @__PURE__ */ jsxRuntime.jsx(Icons.WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
2473
2530
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
2474
- id: "content-manager.actions.delete.dialog.body",
2531
+ id: "content-manager.actions.discard.dialog.body",
2475
2532
  defaultMessage: "Are you sure?"
2476
2533
  }) })
2477
2534
  ] }),
2478
2535
  onConfirm: async () => {
2479
- if (!listViewPathMatch) {
2480
- setSubmitting(true);
2481
- }
2482
- try {
2483
- if (!documentId && collectionType !== SINGLE_TYPES) {
2484
- console.error(
2485
- "You're trying to delete a document without an id, this is likely a bug with Strapi. Please open an issue."
2486
- );
2487
- toggleNotification({
2488
- message: formatMessage({
2489
- id: "content-manager.actions.delete.error",
2490
- defaultMessage: "An error occurred while trying to delete the document."
2491
- }),
2492
- type: "danger"
2493
- });
2494
- return;
2495
- }
2496
- const res = await deleteAction({
2497
- documentId,
2498
- model,
2499
- collectionType,
2500
- params: {
2501
- locale: "*"
2502
- }
2503
- });
2504
- if (!("error" in res)) {
2505
- navigate({ pathname: `../${collectionType}/${model}` }, { replace: true });
2506
- }
2507
- } finally {
2508
- if (!listViewPathMatch) {
2509
- setSubmitting(false);
2510
- }
2511
- }
2536
+ await discard({
2537
+ collectionType,
2538
+ model,
2539
+ documentId,
2540
+ params
2541
+ });
2542
+ }
2543
+ }
2544
+ };
2545
+ };
2546
+ DiscardAction.type = "discard";
2547
+ const DEFAULT_ACTIONS = [PublishAction$1, UpdateAction, UnpublishAction$1, DiscardAction];
2548
+ const intervals = ["years", "months", "days", "hours", "minutes", "seconds"];
2549
+ const RelativeTime = React__namespace.forwardRef(
2550
+ ({ timestamp, customIntervals = [], ...restProps }, forwardedRef) => {
2551
+ const { formatRelativeTime, formatDate, formatTime } = reactIntl.useIntl();
2552
+ const interval = dateFns.intervalToDuration({
2553
+ start: timestamp,
2554
+ end: Date.now()
2555
+ // see https://github.com/date-fns/date-fns/issues/2891 – No idea why it's all partial it returns it every time.
2556
+ });
2557
+ const unit = intervals.find((intervalUnit) => {
2558
+ return interval[intervalUnit] > 0 && Object.keys(interval).includes(intervalUnit);
2559
+ });
2560
+ const relativeTime = dateFns.isPast(timestamp) ? -interval[unit] : interval[unit];
2561
+ const customInterval = customIntervals.find(
2562
+ (custom) => interval[custom.unit] < custom.threshold
2563
+ );
2564
+ const displayText = customInterval ? customInterval.text : formatRelativeTime(relativeTime, unit, { numeric: "auto" });
2565
+ return /* @__PURE__ */ jsxRuntime.jsx(
2566
+ "time",
2567
+ {
2568
+ ref: forwardedRef,
2569
+ dateTime: timestamp.toISOString(),
2570
+ role: "time",
2571
+ title: `${formatDate(timestamp)} ${formatTime(timestamp)}`,
2572
+ ...restProps,
2573
+ children: displayText
2512
2574
  }
2513
- },
2514
- variant: "danger",
2515
- position: ["header", "table-row"]
2516
- };
2575
+ );
2576
+ }
2577
+ );
2578
+ const getDisplayName = ({
2579
+ firstname,
2580
+ lastname,
2581
+ username,
2582
+ email
2583
+ } = {}) => {
2584
+ if (username) {
2585
+ return username;
2586
+ }
2587
+ if (firstname) {
2588
+ return `${firstname} ${lastname ?? ""}`.trim();
2589
+ }
2590
+ return email ?? "";
2517
2591
  };
2518
- DeleteAction$1.type = "delete";
2519
- const DEFAULT_HEADER_ACTIONS = [EditTheModelAction, ConfigureTheViewAction, DeleteAction$1];
2520
- const Panels = () => {
2521
- const isCloning = reactRouterDom.useMatch(CLONE_PATH) !== null;
2522
- const [
2523
- {
2524
- query: { status }
2525
- }
2526
- ] = strapiAdmin.useQueryParams({
2527
- status: "draft"
2528
- });
2529
- const { model, id, document, meta, collectionType } = useDoc();
2530
- const plugins = strapiAdmin.useStrapiApp("Panels", (state) => state.plugins);
2531
- const props = {
2532
- activeTab: status,
2533
- model,
2534
- documentId: id,
2535
- document: isCloning ? void 0 : document,
2536
- meta: isCloning ? void 0 : meta,
2537
- collectionType
2538
- };
2539
- return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 2, children: /* @__PURE__ */ jsxRuntime.jsx(
2540
- strapiAdmin.DescriptionComponentRenderer,
2541
- {
2542
- props,
2543
- descriptions: plugins["content-manager"].apis.getEditViewSidePanels(),
2544
- children: (panels) => panels.map(({ content, id: id2, ...description }) => /* @__PURE__ */ jsxRuntime.jsx(Panel, { ...description, children: content }, id2))
2545
- }
2546
- ) });
2592
+ const capitalise = (str) => str.charAt(0).toUpperCase() + str.slice(1);
2593
+ const DocumentStatus = ({ status = "draft", ...restProps }) => {
2594
+ const statusVariant = status === "draft" ? "secondary" : status === "published" ? "success" : "alternative";
2595
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Status, { ...restProps, showBullet: false, size: "S", variant: statusVariant, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "span", variant: "omega", fontWeight: "bold", children: capitalise(status) }) });
2547
2596
  };
2548
- const ActionsPanel = () => {
2597
+ const Header = ({ isCreating, status, title: documentTitle = "Untitled" }) => {
2549
2598
  const { formatMessage } = reactIntl.useIntl();
2550
- return {
2551
- title: formatMessage({
2552
- id: "content-manager.containers.edit.panels.default.title",
2553
- defaultMessage: "Document"
2554
- }),
2555
- content: /* @__PURE__ */ jsxRuntime.jsx(ActionsPanelContent, {})
2556
- };
2599
+ const isCloning = reactRouterDom.useMatch(CLONE_PATH) !== null;
2600
+ const title = isCreating ? formatMessage({
2601
+ id: "content-manager.containers.edit.title.new",
2602
+ defaultMessage: "Create an entry"
2603
+ }) : documentTitle;
2604
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "flex-start", paddingTop: 6, paddingBottom: 4, gap: 2, children: [
2605
+ /* @__PURE__ */ jsxRuntime.jsx(strapiAdmin.BackButton, {}),
2606
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { width: "100%", justifyContent: "space-between", gap: "80px", alignItems: "flex-start", children: [
2607
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", tag: "h1", children: title }),
2608
+ /* @__PURE__ */ jsxRuntime.jsx(HeaderToolbar, {})
2609
+ ] }),
2610
+ status ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 1, children: /* @__PURE__ */ jsxRuntime.jsx(DocumentStatus, { status: isCloning ? "draft" : status }) }) : null
2611
+ ] });
2557
2612
  };
2558
- ActionsPanel.type = "actions";
2559
- const ActionsPanelContent = () => {
2613
+ const HeaderToolbar = () => {
2614
+ const { formatMessage } = reactIntl.useIntl();
2560
2615
  const isCloning = reactRouterDom.useMatch(CLONE_PATH) !== null;
2561
2616
  const [
2562
2617
  {
@@ -2564,355 +2619,432 @@ const ActionsPanelContent = () => {
2564
2619
  }
2565
2620
  ] = strapiAdmin.useQueryParams();
2566
2621
  const { model, id, document, meta, collectionType } = useDoc();
2567
- const plugins = strapiAdmin.useStrapiApp("ActionsPanel", (state) => state.plugins);
2568
- const props = {
2569
- activeTab: status,
2570
- model,
2571
- documentId: id,
2572
- document: isCloning ? void 0 : document,
2573
- meta: isCloning ? void 0 : meta,
2574
- collectionType
2575
- };
2576
- return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 2, width: "100%", children: [
2622
+ const plugins = strapiAdmin.useStrapiApp("HeaderToolbar", (state) => state.plugins);
2623
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
2577
2624
  /* @__PURE__ */ jsxRuntime.jsx(
2578
2625
  strapiAdmin.DescriptionComponentRenderer,
2579
2626
  {
2580
- props,
2581
- descriptions: plugins["content-manager"].apis.getDocumentActions(),
2582
- children: (actions2) => /* @__PURE__ */ jsxRuntime.jsx(DocumentActions, { actions: actions2 })
2627
+ props: {
2628
+ activeTab: status,
2629
+ model,
2630
+ documentId: id,
2631
+ document: isCloning ? void 0 : document,
2632
+ meta: isCloning ? void 0 : meta,
2633
+ collectionType
2634
+ },
2635
+ descriptions: plugins["content-manager"].apis.getHeaderActions(),
2636
+ children: (actions2) => {
2637
+ if (actions2.length > 0) {
2638
+ return /* @__PURE__ */ jsxRuntime.jsx(HeaderActions, { actions: actions2 });
2639
+ } else {
2640
+ return null;
2641
+ }
2642
+ }
2583
2643
  }
2584
2644
  ),
2585
- /* @__PURE__ */ jsxRuntime.jsx(InjectionZone, { area: "editView.right-links", slug: model })
2645
+ /* @__PURE__ */ jsxRuntime.jsx(
2646
+ strapiAdmin.DescriptionComponentRenderer,
2647
+ {
2648
+ props: {
2649
+ activeTab: status,
2650
+ model,
2651
+ documentId: id,
2652
+ document: isCloning ? void 0 : document,
2653
+ meta: isCloning ? void 0 : meta,
2654
+ collectionType
2655
+ },
2656
+ descriptions: plugins["content-manager"].apis.getDocumentActions(),
2657
+ children: (actions2) => {
2658
+ const headerActions = actions2.filter((action) => {
2659
+ const positions = Array.isArray(action.position) ? action.position : [action.position];
2660
+ return positions.includes("header");
2661
+ });
2662
+ return /* @__PURE__ */ jsxRuntime.jsx(
2663
+ DocumentActionsMenu,
2664
+ {
2665
+ actions: headerActions,
2666
+ label: formatMessage({
2667
+ id: "content-manager.containers.edit.header.more-actions",
2668
+ defaultMessage: "More actions"
2669
+ }),
2670
+ children: /* @__PURE__ */ jsxRuntime.jsx(Information, { activeTab: status })
2671
+ }
2672
+ );
2673
+ }
2674
+ }
2675
+ )
2586
2676
  ] });
2587
2677
  };
2588
- const Panel = React__namespace.forwardRef(({ children, title }, ref) => {
2589
- return /* @__PURE__ */ jsxRuntime.jsxs(
2590
- designSystem.Flex,
2678
+ const Information = ({ activeTab }) => {
2679
+ const { formatMessage } = reactIntl.useIntl();
2680
+ const { document, meta } = useDoc();
2681
+ if (!document || !document.id) {
2682
+ return null;
2683
+ }
2684
+ const createAndUpdateDocument = activeTab === "draft" ? document : meta?.availableStatus.find((status) => status.publishedAt === null);
2685
+ const publishDocument = activeTab === "published" ? document : meta?.availableStatus.find((status) => status.publishedAt !== null);
2686
+ const creator = createAndUpdateDocument?.[CREATED_BY_ATTRIBUTE_NAME] ? getDisplayName(createAndUpdateDocument[CREATED_BY_ATTRIBUTE_NAME]) : null;
2687
+ const updator = createAndUpdateDocument?.[UPDATED_BY_ATTRIBUTE_NAME] ? getDisplayName(createAndUpdateDocument[UPDATED_BY_ATTRIBUTE_NAME]) : null;
2688
+ const information = [
2591
2689
  {
2592
- ref,
2593
- tag: "aside",
2594
- "aria-labelledby": "additional-information",
2595
- background: "neutral0",
2596
- borderColor: "neutral150",
2597
- hasRadius: true,
2598
- paddingBottom: 4,
2599
- paddingLeft: 4,
2600
- paddingRight: 4,
2601
- paddingTop: 4,
2602
- shadow: "tableShadow",
2603
- gap: 3,
2604
- direction: "column",
2605
- justifyContent: "stretch",
2606
- alignItems: "flex-start",
2607
- children: [
2608
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "h2", variant: "sigma", textTransform: "uppercase", children: title }),
2609
- children
2610
- ]
2611
- }
2612
- );
2613
- });
2614
- const HOOKS = {
2615
- /**
2616
- * Hook that allows to mutate the displayed headers of the list view table
2617
- * @constant
2618
- * @type {string}
2619
- */
2620
- INJECT_COLUMN_IN_TABLE: "Admin/CM/pages/ListView/inject-column-in-table",
2621
- /**
2622
- * Hook that allows to mutate the CM's collection types links pre-set filters
2623
- * @constant
2624
- * @type {string}
2625
- */
2626
- MUTATE_COLLECTION_TYPES_LINKS: "Admin/CM/pages/App/mutate-collection-types-links",
2627
- /**
2628
- * Hook that allows to mutate the CM's edit view layout
2629
- * @constant
2630
- * @type {string}
2631
- */
2632
- MUTATE_EDIT_VIEW_LAYOUT: "Admin/CM/pages/EditView/mutate-edit-view-layout",
2633
- /**
2634
- * Hook that allows to mutate the CM's single types links pre-set filters
2635
- * @constant
2636
- * @type {string}
2637
- */
2638
- MUTATE_SINGLE_TYPES_LINKS: "Admin/CM/pages/App/mutate-single-types-links"
2639
- };
2640
- const contentTypesApi = contentManagerApi.injectEndpoints({
2641
- endpoints: (builder) => ({
2642
- getContentTypeConfiguration: builder.query({
2643
- query: (uid) => ({
2644
- url: `/content-manager/content-types/${uid}/configuration`,
2645
- method: "GET"
2690
+ isDisplayed: !!publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME],
2691
+ label: formatMessage({
2692
+ id: "content-manager.containers.edit.information.last-published.label",
2693
+ defaultMessage: "Published"
2646
2694
  }),
2647
- transformResponse: (response) => response.data,
2648
- providesTags: (_result, _error, uid) => [
2649
- { type: "ContentTypesConfiguration", id: uid },
2650
- { type: "ContentTypeSettings", id: "LIST" }
2651
- ]
2652
- }),
2653
- getAllContentTypeSettings: builder.query({
2654
- query: () => "/content-manager/content-types-settings",
2655
- transformResponse: (response) => response.data,
2656
- providesTags: [{ type: "ContentTypeSettings", id: "LIST" }]
2657
- }),
2658
- updateContentTypeConfiguration: builder.mutation({
2659
- query: ({ uid, ...body }) => ({
2660
- url: `/content-manager/content-types/${uid}/configuration`,
2661
- method: "PUT",
2662
- data: body
2695
+ value: formatMessage(
2696
+ {
2697
+ id: "content-manager.containers.edit.information.last-published.value",
2698
+ defaultMessage: `{time}{isAnonymous, select, true {} other { by {author}}}`
2699
+ },
2700
+ {
2701
+ time: /* @__PURE__ */ jsxRuntime.jsx(RelativeTime, { timestamp: new Date(publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME]) }),
2702
+ isAnonymous: !publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME],
2703
+ author: publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME] ? getDisplayName(publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME]) : null
2704
+ }
2705
+ )
2706
+ },
2707
+ {
2708
+ isDisplayed: !!createAndUpdateDocument?.[UPDATED_AT_ATTRIBUTE_NAME],
2709
+ label: formatMessage({
2710
+ id: "content-manager.containers.edit.information.last-draft.label",
2711
+ defaultMessage: "Updated"
2712
+ }),
2713
+ value: formatMessage(
2714
+ {
2715
+ id: "content-manager.containers.edit.information.last-draft.value",
2716
+ defaultMessage: `{time}{isAnonymous, select, true {} other { by {author}}}`
2717
+ },
2718
+ {
2719
+ time: /* @__PURE__ */ jsxRuntime.jsx(
2720
+ RelativeTime,
2721
+ {
2722
+ timestamp: new Date(createAndUpdateDocument?.[UPDATED_AT_ATTRIBUTE_NAME])
2723
+ }
2724
+ ),
2725
+ isAnonymous: !updator,
2726
+ author: updator
2727
+ }
2728
+ )
2729
+ },
2730
+ {
2731
+ isDisplayed: !!createAndUpdateDocument?.[CREATED_AT_ATTRIBUTE_NAME],
2732
+ label: formatMessage({
2733
+ id: "content-manager.containers.edit.information.document.label",
2734
+ defaultMessage: "Created"
2663
2735
  }),
2664
- transformResponse: (response) => response.data,
2665
- invalidatesTags: (_result, _error, { uid }) => [
2666
- { type: "ContentTypesConfiguration", id: uid },
2667
- { type: "ContentTypeSettings", id: "LIST" },
2668
- // Is this necessary?
2669
- { type: "InitialData" }
2670
- ]
2671
- })
2672
- })
2673
- });
2674
- const {
2675
- useGetContentTypeConfigurationQuery,
2676
- useGetAllContentTypeSettingsQuery,
2677
- useUpdateContentTypeConfigurationMutation
2678
- } = contentTypesApi;
2679
- const checkIfAttributeIsDisplayable = (attribute) => {
2680
- const { type } = attribute;
2681
- if (type === "relation") {
2682
- return !attribute.relation.toLowerCase().includes("morph");
2683
- }
2684
- return !["json", "dynamiczone", "richtext", "password", "blocks"].includes(type) && !!type;
2685
- };
2686
- const getMainField = (attribute, mainFieldName, { schemas, components }) => {
2687
- if (!mainFieldName) {
2688
- return void 0;
2689
- }
2690
- const mainFieldType = attribute.type === "component" ? components[attribute.component].attributes[mainFieldName].type : (
2691
- // @ts-expect-error – `targetModel` does exist on the attribute for a relation.
2692
- schemas.find((schema) => schema.uid === attribute.targetModel)?.attributes[mainFieldName].type
2736
+ value: formatMessage(
2737
+ {
2738
+ id: "content-manager.containers.edit.information.document.value",
2739
+ defaultMessage: `{time}{isAnonymous, select, true {} other { by {author}}}`
2740
+ },
2741
+ {
2742
+ time: /* @__PURE__ */ jsxRuntime.jsx(
2743
+ RelativeTime,
2744
+ {
2745
+ timestamp: new Date(createAndUpdateDocument?.[CREATED_AT_ATTRIBUTE_NAME])
2746
+ }
2747
+ ),
2748
+ isAnonymous: !creator,
2749
+ author: creator
2750
+ }
2751
+ )
2752
+ }
2753
+ ].filter((info) => info.isDisplayed);
2754
+ return /* @__PURE__ */ jsxRuntime.jsx(
2755
+ designSystem.Flex,
2756
+ {
2757
+ borderWidth: "1px 0 0 0",
2758
+ borderStyle: "solid",
2759
+ borderColor: "neutral150",
2760
+ direction: "column",
2761
+ marginTop: 2,
2762
+ tag: "dl",
2763
+ padding: 5,
2764
+ gap: 3,
2765
+ alignItems: "flex-start",
2766
+ marginLeft: "-0.4rem",
2767
+ marginRight: "-0.4rem",
2768
+ width: "calc(100% + 8px)",
2769
+ children: information.map((info) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, direction: "column", alignItems: "flex-start", children: [
2770
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "dt", variant: "pi", fontWeight: "bold", children: info.label }),
2771
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "dd", variant: "pi", textColor: "neutral600", children: info.value })
2772
+ ] }, info.label))
2773
+ }
2693
2774
  );
2694
- return {
2695
- name: mainFieldName,
2696
- type: mainFieldType ?? "string"
2697
- };
2698
- };
2699
- const DEFAULT_SETTINGS = {
2700
- bulkable: false,
2701
- filterable: false,
2702
- searchable: false,
2703
- pagination: false,
2704
- defaultSortBy: "",
2705
- defaultSortOrder: "asc",
2706
- mainField: "id",
2707
- pageSize: 10
2708
2775
  };
2709
- const useDocumentLayout = (model) => {
2710
- const { schema, components } = useDocument({ model, collectionType: "" }, { skip: true });
2711
- const [{ query }] = strapiAdmin.useQueryParams();
2712
- const runHookWaterfall = strapiAdmin.useStrapiApp("useDocumentLayout", (state) => state.runHookWaterfall);
2713
- const { toggleNotification } = strapiAdmin.useNotification();
2714
- const { _unstableFormatAPIError: formatAPIError } = strapiAdmin.useAPIErrorHandler();
2715
- const { isLoading: isLoadingSchemas, schemas } = useContentTypeSchema();
2716
- const {
2717
- data,
2718
- isLoading: isLoadingConfigs,
2719
- error,
2720
- isFetching: isFetchingConfigs
2721
- } = useGetContentTypeConfigurationQuery(model);
2722
- const isLoading = isLoadingSchemas || isFetchingConfigs || isLoadingConfigs;
2723
- React__namespace.useEffect(() => {
2724
- if (error) {
2725
- toggleNotification({
2726
- type: "danger",
2727
- message: formatAPIError(error)
2728
- });
2776
+ const HeaderActions = ({ actions: actions2 }) => {
2777
+ const [dialogId, setDialogId] = React__namespace.useState(null);
2778
+ const handleClick = (action) => async (e) => {
2779
+ if (!("options" in action)) {
2780
+ const { onClick = () => false, dialog, id } = action;
2781
+ const muteDialog = await onClick(e);
2782
+ if (dialog && !muteDialog) {
2783
+ e.preventDefault();
2784
+ setDialogId(id);
2785
+ }
2729
2786
  }
2730
- }, [error, formatAPIError, toggleNotification]);
2731
- const editLayout = React__namespace.useMemo(
2732
- () => data && !isLoading ? formatEditLayout(data, { schemas, schema, components }) : {
2733
- layout: [],
2734
- components: {},
2735
- metadatas: {},
2736
- options: {},
2737
- settings: DEFAULT_SETTINGS
2738
- },
2739
- [data, isLoading, schemas, schema, components]
2740
- );
2741
- const listLayout = React__namespace.useMemo(() => {
2742
- return data && !isLoading ? formatListLayout(data, { schemas, schema, components }) : {
2743
- layout: [],
2744
- metadatas: {},
2745
- options: {},
2746
- settings: DEFAULT_SETTINGS
2747
- };
2748
- }, [data, isLoading, schemas, schema, components]);
2749
- const { layout: edit } = React__namespace.useMemo(
2750
- () => runHookWaterfall(HOOKS.MUTATE_EDIT_VIEW_LAYOUT, {
2751
- layout: editLayout,
2752
- query
2753
- }),
2754
- [editLayout, query, runHookWaterfall]
2755
- );
2756
- return {
2757
- error,
2758
- isLoading,
2759
- edit,
2760
- list: listLayout
2761
2787
  };
2762
- };
2763
- const useDocLayout = () => {
2764
- const { model } = useDoc();
2765
- return useDocumentLayout(model);
2766
- };
2767
- const formatEditLayout = (data, {
2768
- schemas,
2769
- schema,
2770
- components
2771
- }) => {
2772
- let currentPanelIndex = 0;
2773
- const panelledEditAttributes = convertEditLayoutToFieldLayouts(
2774
- data.contentType.layouts.edit,
2775
- schema?.attributes,
2776
- data.contentType.metadatas,
2777
- { configurations: data.components, schemas: components },
2778
- schemas
2779
- ).reduce((panels, row) => {
2780
- if (row.some((field) => field.type === "dynamiczone")) {
2781
- panels.push([row]);
2782
- currentPanelIndex += 2;
2788
+ const handleClose = () => {
2789
+ setDialogId(null);
2790
+ };
2791
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 1, children: actions2.map((action) => {
2792
+ if (action.options) {
2793
+ return /* @__PURE__ */ jsxRuntime.jsx(
2794
+ designSystem.SingleSelect,
2795
+ {
2796
+ size: "S",
2797
+ onChange: action.onSelect,
2798
+ "aria-label": action.label,
2799
+ ...action,
2800
+ children: action.options.map(({ label, ...option }) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { ...option, children: label }, option.value))
2801
+ },
2802
+ action.id
2803
+ );
2783
2804
  } else {
2784
- if (!panels[currentPanelIndex]) {
2785
- panels.push([]);
2805
+ if (action.type === "icon") {
2806
+ return /* @__PURE__ */ jsxRuntime.jsxs(React__namespace.Fragment, { children: [
2807
+ /* @__PURE__ */ jsxRuntime.jsx(
2808
+ designSystem.IconButton,
2809
+ {
2810
+ disabled: action.disabled,
2811
+ label: action.label,
2812
+ size: "S",
2813
+ onClick: handleClick(action),
2814
+ children: action.icon
2815
+ }
2816
+ ),
2817
+ action.dialog ? /* @__PURE__ */ jsxRuntime.jsx(
2818
+ HeaderActionDialog,
2819
+ {
2820
+ ...action.dialog,
2821
+ isOpen: dialogId === action.id,
2822
+ onClose: handleClose
2823
+ }
2824
+ ) : null
2825
+ ] }, action.id);
2786
2826
  }
2787
- panels[currentPanelIndex].push(row);
2788
2827
  }
2789
- return panels;
2790
- }, []);
2791
- const componentEditAttributes = Object.entries(data.components).reduce(
2792
- (acc, [uid, configuration]) => {
2793
- acc[uid] = {
2794
- layout: convertEditLayoutToFieldLayouts(
2795
- configuration.layouts.edit,
2796
- components[uid].attributes,
2797
- configuration.metadatas
2798
- ),
2799
- settings: {
2800
- ...configuration.settings,
2801
- icon: components[uid].info.icon,
2802
- displayName: components[uid].info.displayName
2803
- }
2804
- };
2805
- return acc;
2828
+ }) });
2829
+ };
2830
+ const HeaderActionDialog = ({
2831
+ onClose,
2832
+ onCancel,
2833
+ title,
2834
+ content: Content,
2835
+ isOpen
2836
+ }) => {
2837
+ const handleClose = async () => {
2838
+ if (onCancel) {
2839
+ await onCancel();
2840
+ }
2841
+ onClose();
2842
+ };
2843
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Dialog.Content, { children: [
2844
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Header, { children: title }),
2845
+ typeof Content === "function" ? /* @__PURE__ */ jsxRuntime.jsx(Content, { onClose: handleClose }) : Content
2846
+ ] }) });
2847
+ };
2848
+ const ConfigureTheViewAction = ({ collectionType, model }) => {
2849
+ const navigate = reactRouterDom.useNavigate();
2850
+ const { formatMessage } = reactIntl.useIntl();
2851
+ return {
2852
+ label: formatMessage({
2853
+ id: "app.links.configure-view",
2854
+ defaultMessage: "Configure the view"
2855
+ }),
2856
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Icons.ListPlus, {}),
2857
+ onClick: () => {
2858
+ navigate(`../${collectionType}/${model}/configurations/edit`);
2806
2859
  },
2807
- {}
2808
- );
2809
- const editMetadatas = Object.entries(data.contentType.metadatas).reduce(
2810
- (acc, [attribute, metadata]) => {
2811
- return {
2812
- ...acc,
2813
- [attribute]: metadata.edit
2814
- };
2860
+ position: "header"
2861
+ };
2862
+ };
2863
+ ConfigureTheViewAction.type = "configure-the-view";
2864
+ const EditTheModelAction = ({ model }) => {
2865
+ const navigate = reactRouterDom.useNavigate();
2866
+ const { formatMessage } = reactIntl.useIntl();
2867
+ return {
2868
+ label: formatMessage({
2869
+ id: "content-manager.link-to-ctb",
2870
+ defaultMessage: "Edit the model"
2871
+ }),
2872
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Icons.Pencil, {}),
2873
+ onClick: () => {
2874
+ navigate(`/plugins/content-type-builder/content-types/${model}`);
2815
2875
  },
2816
- {}
2817
- );
2876
+ position: "header"
2877
+ };
2878
+ };
2879
+ EditTheModelAction.type = "edit-the-model";
2880
+ const DeleteAction$1 = ({ documentId, model, collectionType, document }) => {
2881
+ const navigate = reactRouterDom.useNavigate();
2882
+ const { formatMessage } = reactIntl.useIntl();
2883
+ const listViewPathMatch = reactRouterDom.useMatch(LIST_PATH);
2884
+ const canDelete = useDocumentRBAC("DeleteAction", (state) => state.canDelete);
2885
+ const { delete: deleteAction } = useDocumentActions();
2886
+ const { toggleNotification } = strapiAdmin.useNotification();
2887
+ const setSubmitting = strapiAdmin.useForm("DeleteAction", (state) => state.setSubmitting);
2888
+ const isLocalized = document?.locale != null;
2818
2889
  return {
2819
- layout: panelledEditAttributes,
2820
- components: componentEditAttributes,
2821
- metadatas: editMetadatas,
2822
- settings: {
2823
- ...data.contentType.settings,
2824
- displayName: schema?.info.displayName
2890
+ disabled: !canDelete || !document,
2891
+ label: formatMessage(
2892
+ {
2893
+ id: "content-manager.actions.delete.label",
2894
+ defaultMessage: "Delete entry{isLocalized, select, true { (all locales)} other {}}"
2895
+ },
2896
+ { isLocalized }
2897
+ ),
2898
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Icons.Trash, {}),
2899
+ dialog: {
2900
+ type: "dialog",
2901
+ title: formatMessage({
2902
+ id: "app.components.ConfirmDialog.title",
2903
+ defaultMessage: "Confirmation"
2904
+ }),
2905
+ content: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 2, children: [
2906
+ /* @__PURE__ */ jsxRuntime.jsx(Icons.WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
2907
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
2908
+ id: "content-manager.actions.delete.dialog.body",
2909
+ defaultMessage: "Are you sure?"
2910
+ }) })
2911
+ ] }),
2912
+ onConfirm: async () => {
2913
+ if (!listViewPathMatch) {
2914
+ setSubmitting(true);
2915
+ }
2916
+ try {
2917
+ if (!documentId && collectionType !== SINGLE_TYPES) {
2918
+ console.error(
2919
+ "You're trying to delete a document without an id, this is likely a bug with Strapi. Please open an issue."
2920
+ );
2921
+ toggleNotification({
2922
+ message: formatMessage({
2923
+ id: "content-manager.actions.delete.error",
2924
+ defaultMessage: "An error occurred while trying to delete the document."
2925
+ }),
2926
+ type: "danger"
2927
+ });
2928
+ return;
2929
+ }
2930
+ const res = await deleteAction({
2931
+ documentId,
2932
+ model,
2933
+ collectionType,
2934
+ params: {
2935
+ locale: "*"
2936
+ }
2937
+ });
2938
+ if (!("error" in res)) {
2939
+ navigate({ pathname: `../${collectionType}/${model}` }, { replace: true });
2940
+ }
2941
+ } finally {
2942
+ if (!listViewPathMatch) {
2943
+ setSubmitting(false);
2944
+ }
2945
+ }
2946
+ }
2825
2947
  },
2826
- options: {
2827
- ...schema?.options,
2828
- ...schema?.pluginOptions,
2829
- ...data.contentType.options
2830
- }
2948
+ variant: "danger",
2949
+ position: ["header", "table-row"]
2831
2950
  };
2832
2951
  };
2833
- const convertEditLayoutToFieldLayouts = (rows, attributes = {}, metadatas, components, schemas = []) => {
2834
- return rows.map(
2835
- (row) => row.map((field) => {
2836
- const attribute = attributes[field.name];
2837
- if (!attribute) {
2838
- return null;
2839
- }
2840
- const { edit: metadata } = metadatas[field.name];
2841
- const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
2842
- return {
2843
- attribute,
2844
- disabled: !metadata.editable,
2845
- hint: metadata.description,
2846
- label: metadata.label ?? "",
2847
- name: field.name,
2848
- // @ts-expect-error – mainField does exist on the metadata for a relation.
2849
- mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
2850
- schemas,
2851
- components: components?.schemas ?? {}
2852
- }),
2853
- placeholder: metadata.placeholder ?? "",
2854
- required: attribute.required ?? false,
2855
- size: field.size,
2856
- unique: "unique" in attribute ? attribute.unique : false,
2857
- visible: metadata.visible ?? true,
2858
- type: attribute.type
2859
- };
2860
- }).filter((field) => field !== null)
2861
- );
2952
+ DeleteAction$1.type = "delete";
2953
+ const DEFAULT_HEADER_ACTIONS = [EditTheModelAction, ConfigureTheViewAction, DeleteAction$1];
2954
+ const Panels = () => {
2955
+ const isCloning = reactRouterDom.useMatch(CLONE_PATH) !== null;
2956
+ const [
2957
+ {
2958
+ query: { status }
2959
+ }
2960
+ ] = strapiAdmin.useQueryParams({
2961
+ status: "draft"
2962
+ });
2963
+ const { model, id, document, meta, collectionType } = useDoc();
2964
+ const plugins = strapiAdmin.useStrapiApp("Panels", (state) => state.plugins);
2965
+ const props = {
2966
+ activeTab: status,
2967
+ model,
2968
+ documentId: id,
2969
+ document: isCloning ? void 0 : document,
2970
+ meta: isCloning ? void 0 : meta,
2971
+ collectionType
2972
+ };
2973
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 2, children: /* @__PURE__ */ jsxRuntime.jsx(
2974
+ strapiAdmin.DescriptionComponentRenderer,
2975
+ {
2976
+ props,
2977
+ descriptions: plugins["content-manager"].apis.getEditViewSidePanels(),
2978
+ children: (panels) => panels.map(({ content, id: id2, ...description }) => /* @__PURE__ */ jsxRuntime.jsx(Panel, { ...description, children: content }, id2))
2979
+ }
2980
+ ) });
2862
2981
  };
2863
- const formatListLayout = (data, {
2864
- schemas,
2865
- schema,
2866
- components
2867
- }) => {
2868
- const listMetadatas = Object.entries(data.contentType.metadatas).reduce(
2869
- (acc, [attribute, metadata]) => {
2870
- return {
2871
- ...acc,
2872
- [attribute]: metadata.list
2873
- };
2874
- },
2875
- {}
2876
- );
2877
- const listAttributes = convertListLayoutToFieldLayouts(
2878
- data.contentType.layouts.list,
2879
- schema?.attributes,
2880
- listMetadatas,
2881
- { configurations: data.components, schemas: components },
2882
- schemas
2883
- );
2982
+ const ActionsPanel = () => {
2983
+ const { formatMessage } = reactIntl.useIntl();
2884
2984
  return {
2885
- layout: listAttributes,
2886
- settings: { ...data.contentType.settings, displayName: schema?.info.displayName },
2887
- metadatas: listMetadatas,
2888
- options: {
2889
- ...schema?.options,
2890
- ...schema?.pluginOptions,
2891
- ...data.contentType.options
2892
- }
2985
+ title: formatMessage({
2986
+ id: "content-manager.containers.edit.panels.default.title",
2987
+ defaultMessage: "Entry"
2988
+ }),
2989
+ content: /* @__PURE__ */ jsxRuntime.jsx(ActionsPanelContent, {})
2893
2990
  };
2894
2991
  };
2895
- const convertListLayoutToFieldLayouts = (columns, attributes = {}, metadatas, components, schemas = []) => {
2896
- return columns.map((name) => {
2897
- const attribute = attributes[name];
2898
- if (!attribute) {
2899
- return null;
2992
+ ActionsPanel.type = "actions";
2993
+ const ActionsPanelContent = () => {
2994
+ const isCloning = reactRouterDom.useMatch(CLONE_PATH) !== null;
2995
+ const [
2996
+ {
2997
+ query: { status = "draft" }
2900
2998
  }
2901
- const metadata = metadatas[name];
2902
- const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
2903
- return {
2904
- attribute,
2905
- label: metadata.label ?? "",
2906
- mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
2907
- schemas,
2908
- components: components?.schemas ?? {}
2909
- }),
2910
- name,
2911
- searchable: metadata.searchable ?? true,
2912
- sortable: metadata.sortable ?? true
2913
- };
2914
- }).filter((field) => field !== null);
2999
+ ] = strapiAdmin.useQueryParams();
3000
+ const { model, id, document, meta, collectionType } = useDoc();
3001
+ const plugins = strapiAdmin.useStrapiApp("ActionsPanel", (state) => state.plugins);
3002
+ const props = {
3003
+ activeTab: status,
3004
+ model,
3005
+ documentId: id,
3006
+ document: isCloning ? void 0 : document,
3007
+ meta: isCloning ? void 0 : meta,
3008
+ collectionType
3009
+ };
3010
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 2, width: "100%", children: [
3011
+ /* @__PURE__ */ jsxRuntime.jsx(
3012
+ strapiAdmin.DescriptionComponentRenderer,
3013
+ {
3014
+ props,
3015
+ descriptions: plugins["content-manager"].apis.getDocumentActions(),
3016
+ children: (actions2) => /* @__PURE__ */ jsxRuntime.jsx(DocumentActions, { actions: actions2 })
3017
+ }
3018
+ ),
3019
+ /* @__PURE__ */ jsxRuntime.jsx(InjectionZone, { area: "editView.right-links", slug: model })
3020
+ ] });
2915
3021
  };
3022
+ const Panel = React__namespace.forwardRef(({ children, title }, ref) => {
3023
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3024
+ designSystem.Flex,
3025
+ {
3026
+ ref,
3027
+ tag: "aside",
3028
+ "aria-labelledby": "additional-information",
3029
+ background: "neutral0",
3030
+ borderColor: "neutral150",
3031
+ hasRadius: true,
3032
+ paddingBottom: 4,
3033
+ paddingLeft: 4,
3034
+ paddingRight: 4,
3035
+ paddingTop: 4,
3036
+ shadow: "tableShadow",
3037
+ gap: 3,
3038
+ direction: "column",
3039
+ justifyContent: "stretch",
3040
+ alignItems: "flex-start",
3041
+ children: [
3042
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "h2", variant: "sigma", textTransform: "uppercase", textColor: "neutral600", children: title }),
3043
+ children
3044
+ ]
3045
+ }
3046
+ );
3047
+ });
2916
3048
  const ConfirmBulkActionDialog = ({
2917
3049
  onToggleDialog,
2918
3050
  isOpen = false,
@@ -2951,6 +3083,7 @@ const ConfirmDialogPublishAll = ({
2951
3083
  const { _unstableFormatAPIError: formatAPIError } = strapiAdmin.useAPIErrorHandler(getTranslation);
2952
3084
  const { model, schema } = useDoc();
2953
3085
  const [{ query }] = strapiAdmin.useQueryParams();
3086
+ const enableDraftRelationsCount = false;
2954
3087
  const {
2955
3088
  data: countDraftRelations = 0,
2956
3089
  isLoading,
@@ -2962,7 +3095,7 @@ const ConfirmDialogPublishAll = ({
2962
3095
  locale: query?.plugins?.i18n?.locale
2963
3096
  },
2964
3097
  {
2965
- skip: selectedEntries.length === 0
3098
+ skip: !enableDraftRelationsCount
2966
3099
  }
2967
3100
  );
2968
3101
  React__namespace.useEffect(() => {
@@ -3147,7 +3280,7 @@ const SelectedEntriesTableContent = ({
3147
3280
  status: row.status
3148
3281
  }
3149
3282
  ) }),
3150
- /* @__PURE__ */ jsxRuntime.jsx(strapiAdmin.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(
3283
+ /* @__PURE__ */ jsxRuntime.jsx(strapiAdmin.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { children: /* @__PURE__ */ jsxRuntime.jsx(
3151
3284
  designSystem.IconButton,
3152
3285
  {
3153
3286
  tag: reactRouterDom.Link,
@@ -3170,9 +3303,10 @@ const SelectedEntriesTableContent = ({
3170
3303
  ),
3171
3304
  target: "_blank",
3172
3305
  marginLeft: "auto",
3173
- children: /* @__PURE__ */ jsxRuntime.jsx(Icons.Pencil, {})
3306
+ variant: "ghost",
3307
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icons.Pencil, { width: "1.6rem", height: "1.6rem" })
3174
3308
  }
3175
- ) })
3309
+ ) }) })
3176
3310
  ] }, row.id)) })
3177
3311
  ] });
3178
3312
  };
@@ -3209,7 +3343,13 @@ const SelectedEntriesModalContent = ({
3209
3343
  );
3210
3344
  const { rows, validationErrors } = React__namespace.useMemo(() => {
3211
3345
  if (data.length > 0 && schema) {
3212
- const validate = createYupSchema(schema.attributes, components);
3346
+ const validate = createYupSchema(
3347
+ schema.attributes,
3348
+ components,
3349
+ // Since this is the "Publish" action, the validation
3350
+ // schema must enforce the rules for published entities
3351
+ { status: "published" }
3352
+ );
3213
3353
  const validationErrors2 = {};
3214
3354
  const rows2 = data.map((entry) => {
3215
3355
  try {
@@ -3559,7 +3699,7 @@ const TableActions = ({ document }) => {
3559
3699
  strapiAdmin.DescriptionComponentRenderer,
3560
3700
  {
3561
3701
  props,
3562
- descriptions: plugins["content-manager"].apis.getDocumentActions(),
3702
+ descriptions: plugins["content-manager"].apis.getDocumentActions().filter((action) => action.name !== "PublishAction"),
3563
3703
  children: (actions2) => {
3564
3704
  const tableRowActions = actions2.filter((action) => {
3565
3705
  const positions = Array.isArray(action.position) ? action.position : [action.position];
@@ -3670,7 +3810,7 @@ const CloneAction = ({ model, documentId }) => {
3670
3810
  }),
3671
3811
  content: /* @__PURE__ */ jsxRuntime.jsx(AutoCloneFailureModalBody, { prohibitedFields }),
3672
3812
  footer: ({ onClose }) => {
3673
- return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", children: [
3813
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Footer, { children: [
3674
3814
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: onClose, variant: "tertiary", children: formatMessage({
3675
3815
  id: "cancel",
3676
3816
  defaultMessage: "Cancel"
@@ -3882,6 +4022,15 @@ const { setInitialData } = actions;
3882
4022
  const reducer = toolkit.combineReducers({
3883
4023
  app: reducer$1
3884
4024
  });
4025
+ const FEATURE_ID = "preview";
4026
+ const previewAdmin = {
4027
+ bootstrap(app) {
4028
+ if (!window.strapi.future.isEnabled(FEATURE_ID)) {
4029
+ return {};
4030
+ }
4031
+ console.log("Bootstrapping preview admin");
4032
+ }
4033
+ };
3885
4034
  const index = {
3886
4035
  register(app) {
3887
4036
  const cm = new ContentManagerPlugin();
@@ -3901,7 +4050,7 @@ const index = {
3901
4050
  app.router.addRoute({
3902
4051
  path: "content-manager/*",
3903
4052
  lazy: async () => {
3904
- const { Layout } = await Promise.resolve().then(() => require("./layout-C6dxWYT7.js"));
4053
+ const { Layout } = await Promise.resolve().then(() => require("./layout-B33V9Tdu.js"));
3905
4054
  return {
3906
4055
  Component: Layout
3907
4056
  };
@@ -3914,11 +4063,14 @@ const index = {
3914
4063
  if (typeof historyAdmin.bootstrap === "function") {
3915
4064
  historyAdmin.bootstrap(app);
3916
4065
  }
4066
+ if (typeof previewAdmin.bootstrap === "function") {
4067
+ previewAdmin.bootstrap(app);
4068
+ }
3917
4069
  },
3918
4070
  async registerTrads({ locales }) {
3919
4071
  const importedTrads = await Promise.all(
3920
4072
  locales.map((locale) => {
3921
- return __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/ar.json": () => Promise.resolve().then(() => require("./ar-BUUWXIYu.js")), "./translations/ca.json": () => Promise.resolve().then(() => require("./ca-Cmk45QO6.js")), "./translations/cs.json": () => Promise.resolve().then(() => require("./cs-CkJy6B2v.js")), "./translations/de.json": () => Promise.resolve().then(() => require("./de-CCEmbAah.js")), "./translations/en.json": () => Promise.resolve().then(() => require("./en-uOUIxfcQ.js")), "./translations/es.json": () => Promise.resolve().then(() => require("./es-EUonQTon.js")), "./translations/eu.json": () => Promise.resolve().then(() => require("./eu-VDH-3ovk.js")), "./translations/fr.json": () => Promise.resolve().then(() => require("./fr-B7kGGg3E.js")), "./translations/gu.json": () => Promise.resolve().then(() => require("./gu-BRmF601H.js")), "./translations/hi.json": () => Promise.resolve().then(() => require("./hi-CCJBptSq.js")), "./translations/hu.json": () => Promise.resolve().then(() => require("./hu-sNV_yLYy.js")), "./translations/id.json": () => Promise.resolve().then(() => require("./id-B5Ser98A.js")), "./translations/it.json": () => Promise.resolve().then(() => require("./it-DkBIs7vD.js")), "./translations/ja.json": () => Promise.resolve().then(() => require("./ja-CcFe8diO.js")), "./translations/ko.json": () => Promise.resolve().then(() => require("./ko-woFZPmLk.js")), "./translations/ml.json": () => Promise.resolve().then(() => require("./ml-C2W8N8k1.js")), "./translations/ms.json": () => Promise.resolve().then(() => require("./ms-BuFotyP_.js")), "./translations/nl.json": () => Promise.resolve().then(() => require("./nl-bbEOHChV.js")), "./translations/pl.json": () => Promise.resolve().then(() => require("./pl-uzwG-hk7.js")), "./translations/pt-BR.json": () => Promise.resolve().then(() => require("./pt-BR-BiOz37D9.js")), "./translations/pt.json": () => Promise.resolve().then(() => require("./pt-CeXQuq50.js")), "./translations/ru.json": () => Promise.resolve().then(() => require("./ru-BT3ybNny.js")), "./translations/sa.json": () => Promise.resolve().then(() => require("./sa-CcvkYInH.js")), "./translations/sk.json": () => Promise.resolve().then(() => require("./sk-CvY09Xjv.js")), "./translations/sv.json": () => Promise.resolve().then(() => require("./sv-MYDuzgvT.js")), "./translations/th.json": () => Promise.resolve().then(() => require("./th-D9_GfAjc.js")), "./translations/tr.json": () => Promise.resolve().then(() => require("./tr-D9UH-O_R.js")), "./translations/uk.json": () => Promise.resolve().then(() => require("./uk-C8EiqJY7.js")), "./translations/vi.json": () => Promise.resolve().then(() => require("./vi-CJlYDheJ.js")), "./translations/zh-Hans.json": () => Promise.resolve().then(() => require("./zh-Hans-9kOncHGw.js")), "./translations/zh.json": () => Promise.resolve().then(() => require("./zh-CQQfszqR.js")) }), `./translations/${locale}.json`).then(({ default: data }) => {
4073
+ return __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/ar.json": () => Promise.resolve().then(() => require("./ar-BUUWXIYu.js")), "./translations/ca.json": () => Promise.resolve().then(() => require("./ca-Cmk45QO6.js")), "./translations/cs.json": () => Promise.resolve().then(() => require("./cs-CkJy6B2v.js")), "./translations/de.json": () => Promise.resolve().then(() => require("./de-CCEmbAah.js")), "./translations/en.json": () => Promise.resolve().then(() => require("./en-Bm0D0IWz.js")), "./translations/es.json": () => Promise.resolve().then(() => require("./es-EUonQTon.js")), "./translations/eu.json": () => Promise.resolve().then(() => require("./eu-VDH-3ovk.js")), "./translations/fr.json": () => Promise.resolve().then(() => require("./fr-B7kGGg3E.js")), "./translations/gu.json": () => Promise.resolve().then(() => require("./gu-BRmF601H.js")), "./translations/hi.json": () => Promise.resolve().then(() => require("./hi-CCJBptSq.js")), "./translations/hu.json": () => Promise.resolve().then(() => require("./hu-sNV_yLYy.js")), "./translations/id.json": () => Promise.resolve().then(() => require("./id-B5Ser98A.js")), "./translations/it.json": () => Promise.resolve().then(() => require("./it-DkBIs7vD.js")), "./translations/ja.json": () => Promise.resolve().then(() => require("./ja-CcFe8diO.js")), "./translations/ko.json": () => Promise.resolve().then(() => require("./ko-woFZPmLk.js")), "./translations/ml.json": () => Promise.resolve().then(() => require("./ml-C2W8N8k1.js")), "./translations/ms.json": () => Promise.resolve().then(() => require("./ms-BuFotyP_.js")), "./translations/nl.json": () => Promise.resolve().then(() => require("./nl-bbEOHChV.js")), "./translations/pl.json": () => Promise.resolve().then(() => require("./pl-uzwG-hk7.js")), "./translations/pt-BR.json": () => Promise.resolve().then(() => require("./pt-BR-BiOz37D9.js")), "./translations/pt.json": () => Promise.resolve().then(() => require("./pt-CeXQuq50.js")), "./translations/ru.json": () => Promise.resolve().then(() => require("./ru-BT3ybNny.js")), "./translations/sa.json": () => Promise.resolve().then(() => require("./sa-CcvkYInH.js")), "./translations/sk.json": () => Promise.resolve().then(() => require("./sk-CvY09Xjv.js")), "./translations/sv.json": () => Promise.resolve().then(() => require("./sv-MYDuzgvT.js")), "./translations/th.json": () => Promise.resolve().then(() => require("./th-D9_GfAjc.js")), "./translations/tr.json": () => Promise.resolve().then(() => require("./tr-D9UH-O_R.js")), "./translations/uk.json": () => Promise.resolve().then(() => require("./uk-C8EiqJY7.js")), "./translations/vi.json": () => Promise.resolve().then(() => require("./vi-CJlYDheJ.js")), "./translations/zh-Hans.json": () => Promise.resolve().then(() => require("./zh-Hans-9kOncHGw.js")), "./translations/zh.json": () => Promise.resolve().then(() => require("./zh-CQQfszqR.js")) }), `./translations/${locale}.json`).then(({ default: data }) => {
3922
4074
  return {
3923
4075
  data: prefixPluginTranslations(data, PLUGIN_ID),
3924
4076
  locale
@@ -3936,6 +4088,7 @@ const index = {
3936
4088
  };
3937
4089
  exports.ATTRIBUTE_TYPES_THAT_CANNOT_BE_MAIN_FIELD = ATTRIBUTE_TYPES_THAT_CANNOT_BE_MAIN_FIELD;
3938
4090
  exports.BulkActionsRenderer = BulkActionsRenderer;
4091
+ exports.CLONE_PATH = CLONE_PATH;
3939
4092
  exports.COLLECTION_TYPES = COLLECTION_TYPES;
3940
4093
  exports.CREATOR_FIELDS = CREATOR_FIELDS;
3941
4094
  exports.DEFAULT_SETTINGS = DEFAULT_SETTINGS;
@@ -3963,6 +4116,7 @@ exports.getMainField = getMainField;
3963
4116
  exports.getTranslation = getTranslation;
3964
4117
  exports.index = index;
3965
4118
  exports.setInitialData = setInitialData;
4119
+ exports.useContentManagerContext = useContentManagerContext;
3966
4120
  exports.useContentTypeSchema = useContentTypeSchema;
3967
4121
  exports.useDoc = useDoc;
3968
4122
  exports.useDocLayout = useDocLayout;
@@ -3975,4 +4129,4 @@ exports.useGetAllDocumentsQuery = useGetAllDocumentsQuery;
3975
4129
  exports.useGetContentTypeConfigurationQuery = useGetContentTypeConfigurationQuery;
3976
4130
  exports.useGetInitialDataQuery = useGetInitialDataQuery;
3977
4131
  exports.useUpdateContentTypeConfigurationMutation = useUpdateContentTypeConfigurationMutation;
3978
- //# sourceMappingURL=index-CdT0kHZ8.js.map
4132
+ //# sourceMappingURL=index-CsrCnNa3.js.map