@strapi/content-manager 0.0.0-experimental.fb442e5e12dd3f611303691bf85a249520ba348b → 0.0.0-experimental.fd379e4937e431407d784eaa5fe7f93cf2a53386

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 (153) hide show
  1. package/dist/_chunks/{ComponentConfigurationPage-B3yDbeU1.mjs → ComponentConfigurationPage-CIjXcRAB.mjs} +4 -4
  2. package/dist/_chunks/{ComponentConfigurationPage-B3yDbeU1.mjs.map → ComponentConfigurationPage-CIjXcRAB.mjs.map} +1 -1
  3. package/dist/_chunks/{ComponentConfigurationPage-KXSuLnQD.js → ComponentConfigurationPage-gsCd80MU.js} +4 -4
  4. package/dist/_chunks/{ComponentConfigurationPage-KXSuLnQD.js.map → ComponentConfigurationPage-gsCd80MU.js.map} +1 -1
  5. package/dist/_chunks/{EditConfigurationPage-D7PrLO8j.mjs → EditConfigurationPage-BglmD_BF.mjs} +4 -4
  6. package/dist/_chunks/{EditConfigurationPage-D7PrLO8j.mjs.map → EditConfigurationPage-BglmD_BF.mjs.map} +1 -1
  7. package/dist/_chunks/{EditConfigurationPage-BQ17--5R.js → EditConfigurationPage-DHDQKBzw.js} +4 -4
  8. package/dist/_chunks/{EditConfigurationPage-BQ17--5R.js.map → EditConfigurationPage-DHDQKBzw.js.map} +1 -1
  9. package/dist/_chunks/{EditViewPage-BgjdnGz2.js → EditViewPage-C4iTxUPU.js} +15 -5
  10. package/dist/_chunks/EditViewPage-C4iTxUPU.js.map +1 -0
  11. package/dist/_chunks/{EditViewPage-B7VgwJaG.mjs → EditViewPage-CiwVPMaK.mjs} +15 -5
  12. package/dist/_chunks/EditViewPage-CiwVPMaK.mjs.map +1 -0
  13. package/dist/_chunks/{Field-tHCw4lGA.mjs → Field-DIjL1b5d.mjs} +98 -85
  14. package/dist/_chunks/Field-DIjL1b5d.mjs.map +1 -0
  15. package/dist/_chunks/{Field-CdK7ZLmv.js → Field-DhXEK8y1.js} +101 -88
  16. package/dist/_chunks/Field-DhXEK8y1.js.map +1 -0
  17. package/dist/_chunks/{Form-BJxdTv3Q.mjs → Form-CmNesrvR.mjs} +16 -8
  18. package/dist/_chunks/Form-CmNesrvR.mjs.map +1 -0
  19. package/dist/_chunks/{Form-C_0KTVvV.js → Form-CwmJ4sWe.js} +16 -8
  20. package/dist/_chunks/Form-CwmJ4sWe.js.map +1 -0
  21. package/dist/_chunks/{History-nuEzM5qm.js → History-BLCCNgCt.js} +24 -11
  22. package/dist/_chunks/History-BLCCNgCt.js.map +1 -0
  23. package/dist/_chunks/{History-DR2txJLE.mjs → History-D-99Wh30.mjs} +25 -12
  24. package/dist/_chunks/History-D-99Wh30.mjs.map +1 -0
  25. package/dist/_chunks/{ListConfigurationPage-CnB86Psm.js → ListConfigurationPage-DxWpeZrO.js} +3 -3
  26. package/dist/_chunks/{ListConfigurationPage-CnB86Psm.js.map → ListConfigurationPage-DxWpeZrO.js.map} +1 -1
  27. package/dist/_chunks/{ListConfigurationPage-voFVtXu6.mjs → ListConfigurationPage-JPWZz7Kg.mjs} +3 -3
  28. package/dist/_chunks/{ListConfigurationPage-voFVtXu6.mjs.map → ListConfigurationPage-JPWZz7Kg.mjs.map} +1 -1
  29. package/dist/_chunks/{ListViewPage-SXIXm-RM.js → ListViewPage-CIQekSFz.js} +55 -40
  30. package/dist/_chunks/ListViewPage-CIQekSFz.js.map +1 -0
  31. package/dist/_chunks/{ListViewPage-B_GaWgRH.mjs → ListViewPage-DSK3f0ST.mjs} +52 -37
  32. package/dist/_chunks/ListViewPage-DSK3f0ST.mjs.map +1 -0
  33. package/dist/_chunks/{NoContentTypePage-BzsQ3hLZ.js → NoContentTypePage-C5cxKvC2.js} +2 -2
  34. package/dist/_chunks/{NoContentTypePage-BzsQ3hLZ.js.map → NoContentTypePage-C5cxKvC2.js.map} +1 -1
  35. package/dist/_chunks/{NoContentTypePage-CYiGpsbj.mjs → NoContentTypePage-D99LU1YP.mjs} +2 -2
  36. package/dist/_chunks/{NoContentTypePage-CYiGpsbj.mjs.map → NoContentTypePage-D99LU1YP.mjs.map} +1 -1
  37. package/dist/_chunks/{NoPermissionsPage-B5baIHal.mjs → NoPermissionsPage-DBrBw-0y.mjs} +2 -2
  38. package/dist/_chunks/{NoPermissionsPage-B5baIHal.mjs.map → NoPermissionsPage-DBrBw-0y.mjs.map} +1 -1
  39. package/dist/_chunks/{NoPermissionsPage-IGkId4C5.js → NoPermissionsPage-Oy4tmUrW.js} +2 -2
  40. package/dist/_chunks/{NoPermissionsPage-IGkId4C5.js.map → NoPermissionsPage-Oy4tmUrW.js.map} +1 -1
  41. package/dist/_chunks/{Relations-CIYDdKU-.mjs → Relations-BBmhcWFV.mjs} +69 -36
  42. package/dist/_chunks/Relations-BBmhcWFV.mjs.map +1 -0
  43. package/dist/_chunks/{Relations-Dhuurpx2.js → Relations-eG-9p_qS.js} +68 -35
  44. package/dist/_chunks/Relations-eG-9p_qS.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-CdT0kHZ8.js → index-BIWDoFLK.js} +2051 -1896
  50. package/dist/_chunks/index-BIWDoFLK.js.map +1 -0
  51. package/dist/_chunks/{index-C9TJPyni.mjs → index-BrUzbQ30.mjs} +2070 -1916
  52. package/dist/_chunks/index-BrUzbQ30.mjs.map +1 -0
  53. package/dist/_chunks/{layout-BNqvLR_b.mjs → layout-_5-cXs34.mjs} +5 -4
  54. package/dist/_chunks/{layout-BNqvLR_b.mjs.map → layout-_5-cXs34.mjs.map} +1 -1
  55. package/dist/_chunks/{layout-C6dxWYT7.js → layout-lMc9i1-Z.js} +5 -4
  56. package/dist/_chunks/{layout-C6dxWYT7.js.map → layout-lMc9i1-Z.js.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-BRHithi8.js} +3 -7
  62. package/dist/_chunks/relations-BRHithi8.js.map +1 -0
  63. package/dist/_chunks/{relations-CkKqKw65.mjs → relations-B_VLk-DD.mjs} +3 -7
  64. package/dist/_chunks/relations-B_VLk-DD.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 +191 -67
  88. package/dist/server/index.js.map +1 -1
  89. package/dist/server/index.mjs +191 -67
  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/index.d.ts.map +1 -1
  94. package/dist/server/src/controllers/relations.d.ts.map +1 -1
  95. package/dist/server/src/controllers/utils/metadata.d.ts +15 -1
  96. package/dist/server/src/controllers/utils/metadata.d.ts.map +1 -1
  97. package/dist/server/src/history/services/history.d.ts.map +1 -1
  98. package/dist/server/src/history/services/lifecycles.d.ts.map +1 -1
  99. package/dist/server/src/history/services/utils.d.ts +1 -0
  100. package/dist/server/src/history/services/utils.d.ts.map +1 -1
  101. package/dist/server/src/index.d.ts +4 -4
  102. package/dist/server/src/policies/hasPermissions.d.ts.map +1 -1
  103. package/dist/server/src/preview/constants.d.ts +2 -0
  104. package/dist/server/src/preview/constants.d.ts.map +1 -0
  105. package/dist/server/src/preview/controllers/index.d.ts +2 -0
  106. package/dist/server/src/preview/controllers/index.d.ts.map +1 -0
  107. package/dist/server/src/preview/controllers/preview.d.ts +9 -0
  108. package/dist/server/src/preview/controllers/preview.d.ts.map +1 -0
  109. package/dist/server/src/preview/index.d.ts +4 -0
  110. package/dist/server/src/preview/index.d.ts.map +1 -0
  111. package/dist/server/src/preview/routes/index.d.ts +8 -0
  112. package/dist/server/src/preview/routes/index.d.ts.map +1 -0
  113. package/dist/server/src/preview/routes/preview.d.ts +4 -0
  114. package/dist/server/src/preview/routes/preview.d.ts.map +1 -0
  115. package/dist/server/src/preview/services/index.d.ts +4 -0
  116. package/dist/server/src/preview/services/index.d.ts.map +1 -0
  117. package/dist/server/src/preview/services/preview.d.ts +6 -0
  118. package/dist/server/src/preview/services/preview.d.ts.map +1 -0
  119. package/dist/server/src/preview/utils.d.ts +7 -0
  120. package/dist/server/src/preview/utils.d.ts.map +1 -0
  121. package/dist/server/src/routes/index.d.ts.map +1 -1
  122. package/dist/server/src/services/document-metadata.d.ts +8 -8
  123. package/dist/server/src/services/document-metadata.d.ts.map +1 -1
  124. package/dist/server/src/services/index.d.ts +4 -4
  125. package/dist/server/src/services/index.d.ts.map +1 -1
  126. package/dist/server/src/services/permission-checker.d.ts.map +1 -1
  127. package/dist/server/src/services/utils/configuration/index.d.ts +2 -2
  128. package/dist/server/src/services/utils/configuration/layouts.d.ts +2 -2
  129. package/dist/server/src/utils/index.d.ts +2 -0
  130. package/dist/server/src/utils/index.d.ts.map +1 -1
  131. package/dist/shared/contracts/collection-types.d.ts +3 -1
  132. package/dist/shared/contracts/collection-types.d.ts.map +1 -1
  133. package/package.json +12 -12
  134. package/dist/_chunks/EditViewPage-B7VgwJaG.mjs.map +0 -1
  135. package/dist/_chunks/EditViewPage-BgjdnGz2.js.map +0 -1
  136. package/dist/_chunks/Field-CdK7ZLmv.js.map +0 -1
  137. package/dist/_chunks/Field-tHCw4lGA.mjs.map +0 -1
  138. package/dist/_chunks/Form-BJxdTv3Q.mjs.map +0 -1
  139. package/dist/_chunks/Form-C_0KTVvV.js.map +0 -1
  140. package/dist/_chunks/History-DR2txJLE.mjs.map +0 -1
  141. package/dist/_chunks/History-nuEzM5qm.js.map +0 -1
  142. package/dist/_chunks/ListViewPage-B_GaWgRH.mjs.map +0 -1
  143. package/dist/_chunks/ListViewPage-SXIXm-RM.js.map +0 -1
  144. package/dist/_chunks/Relations-CIYDdKU-.mjs.map +0 -1
  145. package/dist/_chunks/Relations-Dhuurpx2.js.map +0 -1
  146. package/dist/_chunks/index-C9TJPyni.mjs.map +0 -1
  147. package/dist/_chunks/index-CdT0kHZ8.js.map +0 -1
  148. package/dist/_chunks/relations-CkKqKw65.mjs.map +0 -1
  149. package/dist/_chunks/relations-DtFaDnP1.js.map +0 -1
  150. package/dist/_chunks/usePrev-B9w_-eYc.js.map +0 -1
  151. package/dist/_chunks/usePrev-DH6iah0A.mjs +0 -16
  152. package/dist/_chunks/usePrev-DH6iah0A.mjs.map +0 -1
  153. 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,396 +936,438 @@ 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([row]);
995
+ } else {
996
+ panels[currentPanelIndex].push(row);
928
997
  }
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 };
998
+ }
999
+ return panels;
1000
+ }, []);
1001
+ const componentEditAttributes = Object.entries(data.components).reduce(
1002
+ (acc, [uid, configuration]) => {
1003
+ acc[uid] = {
1004
+ layout: convertEditLayoutToFieldLayouts(
1005
+ configuration.layouts.edit,
1006
+ components[uid].attributes,
1007
+ configuration.metadatas,
1008
+ { configurations: data.components, schemas: components }
1009
+ ),
1010
+ settings: {
1011
+ ...configuration.settings,
1012
+ icon: components[uid].info.icon,
1013
+ displayName: components[uid].info.displayName
948
1014
  }
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
- }
1015
+ };
1016
+ return acc;
967
1017
  },
968
- [trackUsage, deleteManyDocuments, toggleNotification, formatMessage, formatAPIError]
1018
+ {}
969
1019
  );
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
- }
1020
+ const editMetadatas = Object.entries(data.contentType.metadatas).reduce(
1021
+ (acc, [attribute, metadata]) => {
1022
+ return {
1023
+ ...acc,
1024
+ [attribute]: metadata.edit
1025
+ };
1002
1026
  },
1003
- [discardDocument, formatAPIError, formatMessage, toggleNotification]
1027
+ {}
1004
1028
  );
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
- }
1029
+ return {
1030
+ layout: panelledEditAttributes,
1031
+ components: componentEditAttributes,
1032
+ metadatas: editMetadatas,
1033
+ settings: {
1034
+ ...data.contentType.settings,
1035
+ displayName: schema?.info.displayName
1037
1036
  },
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;
1037
+ options: {
1038
+ ...schema?.options,
1039
+ ...schema?.pluginOptions,
1040
+ ...data.contentType.options
1041
+ }
1042
+ };
1043
+ };
1044
+ const convertEditLayoutToFieldLayouts = (rows, attributes = {}, metadatas, components, schemas = []) => {
1045
+ return rows.map(
1046
+ (row) => row.map((field) => {
1047
+ const attribute = attributes[field.name];
1048
+ if (!attribute) {
1049
+ return null;
1067
1050
  }
1068
- },
1069
- [
1070
- // trackUsage,
1071
- publishManyDocuments,
1072
- toggleNotification,
1073
- formatMessage,
1074
- formatAPIError
1075
- ]
1051
+ const { edit: metadata } = metadatas[field.name];
1052
+ const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
1053
+ return {
1054
+ attribute,
1055
+ disabled: !metadata.editable,
1056
+ hint: metadata.description,
1057
+ label: metadata.label ?? "",
1058
+ name: field.name,
1059
+ // @ts-expect-error – mainField does exist on the metadata for a relation.
1060
+ mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
1061
+ schemas,
1062
+ components: components?.schemas ?? {}
1063
+ }),
1064
+ placeholder: metadata.placeholder ?? "",
1065
+ required: attribute.required ?? false,
1066
+ size: field.size,
1067
+ unique: "unique" in attribute ? attribute.unique : false,
1068
+ visible: metadata.visible ?? true,
1069
+ type: attribute.type
1070
+ };
1071
+ }).filter((field) => field !== null)
1076
1072
  );
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 };
1073
+ };
1074
+ const formatListLayout = (data, {
1075
+ schemas,
1076
+ schema,
1077
+ components
1078
+ }) => {
1079
+ const listMetadatas = Object.entries(data.contentType.metadatas).reduce(
1080
+ (acc, [attribute, metadata]) => {
1081
+ return {
1082
+ ...acc,
1083
+ [attribute]: metadata.list
1084
+ };
1085
+ },
1086
+ {}
1087
+ );
1088
+ const listAttributes = convertListLayoutToFieldLayouts(
1089
+ data.contentType.layouts.list,
1090
+ schema?.attributes,
1091
+ listMetadatas,
1092
+ { configurations: data.components, schemas: components },
1093
+ schemas
1094
+ );
1095
+ return {
1096
+ layout: listAttributes,
1097
+ settings: { ...data.contentType.settings, displayName: schema?.info.displayName },
1098
+ metadatas: listMetadatas,
1099
+ options: {
1100
+ ...schema?.options,
1101
+ ...schema?.pluginOptions,
1102
+ ...data.contentType.options
1103
+ }
1104
+ };
1105
+ };
1106
+ const convertListLayoutToFieldLayouts = (columns, attributes = {}, metadatas, components, schemas = []) => {
1107
+ return columns.map((name) => {
1108
+ const attribute = attributes[name];
1109
+ if (!attribute) {
1110
+ return null;
1111
+ }
1112
+ const metadata = metadatas[name];
1113
+ const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
1114
+ return {
1115
+ attribute,
1116
+ label: metadata.label ?? "",
1117
+ mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
1118
+ schemas,
1119
+ components: components?.schemas ?? {}
1120
+ }),
1121
+ name,
1122
+ searchable: metadata.searchable ?? true,
1123
+ sortable: metadata.sortable ?? true
1124
+ };
1125
+ }).filter((field) => field !== null);
1126
+ };
1127
+ const useDocument = (args, opts) => {
1128
+ const { toggleNotification } = strapiAdmin.useNotification();
1129
+ const { _unstableFormatAPIError: formatAPIError } = strapiAdmin.useAPIErrorHandler();
1130
+ const {
1131
+ currentData: data,
1132
+ isLoading: isLoadingDocument,
1133
+ isFetching: isFetchingDocument,
1134
+ error
1135
+ } = useGetDocumentQuery(args, {
1136
+ ...opts,
1137
+ skip: !args.documentId && args.collectionType !== SINGLE_TYPES || opts?.skip
1138
+ });
1139
+ const {
1140
+ components,
1141
+ schema,
1142
+ schemas,
1143
+ isLoading: isLoadingSchema
1144
+ } = useContentTypeSchema(args.model);
1145
+ React__namespace.useEffect(() => {
1146
+ if (error) {
1147
+ toggleNotification({
1148
+ type: "danger",
1149
+ message: formatAPIError(error)
1150
+ });
1151
+ }
1152
+ }, [toggleNotification, error, formatAPIError, args.collectionType]);
1153
+ const validationSchema = React__namespace.useMemo(() => {
1154
+ if (!schema) {
1155
+ return null;
1156
+ }
1157
+ return createYupSchema(schema.attributes, components);
1158
+ }, [schema, components]);
1159
+ const validate = React__namespace.useCallback(
1160
+ (document) => {
1161
+ if (!validationSchema) {
1162
+ throw new Error(
1163
+ "There is no validation schema generated, this is likely due to the schema not being loaded yet."
1164
+ );
1165
+ }
1166
+ try {
1167
+ validationSchema.validateSync(document, { abortEarly: false, strict: true });
1168
+ return null;
1169
+ } catch (error2) {
1170
+ if (error2 instanceof yup.ValidationError) {
1171
+ return strapiAdmin.getYupValidationErrors(error2);
1093
1172
  }
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;
1173
+ throw error2;
1110
1174
  }
1111
1175
  },
1112
- [trackUsage, updateDocument, toggleNotification, formatMessage, formatAPIError]
1176
+ [validationSchema]
1113
1177
  );
1114
- const [unpublishDocument] = useUnpublishDocumentMutation();
1115
- const unpublish = React__namespace.useCallback(
1116
- async ({ collectionType, model, documentId, params }, discardDraft = false) => {
1178
+ const isLoading = isLoadingDocument || isFetchingDocument || isLoadingSchema;
1179
+ const hasError = !!error;
1180
+ return {
1181
+ components,
1182
+ document: data?.data,
1183
+ meta: data?.meta,
1184
+ isLoading,
1185
+ hasError,
1186
+ schema,
1187
+ schemas,
1188
+ validate
1189
+ };
1190
+ };
1191
+ const useDoc = () => {
1192
+ const { id, slug, collectionType, origin } = reactRouterDom.useParams();
1193
+ const [{ query }] = strapiAdmin.useQueryParams();
1194
+ const params = React__namespace.useMemo(() => buildValidParams(query), [query]);
1195
+ if (!collectionType) {
1196
+ throw new Error("Could not find collectionType in url params");
1197
+ }
1198
+ if (!slug) {
1199
+ throw new Error("Could not find model in url params");
1200
+ }
1201
+ const document = useDocument(
1202
+ { documentId: origin || id, model: slug, collectionType, params },
1203
+ {
1204
+ skip: id === "create" || !origin && !id && collectionType !== SINGLE_TYPES
1205
+ }
1206
+ );
1207
+ const returnId = origin || id === "create" ? void 0 : id;
1208
+ return {
1209
+ collectionType,
1210
+ model: slug,
1211
+ id: returnId,
1212
+ ...document
1213
+ };
1214
+ };
1215
+ const useContentManagerContext = () => {
1216
+ const {
1217
+ collectionType,
1218
+ model,
1219
+ id,
1220
+ components,
1221
+ isLoading: isLoadingDoc,
1222
+ schema,
1223
+ schemas
1224
+ } = useDoc();
1225
+ const layout = useDocumentLayout(model);
1226
+ const form = strapiAdmin.useForm("useContentManagerContext", (state) => state);
1227
+ const isSingleType = collectionType === SINGLE_TYPES;
1228
+ const slug = model;
1229
+ const isCreatingEntry = id === "create";
1230
+ useContentTypeSchema();
1231
+ const isLoading = isLoadingDoc || layout.isLoading;
1232
+ const error = layout.error;
1233
+ return {
1234
+ error,
1235
+ isLoading,
1236
+ // Base metadata
1237
+ model,
1238
+ collectionType,
1239
+ id,
1240
+ slug,
1241
+ isCreatingEntry,
1242
+ isSingleType,
1243
+ hasDraftAndPublish: schema?.options?.draftAndPublish ?? false,
1244
+ // All schema infos
1245
+ components,
1246
+ contentType: schema,
1247
+ contentTypes: schemas,
1248
+ // Form state
1249
+ form,
1250
+ // layout infos
1251
+ layout
1252
+ };
1253
+ };
1254
+ const prefixPluginTranslations = (trad, pluginId) => {
1255
+ return Object.keys(trad).reduce((acc, current) => {
1256
+ acc[`${pluginId}.${current}`] = trad[current];
1257
+ return acc;
1258
+ }, {});
1259
+ };
1260
+ const getTranslation = (id) => `content-manager.${id}`;
1261
+ const DEFAULT_UNEXPECTED_ERROR_MSG = {
1262
+ id: "notification.error",
1263
+ defaultMessage: "An error occurred, please try again"
1264
+ };
1265
+ const useDocumentActions = () => {
1266
+ const { toggleNotification } = strapiAdmin.useNotification();
1267
+ const { formatMessage } = reactIntl.useIntl();
1268
+ const { trackUsage } = strapiAdmin.useTracking();
1269
+ const { _unstableFormatAPIError: formatAPIError } = strapiAdmin.useAPIErrorHandler();
1270
+ const navigate = reactRouterDom.useNavigate();
1271
+ const setCurrentStep = strapiAdmin.useGuidedTour("useDocumentActions", (state) => state.setCurrentStep);
1272
+ const [deleteDocument] = useDeleteDocumentMutation();
1273
+ const _delete = React__namespace.useCallback(
1274
+ async ({ collectionType, model, documentId, params }, trackerProperty) => {
1117
1275
  try {
1118
- trackUsage("willUnpublishEntry");
1119
- const res = await unpublishDocument({
1276
+ trackUsage("willDeleteEntry", trackerProperty);
1277
+ const res = await deleteDocument({
1120
1278
  collectionType,
1121
1279
  model,
1122
1280
  documentId,
1123
- params,
1124
- data: {
1125
- discardDraft
1126
- }
1281
+ params
1127
1282
  });
1128
1283
  if ("error" in res) {
1129
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1284
+ toggleNotification({
1285
+ type: "danger",
1286
+ message: formatAPIError(res.error)
1287
+ });
1130
1288
  return { error: res.error };
1131
1289
  }
1132
- trackUsage("didUnpublishEntry");
1133
1290
  toggleNotification({
1134
1291
  type: "success",
1135
1292
  message: formatMessage({
1136
- id: getTranslation("success.record.unpublish"),
1137
- defaultMessage: "Unpublished document"
1293
+ id: getTranslation("success.record.delete"),
1294
+ defaultMessage: "Deleted document"
1138
1295
  })
1139
1296
  });
1297
+ trackUsage("didDeleteEntry", trackerProperty);
1140
1298
  return res.data;
1141
1299
  } catch (err) {
1142
1300
  toggleNotification({
1143
1301
  type: "danger",
1144
1302
  message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1145
1303
  });
1304
+ trackUsage("didNotDeleteEntry", { error: err, ...trackerProperty });
1146
1305
  throw err;
1147
1306
  }
1148
1307
  },
1149
- [trackUsage, unpublishDocument, toggleNotification, formatMessage, formatAPIError]
1308
+ [trackUsage, deleteDocument, toggleNotification, formatMessage, formatAPIError]
1150
1309
  );
1151
- const [unpublishManyDocuments] = useUnpublishManyDocumentsMutation();
1152
- const unpublishMany = React__namespace.useCallback(
1310
+ const [deleteManyDocuments] = useDeleteManyDocumentsMutation();
1311
+ const deleteMany = React__namespace.useCallback(
1153
1312
  async ({ model, documentIds, params }) => {
1154
1313
  try {
1155
- trackUsage("willBulkUnpublishEntries");
1156
- const res = await unpublishManyDocuments({
1314
+ trackUsage("willBulkDeleteEntries");
1315
+ const res = await deleteManyDocuments({
1157
1316
  model,
1158
1317
  documentIds,
1159
1318
  params
1160
1319
  });
1161
1320
  if ("error" in res) {
1162
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1321
+ toggleNotification({
1322
+ type: "danger",
1323
+ message: formatAPIError(res.error)
1324
+ });
1163
1325
  return { error: res.error };
1164
1326
  }
1165
- trackUsage("didBulkUnpublishEntries");
1166
1327
  toggleNotification({
1167
1328
  type: "success",
1168
1329
  title: formatMessage({
1169
- id: getTranslation("success.records.unpublish"),
1170
- defaultMessage: "Successfully unpublished."
1330
+ id: getTranslation("success.records.delete"),
1331
+ defaultMessage: "Successfully deleted."
1171
1332
  }),
1172
1333
  message: ""
1173
1334
  });
1335
+ trackUsage("didBulkDeleteEntries");
1174
1336
  return res.data;
1175
1337
  } catch (err) {
1176
1338
  toggleNotification({
1177
1339
  type: "danger",
1178
1340
  message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1179
1341
  });
1180
- trackUsage("didNotBulkUnpublishEntries");
1342
+ trackUsage("didNotBulkDeleteEntries");
1181
1343
  throw err;
1182
1344
  }
1183
1345
  },
1184
- [trackUsage, unpublishManyDocuments, toggleNotification, formatMessage, formatAPIError]
1346
+ [trackUsage, deleteManyDocuments, toggleNotification, formatMessage, formatAPIError]
1185
1347
  );
1186
- const [createDocument] = useCreateDocumentMutation();
1187
- const create = React__namespace.useCallback(
1188
- async ({ model, params }, data, trackerProperty) => {
1348
+ const [discardDocument] = useDiscardDocumentMutation();
1349
+ const discard = React__namespace.useCallback(
1350
+ async ({ collectionType, model, documentId, params }) => {
1189
1351
  try {
1190
- const res = await createDocument({
1352
+ const res = await discardDocument({
1353
+ collectionType,
1191
1354
  model,
1192
- data,
1355
+ documentId,
1193
1356
  params
1194
1357
  });
1195
1358
  if ("error" in res) {
1196
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1197
- trackUsage("didNotCreateEntry", { error: res.error, ...trackerProperty });
1359
+ toggleNotification({
1360
+ type: "danger",
1361
+ message: formatAPIError(res.error)
1362
+ });
1198
1363
  return { error: res.error };
1199
1364
  }
1200
- trackUsage("didCreateEntry", trackerProperty);
1201
1365
  toggleNotification({
1202
1366
  type: "success",
1203
1367
  message: formatMessage({
1204
- id: getTranslation("success.record.save"),
1205
- defaultMessage: "Saved document"
1206
- })
1368
+ id: "content-manager.success.record.discard",
1369
+ defaultMessage: "Changes discarded"
1370
+ })
1207
1371
  });
1208
1372
  return res.data;
1209
1373
  } catch (err) {
@@ -1211,28 +1375,33 @@ const useDocumentActions = () => {
1211
1375
  type: "danger",
1212
1376
  message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1213
1377
  });
1214
- trackUsage("didNotCreateEntry", { error: err, ...trackerProperty });
1215
1378
  throw err;
1216
1379
  }
1217
1380
  },
1218
- [createDocument, formatAPIError, formatMessage, toggleNotification, trackUsage]
1381
+ [discardDocument, formatAPIError, formatMessage, toggleNotification]
1219
1382
  );
1220
- const [autoCloneDocument] = useAutoCloneDocumentMutation();
1221
- const autoClone = React__namespace.useCallback(
1222
- async ({ model, sourceId }) => {
1383
+ const [publishDocument] = usePublishDocumentMutation();
1384
+ const publish = React__namespace.useCallback(
1385
+ async ({ collectionType, model, documentId, params }, data) => {
1223
1386
  try {
1224
- const res = await autoCloneDocument({
1387
+ trackUsage("willPublishEntry");
1388
+ const res = await publishDocument({
1389
+ collectionType,
1225
1390
  model,
1226
- sourceId
1391
+ documentId,
1392
+ data,
1393
+ params
1227
1394
  });
1228
1395
  if ("error" in res) {
1396
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1229
1397
  return { error: res.error };
1230
1398
  }
1399
+ trackUsage("didPublishEntry");
1231
1400
  toggleNotification({
1232
1401
  type: "success",
1233
1402
  message: formatMessage({
1234
- id: getTranslation("success.record.clone"),
1235
- defaultMessage: "Cloned document"
1403
+ id: getTranslation("success.record.publish"),
1404
+ defaultMessage: "Published document"
1236
1405
  })
1237
1406
  });
1238
1407
  return res.data;
@@ -1244,30 +1413,26 @@ const useDocumentActions = () => {
1244
1413
  throw err;
1245
1414
  }
1246
1415
  },
1247
- [autoCloneDocument, formatAPIError, formatMessage, toggleNotification]
1416
+ [trackUsage, publishDocument, toggleNotification, formatMessage, formatAPIError]
1248
1417
  );
1249
- const [cloneDocument] = useCloneDocumentMutation();
1250
- const clone = React__namespace.useCallback(
1251
- async ({ model, documentId, params }, body, trackerProperty) => {
1418
+ const [publishManyDocuments] = usePublishManyDocumentsMutation();
1419
+ const publishMany = React__namespace.useCallback(
1420
+ async ({ model, documentIds, params }) => {
1252
1421
  try {
1253
- const { id: _id, ...restBody } = body;
1254
- const res = await cloneDocument({
1422
+ const res = await publishManyDocuments({
1255
1423
  model,
1256
- sourceId: documentId,
1257
- data: restBody,
1424
+ documentIds,
1258
1425
  params
1259
1426
  });
1260
1427
  if ("error" in res) {
1261
1428
  toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1262
- trackUsage("didNotCreateEntry", { error: res.error, ...trackerProperty });
1263
1429
  return { error: res.error };
1264
1430
  }
1265
- trackUsage("didCreateEntry", trackerProperty);
1266
1431
  toggleNotification({
1267
1432
  type: "success",
1268
1433
  message: formatMessage({
1269
- id: getTranslation("success.record.clone"),
1270
- defaultMessage: "Cloned document"
1434
+ id: getTranslation("success.record.publish"),
1435
+ defaultMessage: "Published document"
1271
1436
  })
1272
1437
  });
1273
1438
  return res.data;
@@ -1276,1192 +1441,1085 @@ const useDocumentActions = () => {
1276
1441
  type: "danger",
1277
1442
  message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1278
1443
  });
1279
- trackUsage("didNotCreateEntry", { error: err, ...trackerProperty });
1280
1444
  throw err;
1281
1445
  }
1282
1446
  },
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]
1447
+ [
1448
+ // trackUsage,
1449
+ publishManyDocuments,
1450
+ toggleNotification,
1451
+ formatMessage,
1452
+ formatAPIError
1453
+ ]
1292
1454
  );
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
- })
1455
+ const [updateDocument] = useUpdateDocumentMutation();
1456
+ const update = React__namespace.useCallback(
1457
+ async ({ collectionType, model, documentId, params }, data, trackerProperty) => {
1458
+ try {
1459
+ trackUsage("willEditEntry", trackerProperty);
1460
+ const res = await updateDocument({
1461
+ collectionType,
1462
+ model,
1463
+ documentId,
1464
+ data,
1465
+ params
1466
+ });
1467
+ if ("error" in res) {
1468
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1469
+ trackUsage("didNotEditEntry", { error: res.error, ...trackerProperty });
1470
+ return { error: res.error };
1417
1471
  }
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);
1472
+ trackUsage("didEditEntry", trackerProperty);
1473
+ toggleNotification({
1474
+ type: "success",
1475
+ message: formatMessage({
1476
+ id: getTranslation("success.record.save"),
1477
+ defaultMessage: "Saved document"
1478
+ })
1479
+ });
1480
+ return res.data;
1481
+ } catch (err) {
1482
+ trackUsage("didNotEditEntry", { error: err, ...trackerProperty });
1483
+ toggleNotification({
1484
+ type: "danger",
1485
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1486
+ });
1487
+ throw err;
1450
1488
  }
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 })
1489
+ },
1490
+ [trackUsage, updateDocument, toggleNotification, formatMessage, formatAPIError]
1722
1491
  );
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({
1492
+ const [unpublishDocument] = useUnpublishDocumentMutation();
1493
+ const unpublish = React__namespace.useCallback(
1494
+ async ({ collectionType, model, documentId, params }, discardDraft = false) => {
1495
+ try {
1496
+ trackUsage("willUnpublishEntry");
1497
+ const res = await unpublishDocument({
1777
1498
  collectionType,
1778
1499
  model,
1779
1500
  documentId,
1501
+ params,
1502
+ data: {
1503
+ discardDraft
1504
+ }
1505
+ });
1506
+ if ("error" in res) {
1507
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1508
+ return { error: res.error };
1509
+ }
1510
+ trackUsage("didUnpublishEntry");
1511
+ toggleNotification({
1512
+ type: "success",
1513
+ message: formatMessage({
1514
+ id: getTranslation("success.record.unpublish"),
1515
+ defaultMessage: "Unpublished document"
1516
+ })
1517
+ });
1518
+ return res.data;
1519
+ } catch (err) {
1520
+ toggleNotification({
1521
+ type: "danger",
1522
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1523
+ });
1524
+ throw err;
1525
+ }
1526
+ },
1527
+ [trackUsage, unpublishDocument, toggleNotification, formatMessage, formatAPIError]
1528
+ );
1529
+ const [unpublishManyDocuments] = useUnpublishManyDocumentsMutation();
1530
+ const unpublishMany = React__namespace.useCallback(
1531
+ async ({ model, documentIds, params }) => {
1532
+ try {
1533
+ trackUsage("willBulkUnpublishEntries");
1534
+ const res = await unpublishManyDocuments({
1535
+ model,
1536
+ documentIds,
1780
1537
  params
1781
1538
  });
1782
- if (error) {
1783
- throw error;
1539
+ if ("error" in res) {
1540
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1541
+ return { error: res.error };
1784
1542
  }
1785
- if (data) {
1786
- setServerCountOfDraftRelations(data.data);
1543
+ trackUsage("didBulkUnpublishEntries");
1544
+ toggleNotification({
1545
+ type: "success",
1546
+ title: formatMessage({
1547
+ id: getTranslation("success.records.unpublish"),
1548
+ defaultMessage: "Successfully unpublished."
1549
+ }),
1550
+ message: ""
1551
+ });
1552
+ return res.data;
1553
+ } catch (err) {
1554
+ toggleNotification({
1555
+ type: "danger",
1556
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1557
+ });
1558
+ trackUsage("didNotBulkUnpublishEntries");
1559
+ throw err;
1560
+ }
1561
+ },
1562
+ [trackUsage, unpublishManyDocuments, toggleNotification, formatMessage, formatAPIError]
1563
+ );
1564
+ const [createDocument] = useCreateDocumentMutation();
1565
+ const create = React__namespace.useCallback(
1566
+ async ({ model, params }, data, trackerProperty) => {
1567
+ try {
1568
+ const res = await createDocument({
1569
+ model,
1570
+ data,
1571
+ params
1572
+ });
1573
+ if ("error" in res) {
1574
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1575
+ trackUsage("didNotCreateEntry", { error: res.error, ...trackerProperty });
1576
+ return { error: res.error };
1787
1577
  }
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) {
1578
+ trackUsage("didCreateEntry", trackerProperty);
1579
+ toggleNotification({
1580
+ type: "success",
1581
+ message: formatMessage({
1582
+ id: getTranslation("success.record.save"),
1583
+ defaultMessage: "Saved document"
1584
+ })
1585
+ });
1586
+ setCurrentStep("contentManager.success");
1587
+ return res.data;
1588
+ } catch (err) {
1801
1589
  toggleNotification({
1802
1590
  type: "danger",
1591
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1592
+ });
1593
+ trackUsage("didNotCreateEntry", { error: err, ...trackerProperty });
1594
+ throw err;
1595
+ }
1596
+ },
1597
+ [createDocument, formatAPIError, formatMessage, toggleNotification, trackUsage]
1598
+ );
1599
+ const [autoCloneDocument] = useAutoCloneDocumentMutation();
1600
+ const autoClone = React__namespace.useCallback(
1601
+ async ({ model, sourceId }) => {
1602
+ try {
1603
+ const res = await autoCloneDocument({
1604
+ model,
1605
+ sourceId
1606
+ });
1607
+ if ("error" in res) {
1608
+ return { error: res.error };
1609
+ }
1610
+ toggleNotification({
1611
+ type: "success",
1803
1612
  message: formatMessage({
1804
- id: "content-manager.validation.error",
1805
- defaultMessage: "There are validation errors in your document. Please fix them before saving."
1613
+ id: getTranslation("success.record.clone"),
1614
+ defaultMessage: "Cloned document"
1806
1615
  })
1807
1616
  });
1808
- return;
1617
+ return res.data;
1618
+ } catch (err) {
1619
+ toggleNotification({
1620
+ type: "danger",
1621
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1622
+ });
1623
+ throw err;
1809
1624
  }
1810
- const res = await publish(
1811
- {
1812
- collectionType,
1625
+ },
1626
+ [autoCloneDocument, formatMessage, toggleNotification]
1627
+ );
1628
+ const [cloneDocument] = useCloneDocumentMutation();
1629
+ const clone = React__namespace.useCallback(
1630
+ async ({ model, documentId, params }, body, trackerProperty) => {
1631
+ try {
1632
+ const { id: _id, ...restBody } = body;
1633
+ const res = await cloneDocument({
1813
1634
  model,
1814
- documentId,
1635
+ sourceId: documentId,
1636
+ data: restBody,
1815
1637
  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
1638
  });
1824
- } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1825
- setErrors(formatValidationErrors(res.error));
1639
+ if ("error" in res) {
1640
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1641
+ trackUsage("didNotCreateEntry", { error: res.error, ...trackerProperty });
1642
+ return { error: res.error };
1643
+ }
1644
+ trackUsage("didCreateEntry", trackerProperty);
1645
+ toggleNotification({
1646
+ type: "success",
1647
+ message: formatMessage({
1648
+ id: getTranslation("success.record.clone"),
1649
+ defaultMessage: "Cloned document"
1650
+ })
1651
+ });
1652
+ navigate(`../../${res.data.data.documentId}`, { relative: "path" });
1653
+ return res.data;
1654
+ } catch (err) {
1655
+ toggleNotification({
1656
+ type: "danger",
1657
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1658
+ });
1659
+ trackUsage("didNotCreateEntry", { error: err, ...trackerProperty });
1660
+ throw err;
1826
1661
  }
1827
- } finally {
1828
- setSubmitting(false);
1662
+ },
1663
+ [cloneDocument, trackUsage, toggleNotification, formatMessage, formatAPIError, navigate]
1664
+ );
1665
+ const [getDoc] = useLazyGetDocumentQuery();
1666
+ const getDocument = React__namespace.useCallback(
1667
+ async (args) => {
1668
+ const { data } = await getDoc(args);
1669
+ return data;
1670
+ },
1671
+ [getDoc]
1672
+ );
1673
+ return {
1674
+ autoClone,
1675
+ clone,
1676
+ create,
1677
+ delete: _delete,
1678
+ deleteMany,
1679
+ discard,
1680
+ getDocument,
1681
+ publish,
1682
+ publishMany,
1683
+ unpublish,
1684
+ unpublishMany,
1685
+ update
1686
+ };
1687
+ };
1688
+ const ProtectedHistoryPage = React.lazy(
1689
+ () => Promise.resolve().then(() => require("./History-BLCCNgCt.js")).then((mod) => ({ default: mod.ProtectedHistoryPage }))
1690
+ );
1691
+ const routes$1 = [
1692
+ {
1693
+ path: ":collectionType/:slug/:id/history",
1694
+ Component: ProtectedHistoryPage
1695
+ },
1696
+ {
1697
+ path: ":collectionType/:slug/history",
1698
+ Component: ProtectedHistoryPage
1699
+ }
1700
+ ];
1701
+ const ProtectedEditViewPage = React.lazy(
1702
+ () => Promise.resolve().then(() => require("./EditViewPage-C4iTxUPU.js")).then((mod) => ({ default: mod.ProtectedEditViewPage }))
1703
+ );
1704
+ const ProtectedListViewPage = React.lazy(
1705
+ () => Promise.resolve().then(() => require("./ListViewPage-CIQekSFz.js")).then((mod) => ({ default: mod.ProtectedListViewPage }))
1706
+ );
1707
+ const ProtectedListConfiguration = React.lazy(
1708
+ () => Promise.resolve().then(() => require("./ListConfigurationPage-DxWpeZrO.js")).then((mod) => ({
1709
+ default: mod.ProtectedListConfiguration
1710
+ }))
1711
+ );
1712
+ const ProtectedEditConfigurationPage = React.lazy(
1713
+ () => Promise.resolve().then(() => require("./EditConfigurationPage-DHDQKBzw.js")).then((mod) => ({
1714
+ default: mod.ProtectedEditConfigurationPage
1715
+ }))
1716
+ );
1717
+ const ProtectedComponentConfigurationPage = React.lazy(
1718
+ () => Promise.resolve().then(() => require("./ComponentConfigurationPage-gsCd80MU.js")).then((mod) => ({
1719
+ default: mod.ProtectedComponentConfigurationPage
1720
+ }))
1721
+ );
1722
+ const NoPermissions = React.lazy(
1723
+ () => Promise.resolve().then(() => require("./NoPermissionsPage-Oy4tmUrW.js")).then((mod) => ({ default: mod.NoPermissions }))
1724
+ );
1725
+ const NoContentType = React.lazy(
1726
+ () => Promise.resolve().then(() => require("./NoContentTypePage-C5cxKvC2.js")).then((mod) => ({ default: mod.NoContentType }))
1727
+ );
1728
+ const CollectionTypePages = () => {
1729
+ const { collectionType } = reactRouterDom.useParams();
1730
+ if (collectionType !== COLLECTION_TYPES && collectionType !== SINGLE_TYPES) {
1731
+ return /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Navigate, { to: "/404" });
1732
+ }
1733
+ return collectionType === COLLECTION_TYPES ? /* @__PURE__ */ jsxRuntime.jsx(ProtectedListViewPage, {}) : /* @__PURE__ */ jsxRuntime.jsx(ProtectedEditViewPage, {});
1734
+ };
1735
+ const CLONE_RELATIVE_PATH = ":collectionType/:slug/clone/:origin";
1736
+ const CLONE_PATH = `/content-manager/${CLONE_RELATIVE_PATH}`;
1737
+ const LIST_RELATIVE_PATH = ":collectionType/:slug";
1738
+ const LIST_PATH = `/content-manager/${LIST_RELATIVE_PATH}`;
1739
+ const routes = [
1740
+ {
1741
+ path: LIST_RELATIVE_PATH,
1742
+ element: /* @__PURE__ */ jsxRuntime.jsx(CollectionTypePages, {})
1743
+ },
1744
+ {
1745
+ path: ":collectionType/:slug/:id",
1746
+ Component: ProtectedEditViewPage
1747
+ },
1748
+ {
1749
+ path: CLONE_RELATIVE_PATH,
1750
+ Component: ProtectedEditViewPage
1751
+ },
1752
+ {
1753
+ path: ":collectionType/:slug/configurations/list",
1754
+ Component: ProtectedListConfiguration
1755
+ },
1756
+ {
1757
+ path: "components/:slug/configurations/edit",
1758
+ Component: ProtectedComponentConfigurationPage
1759
+ },
1760
+ {
1761
+ path: ":collectionType/:slug/configurations/edit",
1762
+ Component: ProtectedEditConfigurationPage
1763
+ },
1764
+ {
1765
+ path: "403",
1766
+ Component: NoPermissions
1767
+ },
1768
+ {
1769
+ path: "no-content-types",
1770
+ Component: NoContentType
1771
+ },
1772
+ ...routes$1
1773
+ ];
1774
+ const DocumentActions = ({ actions: actions2 }) => {
1775
+ const { formatMessage } = reactIntl.useIntl();
1776
+ const [primaryAction, secondaryAction, ...restActions] = actions2.filter((action) => {
1777
+ if (action.position === void 0) {
1778
+ return true;
1829
1779
  }
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
- },
1780
+ const positions = Array.isArray(action.position) ? action.position : [action.position];
1781
+ return positions.includes("panel");
1782
+ });
1783
+ if (!primaryAction) {
1784
+ return null;
1785
+ }
1786
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 2, alignItems: "stretch", width: "100%", children: [
1787
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
1788
+ /* @__PURE__ */ jsxRuntime.jsx(DocumentActionButton, { ...primaryAction, variant: primaryAction.variant || "default" }),
1789
+ restActions.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
1790
+ DocumentActionsMenu,
1867
1791
  {
1868
- count: totalDraftRelations
1792
+ actions: restActions,
1793
+ label: formatMessage({
1794
+ id: "content-manager.containers.edit.panels.default.more-actions",
1795
+ defaultMessage: "More document actions"
1796
+ })
1869
1797
  }
1870
- ),
1871
- onConfirm: async () => {
1872
- await performPublish();
1798
+ ) : null
1799
+ ] }),
1800
+ secondaryAction ? /* @__PURE__ */ jsxRuntime.jsx(
1801
+ DocumentActionButton,
1802
+ {
1803
+ ...secondaryAction,
1804
+ variant: secondaryAction.variant || "secondary"
1873
1805
  }
1874
- } : void 0
1875
- };
1806
+ ) : null
1807
+ ] });
1876
1808
  };
1877
- PublishAction$1.type = "publish";
1878
- const UpdateAction = ({
1879
- activeTab,
1880
- documentId,
1881
- model,
1882
- collectionType
1883
- }) => {
1884
- const navigate = reactRouterDom.useNavigate();
1809
+ const DocumentActionButton = (action) => {
1810
+ const [dialogId, setDialogId] = React__namespace.useState(null);
1885
1811
  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) {
1812
+ const handleClick = (action2) => async (e) => {
1813
+ const { onClick = () => false, dialog, id } = action2;
1814
+ const muteDialog = await onClick(e);
1815
+ if (dialog && !muteDialog) {
1816
+ switch (dialog.type) {
1817
+ case "notification":
1921
1818
  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
- })
1819
+ title: dialog.title,
1820
+ message: dialog.content,
1821
+ type: dialog.status,
1822
+ timeout: dialog.timeout,
1823
+ onClose: dialog.onClose
1927
1824
  });
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);
1825
+ break;
1826
+ case "dialog":
1827
+ case "modal":
1828
+ e.preventDefault();
1829
+ setDialogId(id);
1987
1830
  }
1988
1831
  }
1989
1832
  };
1990
- };
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
1833
+ const handleClose = () => {
1834
+ setDialogId(null);
1835
+ };
1836
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1837
+ /* @__PURE__ */ jsxRuntime.jsx(
1838
+ designSystem.Button,
1839
+ {
1840
+ flex: "auto",
1841
+ startIcon: action.icon,
1842
+ disabled: action.disabled,
1843
+ onClick: handleClick(action),
1844
+ justifyContent: "center",
1845
+ variant: action.variant || "default",
1846
+ paddingTop: "7px",
1847
+ paddingBottom: "7px",
1848
+ children: action.label
1849
+ }
1850
+ ),
1851
+ action.dialog?.type === "dialog" ? /* @__PURE__ */ jsxRuntime.jsx(
1852
+ DocumentActionConfirmDialog,
1853
+ {
1854
+ ...action.dialog,
1855
+ variant: action.dialog?.variant ?? action.variant,
1856
+ isOpen: dialogId === action.id,
1857
+ onClose: handleClose
1858
+ }
1859
+ ) : null,
1860
+ action.dialog?.type === "modal" ? /* @__PURE__ */ jsxRuntime.jsx(
1861
+ DocumentActionModal,
1862
+ {
1863
+ ...action.dialog,
1864
+ onModalClose: handleClose,
1865
+ isOpen: dialogId === action.id
1866
+ }
1867
+ ) : null
1868
+ ] });
1869
+ };
1870
+ const DocumentActionsMenu = ({
1871
+ actions: actions2,
1872
+ children,
1873
+ label,
1874
+ variant = "tertiary"
2002
1875
  }) => {
1876
+ const [isOpen, setIsOpen] = React__namespace.useState(false);
1877
+ const [dialogId, setDialogId] = React__namespace.useState(null);
2003
1878
  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
1879
  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
- );
1880
+ const isDisabled = actions2.every((action) => action.disabled) || actions2.length === 0;
1881
+ const handleClick = (action) => async (e) => {
1882
+ const { onClick = () => false, dialog, id } = action;
1883
+ const muteDialog = await onClick(e);
1884
+ if (dialog && !muteDialog) {
1885
+ switch (dialog.type) {
1886
+ case "notification":
2031
1887
  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"
1888
+ title: dialog.title,
1889
+ message: dialog.content,
1890
+ type: dialog.status,
1891
+ timeout: dialog.timeout,
1892
+ onClose: dialog.onClose
2037
1893
  });
2038
- }
2039
- return;
1894
+ break;
1895
+ case "dialog":
1896
+ case "modal":
1897
+ setDialogId(id);
2040
1898
  }
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?"
1899
+ }
1900
+ };
1901
+ const handleClose = () => {
1902
+ setDialogId(null);
1903
+ setIsOpen(false);
1904
+ };
1905
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Menu.Root, { open: isOpen, onOpenChange: setIsOpen, children: [
1906
+ /* @__PURE__ */ jsxRuntime.jsxs(
1907
+ designSystem.Menu.Trigger,
1908
+ {
1909
+ disabled: isDisabled,
1910
+ size: "S",
1911
+ endIcon: null,
1912
+ paddingTop: "4px",
1913
+ paddingLeft: "7px",
1914
+ paddingRight: "7px",
1915
+ variant,
1916
+ children: [
1917
+ /* @__PURE__ */ jsxRuntime.jsx(Icons.More, { "aria-hidden": true, focusable: false }),
1918
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.VisuallyHidden, { tag: "span", children: label || formatMessage({
1919
+ id: "content-manager.containers.edit.panels.default.more-actions",
1920
+ defaultMessage: "More document actions"
2060
1921
  }) })
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(
1922
+ ]
1923
+ }
1924
+ ),
1925
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Menu.Content, { maxHeight: void 0, popoverPlacement: "bottom-end", children: [
1926
+ actions2.map((action) => {
1927
+ return /* @__PURE__ */ jsxRuntime.jsx(
1928
+ designSystem.Menu.Item,
2099
1929
  {
2100
- collectionType,
2101
- model,
2102
- documentId,
2103
- params
1930
+ disabled: action.disabled,
1931
+ onSelect: handleClick(action),
1932
+ display: "block",
1933
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", gap: 4, children: [
1934
+ /* @__PURE__ */ jsxRuntime.jsxs(
1935
+ designSystem.Flex,
1936
+ {
1937
+ color: !action.disabled ? convertActionVariantToColor(action.variant) : "inherit",
1938
+ gap: 2,
1939
+ tag: "span",
1940
+ children: [
1941
+ /* @__PURE__ */ jsxRuntime.jsx(
1942
+ designSystem.Flex,
1943
+ {
1944
+ tag: "span",
1945
+ color: !action.disabled ? convertActionVariantToIconColor(action.variant) : "inherit",
1946
+ children: action.icon
1947
+ }
1948
+ ),
1949
+ action.label
1950
+ ]
1951
+ }
1952
+ ),
1953
+ action.id.startsWith("HistoryAction") && /* @__PURE__ */ jsxRuntime.jsx(
1954
+ designSystem.Flex,
1955
+ {
1956
+ alignItems: "center",
1957
+ background: "alternative100",
1958
+ borderStyle: "solid",
1959
+ borderColor: "alternative200",
1960
+ borderWidth: "1px",
1961
+ height: 5,
1962
+ paddingLeft: 2,
1963
+ paddingRight: 2,
1964
+ hasRadius: true,
1965
+ color: "alternative600",
1966
+ children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", fontWeight: "bold", lineHeight: 1, children: formatMessage({ id: "global.new", defaultMessage: "New" }) })
1967
+ }
1968
+ )
1969
+ ] })
2104
1970
  },
2105
- !shouldKeepDraft
1971
+ action.id
2106
1972
  );
2107
- }
2108
- } : void 0,
2109
- variant: "danger",
2110
- position: ["panel", "table-row"]
1973
+ }),
1974
+ children
1975
+ ] }),
1976
+ actions2.map((action) => {
1977
+ return /* @__PURE__ */ jsxRuntime.jsxs(React__namespace.Fragment, { children: [
1978
+ action.dialog?.type === "dialog" ? /* @__PURE__ */ jsxRuntime.jsx(
1979
+ DocumentActionConfirmDialog,
1980
+ {
1981
+ ...action.dialog,
1982
+ variant: action.variant,
1983
+ isOpen: dialogId === action.id,
1984
+ onClose: handleClose
1985
+ }
1986
+ ) : null,
1987
+ action.dialog?.type === "modal" ? /* @__PURE__ */ jsxRuntime.jsx(
1988
+ DocumentActionModal,
1989
+ {
1990
+ ...action.dialog,
1991
+ onModalClose: handleClose,
1992
+ isOpen: dialogId === action.id
1993
+ }
1994
+ ) : null
1995
+ ] }, action.id);
1996
+ })
1997
+ ] });
1998
+ };
1999
+ const convertActionVariantToColor = (variant = "secondary") => {
2000
+ switch (variant) {
2001
+ case "danger":
2002
+ return "danger600";
2003
+ case "secondary":
2004
+ return void 0;
2005
+ case "success":
2006
+ return "success600";
2007
+ default:
2008
+ return "primary600";
2009
+ }
2010
+ };
2011
+ const convertActionVariantToIconColor = (variant = "secondary") => {
2012
+ switch (variant) {
2013
+ case "danger":
2014
+ return "danger600";
2015
+ case "secondary":
2016
+ return "neutral500";
2017
+ case "success":
2018
+ return "success600";
2019
+ default:
2020
+ return "primary600";
2021
+ }
2022
+ };
2023
+ const DocumentActionConfirmDialog = ({
2024
+ onClose,
2025
+ onCancel,
2026
+ onConfirm,
2027
+ title,
2028
+ content,
2029
+ isOpen,
2030
+ variant = "secondary"
2031
+ }) => {
2032
+ const { formatMessage } = reactIntl.useIntl();
2033
+ const handleClose = async () => {
2034
+ if (onCancel) {
2035
+ await onCancel();
2036
+ }
2037
+ onClose();
2038
+ };
2039
+ const handleConfirm = async () => {
2040
+ if (onConfirm) {
2041
+ await onConfirm();
2042
+ }
2043
+ onClose();
2111
2044
  };
2045
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Dialog.Content, { children: [
2046
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Header, { children: title }),
2047
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Body, { children: content }),
2048
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Dialog.Footer, { children: [
2049
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Cancel, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", fullWidth: true, children: formatMessage({
2050
+ id: "app.components.Button.cancel",
2051
+ defaultMessage: "Cancel"
2052
+ }) }) }),
2053
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: handleConfirm, variant, fullWidth: true, children: formatMessage({
2054
+ id: "app.components.Button.confirm",
2055
+ defaultMessage: "Confirm"
2056
+ }) })
2057
+ ] })
2058
+ ] }) });
2112
2059
  };
2113
- UnpublishAction$1.type = "unpublish";
2114
- const DiscardAction = ({
2115
- activeTab,
2116
- documentId,
2117
- model,
2118
- collectionType,
2119
- document
2060
+ const DocumentActionModal = ({
2061
+ isOpen,
2062
+ title,
2063
+ onClose,
2064
+ footer: Footer,
2065
+ content: Content,
2066
+ onModalClose
2120
2067
  }) => {
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
- }
2068
+ const handleClose = () => {
2069
+ if (onClose) {
2070
+ onClose();
2160
2071
  }
2072
+ onModalClose();
2161
2073
  };
2074
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Content, { children: [
2075
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Title, { children: title }) }),
2076
+ typeof Content === "function" ? /* @__PURE__ */ jsxRuntime.jsx(Content, { onClose: handleClose }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Body, { children: Content }),
2077
+ typeof Footer === "function" ? /* @__PURE__ */ jsxRuntime.jsx(Footer, { onClose: handleClose }) : Footer
2078
+ ] }) });
2162
2079
  };
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
- );
2080
+ const transformData = (data) => {
2081
+ if (Array.isArray(data)) {
2082
+ return data.map(transformData);
2198
2083
  }
2199
- );
2200
- const getDisplayName = ({
2201
- firstname,
2202
- lastname,
2203
- username,
2204
- email
2205
- } = {}) => {
2206
- if (username) {
2207
- return username;
2208
- }
2209
- if (firstname) {
2210
- return `${firstname} ${lastname ?? ""}`.trim();
2084
+ if (typeof data === "object" && data !== null) {
2085
+ if ("apiData" in data) {
2086
+ return data.apiData;
2087
+ }
2088
+ return mapValues__default.default(transformData)(data);
2211
2089
  }
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) }) });
2090
+ return data;
2218
2091
  };
2219
- const Header = ({ isCreating, status, title: documentTitle = "Untitled" }) => {
2220
- const { formatMessage } = reactIntl.useIntl();
2092
+ const PublishAction$1 = ({
2093
+ activeTab,
2094
+ documentId,
2095
+ model,
2096
+ collectionType,
2097
+ meta,
2098
+ document
2099
+ }) => {
2100
+ const { schema } = useDoc();
2101
+ const navigate = reactRouterDom.useNavigate();
2102
+ const { toggleNotification } = strapiAdmin.useNotification();
2103
+ const { _unstableFormatValidationErrors: formatValidationErrors } = strapiAdmin.useAPIErrorHandler();
2104
+ const isListView = reactRouterDom.useMatch(LIST_PATH) !== null;
2221
2105
  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
2106
  const { formatMessage } = reactIntl.useIntl();
2237
- const isCloning = reactRouterDom.useMatch(CLONE_PATH) !== null;
2107
+ const canPublish = useDocumentRBAC("PublishAction", ({ canPublish: canPublish2 }) => canPublish2);
2108
+ const { publish } = useDocumentActions();
2238
2109
  const [
2239
- {
2240
- query: { status = "draft" }
2110
+ countDraftRelations,
2111
+ { isLoading: isLoadingDraftRelations, isError: isErrorDraftRelations }
2112
+ ] = useLazyGetDraftRelationCountQuery();
2113
+ const [localCountOfDraftRelations, setLocalCountOfDraftRelations] = React__namespace.useState(0);
2114
+ const [serverCountOfDraftRelations, setServerCountOfDraftRelations] = React__namespace.useState(0);
2115
+ const [{ query, rawQuery }] = strapiAdmin.useQueryParams();
2116
+ const params = React__namespace.useMemo(() => buildValidParams(query), [query]);
2117
+ const modified = strapiAdmin.useForm("PublishAction", ({ modified: modified2 }) => modified2);
2118
+ const setSubmitting = strapiAdmin.useForm("PublishAction", ({ setSubmitting: setSubmitting2 }) => setSubmitting2);
2119
+ const isSubmitting = strapiAdmin.useForm("PublishAction", ({ isSubmitting: isSubmitting2 }) => isSubmitting2);
2120
+ const validate = strapiAdmin.useForm("PublishAction", (state) => state.validate);
2121
+ const setErrors = strapiAdmin.useForm("PublishAction", (state) => state.setErrors);
2122
+ const formValues = strapiAdmin.useForm("PublishAction", ({ values }) => values);
2123
+ React__namespace.useEffect(() => {
2124
+ if (isErrorDraftRelations) {
2125
+ toggleNotification({
2126
+ type: "danger",
2127
+ message: formatMessage({
2128
+ id: getTranslation("error.records.fetch-draft-relatons"),
2129
+ defaultMessage: "An error occurred while fetching draft relations on this document."
2130
+ })
2131
+ });
2241
2132
  }
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
- }
2133
+ }, [isErrorDraftRelations, toggleNotification, formatMessage]);
2134
+ React__namespace.useEffect(() => {
2135
+ const localDraftRelations = /* @__PURE__ */ new Set();
2136
+ const extractDraftRelations = (data) => {
2137
+ const relations = data.connect || [];
2138
+ relations.forEach((relation) => {
2139
+ if (relation.status === "draft") {
2140
+ localDraftRelations.add(relation.id);
2264
2141
  }
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
- );
2142
+ });
2143
+ };
2144
+ const traverseAndExtract = (data) => {
2145
+ Object.entries(data).forEach(([key, value]) => {
2146
+ if (key === "connect" && Array.isArray(value)) {
2147
+ extractDraftRelations({ connect: value });
2148
+ } else if (typeof value === "object" && value !== null) {
2149
+ traverseAndExtract(value);
2295
2150
  }
2151
+ });
2152
+ };
2153
+ if (!documentId || modified) {
2154
+ traverseAndExtract(formValues);
2155
+ setLocalCountOfDraftRelations(localDraftRelations.size);
2156
+ }
2157
+ }, [documentId, modified, formValues, setLocalCountOfDraftRelations]);
2158
+ React__namespace.useEffect(() => {
2159
+ if (!document || !document.documentId || isListView) {
2160
+ return;
2161
+ }
2162
+ const fetchDraftRelationsCount = async () => {
2163
+ const { data, error } = await countDraftRelations({
2164
+ collectionType,
2165
+ model,
2166
+ documentId,
2167
+ params
2168
+ });
2169
+ if (error) {
2170
+ throw error;
2296
2171
  }
2297
- )
2298
- ] });
2299
- };
2300
- const Information = ({ activeTab }) => {
2301
- const { formatMessage } = reactIntl.useIntl();
2302
- const { document, meta } = useDoc();
2303
- if (!document || !document.id) {
2172
+ if (data) {
2173
+ setServerCountOfDraftRelations(data.data);
2174
+ }
2175
+ };
2176
+ fetchDraftRelationsCount();
2177
+ }, [isListView, document, documentId, countDraftRelations, collectionType, model, params]);
2178
+ const isDocumentPublished = (document?.[PUBLISHED_AT_ATTRIBUTE_NAME] || meta?.availableStatus.some((doc) => doc[PUBLISHED_AT_ATTRIBUTE_NAME] !== null)) && document?.status !== "modified";
2179
+ if (!schema?.options?.draftAndPublish) {
2304
2180
  return null;
2305
2181
  }
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(
2182
+ const performPublish = async () => {
2183
+ setSubmitting(true);
2184
+ try {
2185
+ const { errors } = await validate(true, {
2186
+ status: "published"
2187
+ });
2188
+ if (errors) {
2189
+ toggleNotification({
2190
+ type: "danger",
2191
+ message: formatMessage({
2192
+ id: "content-manager.validation.error",
2193
+ defaultMessage: "There are validation errors in your document. Please fix them before saving."
2194
+ })
2195
+ });
2196
+ return;
2197
+ }
2198
+ const res = await publish(
2318
2199
  {
2319
- id: "content-manager.containers.edit.information.last-published.value",
2320
- defaultMessage: `Published {time}{isAnonymous, select, true {} other { by {author}}}`
2200
+ collectionType,
2201
+ model,
2202
+ documentId,
2203
+ params
2321
2204
  },
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
- )
2205
+ transformData(formValues)
2206
+ );
2207
+ if ("data" in res && collectionType !== SINGLE_TYPES) {
2208
+ navigate({
2209
+ pathname: `../${collectionType}/${model}/${res.data.documentId}`,
2210
+ search: rawQuery
2211
+ });
2212
+ } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
2213
+ setErrors(formatValidationErrors(res.error));
2214
+ }
2215
+ } finally {
2216
+ setSubmitting(false);
2217
+ }
2218
+ };
2219
+ const totalDraftRelations = localCountOfDraftRelations + serverCountOfDraftRelations;
2220
+ const enableDraftRelationsCount = false;
2221
+ const hasDraftRelations = enableDraftRelationsCount;
2222
+ return {
2223
+ /**
2224
+ * Disabled when:
2225
+ * - currently if you're cloning a document we don't support publish & clone at the same time.
2226
+ * - the form is submitting
2227
+ * - the active tab is the published tab
2228
+ * - the document is already published & not modified
2229
+ * - the document is being created & not modified
2230
+ * - the user doesn't have the permission to publish
2231
+ */
2232
+ disabled: isCloning || isSubmitting || isLoadingDraftRelations || activeTab === "published" || !modified && isDocumentPublished || !modified && !document?.documentId || !canPublish,
2233
+ label: formatMessage({
2234
+ id: "app.utils.publish",
2235
+ defaultMessage: "Publish"
2236
+ }),
2237
+ onClick: async () => {
2238
+ await performPublish();
2328
2239
  },
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"
2240
+ dialog: hasDraftRelations ? {
2241
+ type: "dialog",
2242
+ variant: "danger",
2243
+ footer: null,
2244
+ title: formatMessage({
2245
+ id: getTranslation(`popUpwarning.warning.bulk-has-draft-relations.title`),
2246
+ defaultMessage: "Confirmation"
2334
2247
  }),
2335
- value: formatMessage(
2248
+ content: formatMessage(
2336
2249
  {
2337
- id: "content-manager.containers.edit.information.last-draft.value",
2338
- defaultMessage: `Modified {time}{isAnonymous, select, true {} other { by {author}}}`
2250
+ id: getTranslation(`popUpwarning.warning.bulk-has-draft-relations.message`),
2251
+ defaultMessage: "This entry is related to {count, plural, one {# draft entry} other {# draft entries}}. Publishing it could leave broken links in your app."
2339
2252
  },
2340
2253
  {
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
2254
+ count: totalDraftRelations
2349
2255
  }
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
2256
+ ),
2257
+ onConfirm: async () => {
2258
+ await performPublish();
2259
+ }
2260
+ } : void 0
2261
+ };
2262
+ };
2263
+ PublishAction$1.type = "publish";
2264
+ const UpdateAction = ({
2265
+ activeTab,
2266
+ documentId,
2267
+ model,
2268
+ collectionType
2269
+ }) => {
2270
+ const navigate = reactRouterDom.useNavigate();
2271
+ const { toggleNotification } = strapiAdmin.useNotification();
2272
+ const { _unstableFormatValidationErrors: formatValidationErrors } = strapiAdmin.useAPIErrorHandler();
2273
+ const cloneMatch = reactRouterDom.useMatch(CLONE_PATH);
2274
+ const isCloning = cloneMatch !== null;
2275
+ const { formatMessage } = reactIntl.useIntl();
2276
+ const { create, update, clone } = useDocumentActions();
2277
+ const [{ query, rawQuery }] = strapiAdmin.useQueryParams();
2278
+ const params = React__namespace.useMemo(() => buildValidParams(query), [query]);
2279
+ const isSubmitting = strapiAdmin.useForm("UpdateAction", ({ isSubmitting: isSubmitting2 }) => isSubmitting2);
2280
+ const modified = strapiAdmin.useForm("UpdateAction", ({ modified: modified2 }) => modified2);
2281
+ const setSubmitting = strapiAdmin.useForm("UpdateAction", ({ setSubmitting: setSubmitting2 }) => setSubmitting2);
2282
+ const document = strapiAdmin.useForm("UpdateAction", ({ values }) => values);
2283
+ const validate = strapiAdmin.useForm("UpdateAction", (state) => state.validate);
2284
+ const setErrors = strapiAdmin.useForm("UpdateAction", (state) => state.setErrors);
2285
+ const resetForm = strapiAdmin.useForm("PublishAction", ({ resetForm: resetForm2 }) => resetForm2);
2286
+ return {
2287
+ /**
2288
+ * Disabled when:
2289
+ * - the form is submitting
2290
+ * - the document is not modified & we're not cloning (you can save a clone entity straight away)
2291
+ * - the active tab is the published tab
2292
+ */
2293
+ disabled: isSubmitting || !modified && !isCloning || activeTab === "published",
2294
+ label: formatMessage({
2295
+ id: "content-manager.containers.Edit.save",
2296
+ defaultMessage: "Save"
2297
+ }),
2298
+ onClick: async () => {
2299
+ setSubmitting(true);
2300
+ try {
2301
+ const { errors } = await validate(true, {
2302
+ status: "draft"
2303
+ });
2304
+ if (errors) {
2305
+ toggleNotification({
2306
+ type: "danger",
2307
+ message: formatMessage({
2308
+ id: "content-manager.validation.error",
2309
+ defaultMessage: "There are validation errors in your document. Please fix them before saving."
2310
+ })
2311
+ });
2312
+ return;
2372
2313
  }
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))
2314
+ if (isCloning) {
2315
+ const res = await clone(
2316
+ {
2317
+ model,
2318
+ documentId: cloneMatch.params.origin,
2319
+ params
2320
+ },
2321
+ transformData(document)
2322
+ );
2323
+ if ("data" in res) {
2324
+ navigate(
2325
+ {
2326
+ pathname: `../${res.data.documentId}`,
2327
+ search: rawQuery
2328
+ },
2329
+ { relative: "path" }
2330
+ );
2331
+ } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
2332
+ setErrors(formatValidationErrors(res.error));
2333
+ }
2334
+ } else if (documentId || collectionType === SINGLE_TYPES) {
2335
+ const res = await update(
2336
+ {
2337
+ collectionType,
2338
+ model,
2339
+ documentId,
2340
+ params
2341
+ },
2342
+ transformData(document)
2343
+ );
2344
+ if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
2345
+ setErrors(formatValidationErrors(res.error));
2346
+ } else {
2347
+ resetForm();
2348
+ }
2349
+ } else {
2350
+ const res = await create(
2351
+ {
2352
+ model,
2353
+ params
2354
+ },
2355
+ transformData(document)
2356
+ );
2357
+ if ("data" in res && collectionType !== SINGLE_TYPES) {
2358
+ navigate(
2359
+ {
2360
+ pathname: `../${res.data.documentId}`,
2361
+ search: rawQuery
2362
+ },
2363
+ { replace: true, relative: "path" }
2364
+ );
2365
+ } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
2366
+ setErrors(formatValidationErrors(res.error));
2367
+ }
2368
+ }
2369
+ } finally {
2370
+ setSubmitting(false);
2371
+ }
2395
2372
  }
2396
- );
2373
+ };
2397
2374
  };
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
- }) });
2375
+ UpdateAction.type = "update";
2376
+ const UNPUBLISH_DRAFT_OPTIONS = {
2377
+ KEEP: "keep",
2378
+ DISCARD: "discard"
2417
2379
  };
2418
- const ConfigureTheViewAction = ({ collectionType, model }) => {
2419
- const navigate = reactRouterDom.useNavigate();
2380
+ const UnpublishAction$1 = ({
2381
+ activeTab,
2382
+ documentId,
2383
+ model,
2384
+ collectionType,
2385
+ document
2386
+ }) => {
2420
2387
  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"
2388
+ const { schema } = useDoc();
2389
+ const canPublish = useDocumentRBAC("UnpublishAction", ({ canPublish: canPublish2 }) => canPublish2);
2390
+ const { unpublish } = useDocumentActions();
2391
+ const [{ query }] = strapiAdmin.useQueryParams();
2392
+ const params = React__namespace.useMemo(() => buildValidParams(query), [query]);
2393
+ const { toggleNotification } = strapiAdmin.useNotification();
2394
+ const [shouldKeepDraft, setShouldKeepDraft] = React__namespace.useState(true);
2395
+ const isDocumentModified = document?.status === "modified";
2396
+ const handleChange = (value) => {
2397
+ setShouldKeepDraft(value === UNPUBLISH_DRAFT_OPTIONS.KEEP);
2431
2398
  };
2432
- };
2433
- ConfigureTheViewAction.type = "configure-the-view";
2434
- const EditTheModelAction = ({ model }) => {
2435
- const navigate = reactRouterDom.useNavigate();
2436
- const { formatMessage } = reactIntl.useIntl();
2399
+ if (!schema?.options?.draftAndPublish) {
2400
+ return null;
2401
+ }
2437
2402
  return {
2403
+ disabled: !canPublish || activeTab === "published" || document?.status !== "published" && document?.status !== "modified",
2438
2404
  label: formatMessage({
2439
- id: "content-manager.link-to-ctb",
2440
- defaultMessage: "Edit the model"
2405
+ id: "app.utils.unpublish",
2406
+ defaultMessage: "Unpublish"
2441
2407
  }),
2442
- icon: /* @__PURE__ */ jsxRuntime.jsx(Icons.Pencil, {}),
2443
- onClick: () => {
2444
- navigate(`/plugins/content-type-builder/content-types/${model}`);
2408
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Icons.Cross, {}),
2409
+ onClick: async () => {
2410
+ if (!documentId && collectionType !== SINGLE_TYPES || isDocumentModified) {
2411
+ if (!documentId) {
2412
+ console.error(
2413
+ "You're trying to unpublish a document without an id, this is likely a bug with Strapi. Please open an issue."
2414
+ );
2415
+ toggleNotification({
2416
+ message: formatMessage({
2417
+ id: "content-manager.actions.unpublish.error",
2418
+ defaultMessage: "An error occurred while trying to unpublish the document."
2419
+ }),
2420
+ type: "danger"
2421
+ });
2422
+ }
2423
+ return;
2424
+ }
2425
+ await unpublish({
2426
+ collectionType,
2427
+ model,
2428
+ documentId,
2429
+ params
2430
+ });
2445
2431
  },
2446
- position: "header"
2432
+ dialog: isDocumentModified ? {
2433
+ type: "dialog",
2434
+ title: formatMessage({
2435
+ id: "app.components.ConfirmDialog.title",
2436
+ defaultMessage: "Confirmation"
2437
+ }),
2438
+ content: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "flex-start", direction: "column", gap: 6, children: [
2439
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { width: "100%", direction: "column", gap: 2, children: [
2440
+ /* @__PURE__ */ jsxRuntime.jsx(Icons.WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
2441
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
2442
+ id: "content-manager.actions.unpublish.dialog.body",
2443
+ defaultMessage: "Are you sure?"
2444
+ }) })
2445
+ ] }),
2446
+ /* @__PURE__ */ jsxRuntime.jsxs(
2447
+ designSystem.Radio.Group,
2448
+ {
2449
+ defaultValue: UNPUBLISH_DRAFT_OPTIONS.KEEP,
2450
+ name: "discard-options",
2451
+ "aria-label": formatMessage({
2452
+ id: "content-manager.actions.unpublish.dialog.radio-label",
2453
+ defaultMessage: "Choose an option to unpublish the document."
2454
+ }),
2455
+ onValueChange: handleChange,
2456
+ children: [
2457
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Radio.Item, { checked: shouldKeepDraft, value: UNPUBLISH_DRAFT_OPTIONS.KEEP, children: formatMessage({
2458
+ id: "content-manager.actions.unpublish.dialog.option.keep-draft",
2459
+ defaultMessage: "Keep draft"
2460
+ }) }),
2461
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Radio.Item, { checked: !shouldKeepDraft, value: UNPUBLISH_DRAFT_OPTIONS.DISCARD, children: formatMessage({
2462
+ id: "content-manager.actions.unpublish.dialog.option.replace-draft",
2463
+ defaultMessage: "Replace draft"
2464
+ }) })
2465
+ ]
2466
+ }
2467
+ )
2468
+ ] }),
2469
+ onConfirm: async () => {
2470
+ if (!documentId && collectionType !== SINGLE_TYPES) {
2471
+ console.error(
2472
+ "You're trying to unpublish a document without an id, this is likely a bug with Strapi. Please open an issue."
2473
+ );
2474
+ toggleNotification({
2475
+ message: formatMessage({
2476
+ id: "content-manager.actions.unpublish.error",
2477
+ defaultMessage: "An error occurred while trying to unpublish the document."
2478
+ }),
2479
+ type: "danger"
2480
+ });
2481
+ }
2482
+ await unpublish(
2483
+ {
2484
+ collectionType,
2485
+ model,
2486
+ documentId,
2487
+ params
2488
+ },
2489
+ !shouldKeepDraft
2490
+ );
2491
+ }
2492
+ } : void 0,
2493
+ variant: "danger",
2494
+ position: ["panel", "table-row"]
2447
2495
  };
2448
2496
  };
2449
- EditTheModelAction.type = "edit-the-model";
2450
- const DeleteAction$1 = ({ documentId, model, collectionType, document }) => {
2451
- const navigate = reactRouterDom.useNavigate();
2497
+ UnpublishAction$1.type = "unpublish";
2498
+ const DiscardAction = ({
2499
+ activeTab,
2500
+ documentId,
2501
+ model,
2502
+ collectionType,
2503
+ document
2504
+ }) => {
2452
2505
  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);
2506
+ const { schema } = useDoc();
2507
+ const canUpdate = useDocumentRBAC("DiscardAction", ({ canUpdate: canUpdate2 }) => canUpdate2);
2508
+ const { discard } = useDocumentActions();
2509
+ const [{ query }] = strapiAdmin.useQueryParams();
2510
+ const params = React__namespace.useMemo(() => buildValidParams(query), [query]);
2511
+ if (!schema?.options?.draftAndPublish) {
2512
+ return null;
2513
+ }
2458
2514
  return {
2459
- disabled: !canDelete || !document,
2515
+ disabled: !canUpdate || activeTab === "published" || document?.status !== "modified",
2460
2516
  label: formatMessage({
2461
- id: "content-manager.actions.delete.label",
2462
- defaultMessage: "Delete document"
2517
+ id: "content-manager.actions.discard.label",
2518
+ defaultMessage: "Discard changes"
2463
2519
  }),
2464
- icon: /* @__PURE__ */ jsxRuntime.jsx(Icons.Trash, {}),
2520
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Icons.Cross, {}),
2521
+ position: ["panel", "table-row"],
2522
+ variant: "danger",
2465
2523
  dialog: {
2466
2524
  type: "dialog",
2467
2525
  title: formatMessage({
@@ -2471,92 +2529,90 @@ const DeleteAction$1 = ({ documentId, model, collectionType, document }) => {
2471
2529
  content: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 2, children: [
2472
2530
  /* @__PURE__ */ jsxRuntime.jsx(Icons.WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
2473
2531
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
2474
- id: "content-manager.actions.delete.dialog.body",
2532
+ id: "content-manager.actions.discard.dialog.body",
2475
2533
  defaultMessage: "Are you sure?"
2476
2534
  }) })
2477
2535
  ] }),
2478
2536
  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
- }
2537
+ await discard({
2538
+ collectionType,
2539
+ model,
2540
+ documentId,
2541
+ params
2542
+ });
2543
+ }
2544
+ }
2545
+ };
2546
+ };
2547
+ DiscardAction.type = "discard";
2548
+ const DEFAULT_ACTIONS = [PublishAction$1, UpdateAction, UnpublishAction$1, DiscardAction];
2549
+ const intervals = ["years", "months", "days", "hours", "minutes", "seconds"];
2550
+ const RelativeTime = React__namespace.forwardRef(
2551
+ ({ timestamp, customIntervals = [], ...restProps }, forwardedRef) => {
2552
+ const { formatRelativeTime, formatDate, formatTime } = reactIntl.useIntl();
2553
+ const interval = dateFns.intervalToDuration({
2554
+ start: timestamp,
2555
+ end: Date.now()
2556
+ // see https://github.com/date-fns/date-fns/issues/2891 – No idea why it's all partial it returns it every time.
2557
+ });
2558
+ const unit = intervals.find((intervalUnit) => {
2559
+ return interval[intervalUnit] > 0 && Object.keys(interval).includes(intervalUnit);
2560
+ });
2561
+ const relativeTime = dateFns.isPast(timestamp) ? -interval[unit] : interval[unit];
2562
+ const customInterval = customIntervals.find(
2563
+ (custom) => interval[custom.unit] < custom.threshold
2564
+ );
2565
+ const displayText = customInterval ? customInterval.text : formatRelativeTime(relativeTime, unit, { numeric: "auto" });
2566
+ return /* @__PURE__ */ jsxRuntime.jsx(
2567
+ "time",
2568
+ {
2569
+ ref: forwardedRef,
2570
+ dateTime: timestamp.toISOString(),
2571
+ role: "time",
2572
+ title: `${formatDate(timestamp)} ${formatTime(timestamp)}`,
2573
+ ...restProps,
2574
+ children: displayText
2512
2575
  }
2513
- },
2514
- variant: "danger",
2515
- position: ["header", "table-row"]
2516
- };
2576
+ );
2577
+ }
2578
+ );
2579
+ const getDisplayName = ({
2580
+ firstname,
2581
+ lastname,
2582
+ username,
2583
+ email
2584
+ } = {}) => {
2585
+ if (username) {
2586
+ return username;
2587
+ }
2588
+ if (firstname) {
2589
+ return `${firstname} ${lastname ?? ""}`.trim();
2590
+ }
2591
+ return email ?? "";
2517
2592
  };
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
- ) });
2593
+ const capitalise = (str) => str.charAt(0).toUpperCase() + str.slice(1);
2594
+ const DocumentStatus = ({ status = "draft", ...restProps }) => {
2595
+ const statusVariant = status === "draft" ? "secondary" : status === "published" ? "success" : "alternative";
2596
+ 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
2597
  };
2548
- const ActionsPanel = () => {
2598
+ const Header = ({ isCreating, status, title: documentTitle = "Untitled" }) => {
2549
2599
  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
- };
2600
+ const isCloning = reactRouterDom.useMatch(CLONE_PATH) !== null;
2601
+ const title = isCreating ? formatMessage({
2602
+ id: "content-manager.containers.edit.title.new",
2603
+ defaultMessage: "Create an entry"
2604
+ }) : documentTitle;
2605
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "flex-start", paddingTop: 6, paddingBottom: 4, gap: 2, children: [
2606
+ /* @__PURE__ */ jsxRuntime.jsx(strapiAdmin.BackButton, {}),
2607
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { width: "100%", justifyContent: "space-between", gap: "80px", alignItems: "flex-start", children: [
2608
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", tag: "h1", children: title }),
2609
+ /* @__PURE__ */ jsxRuntime.jsx(HeaderToolbar, {})
2610
+ ] }),
2611
+ status ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 1, children: /* @__PURE__ */ jsxRuntime.jsx(DocumentStatus, { status: isCloning ? "draft" : status }) }) : null
2612
+ ] });
2557
2613
  };
2558
- ActionsPanel.type = "actions";
2559
- const ActionsPanelContent = () => {
2614
+ const HeaderToolbar = () => {
2615
+ const { formatMessage } = reactIntl.useIntl();
2560
2616
  const isCloning = reactRouterDom.useMatch(CLONE_PATH) !== null;
2561
2617
  const [
2562
2618
  {
@@ -2564,355 +2620,432 @@ const ActionsPanelContent = () => {
2564
2620
  }
2565
2621
  ] = strapiAdmin.useQueryParams();
2566
2622
  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: [
2623
+ const plugins = strapiAdmin.useStrapiApp("HeaderToolbar", (state) => state.plugins);
2624
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
2577
2625
  /* @__PURE__ */ jsxRuntime.jsx(
2578
2626
  strapiAdmin.DescriptionComponentRenderer,
2579
2627
  {
2580
- props,
2581
- descriptions: plugins["content-manager"].apis.getDocumentActions(),
2582
- children: (actions2) => /* @__PURE__ */ jsxRuntime.jsx(DocumentActions, { actions: actions2 })
2628
+ props: {
2629
+ activeTab: status,
2630
+ model,
2631
+ documentId: id,
2632
+ document: isCloning ? void 0 : document,
2633
+ meta: isCloning ? void 0 : meta,
2634
+ collectionType
2635
+ },
2636
+ descriptions: plugins["content-manager"].apis.getHeaderActions(),
2637
+ children: (actions2) => {
2638
+ if (actions2.length > 0) {
2639
+ return /* @__PURE__ */ jsxRuntime.jsx(HeaderActions, { actions: actions2 });
2640
+ } else {
2641
+ return null;
2642
+ }
2643
+ }
2583
2644
  }
2584
2645
  ),
2585
- /* @__PURE__ */ jsxRuntime.jsx(InjectionZone, { area: "editView.right-links", slug: model })
2646
+ /* @__PURE__ */ jsxRuntime.jsx(
2647
+ strapiAdmin.DescriptionComponentRenderer,
2648
+ {
2649
+ props: {
2650
+ activeTab: status,
2651
+ model,
2652
+ documentId: id,
2653
+ document: isCloning ? void 0 : document,
2654
+ meta: isCloning ? void 0 : meta,
2655
+ collectionType
2656
+ },
2657
+ descriptions: plugins["content-manager"].apis.getDocumentActions(),
2658
+ children: (actions2) => {
2659
+ const headerActions = actions2.filter((action) => {
2660
+ const positions = Array.isArray(action.position) ? action.position : [action.position];
2661
+ return positions.includes("header");
2662
+ });
2663
+ return /* @__PURE__ */ jsxRuntime.jsx(
2664
+ DocumentActionsMenu,
2665
+ {
2666
+ actions: headerActions,
2667
+ label: formatMessage({
2668
+ id: "content-manager.containers.edit.header.more-actions",
2669
+ defaultMessage: "More actions"
2670
+ }),
2671
+ children: /* @__PURE__ */ jsxRuntime.jsx(Information, { activeTab: status })
2672
+ }
2673
+ );
2674
+ }
2675
+ }
2676
+ )
2586
2677
  ] });
2587
2678
  };
2588
- const Panel = React__namespace.forwardRef(({ children, title }, ref) => {
2589
- return /* @__PURE__ */ jsxRuntime.jsxs(
2590
- designSystem.Flex,
2679
+ const Information = ({ activeTab }) => {
2680
+ const { formatMessage } = reactIntl.useIntl();
2681
+ const { document, meta } = useDoc();
2682
+ if (!document || !document.id) {
2683
+ return null;
2684
+ }
2685
+ const createAndUpdateDocument = activeTab === "draft" ? document : meta?.availableStatus.find((status) => status.publishedAt === null);
2686
+ const publishDocument = activeTab === "published" ? document : meta?.availableStatus.find((status) => status.publishedAt !== null);
2687
+ const creator = createAndUpdateDocument?.[CREATED_BY_ATTRIBUTE_NAME] ? getDisplayName(createAndUpdateDocument[CREATED_BY_ATTRIBUTE_NAME]) : null;
2688
+ const updator = createAndUpdateDocument?.[UPDATED_BY_ATTRIBUTE_NAME] ? getDisplayName(createAndUpdateDocument[UPDATED_BY_ATTRIBUTE_NAME]) : null;
2689
+ const information = [
2591
2690
  {
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"
2691
+ isDisplayed: !!publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME],
2692
+ label: formatMessage({
2693
+ id: "content-manager.containers.edit.information.last-published.label",
2694
+ defaultMessage: "Published"
2646
2695
  }),
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
2696
+ value: formatMessage(
2697
+ {
2698
+ id: "content-manager.containers.edit.information.last-published.value",
2699
+ defaultMessage: `{time}{isAnonymous, select, true {} other { by {author}}}`
2700
+ },
2701
+ {
2702
+ time: /* @__PURE__ */ jsxRuntime.jsx(RelativeTime, { timestamp: new Date(publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME]) }),
2703
+ isAnonymous: !publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME],
2704
+ author: publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME] ? getDisplayName(publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME]) : null
2705
+ }
2706
+ )
2707
+ },
2708
+ {
2709
+ isDisplayed: !!createAndUpdateDocument?.[UPDATED_AT_ATTRIBUTE_NAME],
2710
+ label: formatMessage({
2711
+ id: "content-manager.containers.edit.information.last-draft.label",
2712
+ defaultMessage: "Updated"
2713
+ }),
2714
+ value: formatMessage(
2715
+ {
2716
+ id: "content-manager.containers.edit.information.last-draft.value",
2717
+ defaultMessage: `{time}{isAnonymous, select, true {} other { by {author}}}`
2718
+ },
2719
+ {
2720
+ time: /* @__PURE__ */ jsxRuntime.jsx(
2721
+ RelativeTime,
2722
+ {
2723
+ timestamp: new Date(createAndUpdateDocument?.[UPDATED_AT_ATTRIBUTE_NAME])
2724
+ }
2725
+ ),
2726
+ isAnonymous: !updator,
2727
+ author: updator
2728
+ }
2729
+ )
2730
+ },
2731
+ {
2732
+ isDisplayed: !!createAndUpdateDocument?.[CREATED_AT_ATTRIBUTE_NAME],
2733
+ label: formatMessage({
2734
+ id: "content-manager.containers.edit.information.document.label",
2735
+ defaultMessage: "Created"
2663
2736
  }),
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
2737
+ value: formatMessage(
2738
+ {
2739
+ id: "content-manager.containers.edit.information.document.value",
2740
+ defaultMessage: `{time}{isAnonymous, select, true {} other { by {author}}}`
2741
+ },
2742
+ {
2743
+ time: /* @__PURE__ */ jsxRuntime.jsx(
2744
+ RelativeTime,
2745
+ {
2746
+ timestamp: new Date(createAndUpdateDocument?.[CREATED_AT_ATTRIBUTE_NAME])
2747
+ }
2748
+ ),
2749
+ isAnonymous: !creator,
2750
+ author: creator
2751
+ }
2752
+ )
2753
+ }
2754
+ ].filter((info) => info.isDisplayed);
2755
+ return /* @__PURE__ */ jsxRuntime.jsx(
2756
+ designSystem.Flex,
2757
+ {
2758
+ borderWidth: "1px 0 0 0",
2759
+ borderStyle: "solid",
2760
+ borderColor: "neutral150",
2761
+ direction: "column",
2762
+ marginTop: 2,
2763
+ tag: "dl",
2764
+ padding: 5,
2765
+ gap: 3,
2766
+ alignItems: "flex-start",
2767
+ marginLeft: "-0.4rem",
2768
+ marginRight: "-0.4rem",
2769
+ width: "calc(100% + 8px)",
2770
+ children: information.map((info) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, direction: "column", alignItems: "flex-start", children: [
2771
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "dt", variant: "pi", fontWeight: "bold", children: info.label }),
2772
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "dd", variant: "pi", textColor: "neutral600", children: info.value })
2773
+ ] }, info.label))
2774
+ }
2693
2775
  );
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
2776
  };
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
- });
2777
+ const HeaderActions = ({ actions: actions2 }) => {
2778
+ const [dialogId, setDialogId] = React__namespace.useState(null);
2779
+ const handleClick = (action) => async (e) => {
2780
+ if (!("options" in action)) {
2781
+ const { onClick = () => false, dialog, id } = action;
2782
+ const muteDialog = await onClick(e);
2783
+ if (dialog && !muteDialog) {
2784
+ e.preventDefault();
2785
+ setDialogId(id);
2786
+ }
2729
2787
  }
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
2788
  };
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;
2789
+ const handleClose = () => {
2790
+ setDialogId(null);
2791
+ };
2792
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 1, children: actions2.map((action) => {
2793
+ if (action.options) {
2794
+ return /* @__PURE__ */ jsxRuntime.jsx(
2795
+ designSystem.SingleSelect,
2796
+ {
2797
+ size: "S",
2798
+ onChange: action.onSelect,
2799
+ "aria-label": action.label,
2800
+ ...action,
2801
+ children: action.options.map(({ label, ...option }) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { ...option, children: label }, option.value))
2802
+ },
2803
+ action.id
2804
+ );
2783
2805
  } else {
2784
- if (!panels[currentPanelIndex]) {
2785
- panels.push([]);
2806
+ if (action.type === "icon") {
2807
+ return /* @__PURE__ */ jsxRuntime.jsxs(React__namespace.Fragment, { children: [
2808
+ /* @__PURE__ */ jsxRuntime.jsx(
2809
+ designSystem.IconButton,
2810
+ {
2811
+ disabled: action.disabled,
2812
+ label: action.label,
2813
+ size: "S",
2814
+ onClick: handleClick(action),
2815
+ children: action.icon
2816
+ }
2817
+ ),
2818
+ action.dialog ? /* @__PURE__ */ jsxRuntime.jsx(
2819
+ HeaderActionDialog,
2820
+ {
2821
+ ...action.dialog,
2822
+ isOpen: dialogId === action.id,
2823
+ onClose: handleClose
2824
+ }
2825
+ ) : null
2826
+ ] }, action.id);
2786
2827
  }
2787
- panels[currentPanelIndex].push(row);
2788
2828
  }
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;
2829
+ }) });
2830
+ };
2831
+ const HeaderActionDialog = ({
2832
+ onClose,
2833
+ onCancel,
2834
+ title,
2835
+ content: Content,
2836
+ isOpen
2837
+ }) => {
2838
+ const handleClose = async () => {
2839
+ if (onCancel) {
2840
+ await onCancel();
2841
+ }
2842
+ onClose();
2843
+ };
2844
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Dialog.Content, { children: [
2845
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Header, { children: title }),
2846
+ typeof Content === "function" ? /* @__PURE__ */ jsxRuntime.jsx(Content, { onClose: handleClose }) : Content
2847
+ ] }) });
2848
+ };
2849
+ const ConfigureTheViewAction = ({ collectionType, model }) => {
2850
+ const navigate = reactRouterDom.useNavigate();
2851
+ const { formatMessage } = reactIntl.useIntl();
2852
+ return {
2853
+ label: formatMessage({
2854
+ id: "app.links.configure-view",
2855
+ defaultMessage: "Configure the view"
2856
+ }),
2857
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Icons.ListPlus, {}),
2858
+ onClick: () => {
2859
+ navigate(`../${collectionType}/${model}/configurations/edit`);
2806
2860
  },
2807
- {}
2808
- );
2809
- const editMetadatas = Object.entries(data.contentType.metadatas).reduce(
2810
- (acc, [attribute, metadata]) => {
2811
- return {
2812
- ...acc,
2813
- [attribute]: metadata.edit
2814
- };
2861
+ position: "header"
2862
+ };
2863
+ };
2864
+ ConfigureTheViewAction.type = "configure-the-view";
2865
+ const EditTheModelAction = ({ model }) => {
2866
+ const navigate = reactRouterDom.useNavigate();
2867
+ const { formatMessage } = reactIntl.useIntl();
2868
+ return {
2869
+ label: formatMessage({
2870
+ id: "content-manager.link-to-ctb",
2871
+ defaultMessage: "Edit the model"
2872
+ }),
2873
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Icons.Pencil, {}),
2874
+ onClick: () => {
2875
+ navigate(`/plugins/content-type-builder/content-types/${model}`);
2815
2876
  },
2816
- {}
2817
- );
2877
+ position: "header"
2878
+ };
2879
+ };
2880
+ EditTheModelAction.type = "edit-the-model";
2881
+ const DeleteAction$1 = ({ documentId, model, collectionType, document }) => {
2882
+ const navigate = reactRouterDom.useNavigate();
2883
+ const { formatMessage } = reactIntl.useIntl();
2884
+ const listViewPathMatch = reactRouterDom.useMatch(LIST_PATH);
2885
+ const canDelete = useDocumentRBAC("DeleteAction", (state) => state.canDelete);
2886
+ const { delete: deleteAction } = useDocumentActions();
2887
+ const { toggleNotification } = strapiAdmin.useNotification();
2888
+ const setSubmitting = strapiAdmin.useForm("DeleteAction", (state) => state.setSubmitting);
2889
+ const isLocalized = document?.locale != null;
2818
2890
  return {
2819
- layout: panelledEditAttributes,
2820
- components: componentEditAttributes,
2821
- metadatas: editMetadatas,
2822
- settings: {
2823
- ...data.contentType.settings,
2824
- displayName: schema?.info.displayName
2891
+ disabled: !canDelete || !document,
2892
+ label: formatMessage(
2893
+ {
2894
+ id: "content-manager.actions.delete.label",
2895
+ defaultMessage: "Delete entry{isLocalized, select, true { (all locales)} other {}}"
2896
+ },
2897
+ { isLocalized }
2898
+ ),
2899
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Icons.Trash, {}),
2900
+ dialog: {
2901
+ type: "dialog",
2902
+ title: formatMessage({
2903
+ id: "app.components.ConfirmDialog.title",
2904
+ defaultMessage: "Confirmation"
2905
+ }),
2906
+ content: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 2, children: [
2907
+ /* @__PURE__ */ jsxRuntime.jsx(Icons.WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
2908
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
2909
+ id: "content-manager.actions.delete.dialog.body",
2910
+ defaultMessage: "Are you sure?"
2911
+ }) })
2912
+ ] }),
2913
+ onConfirm: async () => {
2914
+ if (!listViewPathMatch) {
2915
+ setSubmitting(true);
2916
+ }
2917
+ try {
2918
+ if (!documentId && collectionType !== SINGLE_TYPES) {
2919
+ console.error(
2920
+ "You're trying to delete a document without an id, this is likely a bug with Strapi. Please open an issue."
2921
+ );
2922
+ toggleNotification({
2923
+ message: formatMessage({
2924
+ id: "content-manager.actions.delete.error",
2925
+ defaultMessage: "An error occurred while trying to delete the document."
2926
+ }),
2927
+ type: "danger"
2928
+ });
2929
+ return;
2930
+ }
2931
+ const res = await deleteAction({
2932
+ documentId,
2933
+ model,
2934
+ collectionType,
2935
+ params: {
2936
+ locale: "*"
2937
+ }
2938
+ });
2939
+ if (!("error" in res)) {
2940
+ navigate({ pathname: `../${collectionType}/${model}` }, { replace: true });
2941
+ }
2942
+ } finally {
2943
+ if (!listViewPathMatch) {
2944
+ setSubmitting(false);
2945
+ }
2946
+ }
2947
+ }
2825
2948
  },
2826
- options: {
2827
- ...schema?.options,
2828
- ...schema?.pluginOptions,
2829
- ...data.contentType.options
2830
- }
2949
+ variant: "danger",
2950
+ position: ["header", "table-row"]
2831
2951
  };
2832
2952
  };
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
- );
2953
+ DeleteAction$1.type = "delete";
2954
+ const DEFAULT_HEADER_ACTIONS = [EditTheModelAction, ConfigureTheViewAction, DeleteAction$1];
2955
+ const Panels = () => {
2956
+ const isCloning = reactRouterDom.useMatch(CLONE_PATH) !== null;
2957
+ const [
2958
+ {
2959
+ query: { status }
2960
+ }
2961
+ ] = strapiAdmin.useQueryParams({
2962
+ status: "draft"
2963
+ });
2964
+ const { model, id, document, meta, collectionType } = useDoc();
2965
+ const plugins = strapiAdmin.useStrapiApp("Panels", (state) => state.plugins);
2966
+ const props = {
2967
+ activeTab: status,
2968
+ model,
2969
+ documentId: id,
2970
+ document: isCloning ? void 0 : document,
2971
+ meta: isCloning ? void 0 : meta,
2972
+ collectionType
2973
+ };
2974
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 2, children: /* @__PURE__ */ jsxRuntime.jsx(
2975
+ strapiAdmin.DescriptionComponentRenderer,
2976
+ {
2977
+ props,
2978
+ descriptions: plugins["content-manager"].apis.getEditViewSidePanels(),
2979
+ children: (panels) => panels.map(({ content, id: id2, ...description }) => /* @__PURE__ */ jsxRuntime.jsx(Panel, { ...description, children: content }, id2))
2980
+ }
2981
+ ) });
2862
2982
  };
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
- );
2983
+ const ActionsPanel = () => {
2984
+ const { formatMessage } = reactIntl.useIntl();
2884
2985
  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
- }
2986
+ title: formatMessage({
2987
+ id: "content-manager.containers.edit.panels.default.title",
2988
+ defaultMessage: "Entry"
2989
+ }),
2990
+ content: /* @__PURE__ */ jsxRuntime.jsx(ActionsPanelContent, {})
2893
2991
  };
2894
2992
  };
2895
- const convertListLayoutToFieldLayouts = (columns, attributes = {}, metadatas, components, schemas = []) => {
2896
- return columns.map((name) => {
2897
- const attribute = attributes[name];
2898
- if (!attribute) {
2899
- return null;
2993
+ ActionsPanel.type = "actions";
2994
+ const ActionsPanelContent = () => {
2995
+ const isCloning = reactRouterDom.useMatch(CLONE_PATH) !== null;
2996
+ const [
2997
+ {
2998
+ query: { status = "draft" }
2900
2999
  }
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);
3000
+ ] = strapiAdmin.useQueryParams();
3001
+ const { model, id, document, meta, collectionType } = useDoc();
3002
+ const plugins = strapiAdmin.useStrapiApp("ActionsPanel", (state) => state.plugins);
3003
+ const props = {
3004
+ activeTab: status,
3005
+ model,
3006
+ documentId: id,
3007
+ document: isCloning ? void 0 : document,
3008
+ meta: isCloning ? void 0 : meta,
3009
+ collectionType
3010
+ };
3011
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 2, width: "100%", children: [
3012
+ /* @__PURE__ */ jsxRuntime.jsx(
3013
+ strapiAdmin.DescriptionComponentRenderer,
3014
+ {
3015
+ props,
3016
+ descriptions: plugins["content-manager"].apis.getDocumentActions(),
3017
+ children: (actions2) => /* @__PURE__ */ jsxRuntime.jsx(DocumentActions, { actions: actions2 })
3018
+ }
3019
+ ),
3020
+ /* @__PURE__ */ jsxRuntime.jsx(InjectionZone, { area: "editView.right-links", slug: model })
3021
+ ] });
2915
3022
  };
3023
+ const Panel = React__namespace.forwardRef(({ children, title }, ref) => {
3024
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3025
+ designSystem.Flex,
3026
+ {
3027
+ ref,
3028
+ tag: "aside",
3029
+ "aria-labelledby": "additional-information",
3030
+ background: "neutral0",
3031
+ borderColor: "neutral150",
3032
+ hasRadius: true,
3033
+ paddingBottom: 4,
3034
+ paddingLeft: 4,
3035
+ paddingRight: 4,
3036
+ paddingTop: 4,
3037
+ shadow: "tableShadow",
3038
+ gap: 3,
3039
+ direction: "column",
3040
+ justifyContent: "stretch",
3041
+ alignItems: "flex-start",
3042
+ children: [
3043
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "h2", variant: "sigma", textTransform: "uppercase", textColor: "neutral600", children: title }),
3044
+ children
3045
+ ]
3046
+ }
3047
+ );
3048
+ });
2916
3049
  const ConfirmBulkActionDialog = ({
2917
3050
  onToggleDialog,
2918
3051
  isOpen = false,
@@ -2951,6 +3084,7 @@ const ConfirmDialogPublishAll = ({
2951
3084
  const { _unstableFormatAPIError: formatAPIError } = strapiAdmin.useAPIErrorHandler(getTranslation);
2952
3085
  const { model, schema } = useDoc();
2953
3086
  const [{ query }] = strapiAdmin.useQueryParams();
3087
+ const enableDraftRelationsCount = false;
2954
3088
  const {
2955
3089
  data: countDraftRelations = 0,
2956
3090
  isLoading,
@@ -2962,7 +3096,7 @@ const ConfirmDialogPublishAll = ({
2962
3096
  locale: query?.plugins?.i18n?.locale
2963
3097
  },
2964
3098
  {
2965
- skip: selectedEntries.length === 0
3099
+ skip: !enableDraftRelationsCount
2966
3100
  }
2967
3101
  );
2968
3102
  React__namespace.useEffect(() => {
@@ -3147,7 +3281,7 @@ const SelectedEntriesTableContent = ({
3147
3281
  status: row.status
3148
3282
  }
3149
3283
  ) }),
3150
- /* @__PURE__ */ jsxRuntime.jsx(strapiAdmin.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(
3284
+ /* @__PURE__ */ jsxRuntime.jsx(strapiAdmin.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { children: /* @__PURE__ */ jsxRuntime.jsx(
3151
3285
  designSystem.IconButton,
3152
3286
  {
3153
3287
  tag: reactRouterDom.Link,
@@ -3170,9 +3304,10 @@ const SelectedEntriesTableContent = ({
3170
3304
  ),
3171
3305
  target: "_blank",
3172
3306
  marginLeft: "auto",
3173
- children: /* @__PURE__ */ jsxRuntime.jsx(Icons.Pencil, {})
3307
+ variant: "ghost",
3308
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icons.Pencil, { width: "1.6rem", height: "1.6rem" })
3174
3309
  }
3175
- ) })
3310
+ ) }) })
3176
3311
  ] }, row.id)) })
3177
3312
  ] });
3178
3313
  };
@@ -3209,7 +3344,13 @@ const SelectedEntriesModalContent = ({
3209
3344
  );
3210
3345
  const { rows, validationErrors } = React__namespace.useMemo(() => {
3211
3346
  if (data.length > 0 && schema) {
3212
- const validate = createYupSchema(schema.attributes, components);
3347
+ const validate = createYupSchema(
3348
+ schema.attributes,
3349
+ components,
3350
+ // Since this is the "Publish" action, the validation
3351
+ // schema must enforce the rules for published entities
3352
+ { status: "published" }
3353
+ );
3213
3354
  const validationErrors2 = {};
3214
3355
  const rows2 = data.map((entry) => {
3215
3356
  try {
@@ -3559,7 +3700,7 @@ const TableActions = ({ document }) => {
3559
3700
  strapiAdmin.DescriptionComponentRenderer,
3560
3701
  {
3561
3702
  props,
3562
- descriptions: plugins["content-manager"].apis.getDocumentActions(),
3703
+ descriptions: plugins["content-manager"].apis.getDocumentActions().filter((action) => action.name !== "PublishAction"),
3563
3704
  children: (actions2) => {
3564
3705
  const tableRowActions = actions2.filter((action) => {
3565
3706
  const positions = Array.isArray(action.position) ? action.position : [action.position];
@@ -3670,7 +3811,7 @@ const CloneAction = ({ model, documentId }) => {
3670
3811
  }),
3671
3812
  content: /* @__PURE__ */ jsxRuntime.jsx(AutoCloneFailureModalBody, { prohibitedFields }),
3672
3813
  footer: ({ onClose }) => {
3673
- return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", children: [
3814
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Footer, { children: [
3674
3815
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: onClose, variant: "tertiary", children: formatMessage({
3675
3816
  id: "cancel",
3676
3817
  defaultMessage: "Cancel"
@@ -3882,6 +4023,15 @@ const { setInitialData } = actions;
3882
4023
  const reducer = toolkit.combineReducers({
3883
4024
  app: reducer$1
3884
4025
  });
4026
+ const FEATURE_ID = "preview";
4027
+ const previewAdmin = {
4028
+ bootstrap(app) {
4029
+ if (!window.strapi.future.isEnabled(FEATURE_ID)) {
4030
+ return {};
4031
+ }
4032
+ console.log("Bootstrapping preview admin");
4033
+ }
4034
+ };
3885
4035
  const index = {
3886
4036
  register(app) {
3887
4037
  const cm = new ContentManagerPlugin();
@@ -3901,7 +4051,7 @@ const index = {
3901
4051
  app.router.addRoute({
3902
4052
  path: "content-manager/*",
3903
4053
  lazy: async () => {
3904
- const { Layout } = await Promise.resolve().then(() => require("./layout-C6dxWYT7.js"));
4054
+ const { Layout } = await Promise.resolve().then(() => require("./layout-lMc9i1-Z.js"));
3905
4055
  return {
3906
4056
  Component: Layout
3907
4057
  };
@@ -3914,11 +4064,14 @@ const index = {
3914
4064
  if (typeof historyAdmin.bootstrap === "function") {
3915
4065
  historyAdmin.bootstrap(app);
3916
4066
  }
4067
+ if (typeof previewAdmin.bootstrap === "function") {
4068
+ previewAdmin.bootstrap(app);
4069
+ }
3917
4070
  },
3918
4071
  async registerTrads({ locales }) {
3919
4072
  const importedTrads = await Promise.all(
3920
4073
  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 }) => {
4074
+ 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
4075
  return {
3923
4076
  data: prefixPluginTranslations(data, PLUGIN_ID),
3924
4077
  locale
@@ -3936,6 +4089,7 @@ const index = {
3936
4089
  };
3937
4090
  exports.ATTRIBUTE_TYPES_THAT_CANNOT_BE_MAIN_FIELD = ATTRIBUTE_TYPES_THAT_CANNOT_BE_MAIN_FIELD;
3938
4091
  exports.BulkActionsRenderer = BulkActionsRenderer;
4092
+ exports.CLONE_PATH = CLONE_PATH;
3939
4093
  exports.COLLECTION_TYPES = COLLECTION_TYPES;
3940
4094
  exports.CREATOR_FIELDS = CREATOR_FIELDS;
3941
4095
  exports.DEFAULT_SETTINGS = DEFAULT_SETTINGS;
@@ -3963,6 +4117,7 @@ exports.getMainField = getMainField;
3963
4117
  exports.getTranslation = getTranslation;
3964
4118
  exports.index = index;
3965
4119
  exports.setInitialData = setInitialData;
4120
+ exports.useContentManagerContext = useContentManagerContext;
3966
4121
  exports.useContentTypeSchema = useContentTypeSchema;
3967
4122
  exports.useDoc = useDoc;
3968
4123
  exports.useDocLayout = useDocLayout;
@@ -3975,4 +4130,4 @@ exports.useGetAllDocumentsQuery = useGetAllDocumentsQuery;
3975
4130
  exports.useGetContentTypeConfigurationQuery = useGetContentTypeConfigurationQuery;
3976
4131
  exports.useGetInitialDataQuery = useGetInitialDataQuery;
3977
4132
  exports.useUpdateContentTypeConfigurationMutation = useUpdateContentTypeConfigurationMutation;
3978
- //# sourceMappingURL=index-CdT0kHZ8.js.map
4133
+ //# sourceMappingURL=index-BIWDoFLK.js.map