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

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 (113) hide show
  1. package/dist/_chunks/{ComponentConfigurationPage-B3yDbeU1.mjs → ComponentConfigurationPage-BVM7LScS.mjs} +3 -3
  2. package/dist/_chunks/{ComponentConfigurationPage-B3yDbeU1.mjs.map → ComponentConfigurationPage-BVM7LScS.mjs.map} +1 -1
  3. package/dist/_chunks/{ComponentConfigurationPage-KXSuLnQD.js → ComponentConfigurationPage-DNRPa10q.js} +3 -3
  4. package/dist/_chunks/{ComponentConfigurationPage-KXSuLnQD.js.map → ComponentConfigurationPage-DNRPa10q.js.map} +1 -1
  5. package/dist/_chunks/{EditConfigurationPage-D7PrLO8j.mjs → EditConfigurationPage-BWq-9Zhk.mjs} +3 -3
  6. package/dist/_chunks/{EditConfigurationPage-D7PrLO8j.mjs.map → EditConfigurationPage-BWq-9Zhk.mjs.map} +1 -1
  7. package/dist/_chunks/{EditConfigurationPage-BQ17--5R.js → EditConfigurationPage-BrG0mEfF.js} +3 -3
  8. package/dist/_chunks/{EditConfigurationPage-BQ17--5R.js.map → EditConfigurationPage-BrG0mEfF.js.map} +1 -1
  9. package/dist/_chunks/{EditViewPage-BgjdnGz2.js → EditViewPage-BbY0bNNE.js} +15 -5
  10. package/dist/_chunks/EditViewPage-BbY0bNNE.js.map +1 -0
  11. package/dist/_chunks/{EditViewPage-B7VgwJaG.mjs → EditViewPage-Dx0QnTxn.mjs} +15 -5
  12. package/dist/_chunks/EditViewPage-Dx0QnTxn.mjs.map +1 -0
  13. package/dist/_chunks/{Field-CdK7ZLmv.js → Field-CglKum3X.js} +100 -87
  14. package/dist/_chunks/Field-CglKum3X.js.map +1 -0
  15. package/dist/_chunks/{Field-tHCw4lGA.mjs → Field-DVZSGAon.mjs} +97 -84
  16. package/dist/_chunks/Field-DVZSGAon.mjs.map +1 -0
  17. package/dist/_chunks/{Form-C_0KTVvV.js → Form-B7nYNIUm.js} +16 -8
  18. package/dist/_chunks/Form-B7nYNIUm.js.map +1 -0
  19. package/dist/_chunks/{Form-BJxdTv3Q.mjs → Form-BQwxPqu3.mjs} +16 -8
  20. package/dist/_chunks/Form-BQwxPqu3.mjs.map +1 -0
  21. package/dist/_chunks/{History-nuEzM5qm.js → History-3JN4BIS1.js} +23 -10
  22. package/dist/_chunks/History-3JN4BIS1.js.map +1 -0
  23. package/dist/_chunks/{History-DR2txJLE.mjs → History-Bmu6tx6s.mjs} +24 -11
  24. package/dist/_chunks/History-Bmu6tx6s.mjs.map +1 -0
  25. package/dist/_chunks/{ListConfigurationPage-CnB86Psm.js → ListConfigurationPage-BcC28SV0.js} +2 -2
  26. package/dist/_chunks/{ListConfigurationPage-CnB86Psm.js.map → ListConfigurationPage-BcC28SV0.js.map} +1 -1
  27. package/dist/_chunks/{ListConfigurationPage-voFVtXu6.mjs → ListConfigurationPage-DwijcJ17.mjs} +2 -2
  28. package/dist/_chunks/{ListConfigurationPage-voFVtXu6.mjs.map → ListConfigurationPage-DwijcJ17.mjs.map} +1 -1
  29. package/dist/_chunks/{ListViewPage-B_GaWgRH.mjs → ListViewPage-DzhuS3CW.mjs} +40 -36
  30. package/dist/_chunks/ListViewPage-DzhuS3CW.mjs.map +1 -0
  31. package/dist/_chunks/{ListViewPage-SXIXm-RM.js → ListViewPage-ZPNQ6jWC.js} +43 -39
  32. package/dist/_chunks/ListViewPage-ZPNQ6jWC.js.map +1 -0
  33. package/dist/_chunks/{NoContentTypePage-CYiGpsbj.mjs → NoContentTypePage-BKMgOyd3.mjs} +2 -2
  34. package/dist/_chunks/{NoContentTypePage-CYiGpsbj.mjs.map → NoContentTypePage-BKMgOyd3.mjs.map} +1 -1
  35. package/dist/_chunks/{NoContentTypePage-BzsQ3hLZ.js → NoContentTypePage-DzwJNXvn.js} +2 -2
  36. package/dist/_chunks/{NoContentTypePage-BzsQ3hLZ.js.map → NoContentTypePage-DzwJNXvn.js.map} +1 -1
  37. package/dist/_chunks/{NoPermissionsPage-B5baIHal.mjs → NoPermissionsPage-DBbGJqL9.mjs} +2 -2
  38. package/dist/_chunks/{NoPermissionsPage-B5baIHal.mjs.map → NoPermissionsPage-DBbGJqL9.mjs.map} +1 -1
  39. package/dist/_chunks/{NoPermissionsPage-IGkId4C5.js → NoPermissionsPage-GaweP-NG.js} +2 -2
  40. package/dist/_chunks/{NoPermissionsPage-IGkId4C5.js.map → NoPermissionsPage-GaweP-NG.js.map} +1 -1
  41. package/dist/_chunks/{Relations-CIYDdKU-.mjs → Relations-B7VtRA3g.mjs} +33 -23
  42. package/dist/_chunks/Relations-B7VtRA3g.mjs.map +1 -0
  43. package/dist/_chunks/{Relations-Dhuurpx2.js → Relations-BHXq_cKF.js} +33 -23
  44. package/dist/_chunks/Relations-BHXq_cKF.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-DcA8_tJw.js} +2052 -1891
  50. package/dist/_chunks/index-DcA8_tJw.js.map +1 -0
  51. package/dist/_chunks/{index-C9TJPyni.mjs → index-TaRzG09p.mjs} +2072 -1911
  52. package/dist/_chunks/index-TaRzG09p.mjs.map +1 -0
  53. package/dist/_chunks/{layout-C6dxWYT7.js → layout-CrgXpOxT.js} +5 -4
  54. package/dist/_chunks/{layout-C6dxWYT7.js.map → layout-CrgXpOxT.js.map} +1 -1
  55. package/dist/_chunks/{layout-BNqvLR_b.mjs → layout-aX-RJhd5.mjs} +5 -4
  56. package/dist/_chunks/{layout-BNqvLR_b.mjs.map → layout-aX-RJhd5.mjs.map} +1 -1
  57. package/dist/_chunks/{relations-CkKqKw65.mjs → relations-D3Hlx6QX.mjs} +2 -2
  58. package/dist/_chunks/{relations-CkKqKw65.mjs.map → relations-D3Hlx6QX.mjs.map} +1 -1
  59. package/dist/_chunks/{relations-DtFaDnP1.js → relations-Z6RA1sBS.js} +2 -2
  60. package/dist/_chunks/{relations-DtFaDnP1.js.map → relations-Z6RA1sBS.js.map} +1 -1
  61. package/dist/_chunks/{usePrev-B9w_-eYc.js → useDebounce-CtcjDB3L.js} +14 -1
  62. package/dist/_chunks/useDebounce-CtcjDB3L.js.map +1 -0
  63. package/dist/_chunks/useDebounce-DmuSJIF3.mjs +29 -0
  64. package/dist/_chunks/useDebounce-DmuSJIF3.mjs.map +1 -0
  65. package/dist/admin/index.js +2 -1
  66. package/dist/admin/index.js.map +1 -1
  67. package/dist/admin/index.mjs +5 -4
  68. package/dist/admin/src/exports.d.ts +1 -1
  69. package/dist/admin/src/history/services/historyVersion.d.ts +1 -1
  70. package/dist/admin/src/hooks/useDocument.d.ts +32 -1
  71. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/WysiwygStyles.d.ts +0 -32
  72. package/dist/admin/src/pages/EditView/components/Header.d.ts +11 -11
  73. package/dist/admin/src/services/api.d.ts +1 -1
  74. package/dist/admin/src/services/components.d.ts +2 -2
  75. package/dist/admin/src/services/contentTypes.d.ts +3 -3
  76. package/dist/admin/src/services/documents.d.ts +19 -17
  77. package/dist/admin/src/services/init.d.ts +1 -1
  78. package/dist/admin/src/services/relations.d.ts +2 -2
  79. package/dist/admin/src/services/uid.d.ts +3 -3
  80. package/dist/admin/src/utils/validation.d.ts +4 -1
  81. package/dist/server/index.js +77 -39
  82. package/dist/server/index.js.map +1 -1
  83. package/dist/server/index.mjs +77 -39
  84. package/dist/server/index.mjs.map +1 -1
  85. package/dist/server/src/controllers/collection-types.d.ts.map +1 -1
  86. package/dist/server/src/controllers/relations.d.ts.map +1 -1
  87. package/dist/server/src/history/services/history.d.ts.map +1 -1
  88. package/dist/server/src/history/services/lifecycles.d.ts.map +1 -1
  89. package/dist/server/src/history/services/utils.d.ts +1 -0
  90. package/dist/server/src/history/services/utils.d.ts.map +1 -1
  91. package/dist/server/src/policies/hasPermissions.d.ts.map +1 -1
  92. package/dist/server/src/services/permission-checker.d.ts.map +1 -1
  93. package/dist/shared/contracts/collection-types.d.ts +3 -1
  94. package/dist/shared/contracts/collection-types.d.ts.map +1 -1
  95. package/package.json +12 -12
  96. package/dist/_chunks/EditViewPage-B7VgwJaG.mjs.map +0 -1
  97. package/dist/_chunks/EditViewPage-BgjdnGz2.js.map +0 -1
  98. package/dist/_chunks/Field-CdK7ZLmv.js.map +0 -1
  99. package/dist/_chunks/Field-tHCw4lGA.mjs.map +0 -1
  100. package/dist/_chunks/Form-BJxdTv3Q.mjs.map +0 -1
  101. package/dist/_chunks/Form-C_0KTVvV.js.map +0 -1
  102. package/dist/_chunks/History-DR2txJLE.mjs.map +0 -1
  103. package/dist/_chunks/History-nuEzM5qm.js.map +0 -1
  104. package/dist/_chunks/ListViewPage-B_GaWgRH.mjs.map +0 -1
  105. package/dist/_chunks/ListViewPage-SXIXm-RM.js.map +0 -1
  106. package/dist/_chunks/Relations-CIYDdKU-.mjs.map +0 -1
  107. package/dist/_chunks/Relations-Dhuurpx2.js.map +0 -1
  108. package/dist/_chunks/index-C9TJPyni.mjs.map +0 -1
  109. package/dist/_chunks/index-CdT0kHZ8.js.map +0 -1
  110. package/dist/_chunks/usePrev-B9w_-eYc.js.map +0 -1
  111. package/dist/_chunks/usePrev-DH6iah0A.mjs +0 -16
  112. package/dist/_chunks/usePrev-DH6iah0A.mjs.map +0 -1
  113. package/strapi-server.js +0 -3
@@ -6,10 +6,10 @@ const React = require("react");
6
6
  const designSystem = require("@strapi/design-system");
7
7
  const reactIntl = require("react-intl");
8
8
  const reactRouterDom = require("react-router-dom");
9
- const styledComponents = require("styled-components");
10
9
  const yup = require("yup");
11
10
  const pipe = require("lodash/fp/pipe");
12
11
  const dateFns = require("date-fns");
12
+ const styledComponents = require("styled-components");
13
13
  const qs = require("qs");
14
14
  const toolkit = require("@reduxjs/toolkit");
15
15
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
@@ -121,6 +121,7 @@ const DocumentRBAC = ({ children, permissions }) => {
121
121
  if (!slug) {
122
122
  throw new Error("Cannot find the slug param in the URL");
123
123
  }
124
+ const [{ rawQuery }] = strapiAdmin.useQueryParams();
124
125
  const userPermissions = strapiAdmin.useAuth("DocumentRBAC", (state) => state.permissions);
125
126
  const contentTypePermissions = React__namespace.useMemo(() => {
126
127
  const contentTypePermissions2 = userPermissions.filter(
@@ -131,7 +132,14 @@ const DocumentRBAC = ({ children, permissions }) => {
131
132
  return { ...acc, [action]: [permission] };
132
133
  }, {});
133
134
  }, [slug, userPermissions]);
134
- const { isLoading, allowedActions } = strapiAdmin.useRBAC(contentTypePermissions, permissions ?? void 0);
135
+ const { isLoading, allowedActions } = strapiAdmin.useRBAC(
136
+ contentTypePermissions,
137
+ permissions ?? void 0,
138
+ // TODO: useRBAC context should be typed and built differently
139
+ // We are passing raw query as context to the hook so that it can
140
+ // rely on the locale provided from DocumentRBAC for its permission calculations.
141
+ rawQuery
142
+ );
135
143
  const canCreateFields = !isLoading && allowedActions.canCreate ? extractAndDedupeFields(contentTypePermissions.create) : [];
136
144
  const canReadFields = !isLoading && allowedActions.canRead ? extractAndDedupeFields(contentTypePermissions.read) : [];
137
145
  const canUpdateFields = !isLoading && allowedActions.canUpdate ? extractAndDedupeFields(contentTypePermissions.update) : [];
@@ -179,7 +187,8 @@ const contentManagerApi = strapiAdmin.adminApi.enhanceEndpoints({
179
187
  "Document",
180
188
  "InitialData",
181
189
  "HistoryVersion",
182
- "Relations"
190
+ "Relations",
191
+ "UidAvailability"
183
192
  ]
184
193
  });
185
194
  const documentApi = contentManagerApi.injectEndpoints({
@@ -209,7 +218,10 @@ const documentApi = contentManagerApi.injectEndpoints({
209
218
  params
210
219
  }
211
220
  }),
212
- invalidatesTags: (_result, _error, { model }) => [{ type: "Document", id: `${model}_LIST` }]
221
+ invalidatesTags: (_result, _error, { model }) => [
222
+ { type: "Document", id: `${model}_LIST` },
223
+ { type: "UidAvailability", id: model }
224
+ ]
213
225
  }),
214
226
  /**
215
227
  * Creates a new collection-type document. This should ONLY be used for collection-types.
@@ -226,7 +238,8 @@ const documentApi = contentManagerApi.injectEndpoints({
226
238
  }),
227
239
  invalidatesTags: (result, _error, { model }) => [
228
240
  { type: "Document", id: `${model}_LIST` },
229
- "Relations"
241
+ "Relations",
242
+ { type: "UidAvailability", id: model }
230
243
  ]
231
244
  }),
232
245
  deleteDocument: builder.mutation({
@@ -267,7 +280,8 @@ const documentApi = contentManagerApi.injectEndpoints({
267
280
  id: collectionType !== SINGLE_TYPES ? `${model}_${documentId}` : model
268
281
  },
269
282
  { type: "Document", id: `${model}_LIST` },
270
- "Relations"
283
+ "Relations",
284
+ { type: "UidAvailability", id: model }
271
285
  ];
272
286
  }
273
287
  }),
@@ -392,7 +406,8 @@ const documentApi = contentManagerApi.injectEndpoints({
392
406
  type: "Document",
393
407
  id: collectionType !== SINGLE_TYPES ? `${model}_${documentId}` : model
394
408
  },
395
- "Relations"
409
+ "Relations",
410
+ { type: "UidAvailability", id: model }
396
411
  ];
397
412
  },
398
413
  async onQueryStarted({ data, ...patch }, { dispatch, queryFulfilled }) {
@@ -475,20 +490,39 @@ const buildValidParams = (query) => {
475
490
  const isBaseQueryError = (error) => {
476
491
  return error.name !== void 0;
477
492
  };
478
- const createYupSchema = (attributes = {}, components = {}) => {
493
+ const arrayValidator = (attribute, options) => ({
494
+ message: strapiAdmin.translatedErrors.required,
495
+ test(value) {
496
+ if (options.status === "draft") {
497
+ return true;
498
+ }
499
+ if (!attribute.required) {
500
+ return true;
501
+ }
502
+ if (!value) {
503
+ return false;
504
+ }
505
+ if (Array.isArray(value) && value.length === 0) {
506
+ return false;
507
+ }
508
+ return true;
509
+ }
510
+ });
511
+ const createYupSchema = (attributes = {}, components = {}, options = { status: null }) => {
479
512
  const createModelSchema = (attributes2) => yup__namespace.object().shape(
480
513
  Object.entries(attributes2).reduce((acc, [name, attribute]) => {
481
514
  if (DOCUMENT_META_FIELDS.includes(name)) {
482
515
  return acc;
483
516
  }
484
517
  const validations = [
518
+ addNullableValidation,
485
519
  addRequiredValidation,
486
520
  addMinLengthValidation,
487
521
  addMaxLengthValidation,
488
522
  addMinValidation,
489
523
  addMaxValidation,
490
524
  addRegexValidation
491
- ].map((fn) => fn(attribute));
525
+ ].map((fn) => fn(attribute, options));
492
526
  const transformSchema = pipe__default.default(...validations);
493
527
  switch (attribute.type) {
494
528
  case "component": {
@@ -498,12 +532,12 @@ const createYupSchema = (attributes = {}, components = {}) => {
498
532
  ...acc,
499
533
  [name]: transformSchema(
500
534
  yup__namespace.array().of(createModelSchema(attributes3).nullable(false))
501
- )
535
+ ).test(arrayValidator(attribute, options))
502
536
  };
503
537
  } else {
504
538
  return {
505
539
  ...acc,
506
- [name]: transformSchema(createModelSchema(attributes3))
540
+ [name]: transformSchema(createModelSchema(attributes3).nullable())
507
541
  };
508
542
  }
509
543
  }
@@ -525,7 +559,7 @@ const createYupSchema = (attributes = {}, components = {}) => {
525
559
  }
526
560
  )
527
561
  )
528
- )
562
+ ).test(arrayValidator(attribute, options))
529
563
  };
530
564
  case "relation":
531
565
  return {
@@ -537,7 +571,7 @@ const createYupSchema = (attributes = {}, components = {}) => {
537
571
  } else if (Array.isArray(value)) {
538
572
  return yup__namespace.array().of(
539
573
  yup__namespace.object().shape({
540
- id: yup__namespace.string().required()
574
+ id: yup__namespace.number().required()
541
575
  })
542
576
  );
543
577
  } else if (typeof value === "object") {
@@ -615,13 +649,7 @@ const createAttributeSchema = (attribute) => {
615
649
  return yup__namespace.mixed();
616
650
  }
617
651
  };
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
- }
652
+ const nullableSchema = (schema) => {
625
653
  return schema?.nullable ? schema.nullable() : (
626
654
  // In some cases '.nullable' will not be available on the schema.
627
655
  // e.g. when the schema has been built using yup.lazy (e.g. for relations).
@@ -629,7 +657,22 @@ const addRequiredValidation = (attribute) => (schema) => {
629
657
  schema
630
658
  );
631
659
  };
632
- const addMinLengthValidation = (attribute) => (schema) => {
660
+ const addNullableValidation = () => (schema) => {
661
+ return nullableSchema(schema);
662
+ };
663
+ const addRequiredValidation = (attribute, options) => (schema) => {
664
+ if (options.status === "draft" || !attribute.required) {
665
+ return schema;
666
+ }
667
+ if (attribute.required && "required" in schema) {
668
+ return schema.required(strapiAdmin.translatedErrors.required);
669
+ }
670
+ return schema;
671
+ };
672
+ const addMinLengthValidation = (attribute, options) => (schema) => {
673
+ if (options.status === "draft") {
674
+ return schema;
675
+ }
633
676
  if ("minLength" in attribute && attribute.minLength && Number.isInteger(attribute.minLength) && "min" in schema) {
634
677
  return schema.min(attribute.minLength, {
635
678
  ...strapiAdmin.translatedErrors.minLength,
@@ -651,32 +694,13 @@ const addMaxLengthValidation = (attribute) => (schema) => {
651
694
  }
652
695
  return schema;
653
696
  };
654
- const addMinValidation = (attribute) => (schema) => {
655
- if ("min" in attribute) {
697
+ const addMinValidation = (attribute, options) => (schema) => {
698
+ if (options.status === "draft") {
699
+ return schema;
700
+ }
701
+ if ("min" in attribute && "min" in schema) {
656
702
  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) {
703
+ if (min) {
680
704
  return schema.min(min, {
681
705
  ...strapiAdmin.translatedErrors.min,
682
706
  values: {
@@ -794,19 +818,115 @@ const extractContentTypeComponents = (attributes = {}, allComponents = {}) => {
794
818
  }, {});
795
819
  return componentsByKey;
796
820
  };
797
- const useDocument = (args, opts) => {
821
+ const HOOKS = {
822
+ /**
823
+ * Hook that allows to mutate the displayed headers of the list view table
824
+ * @constant
825
+ * @type {string}
826
+ */
827
+ INJECT_COLUMN_IN_TABLE: "Admin/CM/pages/ListView/inject-column-in-table",
828
+ /**
829
+ * Hook that allows to mutate the CM's collection types links pre-set filters
830
+ * @constant
831
+ * @type {string}
832
+ */
833
+ MUTATE_COLLECTION_TYPES_LINKS: "Admin/CM/pages/App/mutate-collection-types-links",
834
+ /**
835
+ * Hook that allows to mutate the CM's edit view layout
836
+ * @constant
837
+ * @type {string}
838
+ */
839
+ MUTATE_EDIT_VIEW_LAYOUT: "Admin/CM/pages/EditView/mutate-edit-view-layout",
840
+ /**
841
+ * Hook that allows to mutate the CM's single types links pre-set filters
842
+ * @constant
843
+ * @type {string}
844
+ */
845
+ MUTATE_SINGLE_TYPES_LINKS: "Admin/CM/pages/App/mutate-single-types-links"
846
+ };
847
+ const contentTypesApi = contentManagerApi.injectEndpoints({
848
+ endpoints: (builder) => ({
849
+ getContentTypeConfiguration: builder.query({
850
+ query: (uid) => ({
851
+ url: `/content-manager/content-types/${uid}/configuration`,
852
+ method: "GET"
853
+ }),
854
+ transformResponse: (response) => response.data,
855
+ providesTags: (_result, _error, uid) => [
856
+ { type: "ContentTypesConfiguration", id: uid },
857
+ { type: "ContentTypeSettings", id: "LIST" }
858
+ ]
859
+ }),
860
+ getAllContentTypeSettings: builder.query({
861
+ query: () => "/content-manager/content-types-settings",
862
+ transformResponse: (response) => response.data,
863
+ providesTags: [{ type: "ContentTypeSettings", id: "LIST" }]
864
+ }),
865
+ updateContentTypeConfiguration: builder.mutation({
866
+ query: ({ uid, ...body }) => ({
867
+ url: `/content-manager/content-types/${uid}/configuration`,
868
+ method: "PUT",
869
+ data: body
870
+ }),
871
+ transformResponse: (response) => response.data,
872
+ invalidatesTags: (_result, _error, { uid }) => [
873
+ { type: "ContentTypesConfiguration", id: uid },
874
+ { type: "ContentTypeSettings", id: "LIST" },
875
+ // Is this necessary?
876
+ { type: "InitialData" }
877
+ ]
878
+ })
879
+ })
880
+ });
881
+ const {
882
+ useGetContentTypeConfigurationQuery,
883
+ useGetAllContentTypeSettingsQuery,
884
+ useUpdateContentTypeConfigurationMutation
885
+ } = contentTypesApi;
886
+ const checkIfAttributeIsDisplayable = (attribute) => {
887
+ const { type } = attribute;
888
+ if (type === "relation") {
889
+ return !attribute.relation.toLowerCase().includes("morph");
890
+ }
891
+ return !["json", "dynamiczone", "richtext", "password", "blocks"].includes(type) && !!type;
892
+ };
893
+ const getMainField = (attribute, mainFieldName, { schemas, components }) => {
894
+ if (!mainFieldName) {
895
+ return void 0;
896
+ }
897
+ const mainFieldType = attribute.type === "component" ? components[attribute.component].attributes[mainFieldName].type : (
898
+ // @ts-expect-error – `targetModel` does exist on the attribute for a relation.
899
+ schemas.find((schema) => schema.uid === attribute.targetModel)?.attributes[mainFieldName].type
900
+ );
901
+ return {
902
+ name: mainFieldName,
903
+ type: mainFieldType ?? "string"
904
+ };
905
+ };
906
+ const DEFAULT_SETTINGS = {
907
+ bulkable: false,
908
+ filterable: false,
909
+ searchable: false,
910
+ pagination: false,
911
+ defaultSortBy: "",
912
+ defaultSortOrder: "asc",
913
+ mainField: "id",
914
+ pageSize: 10
915
+ };
916
+ const useDocumentLayout = (model) => {
917
+ const { schema, components } = useDocument({ model, collectionType: "" }, { skip: true });
918
+ const [{ query }] = strapiAdmin.useQueryParams();
919
+ const runHookWaterfall = strapiAdmin.useStrapiApp("useDocumentLayout", (state) => state.runHookWaterfall);
798
920
  const { toggleNotification } = strapiAdmin.useNotification();
799
921
  const { _unstableFormatAPIError: formatAPIError } = strapiAdmin.useAPIErrorHandler();
922
+ const { isLoading: isLoadingSchemas, schemas } = useContentTypeSchema();
800
923
  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);
924
+ data,
925
+ isLoading: isLoadingConfigs,
926
+ error,
927
+ isFetching: isFetchingConfigs
928
+ } = useGetContentTypeConfigurationQuery(model);
929
+ const isLoading = isLoadingSchemas || isFetchingConfigs || isLoadingConfigs;
810
930
  React__namespace.useEffect(() => {
811
931
  if (error) {
812
932
  toggleNotification({
@@ -814,362 +934,440 @@ const useDocument = (args, opts) => {
814
934
  message: formatAPIError(error)
815
935
  });
816
936
  }
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
- }
937
+ }, [error, formatAPIError, toggleNotification]);
938
+ const editLayout = React__namespace.useMemo(
939
+ () => data && !isLoading ? formatEditLayout(data, { schemas, schema, components }) : {
940
+ layout: [],
941
+ components: {},
942
+ metadatas: {},
943
+ options: {},
944
+ settings: DEFAULT_SETTINGS
840
945
  },
841
- [validationSchema]
946
+ [data, isLoading, schemas, schema, components]
947
+ );
948
+ const listLayout = React__namespace.useMemo(() => {
949
+ return data && !isLoading ? formatListLayout(data, { schemas, schema, components }) : {
950
+ layout: [],
951
+ metadatas: {},
952
+ options: {},
953
+ settings: DEFAULT_SETTINGS
954
+ };
955
+ }, [data, isLoading, schemas, schema, components]);
956
+ const { layout: edit } = React__namespace.useMemo(
957
+ () => runHookWaterfall(HOOKS.MUTATE_EDIT_VIEW_LAYOUT, {
958
+ layout: editLayout,
959
+ query
960
+ }),
961
+ [editLayout, query, runHookWaterfall]
842
962
  );
843
- const isLoading = isLoadingDocument || isFetchingDocument || isLoadingSchema;
844
963
  return {
845
- components,
846
- document: data?.data,
847
- meta: data?.meta,
964
+ error,
848
965
  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
- )
966
+ edit,
967
+ list: listLayout
873
968
  };
874
969
  };
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"
970
+ const useDocLayout = () => {
971
+ const { model } = useDoc();
972
+ return useDocumentLayout(model);
888
973
  };
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;
974
+ const formatEditLayout = (data, {
975
+ schemas,
976
+ schema,
977
+ components
978
+ }) => {
979
+ let currentPanelIndex = 0;
980
+ const panelledEditAttributes = convertEditLayoutToFieldLayouts(
981
+ data.contentType.layouts.edit,
982
+ schema?.attributes,
983
+ data.contentType.metadatas,
984
+ { configurations: data.components, schemas: components },
985
+ schemas
986
+ ).reduce((panels, row) => {
987
+ if (row.some((field) => field.type === "dynamiczone")) {
988
+ panels.push([row]);
989
+ currentPanelIndex += 2;
990
+ } else {
991
+ if (!panels[currentPanelIndex]) {
992
+ panels.push([]);
928
993
  }
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 };
994
+ panels[currentPanelIndex].push(row);
995
+ }
996
+ return panels;
997
+ }, []);
998
+ const componentEditAttributes = Object.entries(data.components).reduce(
999
+ (acc, [uid, configuration]) => {
1000
+ acc[uid] = {
1001
+ layout: convertEditLayoutToFieldLayouts(
1002
+ configuration.layouts.edit,
1003
+ components[uid].attributes,
1004
+ configuration.metadatas,
1005
+ { configurations: data.components, schemas: components }
1006
+ ),
1007
+ settings: {
1008
+ ...configuration.settings,
1009
+ icon: components[uid].info.icon,
1010
+ displayName: components[uid].info.displayName
948
1011
  }
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
- }
1012
+ };
1013
+ return acc;
967
1014
  },
968
- [trackUsage, deleteManyDocuments, toggleNotification, formatMessage, formatAPIError]
1015
+ {}
969
1016
  );
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
- }
1017
+ const editMetadatas = Object.entries(data.contentType.metadatas).reduce(
1018
+ (acc, [attribute, metadata]) => {
1019
+ return {
1020
+ ...acc,
1021
+ [attribute]: metadata.edit
1022
+ };
1002
1023
  },
1003
- [discardDocument, formatAPIError, formatMessage, toggleNotification]
1024
+ {}
1004
1025
  );
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
- }
1026
+ return {
1027
+ layout: panelledEditAttributes,
1028
+ components: componentEditAttributes,
1029
+ metadatas: editMetadatas,
1030
+ settings: {
1031
+ ...data.contentType.settings,
1032
+ displayName: schema?.info.displayName
1037
1033
  },
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;
1034
+ options: {
1035
+ ...schema?.options,
1036
+ ...schema?.pluginOptions,
1037
+ ...data.contentType.options
1038
+ }
1039
+ };
1040
+ };
1041
+ const convertEditLayoutToFieldLayouts = (rows, attributes = {}, metadatas, components, schemas = []) => {
1042
+ return rows.map(
1043
+ (row) => row.map((field) => {
1044
+ const attribute = attributes[field.name];
1045
+ if (!attribute) {
1046
+ return null;
1067
1047
  }
1068
- },
1069
- [
1070
- // trackUsage,
1071
- publishManyDocuments,
1072
- toggleNotification,
1073
- formatMessage,
1074
- formatAPIError
1075
- ]
1048
+ const { edit: metadata } = metadatas[field.name];
1049
+ const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
1050
+ return {
1051
+ attribute,
1052
+ disabled: !metadata.editable,
1053
+ hint: metadata.description,
1054
+ label: metadata.label ?? "",
1055
+ name: field.name,
1056
+ // @ts-expect-error – mainField does exist on the metadata for a relation.
1057
+ mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
1058
+ schemas,
1059
+ components: components?.schemas ?? {}
1060
+ }),
1061
+ placeholder: metadata.placeholder ?? "",
1062
+ required: attribute.required ?? false,
1063
+ size: field.size,
1064
+ unique: "unique" in attribute ? attribute.unique : false,
1065
+ visible: metadata.visible ?? true,
1066
+ type: attribute.type
1067
+ };
1068
+ }).filter((field) => field !== null)
1076
1069
  );
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({
1070
+ };
1071
+ const formatListLayout = (data, {
1072
+ schemas,
1073
+ schema,
1074
+ components
1075
+ }) => {
1076
+ const listMetadatas = Object.entries(data.contentType.metadatas).reduce(
1077
+ (acc, [attribute, metadata]) => {
1078
+ return {
1079
+ ...acc,
1080
+ [attribute]: metadata.list
1081
+ };
1082
+ },
1083
+ {}
1084
+ );
1085
+ const listAttributes = convertListLayoutToFieldLayouts(
1086
+ data.contentType.layouts.list,
1087
+ schema?.attributes,
1088
+ listMetadatas,
1089
+ { configurations: data.components, schemas: components },
1090
+ schemas
1091
+ );
1092
+ return {
1093
+ layout: listAttributes,
1094
+ settings: { ...data.contentType.settings, displayName: schema?.info.displayName },
1095
+ metadatas: listMetadatas,
1096
+ options: {
1097
+ ...schema?.options,
1098
+ ...schema?.pluginOptions,
1099
+ ...data.contentType.options
1100
+ }
1101
+ };
1102
+ };
1103
+ const convertListLayoutToFieldLayouts = (columns, attributes = {}, metadatas, components, schemas = []) => {
1104
+ return columns.map((name) => {
1105
+ const attribute = attributes[name];
1106
+ if (!attribute) {
1107
+ return null;
1108
+ }
1109
+ const metadata = metadatas[name];
1110
+ const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
1111
+ return {
1112
+ attribute,
1113
+ label: metadata.label ?? "",
1114
+ mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
1115
+ schemas,
1116
+ components: components?.schemas ?? {}
1117
+ }),
1118
+ name,
1119
+ searchable: metadata.searchable ?? true,
1120
+ sortable: metadata.sortable ?? true
1121
+ };
1122
+ }).filter((field) => field !== null);
1123
+ };
1124
+ const useDocument = (args, opts) => {
1125
+ const { toggleNotification } = strapiAdmin.useNotification();
1126
+ const { _unstableFormatAPIError: formatAPIError } = strapiAdmin.useAPIErrorHandler();
1127
+ const {
1128
+ currentData: data,
1129
+ isLoading: isLoadingDocument,
1130
+ isFetching: isFetchingDocument,
1131
+ error
1132
+ } = useGetDocumentQuery(args, {
1133
+ ...opts,
1134
+ skip: !args.documentId && args.collectionType !== SINGLE_TYPES || opts?.skip
1135
+ });
1136
+ const {
1137
+ components,
1138
+ schema,
1139
+ schemas,
1140
+ isLoading: isLoadingSchema
1141
+ } = useContentTypeSchema(args.model);
1142
+ React__namespace.useEffect(() => {
1143
+ if (error) {
1144
+ toggleNotification({
1145
+ type: "danger",
1146
+ message: formatAPIError(error)
1147
+ });
1148
+ }
1149
+ }, [toggleNotification, error, formatAPIError, args.collectionType]);
1150
+ const validationSchema = React__namespace.useMemo(() => {
1151
+ if (!schema) {
1152
+ return null;
1153
+ }
1154
+ return createYupSchema(schema.attributes, components);
1155
+ }, [schema, components]);
1156
+ const validate = React__namespace.useCallback(
1157
+ (document) => {
1158
+ if (!validationSchema) {
1159
+ throw new Error(
1160
+ "There is no validation schema generated, this is likely due to the schema not being loaded yet."
1161
+ );
1162
+ }
1163
+ try {
1164
+ validationSchema.validateSync(document, { abortEarly: false, strict: true });
1165
+ return null;
1166
+ } catch (error2) {
1167
+ if (error2 instanceof yup.ValidationError) {
1168
+ return strapiAdmin.getYupValidationErrors(error2);
1169
+ }
1170
+ throw error2;
1171
+ }
1172
+ },
1173
+ [validationSchema]
1174
+ );
1175
+ const isLoading = isLoadingDocument || isFetchingDocument || isLoadingSchema;
1176
+ const hasError = !!error;
1177
+ return {
1178
+ components,
1179
+ document: data?.data,
1180
+ meta: data?.meta,
1181
+ isLoading,
1182
+ hasError,
1183
+ schema,
1184
+ schemas,
1185
+ validate
1186
+ };
1187
+ };
1188
+ const useDoc = () => {
1189
+ const { id, slug, collectionType, origin } = reactRouterDom.useParams();
1190
+ const [{ query }] = strapiAdmin.useQueryParams();
1191
+ const params = React__namespace.useMemo(() => buildValidParams(query), [query]);
1192
+ if (!collectionType) {
1193
+ throw new Error("Could not find collectionType in url params");
1194
+ }
1195
+ if (!slug) {
1196
+ throw new Error("Could not find model in url params");
1197
+ }
1198
+ const document = useDocument(
1199
+ { documentId: origin || id, model: slug, collectionType, params },
1200
+ {
1201
+ skip: id === "create" || !origin && !id && collectionType !== SINGLE_TYPES
1202
+ }
1203
+ );
1204
+ const returnId = origin || id === "create" ? void 0 : id;
1205
+ return {
1206
+ collectionType,
1207
+ model: slug,
1208
+ id: returnId,
1209
+ ...document
1210
+ };
1211
+ };
1212
+ const useContentManagerContext = () => {
1213
+ const {
1214
+ collectionType,
1215
+ model,
1216
+ id,
1217
+ components,
1218
+ isLoading: isLoadingDoc,
1219
+ schema,
1220
+ schemas
1221
+ } = useDoc();
1222
+ const layout = useDocumentLayout(model);
1223
+ const form = strapiAdmin.useForm("useContentManagerContext", (state) => state);
1224
+ const isSingleType = collectionType === SINGLE_TYPES;
1225
+ const slug = model;
1226
+ const isCreatingEntry = id === "create";
1227
+ useContentTypeSchema();
1228
+ const isLoading = isLoadingDoc || layout.isLoading;
1229
+ const error = layout.error;
1230
+ return {
1231
+ error,
1232
+ isLoading,
1233
+ // Base metadata
1234
+ model,
1235
+ collectionType,
1236
+ id,
1237
+ slug,
1238
+ isCreatingEntry,
1239
+ isSingleType,
1240
+ hasDraftAndPublish: schema?.options?.draftAndPublish ?? false,
1241
+ // All schema infos
1242
+ components,
1243
+ contentType: schema,
1244
+ contentTypes: schemas,
1245
+ // Form state
1246
+ form,
1247
+ // layout infos
1248
+ layout
1249
+ };
1250
+ };
1251
+ const prefixPluginTranslations = (trad, pluginId) => {
1252
+ if (!pluginId) {
1253
+ throw new TypeError("pluginId can't be empty");
1254
+ }
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) => {
1275
+ try {
1276
+ trackUsage("willDeleteEntry", trackerProperty);
1277
+ const res = await deleteDocument({
1083
1278
  collectionType,
1084
1279
  model,
1085
1280
  documentId,
1086
- data,
1087
1281
  params
1088
1282
  });
1089
1283
  if ("error" in res) {
1090
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1091
- trackUsage("didNotEditEntry", { error: res.error, ...trackerProperty });
1284
+ toggleNotification({
1285
+ type: "danger",
1286
+ message: formatAPIError(res.error)
1287
+ });
1092
1288
  return { error: res.error };
1093
1289
  }
1094
- trackUsage("didEditEntry", trackerProperty);
1095
1290
  toggleNotification({
1096
1291
  type: "success",
1097
1292
  message: formatMessage({
1098
- id: getTranslation("success.record.save"),
1099
- defaultMessage: "Saved document"
1293
+ id: getTranslation("success.record.delete"),
1294
+ defaultMessage: "Deleted document"
1100
1295
  })
1101
1296
  });
1297
+ trackUsage("didDeleteEntry", trackerProperty);
1102
1298
  return res.data;
1103
1299
  } catch (err) {
1104
- trackUsage("didNotEditEntry", { error: err, ...trackerProperty });
1105
1300
  toggleNotification({
1106
1301
  type: "danger",
1107
1302
  message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1108
1303
  });
1304
+ trackUsage("didNotDeleteEntry", { error: err, ...trackerProperty });
1109
1305
  throw err;
1110
1306
  }
1111
1307
  },
1112
- [trackUsage, updateDocument, toggleNotification, formatMessage, formatAPIError]
1308
+ [trackUsage, deleteDocument, toggleNotification, formatMessage, formatAPIError]
1113
1309
  );
1114
- const [unpublishDocument] = useUnpublishDocumentMutation();
1115
- const unpublish = React__namespace.useCallback(
1116
- async ({ collectionType, model, documentId, params }, discardDraft = false) => {
1310
+ const [deleteManyDocuments] = useDeleteManyDocumentsMutation();
1311
+ const deleteMany = React__namespace.useCallback(
1312
+ async ({ model, documentIds, params }) => {
1117
1313
  try {
1118
- trackUsage("willUnpublishEntry");
1119
- const res = await unpublishDocument({
1120
- collectionType,
1314
+ trackUsage("willBulkDeleteEntries");
1315
+ const res = await deleteManyDocuments({
1121
1316
  model,
1122
- documentId,
1123
- params,
1124
- data: {
1125
- discardDraft
1126
- }
1317
+ documentIds,
1318
+ params
1127
1319
  });
1128
1320
  if ("error" in res) {
1129
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1321
+ toggleNotification({
1322
+ type: "danger",
1323
+ message: formatAPIError(res.error)
1324
+ });
1130
1325
  return { error: res.error };
1131
1326
  }
1132
- trackUsage("didUnpublishEntry");
1133
1327
  toggleNotification({
1134
1328
  type: "success",
1135
- message: formatMessage({
1136
- id: getTranslation("success.record.unpublish"),
1137
- defaultMessage: "Unpublished document"
1138
- })
1329
+ title: formatMessage({
1330
+ id: getTranslation("success.records.delete"),
1331
+ defaultMessage: "Successfully deleted."
1332
+ }),
1333
+ message: ""
1139
1334
  });
1335
+ trackUsage("didBulkDeleteEntries");
1140
1336
  return res.data;
1141
1337
  } catch (err) {
1142
1338
  toggleNotification({
1143
1339
  type: "danger",
1144
1340
  message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1145
1341
  });
1342
+ trackUsage("didNotBulkDeleteEntries");
1146
1343
  throw err;
1147
1344
  }
1148
1345
  },
1149
- [trackUsage, unpublishDocument, toggleNotification, formatMessage, formatAPIError]
1346
+ [trackUsage, deleteManyDocuments, toggleNotification, formatMessage, formatAPIError]
1150
1347
  );
1151
- const [unpublishManyDocuments] = useUnpublishManyDocumentsMutation();
1152
- const unpublishMany = React__namespace.useCallback(
1153
- async ({ model, documentIds, params }) => {
1348
+ const [discardDocument] = useDiscardDocumentMutation();
1349
+ const discard = React__namespace.useCallback(
1350
+ async ({ collectionType, model, documentId, params }) => {
1154
1351
  try {
1155
- trackUsage("willBulkUnpublishEntries");
1156
- const res = await unpublishManyDocuments({
1352
+ const res = await discardDocument({
1353
+ collectionType,
1157
1354
  model,
1158
- documentIds,
1355
+ documentId,
1159
1356
  params
1160
1357
  });
1161
1358
  if ("error" in res) {
1162
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1359
+ toggleNotification({
1360
+ type: "danger",
1361
+ message: formatAPIError(res.error)
1362
+ });
1163
1363
  return { error: res.error };
1164
1364
  }
1165
- trackUsage("didBulkUnpublishEntries");
1166
1365
  toggleNotification({
1167
1366
  type: "success",
1168
- title: formatMessage({
1169
- id: getTranslation("success.records.unpublish"),
1170
- defaultMessage: "Successfully unpublished."
1171
- }),
1172
- message: ""
1367
+ message: formatMessage({
1368
+ id: "content-manager.success.record.discard",
1369
+ defaultMessage: "Changes discarded"
1370
+ })
1173
1371
  });
1174
1372
  return res.data;
1175
1373
  } catch (err) {
@@ -1177,32 +1375,33 @@ const useDocumentActions = () => {
1177
1375
  type: "danger",
1178
1376
  message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1179
1377
  });
1180
- trackUsage("didNotBulkUnpublishEntries");
1181
1378
  throw err;
1182
1379
  }
1183
1380
  },
1184
- [trackUsage, unpublishManyDocuments, toggleNotification, formatMessage, formatAPIError]
1381
+ [discardDocument, formatAPIError, formatMessage, toggleNotification]
1185
1382
  );
1186
- const [createDocument] = useCreateDocumentMutation();
1187
- const create = React__namespace.useCallback(
1188
- async ({ model, params }, data, trackerProperty) => {
1383
+ const [publishDocument] = usePublishDocumentMutation();
1384
+ const publish = React__namespace.useCallback(
1385
+ async ({ collectionType, model, documentId, params }, data) => {
1189
1386
  try {
1190
- const res = await createDocument({
1387
+ trackUsage("willPublishEntry");
1388
+ const res = await publishDocument({
1389
+ collectionType,
1191
1390
  model,
1391
+ documentId,
1192
1392
  data,
1193
1393
  params
1194
1394
  });
1195
1395
  if ("error" in res) {
1196
1396
  toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1197
- trackUsage("didNotCreateEntry", { error: res.error, ...trackerProperty });
1198
1397
  return { error: res.error };
1199
1398
  }
1200
- trackUsage("didCreateEntry", trackerProperty);
1399
+ trackUsage("didPublishEntry");
1201
1400
  toggleNotification({
1202
1401
  type: "success",
1203
1402
  message: formatMessage({
1204
- id: getTranslation("success.record.save"),
1205
- defaultMessage: "Saved document"
1403
+ id: getTranslation("success.record.publish"),
1404
+ defaultMessage: "Published document"
1206
1405
  })
1207
1406
  });
1208
1407
  return res.data;
@@ -1211,28 +1410,29 @@ const useDocumentActions = () => {
1211
1410
  type: "danger",
1212
1411
  message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1213
1412
  });
1214
- trackUsage("didNotCreateEntry", { error: err, ...trackerProperty });
1215
1413
  throw err;
1216
1414
  }
1217
1415
  },
1218
- [createDocument, formatAPIError, formatMessage, toggleNotification, trackUsage]
1416
+ [trackUsage, publishDocument, toggleNotification, formatMessage, formatAPIError]
1219
1417
  );
1220
- const [autoCloneDocument] = useAutoCloneDocumentMutation();
1221
- const autoClone = React__namespace.useCallback(
1222
- async ({ model, sourceId }) => {
1418
+ const [publishManyDocuments] = usePublishManyDocumentsMutation();
1419
+ const publishMany = React__namespace.useCallback(
1420
+ async ({ model, documentIds, params }) => {
1223
1421
  try {
1224
- const res = await autoCloneDocument({
1422
+ const res = await publishManyDocuments({
1225
1423
  model,
1226
- sourceId
1424
+ documentIds,
1425
+ params
1227
1426
  });
1228
1427
  if ("error" in res) {
1428
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1229
1429
  return { error: res.error };
1230
1430
  }
1231
1431
  toggleNotification({
1232
1432
  type: "success",
1233
1433
  message: formatMessage({
1234
- id: getTranslation("success.record.clone"),
1235
- defaultMessage: "Cloned document"
1434
+ id: getTranslation("success.record.publish"),
1435
+ defaultMessage: "Published document"
1236
1436
  })
1237
1437
  });
1238
1438
  return res.data;
@@ -1244,1224 +1444,1100 @@ const useDocumentActions = () => {
1244
1444
  throw err;
1245
1445
  }
1246
1446
  },
1247
- [autoCloneDocument, formatAPIError, formatMessage, toggleNotification]
1447
+ [
1448
+ // trackUsage,
1449
+ publishManyDocuments,
1450
+ toggleNotification,
1451
+ formatMessage,
1452
+ formatAPIError
1453
+ ]
1248
1454
  );
1249
- const [cloneDocument] = useCloneDocumentMutation();
1250
- const clone = React__namespace.useCallback(
1251
- async ({ model, documentId, params }, body, trackerProperty) => {
1455
+ const [updateDocument] = useUpdateDocumentMutation();
1456
+ const update = React__namespace.useCallback(
1457
+ async ({ collectionType, model, documentId, params }, data, trackerProperty) => {
1252
1458
  try {
1253
- const { id: _id, ...restBody } = body;
1254
- const res = await cloneDocument({
1459
+ trackUsage("willEditEntry", trackerProperty);
1460
+ const res = await updateDocument({
1461
+ collectionType,
1255
1462
  model,
1256
- sourceId: documentId,
1257
- data: restBody,
1463
+ documentId,
1464
+ data,
1258
1465
  params
1259
1466
  });
1260
1467
  if ("error" in res) {
1261
1468
  toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1262
- trackUsage("didNotCreateEntry", { error: res.error, ...trackerProperty });
1469
+ trackUsage("didNotEditEntry", { error: res.error, ...trackerProperty });
1263
1470
  return { error: res.error };
1264
1471
  }
1265
- trackUsage("didCreateEntry", trackerProperty);
1472
+ trackUsage("didEditEntry", trackerProperty);
1266
1473
  toggleNotification({
1267
1474
  type: "success",
1268
1475
  message: formatMessage({
1269
- id: getTranslation("success.record.clone"),
1270
- defaultMessage: "Cloned document"
1476
+ id: getTranslation("success.record.save"),
1477
+ defaultMessage: "Saved document"
1271
1478
  })
1272
1479
  });
1273
1480
  return res.data;
1274
1481
  } catch (err) {
1482
+ trackUsage("didNotEditEntry", { error: err, ...trackerProperty });
1275
1483
  toggleNotification({
1276
1484
  type: "danger",
1277
1485
  message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1278
1486
  });
1279
- trackUsage("didNotCreateEntry", { error: err, ...trackerProperty });
1280
1487
  throw err;
1281
1488
  }
1282
1489
  },
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]
1490
+ [trackUsage, updateDocument, toggleNotification, formatMessage, formatAPIError]
1292
1491
  );
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
- })
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({
1498
+ collectionType,
1499
+ model,
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 };
1417
1509
  }
1418
- ) : null
1419
- ] }),
1420
- secondaryAction ? /* @__PURE__ */ jsxRuntime.jsx(
1421
- DocumentActionButton,
1422
- {
1423
- ...secondaryAction,
1424
- variant: secondaryAction.variant || "secondary"
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;
1425
1525
  }
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);
1450
- }
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 })
1526
+ },
1527
+ [trackUsage, unpublishDocument, toggleNotification, formatMessage, formatAPIError]
1722
1528
  );
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);
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,
1537
+ params
1538
+ });
1539
+ if ("error" in res) {
1540
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1541
+ return { error: res.error };
1765
1542
  }
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({
1777
- collectionType,
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({
1778
1569
  model,
1779
- documentId,
1570
+ data,
1780
1571
  params
1781
1572
  });
1782
- if (error) {
1783
- throw error;
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 };
1784
1577
  }
1785
- if (data) {
1786
- setServerCountOfDraftRelations(data.data);
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) {
1589
+ toggleNotification({
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 };
1787
1609
  }
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) {
1610
+ toggleNotification({
1611
+ type: "success",
1612
+ message: formatMessage({
1613
+ id: getTranslation("success.record.clone"),
1614
+ defaultMessage: "Cloned document"
1615
+ })
1616
+ });
1617
+ return res.data;
1618
+ } catch (err) {
1801
1619
  toggleNotification({
1802
1620
  type: "danger",
1621
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1622
+ });
1623
+ throw err;
1624
+ }
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({
1634
+ model,
1635
+ sourceId: documentId,
1636
+ data: restBody,
1637
+ params
1638
+ });
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",
1803
1647
  message: formatMessage({
1804
- id: "content-manager.validation.error",
1805
- defaultMessage: "There are validation errors in your document. Please fix them before saving."
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;
1661
+ }
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-3JN4BIS1.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-BbY0bNNE.js")).then((mod) => ({ default: mod.ProtectedEditViewPage }))
1703
+ );
1704
+ const ProtectedListViewPage = React.lazy(
1705
+ () => Promise.resolve().then(() => require("./ListViewPage-ZPNQ6jWC.js")).then((mod) => ({ default: mod.ProtectedListViewPage }))
1706
+ );
1707
+ const ProtectedListConfiguration = React.lazy(
1708
+ () => Promise.resolve().then(() => require("./ListConfigurationPage-BcC28SV0.js")).then((mod) => ({
1709
+ default: mod.ProtectedListConfiguration
1710
+ }))
1711
+ );
1712
+ const ProtectedEditConfigurationPage = React.lazy(
1713
+ () => Promise.resolve().then(() => require("./EditConfigurationPage-BrG0mEfF.js")).then((mod) => ({
1714
+ default: mod.ProtectedEditConfigurationPage
1715
+ }))
1716
+ );
1717
+ const ProtectedComponentConfigurationPage = React.lazy(
1718
+ () => Promise.resolve().then(() => require("./ComponentConfigurationPage-DNRPa10q.js")).then((mod) => ({
1719
+ default: mod.ProtectedComponentConfigurationPage
1720
+ }))
1721
+ );
1722
+ const NoPermissions = React.lazy(
1723
+ () => Promise.resolve().then(() => require("./NoPermissionsPage-GaweP-NG.js")).then((mod) => ({ default: mod.NoPermissions }))
1724
+ );
1725
+ const NoContentType = React.lazy(
1726
+ () => Promise.resolve().then(() => require("./NoContentTypePage-DzwJNXvn.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;
1779
+ }
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,
1791
+ {
1792
+ actions: restActions,
1793
+ label: formatMessage({
1794
+ id: "content-manager.containers.edit.panels.default.more-actions",
1795
+ defaultMessage: "More document actions"
1806
1796
  })
1807
- });
1808
- return;
1797
+ }
1798
+ ) : null
1799
+ ] }),
1800
+ secondaryAction ? /* @__PURE__ */ jsxRuntime.jsx(
1801
+ DocumentActionButton,
1802
+ {
1803
+ ...secondaryAction,
1804
+ variant: secondaryAction.variant || "secondary"
1809
1805
  }
1810
- const res = await publish(
1811
- {
1812
- collectionType,
1813
- model,
1814
- documentId,
1815
- 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
- });
1824
- } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1825
- setErrors(formatValidationErrors(res.error));
1806
+ ) : null
1807
+ ] });
1808
+ };
1809
+ const DocumentActionButton = (action) => {
1810
+ const [dialogId, setDialogId] = React__namespace.useState(null);
1811
+ const { toggleNotification } = strapiAdmin.useNotification();
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":
1818
+ toggleNotification({
1819
+ title: dialog.title,
1820
+ message: dialog.content,
1821
+ type: dialog.status,
1822
+ timeout: dialog.timeout,
1823
+ onClose: dialog.onClose
1824
+ });
1825
+ break;
1826
+ case "dialog":
1827
+ case "modal":
1828
+ e.preventDefault();
1829
+ setDialogId(id);
1826
1830
  }
1827
- } finally {
1828
- setSubmitting(false);
1829
1831
  }
1830
1832
  };
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;
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
1851
1849
  }
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
- },
1867
- {
1868
- count: totalDraftRelations
1869
- }
1870
- ),
1871
- onConfirm: async () => {
1872
- await performPublish();
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
1873
1858
  }
1874
- } : void 0
1875
- };
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
+ ] });
1876
1869
  };
1877
- PublishAction$1.type = "publish";
1878
- const UpdateAction = ({
1879
- activeTab,
1880
- documentId,
1881
- model,
1882
- collectionType
1870
+ const DocumentActionsMenu = ({
1871
+ actions: actions2,
1872
+ children,
1873
+ label,
1874
+ variant = "tertiary"
1883
1875
  }) => {
1884
- const navigate = reactRouterDom.useNavigate();
1885
- const { toggleNotification } = strapiAdmin.useNotification();
1886
- const { _unstableFormatValidationErrors: formatValidationErrors } = strapiAdmin.useAPIErrorHandler();
1887
- const cloneMatch = reactRouterDom.useMatch(CLONE_PATH);
1888
- const isCloning = cloneMatch !== null;
1876
+ const [isOpen, setIsOpen] = React__namespace.useState(false);
1877
+ const [dialogId, setDialogId] = React__namespace.useState(null);
1889
1878
  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) {
1879
+ const { toggleNotification } = strapiAdmin.useNotification();
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":
1921
1887
  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
- })
1888
+ title: dialog.title,
1889
+ message: dialog.content,
1890
+ type: dialog.status,
1891
+ timeout: dialog.timeout,
1892
+ onClose: dialog.onClose
1927
1893
  });
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);
1894
+ break;
1895
+ case "dialog":
1896
+ case "modal":
1897
+ setDialogId(id);
1987
1898
  }
1988
1899
  }
1989
1900
  };
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
2002
- }) => {
2003
- 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
- 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);
1901
+ const handleClose = () => {
1902
+ setDialogId(null);
1903
+ setIsOpen(false);
2014
1904
  };
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
- );
2031
- 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"
2037
- });
2038
- }
2039
- return;
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"
1921
+ }) })
1922
+ ]
2040
1923
  }
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"
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,
1929
+ {
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
+ ] })
1970
+ },
1971
+ action.id
1972
+ );
2053
1973
  }),
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?"
2060
- }) })
2061
- ] }),
2062
- /* @__PURE__ */ jsxRuntime.jsxs(
2063
- designSystem.Radio.Group,
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,
2064
1980
  {
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
- ]
1981
+ ...action.dialog,
1982
+ variant: action.variant,
1983
+ isOpen: dialogId === action.id,
1984
+ onClose: handleClose
2082
1985
  }
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(
1986
+ ) : null,
1987
+ action.dialog?.type === "modal" ? /* @__PURE__ */ jsxRuntime.jsx(
1988
+ DocumentActionModal,
2099
1989
  {
2100
- collectionType,
2101
- model,
2102
- documentId,
2103
- params
2104
- },
2105
- !shouldKeepDraft
2106
- );
2107
- }
2108
- } : void 0,
2109
- variant: "danger",
2110
- position: ["panel", "table-row"]
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();
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
+ ] }) });
2059
+ };
2060
+ const DocumentActionModal = ({
2061
+ isOpen,
2062
+ title,
2063
+ onClose,
2064
+ footer: Footer,
2065
+ content: Content,
2066
+ onModalClose
2067
+ }) => {
2068
+ const handleClose = () => {
2069
+ if (onClose) {
2070
+ onClose();
2071
+ }
2072
+ onModalClose();
2111
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
+ ] }) });
2112
2079
  };
2113
- UnpublishAction$1.type = "unpublish";
2114
- const DiscardAction = ({
2080
+ const PublishAction$1 = ({
2115
2081
  activeTab,
2116
2082
  documentId,
2117
2083
  model,
2118
2084
  collectionType,
2085
+ meta,
2119
2086
  document
2120
2087
  }) => {
2121
- const { formatMessage } = reactIntl.useIntl();
2122
2088
  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
- }
2160
- }
2161
- };
2162
- };
2163
- DiscardAction.type = "discard";
2164
- const StyledCrossCircle = styledComponents.styled(Icons.CrossCircle)`
2165
- path {
2166
- fill: currentColor;
2167
- }
2168
- `;
2169
- const DEFAULT_ACTIONS = [PublishAction$1, UpdateAction, UnpublishAction$1, DiscardAction];
2170
- const intervals = ["years", "months", "days", "hours", "minutes", "seconds"];
2171
- const RelativeTime = React__namespace.forwardRef(
2172
- ({ timestamp, customIntervals = [], ...restProps }, forwardedRef) => {
2173
- const { formatRelativeTime, formatDate, formatTime } = reactIntl.useIntl();
2174
- const interval = dateFns.intervalToDuration({
2175
- start: timestamp,
2176
- end: Date.now()
2177
- // see https://github.com/date-fns/date-fns/issues/2891 – No idea why it's all partial it returns it every time.
2178
- });
2179
- const unit = intervals.find((intervalUnit) => {
2180
- return interval[intervalUnit] > 0 && Object.keys(interval).includes(intervalUnit);
2181
- });
2182
- const relativeTime = dateFns.isPast(timestamp) ? -interval[unit] : interval[unit];
2183
- const customInterval = customIntervals.find(
2184
- (custom) => interval[custom.unit] < custom.threshold
2185
- );
2186
- const displayText = customInterval ? customInterval.text : formatRelativeTime(relativeTime, unit, { numeric: "auto" });
2187
- return /* @__PURE__ */ jsxRuntime.jsx(
2188
- "time",
2189
- {
2190
- ref: forwardedRef,
2191
- dateTime: timestamp.toISOString(),
2192
- role: "time",
2193
- title: `${formatDate(timestamp)} ${formatTime(timestamp)}`,
2194
- ...restProps,
2195
- children: displayText
2196
- }
2197
- );
2198
- }
2199
- );
2200
- const getDisplayName = ({
2201
- firstname,
2202
- lastname,
2203
- username,
2204
- email
2205
- } = {}) => {
2206
- if (username) {
2207
- return username;
2208
- }
2209
- if (firstname) {
2210
- return `${firstname} ${lastname ?? ""}`.trim();
2211
- }
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) }) });
2218
- };
2219
- const Header = ({ isCreating, status, title: documentTitle = "Untitled" }) => {
2220
- const { formatMessage } = reactIntl.useIntl();
2089
+ const navigate = reactRouterDom.useNavigate();
2090
+ const { toggleNotification } = strapiAdmin.useNotification();
2091
+ const { _unstableFormatValidationErrors: formatValidationErrors } = strapiAdmin.useAPIErrorHandler();
2092
+ const isListView = reactRouterDom.useMatch(LIST_PATH) !== null;
2221
2093
  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
2094
  const { formatMessage } = reactIntl.useIntl();
2237
- const isCloning = reactRouterDom.useMatch(CLONE_PATH) !== null;
2095
+ const canPublish = useDocumentRBAC("PublishAction", ({ canPublish: canPublish2 }) => canPublish2);
2096
+ const { publish } = useDocumentActions();
2238
2097
  const [
2239
- {
2240
- query: { status = "draft" }
2098
+ countDraftRelations,
2099
+ { isLoading: isLoadingDraftRelations, isError: isErrorDraftRelations }
2100
+ ] = useLazyGetDraftRelationCountQuery();
2101
+ const [localCountOfDraftRelations, setLocalCountOfDraftRelations] = React__namespace.useState(0);
2102
+ const [serverCountOfDraftRelations, setServerCountOfDraftRelations] = React__namespace.useState(0);
2103
+ const [{ query, rawQuery }] = strapiAdmin.useQueryParams();
2104
+ const params = React__namespace.useMemo(() => buildValidParams(query), [query]);
2105
+ const modified = strapiAdmin.useForm("PublishAction", ({ modified: modified2 }) => modified2);
2106
+ const setSubmitting = strapiAdmin.useForm("PublishAction", ({ setSubmitting: setSubmitting2 }) => setSubmitting2);
2107
+ const isSubmitting = strapiAdmin.useForm("PublishAction", ({ isSubmitting: isSubmitting2 }) => isSubmitting2);
2108
+ const validate = strapiAdmin.useForm("PublishAction", (state) => state.validate);
2109
+ const setErrors = strapiAdmin.useForm("PublishAction", (state) => state.setErrors);
2110
+ const formValues = strapiAdmin.useForm("PublishAction", ({ values }) => values);
2111
+ React__namespace.useEffect(() => {
2112
+ if (isErrorDraftRelations) {
2113
+ toggleNotification({
2114
+ type: "danger",
2115
+ message: formatMessage({
2116
+ id: getTranslation("error.records.fetch-draft-relatons"),
2117
+ defaultMessage: "An error occurred while fetching draft relations on this document."
2118
+ })
2119
+ });
2241
2120
  }
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
- }
2121
+ }, [isErrorDraftRelations, toggleNotification, formatMessage]);
2122
+ React__namespace.useEffect(() => {
2123
+ const localDraftRelations = /* @__PURE__ */ new Set();
2124
+ const extractDraftRelations = (data) => {
2125
+ const relations = data.connect || [];
2126
+ relations.forEach((relation) => {
2127
+ if (relation.status === "draft") {
2128
+ localDraftRelations.add(relation.id);
2264
2129
  }
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
- );
2130
+ });
2131
+ };
2132
+ const traverseAndExtract = (data) => {
2133
+ Object.entries(data).forEach(([key, value]) => {
2134
+ if (key === "connect" && Array.isArray(value)) {
2135
+ extractDraftRelations({ connect: value });
2136
+ } else if (typeof value === "object" && value !== null) {
2137
+ traverseAndExtract(value);
2295
2138
  }
2296
- }
2297
- )
2298
- ] });
2299
- };
2300
- const Information = ({ activeTab }) => {
2301
- const { formatMessage } = reactIntl.useIntl();
2302
- const { document, meta } = useDoc();
2303
- if (!document || !document.id) {
2139
+ });
2140
+ };
2141
+ if (!documentId || modified) {
2142
+ traverseAndExtract(formValues);
2143
+ setLocalCountOfDraftRelations(localDraftRelations.size);
2144
+ }
2145
+ }, [documentId, modified, formValues, setLocalCountOfDraftRelations]);
2146
+ React__namespace.useEffect(() => {
2147
+ if (!document || !document.documentId || isListView) {
2148
+ return;
2149
+ }
2150
+ const fetchDraftRelationsCount = async () => {
2151
+ const { data, error } = await countDraftRelations({
2152
+ collectionType,
2153
+ model,
2154
+ documentId,
2155
+ params
2156
+ });
2157
+ if (error) {
2158
+ throw error;
2159
+ }
2160
+ if (data) {
2161
+ setServerCountOfDraftRelations(data.data);
2162
+ }
2163
+ };
2164
+ fetchDraftRelationsCount();
2165
+ }, [isListView, document, documentId, countDraftRelations, collectionType, model, params]);
2166
+ const isDocumentPublished = (document?.[PUBLISHED_AT_ATTRIBUTE_NAME] || meta?.availableStatus.some((doc) => doc[PUBLISHED_AT_ATTRIBUTE_NAME] !== null)) && document?.status !== "modified";
2167
+ if (!schema?.options?.draftAndPublish) {
2304
2168
  return null;
2305
2169
  }
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(
2318
- {
2319
- id: "content-manager.containers.edit.information.last-published.value",
2320
- defaultMessage: `Published {time}{isAnonymous, select, true {} other { by {author}}}`
2321
- },
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
- )
2328
- },
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"
2334
- }),
2335
- value: formatMessage(
2170
+ const performPublish = async () => {
2171
+ setSubmitting(true);
2172
+ try {
2173
+ const { errors } = await validate(true, {
2174
+ status: "published"
2175
+ });
2176
+ if (errors) {
2177
+ toggleNotification({
2178
+ type: "danger",
2179
+ message: formatMessage({
2180
+ id: "content-manager.validation.error",
2181
+ defaultMessage: "There are validation errors in your document. Please fix them before saving."
2182
+ })
2183
+ });
2184
+ return;
2185
+ }
2186
+ const res = await publish(
2336
2187
  {
2337
- id: "content-manager.containers.edit.information.last-draft.value",
2338
- defaultMessage: `Modified {time}{isAnonymous, select, true {} other { by {author}}}`
2188
+ collectionType,
2189
+ model,
2190
+ documentId,
2191
+ params
2339
2192
  },
2340
- {
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
2349
- }
2350
- )
2193
+ formDocumentToData(formValues)
2194
+ );
2195
+ if ("data" in res && collectionType !== SINGLE_TYPES) {
2196
+ navigate({
2197
+ pathname: `../${collectionType}/${model}/${res.data.documentId}`,
2198
+ search: rawQuery
2199
+ });
2200
+ } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
2201
+ setErrors(formatValidationErrors(res.error));
2202
+ }
2203
+ } finally {
2204
+ setSubmitting(false);
2205
+ }
2206
+ };
2207
+ const totalDraftRelations = localCountOfDraftRelations + serverCountOfDraftRelations;
2208
+ const enableDraftRelationsCount = false;
2209
+ const hasDraftRelations = enableDraftRelationsCount;
2210
+ return {
2211
+ /**
2212
+ * Disabled when:
2213
+ * - currently if you're cloning a document we don't support publish & clone at the same time.
2214
+ * - the form is submitting
2215
+ * - the active tab is the published tab
2216
+ * - the document is already published & not modified
2217
+ * - the document is being created & not modified
2218
+ * - the user doesn't have the permission to publish
2219
+ */
2220
+ disabled: isCloning || isSubmitting || isLoadingDraftRelations || activeTab === "published" || !modified && isDocumentPublished || !modified && !document?.documentId || !canPublish,
2221
+ label: formatMessage({
2222
+ id: "app.utils.publish",
2223
+ defaultMessage: "Publish"
2224
+ }),
2225
+ onClick: async () => {
2226
+ await performPublish();
2351
2227
  },
2352
- {
2353
- isDisplayed: !!createAndUpdateDocument?.[CREATED_AT_ATTRIBUTE_NAME],
2354
- label: formatMessage({
2355
- id: "content-manager.containers.edit.information.document.label",
2356
- defaultMessage: "Document"
2228
+ dialog: hasDraftRelations ? {
2229
+ type: "dialog",
2230
+ variant: "danger",
2231
+ footer: null,
2232
+ title: formatMessage({
2233
+ id: getTranslation(`popUpwarning.warning.bulk-has-draft-relations.title`),
2234
+ defaultMessage: "Confirmation"
2357
2235
  }),
2358
- value: formatMessage(
2236
+ content: formatMessage(
2359
2237
  {
2360
- id: "content-manager.containers.edit.information.document.value",
2361
- defaultMessage: `Created {time}{isAnonymous, select, true {} other { by {author}}}`
2238
+ id: getTranslation(`popUpwarning.warning.bulk-has-draft-relations.message`),
2239
+ defaultMessage: "This entry is related to {count, plural, one {# draft entry} other {# draft entries}}. Publishing it could leave broken links in your app."
2362
2240
  },
2363
2241
  {
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
2242
+ count: totalDraftRelations
2372
2243
  }
2373
- )
2244
+ ),
2245
+ onConfirm: async () => {
2246
+ await performPublish();
2247
+ }
2248
+ } : void 0
2249
+ };
2250
+ };
2251
+ PublishAction$1.type = "publish";
2252
+ const formDocumentToData = (document) => {
2253
+ return Object.keys(document).reduce((acc, key) => {
2254
+ if (document[key] !== void 0) {
2255
+ acc[key] = document[key];
2374
2256
  }
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))
2257
+ if (typeof document[key] === "object" && document[key] !== null) {
2258
+ if ("connect" in document[key] && document[key].connect !== null) {
2259
+ acc[key] = {
2260
+ ...acc[key],
2261
+ connect: document[key].connect.map((item) => {
2262
+ return {
2263
+ documentId: item.documentId
2264
+ };
2265
+ })
2266
+ };
2267
+ }
2268
+ if ("disconnect" in document[key] && document[key].disconnect !== null) {
2269
+ acc[key] = {
2270
+ ...acc[key],
2271
+ disconnect: document[key].disconnect.map((item) => {
2272
+ return {
2273
+ documentId: item.documentId
2274
+ };
2275
+ })
2276
+ };
2277
+ }
2278
+ }
2279
+ return acc;
2280
+ }, {});
2281
+ };
2282
+ const UpdateAction = ({
2283
+ activeTab,
2284
+ documentId,
2285
+ model,
2286
+ collectionType
2287
+ }) => {
2288
+ const navigate = reactRouterDom.useNavigate();
2289
+ const { toggleNotification } = strapiAdmin.useNotification();
2290
+ const { _unstableFormatValidationErrors: formatValidationErrors } = strapiAdmin.useAPIErrorHandler();
2291
+ const cloneMatch = reactRouterDom.useMatch(CLONE_PATH);
2292
+ const isCloning = cloneMatch !== null;
2293
+ const { formatMessage } = reactIntl.useIntl();
2294
+ const { create, update, clone } = useDocumentActions();
2295
+ const [{ query, rawQuery }] = strapiAdmin.useQueryParams();
2296
+ const params = React__namespace.useMemo(() => buildValidParams(query), [query]);
2297
+ const isSubmitting = strapiAdmin.useForm("UpdateAction", ({ isSubmitting: isSubmitting2 }) => isSubmitting2);
2298
+ const modified = strapiAdmin.useForm("UpdateAction", ({ modified: modified2 }) => modified2);
2299
+ const setSubmitting = strapiAdmin.useForm("UpdateAction", ({ setSubmitting: setSubmitting2 }) => setSubmitting2);
2300
+ const document = strapiAdmin.useForm("UpdateAction", ({ values }) => values);
2301
+ const validate = strapiAdmin.useForm("UpdateAction", (state) => state.validate);
2302
+ const setErrors = strapiAdmin.useForm("UpdateAction", (state) => state.setErrors);
2303
+ const resetForm = strapiAdmin.useForm("PublishAction", ({ resetForm: resetForm2 }) => resetForm2);
2304
+ return {
2305
+ /**
2306
+ * Disabled when:
2307
+ * - the form is submitting
2308
+ * - the document is not modified & we're not cloning (you can save a clone entity straight away)
2309
+ * - the active tab is the published tab
2310
+ */
2311
+ disabled: isSubmitting || !modified && !isCloning || activeTab === "published",
2312
+ label: formatMessage({
2313
+ id: "content-manager.containers.Edit.save",
2314
+ defaultMessage: "Save"
2315
+ }),
2316
+ onClick: async () => {
2317
+ setSubmitting(true);
2318
+ try {
2319
+ const { errors } = await validate(true, {
2320
+ status: "draft"
2321
+ });
2322
+ if (errors) {
2323
+ toggleNotification({
2324
+ type: "danger",
2325
+ message: formatMessage({
2326
+ id: "content-manager.validation.error",
2327
+ defaultMessage: "There are validation errors in your document. Please fix them before saving."
2328
+ })
2329
+ });
2330
+ return;
2331
+ }
2332
+ if (isCloning) {
2333
+ const res = await clone(
2334
+ {
2335
+ model,
2336
+ documentId: cloneMatch.params.origin,
2337
+ params
2338
+ },
2339
+ formDocumentToData(document)
2340
+ );
2341
+ if ("data" in res) {
2342
+ navigate(
2343
+ {
2344
+ pathname: `../${res.data.documentId}`,
2345
+ search: rawQuery
2346
+ },
2347
+ { relative: "path" }
2348
+ );
2349
+ } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
2350
+ setErrors(formatValidationErrors(res.error));
2351
+ }
2352
+ } else if (documentId || collectionType === SINGLE_TYPES) {
2353
+ const res = await update(
2354
+ {
2355
+ collectionType,
2356
+ model,
2357
+ documentId,
2358
+ params
2359
+ },
2360
+ formDocumentToData(document)
2361
+ );
2362
+ if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
2363
+ setErrors(formatValidationErrors(res.error));
2364
+ } else {
2365
+ resetForm();
2366
+ }
2367
+ } else {
2368
+ const res = await create(
2369
+ {
2370
+ model,
2371
+ params
2372
+ },
2373
+ formDocumentToData(document)
2374
+ );
2375
+ if ("data" in res && collectionType !== SINGLE_TYPES) {
2376
+ navigate(
2377
+ {
2378
+ pathname: `../${res.data.documentId}`,
2379
+ search: rawQuery
2380
+ },
2381
+ { replace: true, relative: "path" }
2382
+ );
2383
+ } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
2384
+ setErrors(formatValidationErrors(res.error));
2385
+ }
2386
+ }
2387
+ } finally {
2388
+ setSubmitting(false);
2389
+ }
2395
2390
  }
2396
- );
2391
+ };
2397
2392
  };
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
- }) });
2393
+ UpdateAction.type = "update";
2394
+ const UNPUBLISH_DRAFT_OPTIONS = {
2395
+ KEEP: "keep",
2396
+ DISCARD: "discard"
2417
2397
  };
2418
- const ConfigureTheViewAction = ({ collectionType, model }) => {
2419
- const navigate = reactRouterDom.useNavigate();
2398
+ const UnpublishAction$1 = ({
2399
+ activeTab,
2400
+ documentId,
2401
+ model,
2402
+ collectionType,
2403
+ document
2404
+ }) => {
2420
2405
  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"
2406
+ const { schema } = useDoc();
2407
+ const canPublish = useDocumentRBAC("UnpublishAction", ({ canPublish: canPublish2 }) => canPublish2);
2408
+ const { unpublish } = useDocumentActions();
2409
+ const [{ query }] = strapiAdmin.useQueryParams();
2410
+ const params = React__namespace.useMemo(() => buildValidParams(query), [query]);
2411
+ const { toggleNotification } = strapiAdmin.useNotification();
2412
+ const [shouldKeepDraft, setShouldKeepDraft] = React__namespace.useState(true);
2413
+ const isDocumentModified = document?.status === "modified";
2414
+ const handleChange = (value) => {
2415
+ setShouldKeepDraft(value === UNPUBLISH_DRAFT_OPTIONS.KEEP);
2431
2416
  };
2432
- };
2433
- ConfigureTheViewAction.type = "configure-the-view";
2434
- const EditTheModelAction = ({ model }) => {
2435
- const navigate = reactRouterDom.useNavigate();
2436
- const { formatMessage } = reactIntl.useIntl();
2417
+ if (!schema?.options?.draftAndPublish) {
2418
+ return null;
2419
+ }
2437
2420
  return {
2421
+ disabled: !canPublish || activeTab === "published" || document?.status !== "published" && document?.status !== "modified",
2438
2422
  label: formatMessage({
2439
- id: "content-manager.link-to-ctb",
2440
- defaultMessage: "Edit the model"
2423
+ id: "app.utils.unpublish",
2424
+ defaultMessage: "Unpublish"
2441
2425
  }),
2442
- icon: /* @__PURE__ */ jsxRuntime.jsx(Icons.Pencil, {}),
2443
- onClick: () => {
2444
- navigate(`/plugins/content-type-builder/content-types/${model}`);
2426
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Icons.Cross, {}),
2427
+ onClick: async () => {
2428
+ if (!documentId && collectionType !== SINGLE_TYPES || isDocumentModified) {
2429
+ if (!documentId) {
2430
+ console.error(
2431
+ "You're trying to unpublish a document without an id, this is likely a bug with Strapi. Please open an issue."
2432
+ );
2433
+ toggleNotification({
2434
+ message: formatMessage({
2435
+ id: "content-manager.actions.unpublish.error",
2436
+ defaultMessage: "An error occurred while trying to unpublish the document."
2437
+ }),
2438
+ type: "danger"
2439
+ });
2440
+ }
2441
+ return;
2442
+ }
2443
+ await unpublish({
2444
+ collectionType,
2445
+ model,
2446
+ documentId,
2447
+ params
2448
+ });
2445
2449
  },
2446
- position: "header"
2450
+ dialog: isDocumentModified ? {
2451
+ type: "dialog",
2452
+ title: formatMessage({
2453
+ id: "app.components.ConfirmDialog.title",
2454
+ defaultMessage: "Confirmation"
2455
+ }),
2456
+ content: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "flex-start", direction: "column", gap: 6, children: [
2457
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { width: "100%", direction: "column", gap: 2, children: [
2458
+ /* @__PURE__ */ jsxRuntime.jsx(Icons.WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
2459
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
2460
+ id: "content-manager.actions.unpublish.dialog.body",
2461
+ defaultMessage: "Are you sure?"
2462
+ }) })
2463
+ ] }),
2464
+ /* @__PURE__ */ jsxRuntime.jsxs(
2465
+ designSystem.Radio.Group,
2466
+ {
2467
+ defaultValue: UNPUBLISH_DRAFT_OPTIONS.KEEP,
2468
+ name: "discard-options",
2469
+ "aria-label": formatMessage({
2470
+ id: "content-manager.actions.unpublish.dialog.radio-label",
2471
+ defaultMessage: "Choose an option to unpublish the document."
2472
+ }),
2473
+ onValueChange: handleChange,
2474
+ children: [
2475
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Radio.Item, { checked: shouldKeepDraft, value: UNPUBLISH_DRAFT_OPTIONS.KEEP, children: formatMessage({
2476
+ id: "content-manager.actions.unpublish.dialog.option.keep-draft",
2477
+ defaultMessage: "Keep draft"
2478
+ }) }),
2479
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Radio.Item, { checked: !shouldKeepDraft, value: UNPUBLISH_DRAFT_OPTIONS.DISCARD, children: formatMessage({
2480
+ id: "content-manager.actions.unpublish.dialog.option.replace-draft",
2481
+ defaultMessage: "Replace draft"
2482
+ }) })
2483
+ ]
2484
+ }
2485
+ )
2486
+ ] }),
2487
+ onConfirm: async () => {
2488
+ if (!documentId && collectionType !== SINGLE_TYPES) {
2489
+ console.error(
2490
+ "You're trying to unpublish a document without an id, this is likely a bug with Strapi. Please open an issue."
2491
+ );
2492
+ toggleNotification({
2493
+ message: formatMessage({
2494
+ id: "content-manager.actions.unpublish.error",
2495
+ defaultMessage: "An error occurred while trying to unpublish the document."
2496
+ }),
2497
+ type: "danger"
2498
+ });
2499
+ }
2500
+ await unpublish(
2501
+ {
2502
+ collectionType,
2503
+ model,
2504
+ documentId,
2505
+ params
2506
+ },
2507
+ !shouldKeepDraft
2508
+ );
2509
+ }
2510
+ } : void 0,
2511
+ variant: "danger",
2512
+ position: ["panel", "table-row"]
2447
2513
  };
2448
2514
  };
2449
- EditTheModelAction.type = "edit-the-model";
2450
- const DeleteAction$1 = ({ documentId, model, collectionType, document }) => {
2451
- const navigate = reactRouterDom.useNavigate();
2515
+ UnpublishAction$1.type = "unpublish";
2516
+ const DiscardAction = ({
2517
+ activeTab,
2518
+ documentId,
2519
+ model,
2520
+ collectionType,
2521
+ document
2522
+ }) => {
2452
2523
  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);
2524
+ const { schema } = useDoc();
2525
+ const canUpdate = useDocumentRBAC("DiscardAction", ({ canUpdate: canUpdate2 }) => canUpdate2);
2526
+ const { discard } = useDocumentActions();
2527
+ const [{ query }] = strapiAdmin.useQueryParams();
2528
+ const params = React__namespace.useMemo(() => buildValidParams(query), [query]);
2529
+ if (!schema?.options?.draftAndPublish) {
2530
+ return null;
2531
+ }
2458
2532
  return {
2459
- disabled: !canDelete || !document,
2533
+ disabled: !canUpdate || activeTab === "published" || document?.status !== "modified",
2460
2534
  label: formatMessage({
2461
- id: "content-manager.actions.delete.label",
2462
- defaultMessage: "Delete document"
2535
+ id: "content-manager.actions.discard.label",
2536
+ defaultMessage: "Discard changes"
2463
2537
  }),
2464
- icon: /* @__PURE__ */ jsxRuntime.jsx(Icons.Trash, {}),
2538
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Icons.Cross, {}),
2539
+ position: ["panel", "table-row"],
2540
+ variant: "danger",
2465
2541
  dialog: {
2466
2542
  type: "dialog",
2467
2543
  title: formatMessage({
@@ -2471,92 +2547,90 @@ const DeleteAction$1 = ({ documentId, model, collectionType, document }) => {
2471
2547
  content: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 2, children: [
2472
2548
  /* @__PURE__ */ jsxRuntime.jsx(Icons.WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
2473
2549
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
2474
- id: "content-manager.actions.delete.dialog.body",
2550
+ id: "content-manager.actions.discard.dialog.body",
2475
2551
  defaultMessage: "Are you sure?"
2476
2552
  }) })
2477
2553
  ] }),
2478
2554
  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
- }
2555
+ await discard({
2556
+ collectionType,
2557
+ model,
2558
+ documentId,
2559
+ params
2560
+ });
2561
+ }
2562
+ }
2563
+ };
2564
+ };
2565
+ DiscardAction.type = "discard";
2566
+ const DEFAULT_ACTIONS = [PublishAction$1, UpdateAction, UnpublishAction$1, DiscardAction];
2567
+ const intervals = ["years", "months", "days", "hours", "minutes", "seconds"];
2568
+ const RelativeTime = React__namespace.forwardRef(
2569
+ ({ timestamp, customIntervals = [], ...restProps }, forwardedRef) => {
2570
+ const { formatRelativeTime, formatDate, formatTime } = reactIntl.useIntl();
2571
+ const interval = dateFns.intervalToDuration({
2572
+ start: timestamp,
2573
+ end: Date.now()
2574
+ // see https://github.com/date-fns/date-fns/issues/2891 – No idea why it's all partial it returns it every time.
2575
+ });
2576
+ const unit = intervals.find((intervalUnit) => {
2577
+ return interval[intervalUnit] > 0 && Object.keys(interval).includes(intervalUnit);
2578
+ });
2579
+ const relativeTime = dateFns.isPast(timestamp) ? -interval[unit] : interval[unit];
2580
+ const customInterval = customIntervals.find(
2581
+ (custom) => interval[custom.unit] < custom.threshold
2582
+ );
2583
+ const displayText = customInterval ? customInterval.text : formatRelativeTime(relativeTime, unit, { numeric: "auto" });
2584
+ return /* @__PURE__ */ jsxRuntime.jsx(
2585
+ "time",
2586
+ {
2587
+ ref: forwardedRef,
2588
+ dateTime: timestamp.toISOString(),
2589
+ role: "time",
2590
+ title: `${formatDate(timestamp)} ${formatTime(timestamp)}`,
2591
+ ...restProps,
2592
+ children: displayText
2512
2593
  }
2513
- },
2514
- variant: "danger",
2515
- position: ["header", "table-row"]
2516
- };
2594
+ );
2595
+ }
2596
+ );
2597
+ const getDisplayName = ({
2598
+ firstname,
2599
+ lastname,
2600
+ username,
2601
+ email
2602
+ } = {}) => {
2603
+ if (username) {
2604
+ return username;
2605
+ }
2606
+ if (firstname) {
2607
+ return `${firstname} ${lastname ?? ""}`.trim();
2608
+ }
2609
+ return email ?? "";
2517
2610
  };
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
- ) });
2611
+ const capitalise = (str) => str.charAt(0).toUpperCase() + str.slice(1);
2612
+ const DocumentStatus = ({ status = "draft", ...restProps }) => {
2613
+ const statusVariant = status === "draft" ? "secondary" : status === "published" ? "success" : "alternative";
2614
+ 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
2615
  };
2548
- const ActionsPanel = () => {
2616
+ const Header = ({ isCreating, status, title: documentTitle = "Untitled" }) => {
2549
2617
  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
- };
2618
+ const isCloning = reactRouterDom.useMatch(CLONE_PATH) !== null;
2619
+ const title = isCreating ? formatMessage({
2620
+ id: "content-manager.containers.edit.title.new",
2621
+ defaultMessage: "Create an entry"
2622
+ }) : documentTitle;
2623
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "flex-start", paddingTop: 6, paddingBottom: 4, gap: 2, children: [
2624
+ /* @__PURE__ */ jsxRuntime.jsx(strapiAdmin.BackButton, {}),
2625
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { width: "100%", justifyContent: "space-between", gap: "80px", alignItems: "flex-start", children: [
2626
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", tag: "h1", children: title }),
2627
+ /* @__PURE__ */ jsxRuntime.jsx(HeaderToolbar, {})
2628
+ ] }),
2629
+ status ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 1, children: /* @__PURE__ */ jsxRuntime.jsx(DocumentStatus, { status: isCloning ? "draft" : status }) }) : null
2630
+ ] });
2557
2631
  };
2558
- ActionsPanel.type = "actions";
2559
- const ActionsPanelContent = () => {
2632
+ const HeaderToolbar = () => {
2633
+ const { formatMessage } = reactIntl.useIntl();
2560
2634
  const isCloning = reactRouterDom.useMatch(CLONE_PATH) !== null;
2561
2635
  const [
2562
2636
  {
@@ -2564,355 +2638,432 @@ const ActionsPanelContent = () => {
2564
2638
  }
2565
2639
  ] = strapiAdmin.useQueryParams();
2566
2640
  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: [
2641
+ const plugins = strapiAdmin.useStrapiApp("HeaderToolbar", (state) => state.plugins);
2642
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
2577
2643
  /* @__PURE__ */ jsxRuntime.jsx(
2578
2644
  strapiAdmin.DescriptionComponentRenderer,
2579
2645
  {
2580
- props,
2581
- descriptions: plugins["content-manager"].apis.getDocumentActions(),
2582
- children: (actions2) => /* @__PURE__ */ jsxRuntime.jsx(DocumentActions, { actions: actions2 })
2646
+ props: {
2647
+ activeTab: status,
2648
+ model,
2649
+ documentId: id,
2650
+ document: isCloning ? void 0 : document,
2651
+ meta: isCloning ? void 0 : meta,
2652
+ collectionType
2653
+ },
2654
+ descriptions: plugins["content-manager"].apis.getHeaderActions(),
2655
+ children: (actions2) => {
2656
+ if (actions2.length > 0) {
2657
+ return /* @__PURE__ */ jsxRuntime.jsx(HeaderActions, { actions: actions2 });
2658
+ } else {
2659
+ return null;
2660
+ }
2661
+ }
2583
2662
  }
2584
2663
  ),
2585
- /* @__PURE__ */ jsxRuntime.jsx(InjectionZone, { area: "editView.right-links", slug: model })
2664
+ /* @__PURE__ */ jsxRuntime.jsx(
2665
+ strapiAdmin.DescriptionComponentRenderer,
2666
+ {
2667
+ props: {
2668
+ activeTab: status,
2669
+ model,
2670
+ documentId: id,
2671
+ document: isCloning ? void 0 : document,
2672
+ meta: isCloning ? void 0 : meta,
2673
+ collectionType
2674
+ },
2675
+ descriptions: plugins["content-manager"].apis.getDocumentActions(),
2676
+ children: (actions2) => {
2677
+ const headerActions = actions2.filter((action) => {
2678
+ const positions = Array.isArray(action.position) ? action.position : [action.position];
2679
+ return positions.includes("header");
2680
+ });
2681
+ return /* @__PURE__ */ jsxRuntime.jsx(
2682
+ DocumentActionsMenu,
2683
+ {
2684
+ actions: headerActions,
2685
+ label: formatMessage({
2686
+ id: "content-manager.containers.edit.header.more-actions",
2687
+ defaultMessage: "More actions"
2688
+ }),
2689
+ children: /* @__PURE__ */ jsxRuntime.jsx(Information, { activeTab: status })
2690
+ }
2691
+ );
2692
+ }
2693
+ }
2694
+ )
2586
2695
  ] });
2587
2696
  };
2588
- const Panel = React__namespace.forwardRef(({ children, title }, ref) => {
2589
- return /* @__PURE__ */ jsxRuntime.jsxs(
2590
- designSystem.Flex,
2697
+ const Information = ({ activeTab }) => {
2698
+ const { formatMessage } = reactIntl.useIntl();
2699
+ const { document, meta } = useDoc();
2700
+ if (!document || !document.id) {
2701
+ return null;
2702
+ }
2703
+ const createAndUpdateDocument = activeTab === "draft" ? document : meta?.availableStatus.find((status) => status.publishedAt === null);
2704
+ const publishDocument = activeTab === "published" ? document : meta?.availableStatus.find((status) => status.publishedAt !== null);
2705
+ const creator = createAndUpdateDocument?.[CREATED_BY_ATTRIBUTE_NAME] ? getDisplayName(createAndUpdateDocument[CREATED_BY_ATTRIBUTE_NAME]) : null;
2706
+ const updator = createAndUpdateDocument?.[UPDATED_BY_ATTRIBUTE_NAME] ? getDisplayName(createAndUpdateDocument[UPDATED_BY_ATTRIBUTE_NAME]) : null;
2707
+ const information = [
2591
2708
  {
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"
2709
+ isDisplayed: !!publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME],
2710
+ label: formatMessage({
2711
+ id: "content-manager.containers.edit.information.last-published.label",
2712
+ defaultMessage: "Published"
2646
2713
  }),
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
2714
+ value: formatMessage(
2715
+ {
2716
+ id: "content-manager.containers.edit.information.last-published.value",
2717
+ defaultMessage: `{time}{isAnonymous, select, true {} other { by {author}}}`
2718
+ },
2719
+ {
2720
+ time: /* @__PURE__ */ jsxRuntime.jsx(RelativeTime, { timestamp: new Date(publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME]) }),
2721
+ isAnonymous: !publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME],
2722
+ author: publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME] ? getDisplayName(publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME]) : null
2723
+ }
2724
+ )
2725
+ },
2726
+ {
2727
+ isDisplayed: !!createAndUpdateDocument?.[UPDATED_AT_ATTRIBUTE_NAME],
2728
+ label: formatMessage({
2729
+ id: "content-manager.containers.edit.information.last-draft.label",
2730
+ defaultMessage: "Updated"
2731
+ }),
2732
+ value: formatMessage(
2733
+ {
2734
+ id: "content-manager.containers.edit.information.last-draft.value",
2735
+ defaultMessage: `{time}{isAnonymous, select, true {} other { by {author}}}`
2736
+ },
2737
+ {
2738
+ time: /* @__PURE__ */ jsxRuntime.jsx(
2739
+ RelativeTime,
2740
+ {
2741
+ timestamp: new Date(createAndUpdateDocument?.[UPDATED_AT_ATTRIBUTE_NAME])
2742
+ }
2743
+ ),
2744
+ isAnonymous: !updator,
2745
+ author: updator
2746
+ }
2747
+ )
2748
+ },
2749
+ {
2750
+ isDisplayed: !!createAndUpdateDocument?.[CREATED_AT_ATTRIBUTE_NAME],
2751
+ label: formatMessage({
2752
+ id: "content-manager.containers.edit.information.document.label",
2753
+ defaultMessage: "Created"
2663
2754
  }),
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
2755
+ value: formatMessage(
2756
+ {
2757
+ id: "content-manager.containers.edit.information.document.value",
2758
+ defaultMessage: `{time}{isAnonymous, select, true {} other { by {author}}}`
2759
+ },
2760
+ {
2761
+ time: /* @__PURE__ */ jsxRuntime.jsx(
2762
+ RelativeTime,
2763
+ {
2764
+ timestamp: new Date(createAndUpdateDocument?.[CREATED_AT_ATTRIBUTE_NAME])
2765
+ }
2766
+ ),
2767
+ isAnonymous: !creator,
2768
+ author: creator
2769
+ }
2770
+ )
2771
+ }
2772
+ ].filter((info) => info.isDisplayed);
2773
+ return /* @__PURE__ */ jsxRuntime.jsx(
2774
+ designSystem.Flex,
2775
+ {
2776
+ borderWidth: "1px 0 0 0",
2777
+ borderStyle: "solid",
2778
+ borderColor: "neutral150",
2779
+ direction: "column",
2780
+ marginTop: 2,
2781
+ tag: "dl",
2782
+ padding: 5,
2783
+ gap: 3,
2784
+ alignItems: "flex-start",
2785
+ marginLeft: "-0.4rem",
2786
+ marginRight: "-0.4rem",
2787
+ width: "calc(100% + 8px)",
2788
+ children: information.map((info) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, direction: "column", alignItems: "flex-start", children: [
2789
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "dt", variant: "pi", fontWeight: "bold", children: info.label }),
2790
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "dd", variant: "pi", textColor: "neutral600", children: info.value })
2791
+ ] }, info.label))
2792
+ }
2693
2793
  );
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
2794
  };
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
- });
2795
+ const HeaderActions = ({ actions: actions2 }) => {
2796
+ const [dialogId, setDialogId] = React__namespace.useState(null);
2797
+ const handleClick = (action) => async (e) => {
2798
+ if (!("options" in action)) {
2799
+ const { onClick = () => false, dialog, id } = action;
2800
+ const muteDialog = await onClick(e);
2801
+ if (dialog && !muteDialog) {
2802
+ e.preventDefault();
2803
+ setDialogId(id);
2804
+ }
2729
2805
  }
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
2806
  };
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;
2807
+ const handleClose = () => {
2808
+ setDialogId(null);
2809
+ };
2810
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 1, children: actions2.map((action) => {
2811
+ if (action.options) {
2812
+ return /* @__PURE__ */ jsxRuntime.jsx(
2813
+ designSystem.SingleSelect,
2814
+ {
2815
+ size: "S",
2816
+ onChange: action.onSelect,
2817
+ "aria-label": action.label,
2818
+ ...action,
2819
+ children: action.options.map(({ label, ...option }) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { ...option, children: label }, option.value))
2820
+ },
2821
+ action.id
2822
+ );
2783
2823
  } else {
2784
- if (!panels[currentPanelIndex]) {
2785
- panels.push([]);
2824
+ if (action.type === "icon") {
2825
+ return /* @__PURE__ */ jsxRuntime.jsxs(React__namespace.Fragment, { children: [
2826
+ /* @__PURE__ */ jsxRuntime.jsx(
2827
+ designSystem.IconButton,
2828
+ {
2829
+ disabled: action.disabled,
2830
+ label: action.label,
2831
+ size: "S",
2832
+ onClick: handleClick(action),
2833
+ children: action.icon
2834
+ }
2835
+ ),
2836
+ action.dialog ? /* @__PURE__ */ jsxRuntime.jsx(
2837
+ HeaderActionDialog,
2838
+ {
2839
+ ...action.dialog,
2840
+ isOpen: dialogId === action.id,
2841
+ onClose: handleClose
2842
+ }
2843
+ ) : null
2844
+ ] }, action.id);
2786
2845
  }
2787
- panels[currentPanelIndex].push(row);
2788
2846
  }
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;
2806
- },
2807
- {}
2808
- );
2809
- const editMetadatas = Object.entries(data.contentType.metadatas).reduce(
2810
- (acc, [attribute, metadata]) => {
2811
- return {
2812
- ...acc,
2813
- [attribute]: metadata.edit
2814
- };
2847
+ }) });
2848
+ };
2849
+ const HeaderActionDialog = ({
2850
+ onClose,
2851
+ onCancel,
2852
+ title,
2853
+ content: Content,
2854
+ isOpen
2855
+ }) => {
2856
+ const handleClose = async () => {
2857
+ if (onCancel) {
2858
+ await onCancel();
2859
+ }
2860
+ onClose();
2861
+ };
2862
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Dialog.Content, { children: [
2863
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Header, { children: title }),
2864
+ typeof Content === "function" ? /* @__PURE__ */ jsxRuntime.jsx(Content, { onClose: handleClose }) : Content
2865
+ ] }) });
2866
+ };
2867
+ const ConfigureTheViewAction = ({ collectionType, model }) => {
2868
+ const navigate = reactRouterDom.useNavigate();
2869
+ const { formatMessage } = reactIntl.useIntl();
2870
+ return {
2871
+ label: formatMessage({
2872
+ id: "app.links.configure-view",
2873
+ defaultMessage: "Configure the view"
2874
+ }),
2875
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Icons.ListPlus, {}),
2876
+ onClick: () => {
2877
+ navigate(`../${collectionType}/${model}/configurations/edit`);
2815
2878
  },
2816
- {}
2817
- );
2879
+ position: "header"
2880
+ };
2881
+ };
2882
+ ConfigureTheViewAction.type = "configure-the-view";
2883
+ const EditTheModelAction = ({ model }) => {
2884
+ const navigate = reactRouterDom.useNavigate();
2885
+ const { formatMessage } = reactIntl.useIntl();
2818
2886
  return {
2819
- layout: panelledEditAttributes,
2820
- components: componentEditAttributes,
2821
- metadatas: editMetadatas,
2822
- settings: {
2823
- ...data.contentType.settings,
2824
- displayName: schema?.info.displayName
2887
+ label: formatMessage({
2888
+ id: "content-manager.link-to-ctb",
2889
+ defaultMessage: "Edit the model"
2890
+ }),
2891
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Icons.Pencil, {}),
2892
+ onClick: () => {
2893
+ navigate(`/plugins/content-type-builder/content-types/${model}`);
2825
2894
  },
2826
- options: {
2827
- ...schema?.options,
2828
- ...schema?.pluginOptions,
2829
- ...data.contentType.options
2830
- }
2895
+ position: "header"
2831
2896
  };
2832
2897
  };
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;
2898
+ EditTheModelAction.type = "edit-the-model";
2899
+ const DeleteAction$1 = ({ documentId, model, collectionType, document }) => {
2900
+ const navigate = reactRouterDom.useNavigate();
2901
+ const { formatMessage } = reactIntl.useIntl();
2902
+ const listViewPathMatch = reactRouterDom.useMatch(LIST_PATH);
2903
+ const canDelete = useDocumentRBAC("DeleteAction", (state) => state.canDelete);
2904
+ const { delete: deleteAction } = useDocumentActions();
2905
+ const { toggleNotification } = strapiAdmin.useNotification();
2906
+ const setSubmitting = strapiAdmin.useForm("DeleteAction", (state) => state.setSubmitting);
2907
+ const isLocalized = document?.locale != null;
2908
+ return {
2909
+ disabled: !canDelete || !document,
2910
+ label: formatMessage(
2911
+ {
2912
+ id: "content-manager.actions.delete.label",
2913
+ defaultMessage: "Delete entry{isLocalized, select, true { (all locales)} other {}}"
2914
+ },
2915
+ { isLocalized }
2916
+ ),
2917
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Icons.Trash, {}),
2918
+ dialog: {
2919
+ type: "dialog",
2920
+ title: formatMessage({
2921
+ id: "app.components.ConfirmDialog.title",
2922
+ defaultMessage: "Confirmation"
2923
+ }),
2924
+ content: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 2, children: [
2925
+ /* @__PURE__ */ jsxRuntime.jsx(Icons.WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
2926
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
2927
+ id: "content-manager.actions.delete.dialog.body",
2928
+ defaultMessage: "Are you sure?"
2929
+ }) })
2930
+ ] }),
2931
+ onConfirm: async () => {
2932
+ if (!listViewPathMatch) {
2933
+ setSubmitting(true);
2934
+ }
2935
+ try {
2936
+ if (!documentId && collectionType !== SINGLE_TYPES) {
2937
+ console.error(
2938
+ "You're trying to delete a document without an id, this is likely a bug with Strapi. Please open an issue."
2939
+ );
2940
+ toggleNotification({
2941
+ message: formatMessage({
2942
+ id: "content-manager.actions.delete.error",
2943
+ defaultMessage: "An error occurred while trying to delete the document."
2944
+ }),
2945
+ type: "danger"
2946
+ });
2947
+ return;
2948
+ }
2949
+ const res = await deleteAction({
2950
+ documentId,
2951
+ model,
2952
+ collectionType,
2953
+ params: {
2954
+ locale: "*"
2955
+ }
2956
+ });
2957
+ if (!("error" in res)) {
2958
+ navigate({ pathname: `../${collectionType}/${model}` }, { replace: true });
2959
+ }
2960
+ } finally {
2961
+ if (!listViewPathMatch) {
2962
+ setSubmitting(false);
2963
+ }
2964
+ }
2839
2965
  }
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
- );
2862
- };
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
2966
  },
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
- );
2884
- 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
2967
+ variant: "danger",
2968
+ position: ["header", "table-row"]
2969
+ };
2970
+ };
2971
+ DeleteAction$1.type = "delete";
2972
+ const DEFAULT_HEADER_ACTIONS = [EditTheModelAction, ConfigureTheViewAction, DeleteAction$1];
2973
+ const Panels = () => {
2974
+ const isCloning = reactRouterDom.useMatch(CLONE_PATH) !== null;
2975
+ const [
2976
+ {
2977
+ query: { status }
2978
+ }
2979
+ ] = strapiAdmin.useQueryParams({
2980
+ status: "draft"
2981
+ });
2982
+ const { model, id, document, meta, collectionType } = useDoc();
2983
+ const plugins = strapiAdmin.useStrapiApp("Panels", (state) => state.plugins);
2984
+ const props = {
2985
+ activeTab: status,
2986
+ model,
2987
+ documentId: id,
2988
+ document: isCloning ? void 0 : document,
2989
+ meta: isCloning ? void 0 : meta,
2990
+ collectionType
2991
+ };
2992
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 2, children: /* @__PURE__ */ jsxRuntime.jsx(
2993
+ strapiAdmin.DescriptionComponentRenderer,
2994
+ {
2995
+ props,
2996
+ descriptions: plugins["content-manager"].apis.getEditViewSidePanels(),
2997
+ children: (panels) => panels.map(({ content, id: id2, ...description }) => /* @__PURE__ */ jsxRuntime.jsx(Panel, { ...description, children: content }, id2))
2892
2998
  }
2999
+ ) });
3000
+ };
3001
+ const ActionsPanel = () => {
3002
+ const { formatMessage } = reactIntl.useIntl();
3003
+ return {
3004
+ title: formatMessage({
3005
+ id: "content-manager.containers.edit.panels.default.title",
3006
+ defaultMessage: "Entry"
3007
+ }),
3008
+ content: /* @__PURE__ */ jsxRuntime.jsx(ActionsPanelContent, {})
2893
3009
  };
2894
3010
  };
2895
- const convertListLayoutToFieldLayouts = (columns, attributes = {}, metadatas, components, schemas = []) => {
2896
- return columns.map((name) => {
2897
- const attribute = attributes[name];
2898
- if (!attribute) {
2899
- return null;
3011
+ ActionsPanel.type = "actions";
3012
+ const ActionsPanelContent = () => {
3013
+ const isCloning = reactRouterDom.useMatch(CLONE_PATH) !== null;
3014
+ const [
3015
+ {
3016
+ query: { status = "draft" }
2900
3017
  }
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);
3018
+ ] = strapiAdmin.useQueryParams();
3019
+ const { model, id, document, meta, collectionType } = useDoc();
3020
+ const plugins = strapiAdmin.useStrapiApp("ActionsPanel", (state) => state.plugins);
3021
+ const props = {
3022
+ activeTab: status,
3023
+ model,
3024
+ documentId: id,
3025
+ document: isCloning ? void 0 : document,
3026
+ meta: isCloning ? void 0 : meta,
3027
+ collectionType
3028
+ };
3029
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 2, width: "100%", children: [
3030
+ /* @__PURE__ */ jsxRuntime.jsx(
3031
+ strapiAdmin.DescriptionComponentRenderer,
3032
+ {
3033
+ props,
3034
+ descriptions: plugins["content-manager"].apis.getDocumentActions(),
3035
+ children: (actions2) => /* @__PURE__ */ jsxRuntime.jsx(DocumentActions, { actions: actions2 })
3036
+ }
3037
+ ),
3038
+ /* @__PURE__ */ jsxRuntime.jsx(InjectionZone, { area: "editView.right-links", slug: model })
3039
+ ] });
2915
3040
  };
3041
+ const Panel = React__namespace.forwardRef(({ children, title }, ref) => {
3042
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3043
+ designSystem.Flex,
3044
+ {
3045
+ ref,
3046
+ tag: "aside",
3047
+ "aria-labelledby": "additional-information",
3048
+ background: "neutral0",
3049
+ borderColor: "neutral150",
3050
+ hasRadius: true,
3051
+ paddingBottom: 4,
3052
+ paddingLeft: 4,
3053
+ paddingRight: 4,
3054
+ paddingTop: 4,
3055
+ shadow: "tableShadow",
3056
+ gap: 3,
3057
+ direction: "column",
3058
+ justifyContent: "stretch",
3059
+ alignItems: "flex-start",
3060
+ children: [
3061
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "h2", variant: "sigma", textTransform: "uppercase", children: title }),
3062
+ children
3063
+ ]
3064
+ }
3065
+ );
3066
+ });
2916
3067
  const ConfirmBulkActionDialog = ({
2917
3068
  onToggleDialog,
2918
3069
  isOpen = false,
@@ -2951,6 +3102,7 @@ const ConfirmDialogPublishAll = ({
2951
3102
  const { _unstableFormatAPIError: formatAPIError } = strapiAdmin.useAPIErrorHandler(getTranslation);
2952
3103
  const { model, schema } = useDoc();
2953
3104
  const [{ query }] = strapiAdmin.useQueryParams();
3105
+ const enableDraftRelationsCount = false;
2954
3106
  const {
2955
3107
  data: countDraftRelations = 0,
2956
3108
  isLoading,
@@ -2962,7 +3114,7 @@ const ConfirmDialogPublishAll = ({
2962
3114
  locale: query?.plugins?.i18n?.locale
2963
3115
  },
2964
3116
  {
2965
- skip: selectedEntries.length === 0
3117
+ skip: !enableDraftRelationsCount
2966
3118
  }
2967
3119
  );
2968
3120
  React__namespace.useEffect(() => {
@@ -3147,7 +3299,7 @@ const SelectedEntriesTableContent = ({
3147
3299
  status: row.status
3148
3300
  }
3149
3301
  ) }),
3150
- /* @__PURE__ */ jsxRuntime.jsx(strapiAdmin.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(
3302
+ /* @__PURE__ */ jsxRuntime.jsx(strapiAdmin.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { children: /* @__PURE__ */ jsxRuntime.jsx(
3151
3303
  designSystem.IconButton,
3152
3304
  {
3153
3305
  tag: reactRouterDom.Link,
@@ -3170,9 +3322,10 @@ const SelectedEntriesTableContent = ({
3170
3322
  ),
3171
3323
  target: "_blank",
3172
3324
  marginLeft: "auto",
3173
- children: /* @__PURE__ */ jsxRuntime.jsx(Icons.Pencil, {})
3325
+ variant: "ghost",
3326
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icons.Pencil, { width: "1.6rem", height: "1.6rem" })
3174
3327
  }
3175
- ) })
3328
+ ) }) })
3176
3329
  ] }, row.id)) })
3177
3330
  ] });
3178
3331
  };
@@ -3209,7 +3362,13 @@ const SelectedEntriesModalContent = ({
3209
3362
  );
3210
3363
  const { rows, validationErrors } = React__namespace.useMemo(() => {
3211
3364
  if (data.length > 0 && schema) {
3212
- const validate = createYupSchema(schema.attributes, components);
3365
+ const validate = createYupSchema(
3366
+ schema.attributes,
3367
+ components,
3368
+ // Since this is the "Publish" action, the validation
3369
+ // schema must enforce the rules for published entities
3370
+ { status: "published" }
3371
+ );
3213
3372
  const validationErrors2 = {};
3214
3373
  const rows2 = data.map((entry) => {
3215
3374
  try {
@@ -3559,7 +3718,7 @@ const TableActions = ({ document }) => {
3559
3718
  strapiAdmin.DescriptionComponentRenderer,
3560
3719
  {
3561
3720
  props,
3562
- descriptions: plugins["content-manager"].apis.getDocumentActions(),
3721
+ descriptions: plugins["content-manager"].apis.getDocumentActions().filter((action) => action.name !== "PublishAction"),
3563
3722
  children: (actions2) => {
3564
3723
  const tableRowActions = actions2.filter((action) => {
3565
3724
  const positions = Array.isArray(action.position) ? action.position : [action.position];
@@ -3670,7 +3829,7 @@ const CloneAction = ({ model, documentId }) => {
3670
3829
  }),
3671
3830
  content: /* @__PURE__ */ jsxRuntime.jsx(AutoCloneFailureModalBody, { prohibitedFields }),
3672
3831
  footer: ({ onClose }) => {
3673
- return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", children: [
3832
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Footer, { children: [
3674
3833
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: onClose, variant: "tertiary", children: formatMessage({
3675
3834
  id: "cancel",
3676
3835
  defaultMessage: "Cancel"
@@ -3901,7 +4060,7 @@ const index = {
3901
4060
  app.router.addRoute({
3902
4061
  path: "content-manager/*",
3903
4062
  lazy: async () => {
3904
- const { Layout } = await Promise.resolve().then(() => require("./layout-C6dxWYT7.js"));
4063
+ const { Layout } = await Promise.resolve().then(() => require("./layout-CrgXpOxT.js"));
3905
4064
  return {
3906
4065
  Component: Layout
3907
4066
  };
@@ -3918,7 +4077,7 @@ const index = {
3918
4077
  async registerTrads({ locales }) {
3919
4078
  const importedTrads = await Promise.all(
3920
4079
  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 }) => {
4080
+ 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
4081
  return {
3923
4082
  data: prefixPluginTranslations(data, PLUGIN_ID),
3924
4083
  locale
@@ -3936,6 +4095,7 @@ const index = {
3936
4095
  };
3937
4096
  exports.ATTRIBUTE_TYPES_THAT_CANNOT_BE_MAIN_FIELD = ATTRIBUTE_TYPES_THAT_CANNOT_BE_MAIN_FIELD;
3938
4097
  exports.BulkActionsRenderer = BulkActionsRenderer;
4098
+ exports.CLONE_PATH = CLONE_PATH;
3939
4099
  exports.COLLECTION_TYPES = COLLECTION_TYPES;
3940
4100
  exports.CREATOR_FIELDS = CREATOR_FIELDS;
3941
4101
  exports.DEFAULT_SETTINGS = DEFAULT_SETTINGS;
@@ -3963,6 +4123,7 @@ exports.getMainField = getMainField;
3963
4123
  exports.getTranslation = getTranslation;
3964
4124
  exports.index = index;
3965
4125
  exports.setInitialData = setInitialData;
4126
+ exports.useContentManagerContext = useContentManagerContext;
3966
4127
  exports.useContentTypeSchema = useContentTypeSchema;
3967
4128
  exports.useDoc = useDoc;
3968
4129
  exports.useDocLayout = useDocLayout;
@@ -3975,4 +4136,4 @@ exports.useGetAllDocumentsQuery = useGetAllDocumentsQuery;
3975
4136
  exports.useGetContentTypeConfigurationQuery = useGetContentTypeConfigurationQuery;
3976
4137
  exports.useGetInitialDataQuery = useGetInitialDataQuery;
3977
4138
  exports.useUpdateContentTypeConfigurationMutation = useUpdateContentTypeConfigurationMutation;
3978
- //# sourceMappingURL=index-CdT0kHZ8.js.map
4139
+ //# sourceMappingURL=index-DcA8_tJw.js.map