@strapi/content-manager 5.0.0-rc.2 → 5.0.0-rc.21

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 (116) hide show
  1. package/dist/_chunks/{ComponentConfigurationPage-Bv-IXOYu.js → ComponentConfigurationPage-DnnZJc1F.js} +3 -3
  2. package/dist/_chunks/{ComponentConfigurationPage-Bv-IXOYu.js.map → ComponentConfigurationPage-DnnZJc1F.js.map} +1 -1
  3. package/dist/_chunks/{ComponentConfigurationPage-BxJCkKZV.mjs → ComponentConfigurationPage-hLMNf7KI.mjs} +3 -3
  4. package/dist/_chunks/{ComponentConfigurationPage-BxJCkKZV.mjs.map → ComponentConfigurationPage-hLMNf7KI.mjs.map} +1 -1
  5. package/dist/_chunks/{EditConfigurationPage-QZl5zOz-.js → EditConfigurationPage-CpLj5gYZ.js} +3 -3
  6. package/dist/_chunks/{EditConfigurationPage-QZl5zOz-.js.map → EditConfigurationPage-CpLj5gYZ.js.map} +1 -1
  7. package/dist/_chunks/{EditConfigurationPage-BGwHNypQ.mjs → EditConfigurationPage-Dh6sq-G4.mjs} +3 -3
  8. package/dist/_chunks/{EditConfigurationPage-BGwHNypQ.mjs.map → EditConfigurationPage-Dh6sq-G4.mjs.map} +1 -1
  9. package/dist/_chunks/{EditViewPage-CtdtssrH.mjs → EditViewPage-BU1ugeVi.mjs} +19 -8
  10. package/dist/_chunks/EditViewPage-BU1ugeVi.mjs.map +1 -0
  11. package/dist/_chunks/{EditViewPage-DxKueadW.js → EditViewPage-D2QVRr_2.js} +19 -8
  12. package/dist/_chunks/EditViewPage-D2QVRr_2.js.map +1 -0
  13. package/dist/_chunks/{Field-BPw8fE3W.js → Field-BEDX9i_V.js} +120 -92
  14. package/dist/_chunks/Field-BEDX9i_V.js.map +1 -0
  15. package/dist/_chunks/{Field-BU7_nR4F.mjs → Field-VSPY6uzs.mjs} +118 -90
  16. package/dist/_chunks/Field-VSPY6uzs.mjs.map +1 -0
  17. package/dist/_chunks/{Form-ffghBTPI.mjs → Form-05Oaes1N.mjs} +35 -16
  18. package/dist/_chunks/Form-05Oaes1N.mjs.map +1 -0
  19. package/dist/_chunks/{Form-DtvmbGdZ.js → Form-DCaY8xBX.js} +35 -16
  20. package/dist/_chunks/Form-DCaY8xBX.js.map +1 -0
  21. package/dist/_chunks/{History-D6PRyNcx.mjs → History-BqO2G3MV.mjs} +4 -4
  22. package/dist/_chunks/{History-D6PRyNcx.mjs.map → History-BqO2G3MV.mjs.map} +1 -1
  23. package/dist/_chunks/{History-CSr8y9KM.js → History-BrJ1tUvt.js} +4 -4
  24. package/dist/_chunks/{History-CSr8y9KM.js.map → History-BrJ1tUvt.js.map} +1 -1
  25. package/dist/_chunks/{ListConfigurationPage-BC9bCi9k.mjs → ListConfigurationPage-C6rsFlme.mjs} +14 -4
  26. package/dist/_chunks/ListConfigurationPage-C6rsFlme.mjs.map +1 -0
  27. package/dist/_chunks/{ListConfigurationPage-DsmAQ3YM.js → ListConfigurationPage-Eane5LKE.js} +14 -4
  28. package/dist/_chunks/ListConfigurationPage-Eane5LKE.js.map +1 -0
  29. package/dist/_chunks/{ListViewPage-DqAIb_ie.js → ListViewPage-Coj-RPsx.js} +49 -40
  30. package/dist/_chunks/ListViewPage-Coj-RPsx.js.map +1 -0
  31. package/dist/_chunks/{ListViewPage-B1GyNqfn.mjs → ListViewPage-yE_zYhcI.mjs} +47 -38
  32. package/dist/_chunks/ListViewPage-yE_zYhcI.mjs.map +1 -0
  33. package/dist/_chunks/{NoContentTypePage-xjvn5XwY.js → NoContentTypePage-BDJ0dshy.js} +2 -2
  34. package/dist/_chunks/{NoContentTypePage-xjvn5XwY.js.map → NoContentTypePage-BDJ0dshy.js.map} +1 -1
  35. package/dist/_chunks/{NoContentTypePage-CJ-HJriz.mjs → NoContentTypePage-NW_FSVdY.mjs} +2 -2
  36. package/dist/_chunks/{NoContentTypePage-CJ-HJriz.mjs.map → NoContentTypePage-NW_FSVdY.mjs.map} +1 -1
  37. package/dist/_chunks/{NoPermissionsPage-DObTkKmZ.js → NoPermissionsPage-BOtb5FTM.js} +2 -2
  38. package/dist/_chunks/{NoPermissionsPage-DObTkKmZ.js.map → NoPermissionsPage-BOtb5FTM.js.map} +1 -1
  39. package/dist/_chunks/{NoPermissionsPage--afHbbbD.mjs → NoPermissionsPage-h0I3ImsX.mjs} +2 -2
  40. package/dist/_chunks/{NoPermissionsPage--afHbbbD.mjs.map → NoPermissionsPage-h0I3ImsX.mjs.map} +1 -1
  41. package/dist/_chunks/{Relations-t4Q0DpqW.js → Relations-CVh0DOKv.js} +4 -4
  42. package/dist/_chunks/{Relations-t4Q0DpqW.js.map → Relations-CVh0DOKv.js.map} +1 -1
  43. package/dist/_chunks/{Relations-heq-nLGU.mjs → Relations-FP0uWpBz.mjs} +4 -4
  44. package/dist/_chunks/{Relations-heq-nLGU.mjs.map → Relations-FP0uWpBz.mjs.map} +1 -1
  45. package/dist/_chunks/{en-uOUIxfcQ.js → en-BlhnxQfj.js} +7 -6
  46. package/dist/_chunks/{en-uOUIxfcQ.js.map → en-BlhnxQfj.js.map} +1 -1
  47. package/dist/_chunks/{en-BrCTWlZv.mjs → en-C8YBvRrK.mjs} +7 -6
  48. package/dist/_chunks/{en-BrCTWlZv.mjs.map → en-C8YBvRrK.mjs.map} +1 -1
  49. package/dist/_chunks/{index-BcQ8cRyl.mjs → index-CPCHQ3X_.mjs} +1927 -1765
  50. package/dist/_chunks/index-CPCHQ3X_.mjs.map +1 -0
  51. package/dist/_chunks/{index-1zxclxo_.js → index-DTKVhcla.js} +1907 -1745
  52. package/dist/_chunks/index-DTKVhcla.js.map +1 -0
  53. package/dist/_chunks/{layout-Jl9mJFJZ.mjs → layout-B4UhJ8MJ.mjs} +22 -9
  54. package/dist/_chunks/layout-B4UhJ8MJ.mjs.map +1 -0
  55. package/dist/_chunks/{layout-tVvbqota.js → layout-CWgZzMYf.js} +21 -8
  56. package/dist/_chunks/layout-CWgZzMYf.js.map +1 -0
  57. package/dist/_chunks/{relations-f4Pv7Kgo.mjs → relations-B83Ge9a7.mjs} +2 -2
  58. package/dist/_chunks/{relations-f4Pv7Kgo.mjs.map → relations-B83Ge9a7.mjs.map} +1 -1
  59. package/dist/_chunks/{relations-CK2Jd0HM.js → relations-D81a_2zw.js} +2 -2
  60. package/dist/_chunks/{relations-CK2Jd0HM.js.map → relations-D81a_2zw.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 +30 -1
  71. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/EditorLayout.d.ts +2 -2
  72. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/WysiwygFooter.d.ts +2 -2
  73. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/WysiwygStyles.d.ts +4 -48
  74. package/dist/admin/src/pages/EditView/components/Header.d.ts +10 -11
  75. package/dist/admin/src/services/api.d.ts +1 -1
  76. package/dist/admin/src/services/components.d.ts +2 -2
  77. package/dist/admin/src/services/contentTypes.d.ts +3 -3
  78. package/dist/admin/src/services/documents.d.ts +19 -17
  79. package/dist/admin/src/services/init.d.ts +1 -1
  80. package/dist/admin/src/services/relations.d.ts +2 -2
  81. package/dist/admin/src/services/uid.d.ts +3 -3
  82. package/dist/admin/src/utils/validation.d.ts +4 -1
  83. package/dist/server/index.js +147 -82
  84. package/dist/server/index.js.map +1 -1
  85. package/dist/server/index.mjs +148 -83
  86. package/dist/server/index.mjs.map +1 -1
  87. package/dist/server/src/controllers/collection-types.d.ts.map +1 -1
  88. package/dist/server/src/controllers/relations.d.ts.map +1 -1
  89. package/dist/server/src/history/services/history.d.ts.map +1 -1
  90. package/dist/server/src/history/services/lifecycles.d.ts.map +1 -1
  91. package/dist/server/src/history/services/utils.d.ts +2 -1
  92. package/dist/server/src/history/services/utils.d.ts.map +1 -1
  93. package/dist/server/src/policies/hasPermissions.d.ts.map +1 -1
  94. package/dist/server/src/services/document-metadata.d.ts.map +1 -1
  95. package/dist/server/src/services/permission-checker.d.ts.map +1 -1
  96. package/dist/shared/contracts/collection-types.d.ts +3 -1
  97. package/dist/shared/contracts/collection-types.d.ts.map +1 -1
  98. package/package.json +11 -11
  99. package/dist/_chunks/EditViewPage-CtdtssrH.mjs.map +0 -1
  100. package/dist/_chunks/EditViewPage-DxKueadW.js.map +0 -1
  101. package/dist/_chunks/Field-BPw8fE3W.js.map +0 -1
  102. package/dist/_chunks/Field-BU7_nR4F.mjs.map +0 -1
  103. package/dist/_chunks/Form-DtvmbGdZ.js.map +0 -1
  104. package/dist/_chunks/Form-ffghBTPI.mjs.map +0 -1
  105. package/dist/_chunks/ListConfigurationPage-BC9bCi9k.mjs.map +0 -1
  106. package/dist/_chunks/ListConfigurationPage-DsmAQ3YM.js.map +0 -1
  107. package/dist/_chunks/ListViewPage-B1GyNqfn.mjs.map +0 -1
  108. package/dist/_chunks/ListViewPage-DqAIb_ie.js.map +0 -1
  109. package/dist/_chunks/index-1zxclxo_.js.map +0 -1
  110. package/dist/_chunks/index-BcQ8cRyl.mjs.map +0 -1
  111. package/dist/_chunks/layout-Jl9mJFJZ.mjs.map +0 -1
  112. package/dist/_chunks/layout-tVvbqota.js.map +0 -1
  113. package/dist/_chunks/usePrev-B9w_-eYc.js.map +0 -1
  114. package/dist/_chunks/usePrev-DH6iah0A.mjs +0 -16
  115. package/dist/_chunks/usePrev-DH6iah0A.mjs.map +0 -1
  116. package/strapi-server.js +0 -3
@@ -1,16 +1,16 @@
1
- import { CrossCircle, More, WarningCircle, ListPlus, Pencil, Trash, Check, CheckCircle, ArrowsCounterClockwise, ChevronRight, Duplicate, ClockCounterClockwise, Feather } from "@strapi/icons";
1
+ import { More, Cross, WarningCircle, ListPlus, Pencil, Trash, Check, CrossCircle, CheckCircle, ArrowsCounterClockwise, ChevronRight, Duplicate, ClockCounterClockwise, Feather } from "@strapi/icons";
2
2
  import { jsx, Fragment, jsxs } from "react/jsx-runtime";
3
- import { useStrapiApp, createContext, useAuth, useRBAC, Page, adminApi, translatedErrors, useNotification, useAPIErrorHandler, getYupValidationErrors, useQueryParams, useTracking, useForm, BackButton, DescriptionComponentRenderer, useTable, Table } from "@strapi/admin/strapi-admin";
3
+ import { useStrapiApp, createContext, useAuth, useRBAC, Page, adminApi, translatedErrors, useNotification, useAPIErrorHandler, useQueryParams, getYupValidationErrors, useForm, useTracking, useGuidedTour, BackButton, DescriptionComponentRenderer, useTable, Table } from "@strapi/admin/strapi-admin";
4
4
  import * as React from "react";
5
5
  import { lazy } from "react";
6
- import { Button, Menu, VisuallyHidden, Flex, Box, Typography, Dialog, Modal, Radio, Status, SingleSelect, SingleSelectOption, Loader, IconButton, Tooltip, LinkButton } from "@strapi/design-system";
6
+ import { Button, Menu, VisuallyHidden, Flex, Typography, Dialog, Modal, Radio, Status, Box, SingleSelect, SingleSelectOption, IconButton, Loader, Tooltip, LinkButton } from "@strapi/design-system";
7
7
  import { useIntl } from "react-intl";
8
- import { useParams, Navigate, useNavigate, useMatch, useLocation, Link, NavLink } from "react-router-dom";
9
- import { styled } from "styled-components";
8
+ import { useParams, useNavigate, Navigate, useMatch, useLocation, Link, NavLink } from "react-router-dom";
10
9
  import * as yup from "yup";
11
10
  import { ValidationError } from "yup";
12
11
  import pipe from "lodash/fp/pipe";
13
12
  import { intervalToDuration, isPast } from "date-fns";
13
+ import { styled } from "styled-components";
14
14
  import { stringify } from "qs";
15
15
  import { createSlice, combineReducers } from "@reduxjs/toolkit";
16
16
  const __variableDynamicImportRuntimeHelper = (glob, path) => {
@@ -158,7 +158,8 @@ const contentManagerApi = adminApi.enhanceEndpoints({
158
158
  "Document",
159
159
  "InitialData",
160
160
  "HistoryVersion",
161
- "Relations"
161
+ "Relations",
162
+ "UidAvailability"
162
163
  ]
163
164
  });
164
165
  const documentApi = contentManagerApi.injectEndpoints({
@@ -188,7 +189,10 @@ const documentApi = contentManagerApi.injectEndpoints({
188
189
  params
189
190
  }
190
191
  }),
191
- invalidatesTags: (_result, _error, { model }) => [{ type: "Document", id: `${model}_LIST` }]
192
+ invalidatesTags: (_result, _error, { model }) => [
193
+ { type: "Document", id: `${model}_LIST` },
194
+ { type: "UidAvailability", id: model }
195
+ ]
192
196
  }),
193
197
  /**
194
198
  * Creates a new collection-type document. This should ONLY be used for collection-types.
@@ -205,7 +209,8 @@ const documentApi = contentManagerApi.injectEndpoints({
205
209
  }),
206
210
  invalidatesTags: (result, _error, { model }) => [
207
211
  { type: "Document", id: `${model}_LIST` },
208
- "Relations"
212
+ "Relations",
213
+ { type: "UidAvailability", id: model }
209
214
  ]
210
215
  }),
211
216
  deleteDocument: builder.mutation({
@@ -246,7 +251,8 @@ const documentApi = contentManagerApi.injectEndpoints({
246
251
  id: collectionType !== SINGLE_TYPES ? `${model}_${documentId}` : model
247
252
  },
248
253
  { type: "Document", id: `${model}_LIST` },
249
- "Relations"
254
+ "Relations",
255
+ { type: "UidAvailability", id: model }
250
256
  ];
251
257
  }
252
258
  }),
@@ -264,6 +270,7 @@ const documentApi = contentManagerApi.injectEndpoints({
264
270
  }),
265
271
  providesTags: (result, _error, arg) => {
266
272
  return [
273
+ { type: "Document", id: `ALL_LIST` },
267
274
  { type: "Document", id: `${arg.model}_LIST` },
268
275
  ...result?.results.map(({ documentId }) => ({
269
276
  type: "Document",
@@ -302,6 +309,11 @@ const documentApi = contentManagerApi.injectEndpoints({
302
309
  {
303
310
  type: "Document",
304
311
  id: collectionType !== SINGLE_TYPES ? `${model}_${result && "documentId" in result ? result.documentId : documentId}` : model
312
+ },
313
+ // Make it easy to invalidate all individual documents queries for a model
314
+ {
315
+ type: "Document",
316
+ id: `${model}_ALL_ITEMS`
305
317
  }
306
318
  ];
307
319
  }
@@ -365,8 +377,21 @@ const documentApi = contentManagerApi.injectEndpoints({
365
377
  type: "Document",
366
378
  id: collectionType !== SINGLE_TYPES ? `${model}_${documentId}` : model
367
379
  },
368
- "Relations"
380
+ "Relations",
381
+ { type: "UidAvailability", id: model }
369
382
  ];
383
+ },
384
+ async onQueryStarted({ data, ...patch }, { dispatch, queryFulfilled }) {
385
+ const patchResult = dispatch(
386
+ documentApi.util.updateQueryData("getDocument", patch, (draft) => {
387
+ Object.assign(draft.data, data);
388
+ })
389
+ );
390
+ try {
391
+ await queryFulfilled;
392
+ } catch {
393
+ patchResult.undo();
394
+ }
370
395
  }
371
396
  }),
372
397
  unpublishDocument: builder.mutation({
@@ -436,7 +461,7 @@ const buildValidParams = (query) => {
436
461
  const isBaseQueryError = (error) => {
437
462
  return error.name !== void 0;
438
463
  };
439
- const createYupSchema = (attributes = {}, components = {}) => {
464
+ const createYupSchema = (attributes = {}, components = {}, options = { status: null }) => {
440
465
  const createModelSchema = (attributes2) => yup.object().shape(
441
466
  Object.entries(attributes2).reduce((acc, [name, attribute]) => {
442
467
  if (DOCUMENT_META_FIELDS.includes(name)) {
@@ -449,7 +474,7 @@ const createYupSchema = (attributes = {}, components = {}) => {
449
474
  addMinValidation,
450
475
  addMaxValidation,
451
476
  addRegexValidation
452
- ].map((fn) => fn(attribute));
477
+ ].map((fn) => fn(attribute, options));
453
478
  const transformSchema = pipe(...validations);
454
479
  switch (attribute.type) {
455
480
  case "component": {
@@ -550,6 +575,14 @@ const createAttributeSchema = (attribute) => {
550
575
  if (!value || typeof value === "string" && value.length === 0) {
551
576
  return true;
552
577
  }
578
+ if (typeof value === "object") {
579
+ try {
580
+ JSON.stringify(value);
581
+ return true;
582
+ } catch (err) {
583
+ return false;
584
+ }
585
+ }
553
586
  try {
554
587
  JSON.parse(value);
555
588
  return true;
@@ -568,13 +601,7 @@ const createAttributeSchema = (attribute) => {
568
601
  return yup.mixed();
569
602
  }
570
603
  };
571
- const addRequiredValidation = (attribute) => (schema) => {
572
- if ((attribute.type === "component" && attribute.repeatable || attribute.type === "dynamiczone") && attribute.required && "min" in schema) {
573
- return schema.min(1, translatedErrors.required);
574
- }
575
- if (attribute.required && attribute.type !== "relation") {
576
- return schema.required(translatedErrors.required);
577
- }
604
+ const nullableSchema = (schema) => {
578
605
  return schema?.nullable ? schema.nullable() : (
579
606
  // In some cases '.nullable' will not be available on the schema.
580
607
  // e.g. when the schema has been built using yup.lazy (e.g. for relations).
@@ -582,7 +609,22 @@ const addRequiredValidation = (attribute) => (schema) => {
582
609
  schema
583
610
  );
584
611
  };
585
- const addMinLengthValidation = (attribute) => (schema) => {
612
+ const addRequiredValidation = (attribute, options) => (schema) => {
613
+ if (options.status === "draft") {
614
+ return nullableSchema(schema);
615
+ }
616
+ if ((attribute.type === "component" && attribute.repeatable || attribute.type === "dynamiczone") && attribute.required && "min" in schema) {
617
+ return schema.min(1, translatedErrors.required);
618
+ }
619
+ if (attribute.required && attribute.type !== "relation") {
620
+ return schema.required(translatedErrors.required);
621
+ }
622
+ return nullableSchema(schema);
623
+ };
624
+ const addMinLengthValidation = (attribute, options) => (schema) => {
625
+ if (options.status === "draft") {
626
+ return schema;
627
+ }
586
628
  if ("minLength" in attribute && attribute.minLength && Number.isInteger(attribute.minLength) && "min" in schema) {
587
629
  return schema.min(attribute.minLength, {
588
630
  ...translatedErrors.minLength,
@@ -604,11 +646,11 @@ const addMaxLengthValidation = (attribute) => (schema) => {
604
646
  }
605
647
  return schema;
606
648
  };
607
- const addMinValidation = (attribute) => (schema) => {
649
+ const addMinValidation = (attribute, options) => (schema) => {
608
650
  if ("min" in attribute) {
609
651
  const min = toInteger(attribute.min);
610
652
  if (attribute.type === "component" && attribute.repeatable || attribute.type === "dynamiczone") {
611
- if (!attribute.required && "test" in schema && min) {
653
+ if (options.status !== "draft" && !attribute.required && "test" in schema && min) {
612
654
  return schema.test(
613
655
  "custom-min",
614
656
  {
@@ -747,19 +789,115 @@ const extractContentTypeComponents = (attributes = {}, allComponents = {}) => {
747
789
  }, {});
748
790
  return componentsByKey;
749
791
  };
750
- const useDocument = (args, opts) => {
792
+ const HOOKS = {
793
+ /**
794
+ * Hook that allows to mutate the displayed headers of the list view table
795
+ * @constant
796
+ * @type {string}
797
+ */
798
+ INJECT_COLUMN_IN_TABLE: "Admin/CM/pages/ListView/inject-column-in-table",
799
+ /**
800
+ * Hook that allows to mutate the CM's collection types links pre-set filters
801
+ * @constant
802
+ * @type {string}
803
+ */
804
+ MUTATE_COLLECTION_TYPES_LINKS: "Admin/CM/pages/App/mutate-collection-types-links",
805
+ /**
806
+ * Hook that allows to mutate the CM's edit view layout
807
+ * @constant
808
+ * @type {string}
809
+ */
810
+ MUTATE_EDIT_VIEW_LAYOUT: "Admin/CM/pages/EditView/mutate-edit-view-layout",
811
+ /**
812
+ * Hook that allows to mutate the CM's single types links pre-set filters
813
+ * @constant
814
+ * @type {string}
815
+ */
816
+ MUTATE_SINGLE_TYPES_LINKS: "Admin/CM/pages/App/mutate-single-types-links"
817
+ };
818
+ const contentTypesApi = contentManagerApi.injectEndpoints({
819
+ endpoints: (builder) => ({
820
+ getContentTypeConfiguration: builder.query({
821
+ query: (uid) => ({
822
+ url: `/content-manager/content-types/${uid}/configuration`,
823
+ method: "GET"
824
+ }),
825
+ transformResponse: (response) => response.data,
826
+ providesTags: (_result, _error, uid) => [
827
+ { type: "ContentTypesConfiguration", id: uid },
828
+ { type: "ContentTypeSettings", id: "LIST" }
829
+ ]
830
+ }),
831
+ getAllContentTypeSettings: builder.query({
832
+ query: () => "/content-manager/content-types-settings",
833
+ transformResponse: (response) => response.data,
834
+ providesTags: [{ type: "ContentTypeSettings", id: "LIST" }]
835
+ }),
836
+ updateContentTypeConfiguration: builder.mutation({
837
+ query: ({ uid, ...body }) => ({
838
+ url: `/content-manager/content-types/${uid}/configuration`,
839
+ method: "PUT",
840
+ data: body
841
+ }),
842
+ transformResponse: (response) => response.data,
843
+ invalidatesTags: (_result, _error, { uid }) => [
844
+ { type: "ContentTypesConfiguration", id: uid },
845
+ { type: "ContentTypeSettings", id: "LIST" },
846
+ // Is this necessary?
847
+ { type: "InitialData" }
848
+ ]
849
+ })
850
+ })
851
+ });
852
+ const {
853
+ useGetContentTypeConfigurationQuery,
854
+ useGetAllContentTypeSettingsQuery,
855
+ useUpdateContentTypeConfigurationMutation
856
+ } = contentTypesApi;
857
+ const checkIfAttributeIsDisplayable = (attribute) => {
858
+ const { type } = attribute;
859
+ if (type === "relation") {
860
+ return !attribute.relation.toLowerCase().includes("morph");
861
+ }
862
+ return !["json", "dynamiczone", "richtext", "password", "blocks"].includes(type) && !!type;
863
+ };
864
+ const getMainField = (attribute, mainFieldName, { schemas, components }) => {
865
+ if (!mainFieldName) {
866
+ return void 0;
867
+ }
868
+ const mainFieldType = attribute.type === "component" ? components[attribute.component].attributes[mainFieldName].type : (
869
+ // @ts-expect-error – `targetModel` does exist on the attribute for a relation.
870
+ schemas.find((schema) => schema.uid === attribute.targetModel)?.attributes[mainFieldName].type
871
+ );
872
+ return {
873
+ name: mainFieldName,
874
+ type: mainFieldType ?? "string"
875
+ };
876
+ };
877
+ const DEFAULT_SETTINGS = {
878
+ bulkable: false,
879
+ filterable: false,
880
+ searchable: false,
881
+ pagination: false,
882
+ defaultSortBy: "",
883
+ defaultSortOrder: "asc",
884
+ mainField: "id",
885
+ pageSize: 10
886
+ };
887
+ const useDocumentLayout = (model) => {
888
+ const { schema, components } = useDocument({ model, collectionType: "" }, { skip: true });
889
+ const [{ query }] = useQueryParams();
890
+ const runHookWaterfall = useStrapiApp("useDocumentLayout", (state) => state.runHookWaterfall);
751
891
  const { toggleNotification } = useNotification();
752
892
  const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
893
+ const { isLoading: isLoadingSchemas, schemas } = useContentTypeSchema();
753
894
  const {
754
- currentData: data,
755
- isLoading: isLoadingDocument,
756
- isFetching: isFetchingDocument,
757
- error
758
- } = useGetDocumentQuery(args, {
759
- ...opts,
760
- skip: !args.documentId && args.collectionType !== SINGLE_TYPES || opts?.skip
761
- });
762
- const { components, schema, isLoading: isLoadingSchema } = useContentTypeSchema(args.model);
895
+ data,
896
+ isLoading: isLoadingConfigs,
897
+ error,
898
+ isFetching: isFetchingConfigs
899
+ } = useGetContentTypeConfigurationQuery(model);
900
+ const isLoading = isLoadingSchemas || isFetchingConfigs || isLoadingConfigs;
763
901
  React.useEffect(() => {
764
902
  if (error) {
765
903
  toggleNotification({
@@ -767,388 +905,642 @@ const useDocument = (args, opts) => {
767
905
  message: formatAPIError(error)
768
906
  });
769
907
  }
770
- }, [toggleNotification, error, formatAPIError, args.collectionType]);
771
- const validationSchema = React.useMemo(() => {
772
- if (!schema) {
773
- return null;
774
- }
775
- return createYupSchema(schema.attributes, components);
776
- }, [schema, components]);
777
- const validate = React.useCallback(
778
- (document) => {
779
- if (!validationSchema) {
780
- throw new Error(
781
- "There is no validation schema generated, this is likely due to the schema not being loaded yet."
782
- );
783
- }
784
- try {
785
- validationSchema.validateSync(document, { abortEarly: false, strict: true });
786
- return null;
787
- } catch (error2) {
788
- if (error2 instanceof ValidationError) {
789
- return getYupValidationErrors(error2);
790
- }
791
- throw error2;
792
- }
908
+ }, [error, formatAPIError, toggleNotification]);
909
+ const editLayout = React.useMemo(
910
+ () => data && !isLoading ? formatEditLayout(data, { schemas, schema, components }) : {
911
+ layout: [],
912
+ components: {},
913
+ metadatas: {},
914
+ options: {},
915
+ settings: DEFAULT_SETTINGS
793
916
  },
794
- [validationSchema]
917
+ [data, isLoading, schemas, schema, components]
918
+ );
919
+ const listLayout = React.useMemo(() => {
920
+ return data && !isLoading ? formatListLayout(data, { schemas, schema, components }) : {
921
+ layout: [],
922
+ metadatas: {},
923
+ options: {},
924
+ settings: DEFAULT_SETTINGS
925
+ };
926
+ }, [data, isLoading, schemas, schema, components]);
927
+ const { layout: edit } = React.useMemo(
928
+ () => runHookWaterfall(HOOKS.MUTATE_EDIT_VIEW_LAYOUT, {
929
+ layout: editLayout,
930
+ query
931
+ }),
932
+ [editLayout, query, runHookWaterfall]
795
933
  );
796
- const isLoading = isLoadingDocument || isFetchingDocument || isLoadingSchema;
797
934
  return {
798
- components,
799
- document: data?.data,
800
- meta: data?.meta,
935
+ error,
801
936
  isLoading,
802
- schema,
803
- validate
804
- };
805
- };
806
- const useDoc = () => {
807
- const { id, slug, collectionType, origin } = useParams();
808
- const [{ query }] = useQueryParams();
809
- const params = React.useMemo(() => buildValidParams(query), [query]);
810
- if (!collectionType) {
811
- throw new Error("Could not find collectionType in url params");
812
- }
813
- if (!slug) {
814
- throw new Error("Could not find model in url params");
815
- }
816
- return {
817
- collectionType,
818
- model: slug,
819
- id: origin || id === "create" ? void 0 : id,
820
- ...useDocument(
821
- { documentId: origin || id, model: slug, collectionType, params },
822
- {
823
- skip: id === "create" || !origin && !id && collectionType !== SINGLE_TYPES
824
- }
825
- )
937
+ edit,
938
+ list: listLayout
826
939
  };
827
940
  };
828
- const prefixPluginTranslations = (trad, pluginId) => {
829
- if (!pluginId) {
830
- throw new TypeError("pluginId can't be empty");
831
- }
832
- return Object.keys(trad).reduce((acc, current) => {
833
- acc[`${pluginId}.${current}`] = trad[current];
834
- return acc;
835
- }, {});
836
- };
837
- const getTranslation = (id) => `content-manager.${id}`;
838
- const DEFAULT_UNEXPECTED_ERROR_MSG = {
839
- id: "notification.error",
840
- defaultMessage: "An error occurred, please try again"
941
+ const useDocLayout = () => {
942
+ const { model } = useDoc();
943
+ return useDocumentLayout(model);
841
944
  };
842
- const useDocumentActions = () => {
843
- const { toggleNotification } = useNotification();
844
- const { formatMessage } = useIntl();
845
- const { trackUsage } = useTracking();
846
- const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
847
- const [deleteDocument] = useDeleteDocumentMutation();
848
- const _delete = React.useCallback(
849
- async ({ collectionType, model, documentId, params }, trackerProperty) => {
850
- try {
851
- trackUsage("willDeleteEntry", trackerProperty);
852
- const res = await deleteDocument({
853
- collectionType,
854
- model,
855
- documentId,
856
- params
857
- });
858
- if ("error" in res) {
859
- toggleNotification({
860
- type: "danger",
861
- message: formatAPIError(res.error)
862
- });
863
- return { error: res.error };
864
- }
865
- toggleNotification({
866
- type: "success",
867
- message: formatMessage({
868
- id: getTranslation("success.record.delete"),
869
- defaultMessage: "Deleted document"
870
- })
871
- });
872
- trackUsage("didDeleteEntry", trackerProperty);
873
- return res.data;
874
- } catch (err) {
875
- toggleNotification({
876
- type: "danger",
877
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
878
- });
879
- trackUsage("didNotDeleteEntry", { error: err, ...trackerProperty });
880
- throw err;
945
+ const formatEditLayout = (data, {
946
+ schemas,
947
+ schema,
948
+ components
949
+ }) => {
950
+ let currentPanelIndex = 0;
951
+ const panelledEditAttributes = convertEditLayoutToFieldLayouts(
952
+ data.contentType.layouts.edit,
953
+ schema?.attributes,
954
+ data.contentType.metadatas,
955
+ { configurations: data.components, schemas: components },
956
+ schemas
957
+ ).reduce((panels, row) => {
958
+ if (row.some((field) => field.type === "dynamiczone")) {
959
+ panels.push([row]);
960
+ currentPanelIndex += 2;
961
+ } else {
962
+ if (!panels[currentPanelIndex]) {
963
+ panels.push([]);
881
964
  }
882
- },
883
- [trackUsage, deleteDocument, toggleNotification, formatMessage, formatAPIError]
884
- );
885
- const [deleteManyDocuments] = useDeleteManyDocumentsMutation();
886
- const deleteMany = React.useCallback(
887
- async ({ model, documentIds, params }) => {
888
- try {
889
- trackUsage("willBulkDeleteEntries");
890
- const res = await deleteManyDocuments({
891
- model,
892
- documentIds,
893
- params
894
- });
895
- if ("error" in res) {
896
- toggleNotification({
897
- type: "danger",
898
- message: formatAPIError(res.error)
899
- });
900
- return { error: res.error };
965
+ panels[currentPanelIndex].push(row);
966
+ }
967
+ return panels;
968
+ }, []);
969
+ const componentEditAttributes = Object.entries(data.components).reduce(
970
+ (acc, [uid, configuration]) => {
971
+ acc[uid] = {
972
+ layout: convertEditLayoutToFieldLayouts(
973
+ configuration.layouts.edit,
974
+ components[uid].attributes,
975
+ configuration.metadatas,
976
+ { configurations: data.components, schemas: components }
977
+ ),
978
+ settings: {
979
+ ...configuration.settings,
980
+ icon: components[uid].info.icon,
981
+ displayName: components[uid].info.displayName
901
982
  }
902
- toggleNotification({
903
- type: "success",
904
- title: formatMessage({
905
- id: getTranslation("success.records.delete"),
906
- defaultMessage: "Successfully deleted."
907
- }),
908
- message: ""
909
- });
910
- trackUsage("didBulkDeleteEntries");
911
- return res.data;
912
- } catch (err) {
913
- toggleNotification({
914
- type: "danger",
915
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
916
- });
917
- trackUsage("didNotBulkDeleteEntries");
918
- throw err;
919
- }
983
+ };
984
+ return acc;
920
985
  },
921
- [trackUsage, deleteManyDocuments, toggleNotification, formatMessage, formatAPIError]
986
+ {}
922
987
  );
923
- const [discardDocument] = useDiscardDocumentMutation();
924
- const discard = React.useCallback(
925
- async ({ collectionType, model, documentId, params }) => {
926
- try {
927
- const res = await discardDocument({
928
- collectionType,
929
- model,
930
- documentId,
931
- params
932
- });
933
- if ("error" in res) {
934
- toggleNotification({
935
- type: "danger",
936
- message: formatAPIError(res.error)
937
- });
938
- return { error: res.error };
939
- }
940
- toggleNotification({
941
- type: "success",
942
- message: formatMessage({
943
- id: "content-manager.success.record.discard",
944
- defaultMessage: "Changes discarded"
945
- })
946
- });
947
- return res.data;
948
- } catch (err) {
949
- toggleNotification({
950
- type: "danger",
951
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
952
- });
953
- throw err;
954
- }
988
+ const editMetadatas = Object.entries(data.contentType.metadatas).reduce(
989
+ (acc, [attribute, metadata]) => {
990
+ return {
991
+ ...acc,
992
+ [attribute]: metadata.edit
993
+ };
955
994
  },
956
- [discardDocument, formatAPIError, formatMessage, toggleNotification]
995
+ {}
957
996
  );
958
- const [publishDocument] = usePublishDocumentMutation();
959
- const publish = React.useCallback(
960
- async ({ collectionType, model, documentId, params }, data) => {
961
- try {
962
- trackUsage("willPublishEntry");
963
- const res = await publishDocument({
964
- collectionType,
965
- model,
966
- documentId,
967
- data,
968
- params
969
- });
970
- if ("error" in res) {
971
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
972
- return { error: res.error };
973
- }
974
- trackUsage("didPublishEntry");
975
- toggleNotification({
976
- type: "success",
977
- message: formatMessage({
978
- id: getTranslation("success.record.publish"),
979
- defaultMessage: "Published document"
980
- })
981
- });
982
- return res.data;
983
- } catch (err) {
984
- toggleNotification({
985
- type: "danger",
986
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
987
- });
988
- throw err;
989
- }
997
+ return {
998
+ layout: panelledEditAttributes,
999
+ components: componentEditAttributes,
1000
+ metadatas: editMetadatas,
1001
+ settings: {
1002
+ ...data.contentType.settings,
1003
+ displayName: schema?.info.displayName
990
1004
  },
991
- [trackUsage, publishDocument, toggleNotification, formatMessage, formatAPIError]
992
- );
993
- const [publishManyDocuments] = usePublishManyDocumentsMutation();
994
- const publishMany = React.useCallback(
995
- async ({ model, documentIds, params }) => {
996
- try {
997
- const res = await publishManyDocuments({
998
- model,
999
- documentIds,
1000
- params
1001
- });
1002
- if ("error" in res) {
1003
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1004
- return { error: res.error };
1005
- }
1006
- toggleNotification({
1007
- type: "success",
1008
- message: formatMessage({
1009
- id: getTranslation("success.record.publish"),
1010
- defaultMessage: "Published document"
1011
- })
1012
- });
1013
- return res.data;
1014
- } catch (err) {
1015
- toggleNotification({
1016
- type: "danger",
1017
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1018
- });
1019
- throw err;
1005
+ options: {
1006
+ ...schema?.options,
1007
+ ...schema?.pluginOptions,
1008
+ ...data.contentType.options
1009
+ }
1010
+ };
1011
+ };
1012
+ const convertEditLayoutToFieldLayouts = (rows, attributes = {}, metadatas, components, schemas = []) => {
1013
+ return rows.map(
1014
+ (row) => row.map((field) => {
1015
+ const attribute = attributes[field.name];
1016
+ if (!attribute) {
1017
+ return null;
1020
1018
  }
1021
- },
1022
- [
1023
- // trackUsage,
1024
- publishManyDocuments,
1025
- toggleNotification,
1026
- formatMessage,
1027
- formatAPIError
1028
- ]
1019
+ const { edit: metadata } = metadatas[field.name];
1020
+ const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
1021
+ return {
1022
+ attribute,
1023
+ disabled: !metadata.editable,
1024
+ hint: metadata.description,
1025
+ label: metadata.label ?? "",
1026
+ name: field.name,
1027
+ // @ts-expect-error – mainField does exist on the metadata for a relation.
1028
+ mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
1029
+ schemas,
1030
+ components: components?.schemas ?? {}
1031
+ }),
1032
+ placeholder: metadata.placeholder ?? "",
1033
+ required: attribute.required ?? false,
1034
+ size: field.size,
1035
+ unique: "unique" in attribute ? attribute.unique : false,
1036
+ visible: metadata.visible ?? true,
1037
+ type: attribute.type
1038
+ };
1039
+ }).filter((field) => field !== null)
1029
1040
  );
1030
- const [updateDocument] = useUpdateDocumentMutation();
1031
- const update = React.useCallback(
1032
- async ({ collectionType, model, documentId, params }, data, trackerProperty) => {
1033
- try {
1034
- trackUsage("willEditEntry", trackerProperty);
1035
- const res = await updateDocument({
1036
- collectionType,
1037
- model,
1038
- documentId,
1039
- data,
1040
- params
1041
- });
1042
- if ("error" in res) {
1043
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1044
- trackUsage("didNotEditEntry", { error: res.error, ...trackerProperty });
1045
- return { error: res.error };
1041
+ };
1042
+ const formatListLayout = (data, {
1043
+ schemas,
1044
+ schema,
1045
+ components
1046
+ }) => {
1047
+ const listMetadatas = Object.entries(data.contentType.metadatas).reduce(
1048
+ (acc, [attribute, metadata]) => {
1049
+ return {
1050
+ ...acc,
1051
+ [attribute]: metadata.list
1052
+ };
1053
+ },
1054
+ {}
1055
+ );
1056
+ const listAttributes = convertListLayoutToFieldLayouts(
1057
+ data.contentType.layouts.list,
1058
+ schema?.attributes,
1059
+ listMetadatas,
1060
+ { configurations: data.components, schemas: components },
1061
+ schemas
1062
+ );
1063
+ return {
1064
+ layout: listAttributes,
1065
+ settings: { ...data.contentType.settings, displayName: schema?.info.displayName },
1066
+ metadatas: listMetadatas,
1067
+ options: {
1068
+ ...schema?.options,
1069
+ ...schema?.pluginOptions,
1070
+ ...data.contentType.options
1071
+ }
1072
+ };
1073
+ };
1074
+ const convertListLayoutToFieldLayouts = (columns, attributes = {}, metadatas, components, schemas = []) => {
1075
+ return columns.map((name) => {
1076
+ const attribute = attributes[name];
1077
+ if (!attribute) {
1078
+ return null;
1079
+ }
1080
+ const metadata = metadatas[name];
1081
+ const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
1082
+ return {
1083
+ attribute,
1084
+ label: metadata.label ?? "",
1085
+ mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
1086
+ schemas,
1087
+ components: components?.schemas ?? {}
1088
+ }),
1089
+ name,
1090
+ searchable: metadata.searchable ?? true,
1091
+ sortable: metadata.sortable ?? true
1092
+ };
1093
+ }).filter((field) => field !== null);
1094
+ };
1095
+ const useDocument = (args, opts) => {
1096
+ const { toggleNotification } = useNotification();
1097
+ const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
1098
+ const {
1099
+ currentData: data,
1100
+ isLoading: isLoadingDocument,
1101
+ isFetching: isFetchingDocument,
1102
+ error
1103
+ } = useGetDocumentQuery(args, {
1104
+ ...opts,
1105
+ skip: !args.documentId && args.collectionType !== SINGLE_TYPES || opts?.skip
1106
+ });
1107
+ const {
1108
+ components,
1109
+ schema,
1110
+ schemas,
1111
+ isLoading: isLoadingSchema
1112
+ } = useContentTypeSchema(args.model);
1113
+ React.useEffect(() => {
1114
+ if (error) {
1115
+ toggleNotification({
1116
+ type: "danger",
1117
+ message: formatAPIError(error)
1118
+ });
1119
+ }
1120
+ }, [toggleNotification, error, formatAPIError, args.collectionType]);
1121
+ const validationSchema = React.useMemo(() => {
1122
+ if (!schema) {
1123
+ return null;
1124
+ }
1125
+ return createYupSchema(schema.attributes, components);
1126
+ }, [schema, components]);
1127
+ const validate = React.useCallback(
1128
+ (document) => {
1129
+ if (!validationSchema) {
1130
+ throw new Error(
1131
+ "There is no validation schema generated, this is likely due to the schema not being loaded yet."
1132
+ );
1133
+ }
1134
+ try {
1135
+ validationSchema.validateSync(document, { abortEarly: false, strict: true });
1136
+ return null;
1137
+ } catch (error2) {
1138
+ if (error2 instanceof ValidationError) {
1139
+ return getYupValidationErrors(error2);
1046
1140
  }
1047
- trackUsage("didEditEntry", trackerProperty);
1048
- toggleNotification({
1049
- type: "success",
1050
- message: formatMessage({
1051
- id: getTranslation("success.record.save"),
1052
- defaultMessage: "Saved document"
1053
- })
1054
- });
1055
- return res.data;
1056
- } catch (err) {
1057
- trackUsage("didNotEditEntry", { error: err, ...trackerProperty });
1058
- toggleNotification({
1059
- type: "danger",
1060
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1061
- });
1062
- throw err;
1141
+ throw error2;
1063
1142
  }
1064
1143
  },
1065
- [trackUsage, updateDocument, toggleNotification, formatMessage, formatAPIError]
1144
+ [validationSchema]
1066
1145
  );
1067
- const [unpublishDocument] = useUnpublishDocumentMutation();
1068
- const unpublish = React.useCallback(
1069
- async ({ collectionType, model, documentId, params }, discardDraft = false) => {
1146
+ const isLoading = isLoadingDocument || isFetchingDocument || isLoadingSchema;
1147
+ return {
1148
+ components,
1149
+ document: data?.data,
1150
+ meta: data?.meta,
1151
+ isLoading,
1152
+ schema,
1153
+ schemas,
1154
+ validate
1155
+ };
1156
+ };
1157
+ const useDoc = () => {
1158
+ const { id, slug, collectionType, origin } = useParams();
1159
+ const [{ query }] = useQueryParams();
1160
+ const params = React.useMemo(() => buildValidParams(query), [query]);
1161
+ if (!collectionType) {
1162
+ throw new Error("Could not find collectionType in url params");
1163
+ }
1164
+ if (!slug) {
1165
+ throw new Error("Could not find model in url params");
1166
+ }
1167
+ return {
1168
+ collectionType,
1169
+ model: slug,
1170
+ id: origin || id === "create" ? void 0 : id,
1171
+ ...useDocument(
1172
+ { documentId: origin || id, model: slug, collectionType, params },
1173
+ {
1174
+ skip: id === "create" || !origin && !id && collectionType !== SINGLE_TYPES
1175
+ }
1176
+ )
1177
+ };
1178
+ };
1179
+ const useContentManagerContext = () => {
1180
+ const {
1181
+ collectionType,
1182
+ model,
1183
+ id,
1184
+ components,
1185
+ isLoading: isLoadingDoc,
1186
+ schema,
1187
+ schemas
1188
+ } = useDoc();
1189
+ const layout = useDocumentLayout(model);
1190
+ const form = useForm("useContentManagerContext", (state) => state);
1191
+ const isSingleType = collectionType === SINGLE_TYPES;
1192
+ const slug = model;
1193
+ const isCreatingEntry = id === "create";
1194
+ useContentTypeSchema();
1195
+ const isLoading = isLoadingDoc || layout.isLoading;
1196
+ const error = layout.error;
1197
+ return {
1198
+ error,
1199
+ isLoading,
1200
+ // Base metadata
1201
+ model,
1202
+ collectionType,
1203
+ id,
1204
+ slug,
1205
+ isCreatingEntry,
1206
+ isSingleType,
1207
+ hasDraftAndPublish: schema?.options?.draftAndPublish ?? false,
1208
+ // All schema infos
1209
+ components,
1210
+ contentType: schema,
1211
+ contentTypes: schemas,
1212
+ // Form state
1213
+ form,
1214
+ // layout infos
1215
+ layout
1216
+ };
1217
+ };
1218
+ const prefixPluginTranslations = (trad, pluginId) => {
1219
+ if (!pluginId) {
1220
+ throw new TypeError("pluginId can't be empty");
1221
+ }
1222
+ return Object.keys(trad).reduce((acc, current) => {
1223
+ acc[`${pluginId}.${current}`] = trad[current];
1224
+ return acc;
1225
+ }, {});
1226
+ };
1227
+ const getTranslation = (id) => `content-manager.${id}`;
1228
+ const DEFAULT_UNEXPECTED_ERROR_MSG = {
1229
+ id: "notification.error",
1230
+ defaultMessage: "An error occurred, please try again"
1231
+ };
1232
+ const useDocumentActions = () => {
1233
+ const { toggleNotification } = useNotification();
1234
+ const { formatMessage } = useIntl();
1235
+ const { trackUsage } = useTracking();
1236
+ const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
1237
+ const navigate = useNavigate();
1238
+ const setCurrentStep = useGuidedTour("useDocumentActions", (state) => state.setCurrentStep);
1239
+ const [deleteDocument] = useDeleteDocumentMutation();
1240
+ const _delete = React.useCallback(
1241
+ async ({ collectionType, model, documentId, params }, trackerProperty) => {
1070
1242
  try {
1071
- trackUsage("willUnpublishEntry");
1072
- const res = await unpublishDocument({
1243
+ trackUsage("willDeleteEntry", trackerProperty);
1244
+ const res = await deleteDocument({
1073
1245
  collectionType,
1074
1246
  model,
1075
1247
  documentId,
1076
- params,
1077
- data: {
1078
- discardDraft
1079
- }
1248
+ params
1080
1249
  });
1081
1250
  if ("error" in res) {
1082
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1251
+ toggleNotification({
1252
+ type: "danger",
1253
+ message: formatAPIError(res.error)
1254
+ });
1083
1255
  return { error: res.error };
1084
1256
  }
1085
- trackUsage("didUnpublishEntry");
1086
1257
  toggleNotification({
1087
1258
  type: "success",
1088
1259
  message: formatMessage({
1089
- id: getTranslation("success.record.unpublish"),
1090
- defaultMessage: "Unpublished document"
1260
+ id: getTranslation("success.record.delete"),
1261
+ defaultMessage: "Deleted document"
1091
1262
  })
1092
1263
  });
1264
+ trackUsage("didDeleteEntry", trackerProperty);
1093
1265
  return res.data;
1094
1266
  } catch (err) {
1095
1267
  toggleNotification({
1096
1268
  type: "danger",
1097
1269
  message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1098
1270
  });
1271
+ trackUsage("didNotDeleteEntry", { error: err, ...trackerProperty });
1099
1272
  throw err;
1100
1273
  }
1101
1274
  },
1102
- [trackUsage, unpublishDocument, toggleNotification, formatMessage, formatAPIError]
1275
+ [trackUsage, deleteDocument, toggleNotification, formatMessage, formatAPIError]
1103
1276
  );
1104
- const [unpublishManyDocuments] = useUnpublishManyDocumentsMutation();
1105
- const unpublishMany = React.useCallback(
1277
+ const [deleteManyDocuments] = useDeleteManyDocumentsMutation();
1278
+ const deleteMany = React.useCallback(
1106
1279
  async ({ model, documentIds, params }) => {
1107
1280
  try {
1108
- trackUsage("willBulkUnpublishEntries");
1109
- const res = await unpublishManyDocuments({
1281
+ trackUsage("willBulkDeleteEntries");
1282
+ const res = await deleteManyDocuments({
1110
1283
  model,
1111
1284
  documentIds,
1112
1285
  params
1113
1286
  });
1114
1287
  if ("error" in res) {
1115
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1288
+ toggleNotification({
1289
+ type: "danger",
1290
+ message: formatAPIError(res.error)
1291
+ });
1116
1292
  return { error: res.error };
1117
1293
  }
1118
- trackUsage("didBulkUnpublishEntries");
1119
1294
  toggleNotification({
1120
1295
  type: "success",
1121
1296
  title: formatMessage({
1122
- id: getTranslation("success.records.unpublish"),
1123
- defaultMessage: "Successfully unpublished."
1297
+ id: getTranslation("success.records.delete"),
1298
+ defaultMessage: "Successfully deleted."
1124
1299
  }),
1125
1300
  message: ""
1126
1301
  });
1302
+ trackUsage("didBulkDeleteEntries");
1127
1303
  return res.data;
1128
1304
  } catch (err) {
1129
1305
  toggleNotification({
1130
1306
  type: "danger",
1131
1307
  message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1132
1308
  });
1133
- trackUsage("didNotBulkUnpublishEntries");
1309
+ trackUsage("didNotBulkDeleteEntries");
1134
1310
  throw err;
1135
1311
  }
1136
1312
  },
1137
- [trackUsage, unpublishManyDocuments, toggleNotification, formatMessage, formatAPIError]
1313
+ [trackUsage, deleteManyDocuments, toggleNotification, formatMessage, formatAPIError]
1138
1314
  );
1139
- const [createDocument] = useCreateDocumentMutation();
1140
- const create = React.useCallback(
1141
- async ({ model, params }, data, trackerProperty) => {
1315
+ const [discardDocument] = useDiscardDocumentMutation();
1316
+ const discard = React.useCallback(
1317
+ async ({ collectionType, model, documentId, params }) => {
1142
1318
  try {
1143
- const res = await createDocument({
1319
+ const res = await discardDocument({
1320
+ collectionType,
1144
1321
  model,
1145
- data,
1322
+ documentId,
1146
1323
  params
1147
1324
  });
1148
1325
  if ("error" in res) {
1149
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1150
- trackUsage("didNotCreateEntry", { error: res.error, ...trackerProperty });
1151
- return { error: res.error };
1326
+ toggleNotification({
1327
+ type: "danger",
1328
+ message: formatAPIError(res.error)
1329
+ });
1330
+ return { error: res.error };
1331
+ }
1332
+ toggleNotification({
1333
+ type: "success",
1334
+ message: formatMessage({
1335
+ id: "content-manager.success.record.discard",
1336
+ defaultMessage: "Changes discarded"
1337
+ })
1338
+ });
1339
+ return res.data;
1340
+ } catch (err) {
1341
+ toggleNotification({
1342
+ type: "danger",
1343
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1344
+ });
1345
+ throw err;
1346
+ }
1347
+ },
1348
+ [discardDocument, formatAPIError, formatMessage, toggleNotification]
1349
+ );
1350
+ const [publishDocument] = usePublishDocumentMutation();
1351
+ const publish = React.useCallback(
1352
+ async ({ collectionType, model, documentId, params }, data) => {
1353
+ try {
1354
+ trackUsage("willPublishEntry");
1355
+ const res = await publishDocument({
1356
+ collectionType,
1357
+ model,
1358
+ documentId,
1359
+ data,
1360
+ params
1361
+ });
1362
+ if ("error" in res) {
1363
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1364
+ return { error: res.error };
1365
+ }
1366
+ trackUsage("didPublishEntry");
1367
+ toggleNotification({
1368
+ type: "success",
1369
+ message: formatMessage({
1370
+ id: getTranslation("success.record.publish"),
1371
+ defaultMessage: "Published document"
1372
+ })
1373
+ });
1374
+ return res.data;
1375
+ } catch (err) {
1376
+ toggleNotification({
1377
+ type: "danger",
1378
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1379
+ });
1380
+ throw err;
1381
+ }
1382
+ },
1383
+ [trackUsage, publishDocument, toggleNotification, formatMessage, formatAPIError]
1384
+ );
1385
+ const [publishManyDocuments] = usePublishManyDocumentsMutation();
1386
+ const publishMany = React.useCallback(
1387
+ async ({ model, documentIds, params }) => {
1388
+ try {
1389
+ const res = await publishManyDocuments({
1390
+ model,
1391
+ documentIds,
1392
+ params
1393
+ });
1394
+ if ("error" in res) {
1395
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1396
+ return { error: res.error };
1397
+ }
1398
+ toggleNotification({
1399
+ type: "success",
1400
+ message: formatMessage({
1401
+ id: getTranslation("success.record.publish"),
1402
+ defaultMessage: "Published document"
1403
+ })
1404
+ });
1405
+ return res.data;
1406
+ } catch (err) {
1407
+ toggleNotification({
1408
+ type: "danger",
1409
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1410
+ });
1411
+ throw err;
1412
+ }
1413
+ },
1414
+ [
1415
+ // trackUsage,
1416
+ publishManyDocuments,
1417
+ toggleNotification,
1418
+ formatMessage,
1419
+ formatAPIError
1420
+ ]
1421
+ );
1422
+ const [updateDocument] = useUpdateDocumentMutation();
1423
+ const update = React.useCallback(
1424
+ async ({ collectionType, model, documentId, params }, data, trackerProperty) => {
1425
+ try {
1426
+ trackUsage("willEditEntry", trackerProperty);
1427
+ const res = await updateDocument({
1428
+ collectionType,
1429
+ model,
1430
+ documentId,
1431
+ data,
1432
+ params
1433
+ });
1434
+ if ("error" in res) {
1435
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1436
+ trackUsage("didNotEditEntry", { error: res.error, ...trackerProperty });
1437
+ return { error: res.error };
1438
+ }
1439
+ trackUsage("didEditEntry", trackerProperty);
1440
+ toggleNotification({
1441
+ type: "success",
1442
+ message: formatMessage({
1443
+ id: getTranslation("success.record.save"),
1444
+ defaultMessage: "Saved document"
1445
+ })
1446
+ });
1447
+ return res.data;
1448
+ } catch (err) {
1449
+ trackUsage("didNotEditEntry", { error: err, ...trackerProperty });
1450
+ toggleNotification({
1451
+ type: "danger",
1452
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1453
+ });
1454
+ throw err;
1455
+ }
1456
+ },
1457
+ [trackUsage, updateDocument, toggleNotification, formatMessage, formatAPIError]
1458
+ );
1459
+ const [unpublishDocument] = useUnpublishDocumentMutation();
1460
+ const unpublish = React.useCallback(
1461
+ async ({ collectionType, model, documentId, params }, discardDraft = false) => {
1462
+ try {
1463
+ trackUsage("willUnpublishEntry");
1464
+ const res = await unpublishDocument({
1465
+ collectionType,
1466
+ model,
1467
+ documentId,
1468
+ params,
1469
+ data: {
1470
+ discardDraft
1471
+ }
1472
+ });
1473
+ if ("error" in res) {
1474
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1475
+ return { error: res.error };
1476
+ }
1477
+ trackUsage("didUnpublishEntry");
1478
+ toggleNotification({
1479
+ type: "success",
1480
+ message: formatMessage({
1481
+ id: getTranslation("success.record.unpublish"),
1482
+ defaultMessage: "Unpublished document"
1483
+ })
1484
+ });
1485
+ return res.data;
1486
+ } catch (err) {
1487
+ toggleNotification({
1488
+ type: "danger",
1489
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1490
+ });
1491
+ throw err;
1492
+ }
1493
+ },
1494
+ [trackUsage, unpublishDocument, toggleNotification, formatMessage, formatAPIError]
1495
+ );
1496
+ const [unpublishManyDocuments] = useUnpublishManyDocumentsMutation();
1497
+ const unpublishMany = React.useCallback(
1498
+ async ({ model, documentIds, params }) => {
1499
+ try {
1500
+ trackUsage("willBulkUnpublishEntries");
1501
+ const res = await unpublishManyDocuments({
1502
+ model,
1503
+ documentIds,
1504
+ params
1505
+ });
1506
+ if ("error" in res) {
1507
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1508
+ return { error: res.error };
1509
+ }
1510
+ trackUsage("didBulkUnpublishEntries");
1511
+ toggleNotification({
1512
+ type: "success",
1513
+ title: formatMessage({
1514
+ id: getTranslation("success.records.unpublish"),
1515
+ defaultMessage: "Successfully unpublished."
1516
+ }),
1517
+ message: ""
1518
+ });
1519
+ return res.data;
1520
+ } catch (err) {
1521
+ toggleNotification({
1522
+ type: "danger",
1523
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1524
+ });
1525
+ trackUsage("didNotBulkUnpublishEntries");
1526
+ throw err;
1527
+ }
1528
+ },
1529
+ [trackUsage, unpublishManyDocuments, toggleNotification, formatMessage, formatAPIError]
1530
+ );
1531
+ const [createDocument] = useCreateDocumentMutation();
1532
+ const create = React.useCallback(
1533
+ async ({ model, params }, data, trackerProperty) => {
1534
+ try {
1535
+ const res = await createDocument({
1536
+ model,
1537
+ data,
1538
+ params
1539
+ });
1540
+ if ("error" in res) {
1541
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1542
+ trackUsage("didNotCreateEntry", { error: res.error, ...trackerProperty });
1543
+ return { error: res.error };
1152
1544
  }
1153
1545
  trackUsage("didCreateEntry", trackerProperty);
1154
1546
  toggleNotification({
@@ -1158,6 +1550,7 @@ const useDocumentActions = () => {
1158
1550
  defaultMessage: "Saved document"
1159
1551
  })
1160
1552
  });
1553
+ setCurrentStep("contentManager.success");
1161
1554
  return res.data;
1162
1555
  } catch (err) {
1163
1556
  toggleNotification({
@@ -1197,7 +1590,7 @@ const useDocumentActions = () => {
1197
1590
  throw err;
1198
1591
  }
1199
1592
  },
1200
- [autoCloneDocument, formatAPIError, formatMessage, toggleNotification]
1593
+ [autoCloneDocument, formatMessage, toggleNotification]
1201
1594
  );
1202
1595
  const [cloneDocument] = useCloneDocumentMutation();
1203
1596
  const clone = React.useCallback(
@@ -1223,6 +1616,7 @@ const useDocumentActions = () => {
1223
1616
  defaultMessage: "Cloned document"
1224
1617
  })
1225
1618
  });
1619
+ navigate(`../../${res.data.data.documentId}`, { relative: "path" });
1226
1620
  return res.data;
1227
1621
  } catch (err) {
1228
1622
  toggleNotification({
@@ -1233,7 +1627,7 @@ const useDocumentActions = () => {
1233
1627
  throw err;
1234
1628
  }
1235
1629
  },
1236
- [cloneDocument, trackUsage, toggleNotification, formatMessage, formatAPIError]
1630
+ [cloneDocument, trackUsage, toggleNotification, formatMessage, formatAPIError, navigate]
1237
1631
  );
1238
1632
  const [getDoc] = useLazyGetDocumentQuery();
1239
1633
  const getDocument = React.useCallback(
@@ -1259,7 +1653,7 @@ const useDocumentActions = () => {
1259
1653
  };
1260
1654
  };
1261
1655
  const ProtectedHistoryPage = lazy(
1262
- () => import("./History-D6PRyNcx.mjs").then((mod) => ({ default: mod.ProtectedHistoryPage }))
1656
+ () => import("./History-BqO2G3MV.mjs").then((mod) => ({ default: mod.ProtectedHistoryPage }))
1263
1657
  );
1264
1658
  const routes$1 = [
1265
1659
  {
@@ -1272,31 +1666,31 @@ const routes$1 = [
1272
1666
  }
1273
1667
  ];
1274
1668
  const ProtectedEditViewPage = lazy(
1275
- () => import("./EditViewPage-CtdtssrH.mjs").then((mod) => ({ default: mod.ProtectedEditViewPage }))
1669
+ () => import("./EditViewPage-BU1ugeVi.mjs").then((mod) => ({ default: mod.ProtectedEditViewPage }))
1276
1670
  );
1277
1671
  const ProtectedListViewPage = lazy(
1278
- () => import("./ListViewPage-B1GyNqfn.mjs").then((mod) => ({ default: mod.ProtectedListViewPage }))
1672
+ () => import("./ListViewPage-yE_zYhcI.mjs").then((mod) => ({ default: mod.ProtectedListViewPage }))
1279
1673
  );
1280
1674
  const ProtectedListConfiguration = lazy(
1281
- () => import("./ListConfigurationPage-BC9bCi9k.mjs").then((mod) => ({
1675
+ () => import("./ListConfigurationPage-C6rsFlme.mjs").then((mod) => ({
1282
1676
  default: mod.ProtectedListConfiguration
1283
1677
  }))
1284
1678
  );
1285
1679
  const ProtectedEditConfigurationPage = lazy(
1286
- () => import("./EditConfigurationPage-BGwHNypQ.mjs").then((mod) => ({
1680
+ () => import("./EditConfigurationPage-Dh6sq-G4.mjs").then((mod) => ({
1287
1681
  default: mod.ProtectedEditConfigurationPage
1288
1682
  }))
1289
1683
  );
1290
1684
  const ProtectedComponentConfigurationPage = lazy(
1291
- () => import("./ComponentConfigurationPage-BxJCkKZV.mjs").then((mod) => ({
1685
+ () => import("./ComponentConfigurationPage-hLMNf7KI.mjs").then((mod) => ({
1292
1686
  default: mod.ProtectedComponentConfigurationPage
1293
1687
  }))
1294
1688
  );
1295
1689
  const NoPermissions = lazy(
1296
- () => import("./NoPermissionsPage--afHbbbD.mjs").then((mod) => ({ default: mod.NoPermissions }))
1690
+ () => import("./NoPermissionsPage-h0I3ImsX.mjs").then((mod) => ({ default: mod.NoPermissions }))
1297
1691
  );
1298
1692
  const NoContentType = lazy(
1299
- () => import("./NoContentTypePage-CJ-HJriz.mjs").then((mod) => ({ default: mod.NoContentType }))
1693
+ () => import("./NoContentTypePage-NW_FSVdY.mjs").then((mod) => ({ default: mod.NoContentType }))
1300
1694
  );
1301
1695
  const CollectionTypePages = () => {
1302
1696
  const { collectionType } = useParams();
@@ -1339,1064 +1733,746 @@ const routes = [
1339
1733
  Component: NoPermissions
1340
1734
  },
1341
1735
  {
1342
- path: "no-content-types",
1343
- Component: NoContentType
1344
- },
1345
- ...routes$1
1346
- ];
1347
- const DocumentActions = ({ actions: actions2 }) => {
1348
- const { formatMessage } = useIntl();
1349
- const [primaryAction, secondaryAction, ...restActions] = actions2.filter((action) => {
1350
- if (action.position === void 0) {
1351
- return true;
1352
- }
1353
- const positions = Array.isArray(action.position) ? action.position : [action.position];
1354
- return positions.includes("panel");
1355
- });
1356
- if (!primaryAction) {
1357
- return null;
1358
- }
1359
- return /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, alignItems: "stretch", width: "100%", children: [
1360
- /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
1361
- /* @__PURE__ */ jsx(DocumentActionButton, { ...primaryAction, variant: primaryAction.variant || "default" }),
1362
- restActions.length > 0 ? /* @__PURE__ */ jsx(
1363
- DocumentActionsMenu,
1364
- {
1365
- actions: restActions,
1366
- label: formatMessage({
1367
- id: "content-manager.containers.edit.panels.default.more-actions",
1368
- defaultMessage: "More document actions"
1369
- })
1370
- }
1371
- ) : null
1372
- ] }),
1373
- secondaryAction ? /* @__PURE__ */ jsx(
1374
- DocumentActionButton,
1375
- {
1376
- ...secondaryAction,
1377
- variant: secondaryAction.variant || "secondary"
1378
- }
1379
- ) : null
1380
- ] });
1381
- };
1382
- const DocumentActionButton = (action) => {
1383
- const [dialogId, setDialogId] = React.useState(null);
1384
- const { toggleNotification } = useNotification();
1385
- const handleClick = (action2) => async (e) => {
1386
- const { onClick = () => false, dialog, id } = action2;
1387
- const muteDialog = await onClick(e);
1388
- if (dialog && !muteDialog) {
1389
- switch (dialog.type) {
1390
- case "notification":
1391
- toggleNotification({
1392
- title: dialog.title,
1393
- message: dialog.content,
1394
- type: dialog.status,
1395
- timeout: dialog.timeout,
1396
- onClose: dialog.onClose
1397
- });
1398
- break;
1399
- case "dialog":
1400
- case "modal":
1401
- e.preventDefault();
1402
- setDialogId(id);
1403
- }
1404
- }
1405
- };
1406
- const handleClose = () => {
1407
- setDialogId(null);
1408
- };
1409
- return /* @__PURE__ */ jsxs(Fragment, { children: [
1410
- /* @__PURE__ */ jsx(
1411
- Button,
1412
- {
1413
- flex: 1,
1414
- startIcon: action.icon,
1415
- disabled: action.disabled,
1416
- onClick: handleClick(action),
1417
- justifyContent: "center",
1418
- variant: action.variant || "default",
1419
- children: action.label
1420
- }
1421
- ),
1422
- action.dialog?.type === "dialog" ? /* @__PURE__ */ jsx(
1423
- DocumentActionConfirmDialog,
1424
- {
1425
- ...action.dialog,
1426
- variant: action.dialog?.variant ?? action.variant,
1427
- isOpen: dialogId === action.id,
1428
- onClose: handleClose
1429
- }
1430
- ) : null,
1431
- action.dialog?.type === "modal" ? /* @__PURE__ */ jsx(
1432
- DocumentActionModal,
1433
- {
1434
- ...action.dialog,
1435
- onModalClose: handleClose,
1436
- isOpen: dialogId === action.id
1437
- }
1438
- ) : null
1439
- ] });
1440
- };
1441
- const DocumentActionsMenu = ({
1442
- actions: actions2,
1443
- children,
1444
- label,
1445
- variant = "tertiary"
1446
- }) => {
1447
- const [isOpen, setIsOpen] = React.useState(false);
1448
- const [dialogId, setDialogId] = React.useState(null);
1449
- const { formatMessage } = useIntl();
1450
- const { toggleNotification } = useNotification();
1451
- const isDisabled = actions2.every((action) => action.disabled) || actions2.length === 0;
1452
- const handleClick = (action) => async (e) => {
1453
- const { onClick = () => false, dialog, id } = action;
1454
- const muteDialog = await onClick(e);
1455
- if (dialog && !muteDialog) {
1456
- switch (dialog.type) {
1457
- case "notification":
1458
- toggleNotification({
1459
- title: dialog.title,
1460
- message: dialog.content,
1461
- type: dialog.status,
1462
- timeout: dialog.timeout,
1463
- onClose: dialog.onClose
1464
- });
1465
- break;
1466
- case "dialog":
1467
- case "modal":
1468
- setDialogId(id);
1469
- }
1470
- }
1471
- };
1472
- const handleClose = () => {
1473
- setDialogId(null);
1474
- setIsOpen(false);
1475
- };
1476
- return /* @__PURE__ */ jsxs(Menu.Root, { open: isOpen, onOpenChange: setIsOpen, children: [
1477
- /* @__PURE__ */ jsxs(
1478
- Menu.Trigger,
1479
- {
1480
- disabled: isDisabled,
1481
- size: "S",
1482
- endIcon: null,
1483
- paddingTop: "7px",
1484
- paddingLeft: "9px",
1485
- paddingRight: "9px",
1486
- variant,
1487
- children: [
1488
- /* @__PURE__ */ jsx(More, { "aria-hidden": true, focusable: false }),
1489
- /* @__PURE__ */ jsx(VisuallyHidden, { tag: "span", children: label || formatMessage({
1490
- id: "content-manager.containers.edit.panels.default.more-actions",
1491
- defaultMessage: "More document actions"
1492
- }) })
1493
- ]
1494
- }
1495
- ),
1496
- /* @__PURE__ */ jsxs(Menu.Content, { top: "4px", maxHeight: void 0, popoverPlacement: "bottom-end", children: [
1497
- actions2.map((action) => {
1498
- return /* @__PURE__ */ jsx(
1499
- Menu.Item,
1500
- {
1501
- disabled: action.disabled,
1502
- onSelect: handleClick(action),
1503
- display: "block",
1504
- children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", gap: 4, children: [
1505
- /* @__PURE__ */ jsxs(Flex, { color: convertActionVariantToColor(action.variant), gap: 2, tag: "span", children: [
1506
- /* @__PURE__ */ jsx(Box, { tag: "span", color: convertActionVariantToIconColor(action.variant), children: action.icon }),
1507
- action.label
1508
- ] }),
1509
- action.id.startsWith("HistoryAction") && /* @__PURE__ */ jsx(
1510
- Flex,
1511
- {
1512
- alignItems: "center",
1513
- background: "alternative100",
1514
- borderStyle: "solid",
1515
- borderColor: "alternative200",
1516
- borderWidth: "1px",
1517
- height: 5,
1518
- paddingLeft: 2,
1519
- paddingRight: 2,
1520
- hasRadius: true,
1521
- color: "alternative600",
1522
- children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", fontWeight: "bold", lineHeight: 1, children: formatMessage({ id: "global.new", defaultMessage: "New" }) })
1523
- }
1524
- )
1525
- ] })
1526
- },
1527
- action.id
1528
- );
1529
- }),
1530
- children
1531
- ] }),
1532
- actions2.map((action) => {
1533
- return /* @__PURE__ */ jsxs(React.Fragment, { children: [
1534
- action.dialog?.type === "dialog" ? /* @__PURE__ */ jsx(
1535
- DocumentActionConfirmDialog,
1536
- {
1537
- ...action.dialog,
1538
- variant: action.variant,
1539
- isOpen: dialogId === action.id,
1540
- onClose: handleClose
1541
- }
1542
- ) : null,
1543
- action.dialog?.type === "modal" ? /* @__PURE__ */ jsx(
1544
- DocumentActionModal,
1545
- {
1546
- ...action.dialog,
1547
- onModalClose: handleClose,
1548
- isOpen: dialogId === action.id
1549
- }
1550
- ) : null
1551
- ] }, action.id);
1552
- })
1553
- ] });
1554
- };
1555
- const convertActionVariantToColor = (variant = "secondary") => {
1556
- switch (variant) {
1557
- case "danger":
1558
- return "danger600";
1559
- case "secondary":
1560
- return void 0;
1561
- case "success":
1562
- return "success600";
1563
- default:
1564
- return "primary600";
1565
- }
1566
- };
1567
- const convertActionVariantToIconColor = (variant = "secondary") => {
1568
- switch (variant) {
1569
- case "danger":
1570
- return "danger600";
1571
- case "secondary":
1572
- return "neutral500";
1573
- case "success":
1574
- return "success600";
1575
- default:
1576
- return "primary600";
1577
- }
1578
- };
1579
- const DocumentActionConfirmDialog = ({
1580
- onClose,
1581
- onCancel,
1582
- onConfirm,
1583
- title,
1584
- content,
1585
- isOpen,
1586
- variant = "secondary"
1587
- }) => {
1588
- const { formatMessage } = useIntl();
1589
- const handleClose = async () => {
1590
- if (onCancel) {
1591
- await onCancel();
1592
- }
1593
- onClose();
1594
- };
1595
- const handleConfirm = async () => {
1596
- if (onConfirm) {
1597
- await onConfirm();
1598
- }
1599
- onClose();
1600
- };
1601
- return /* @__PURE__ */ jsx(Dialog.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxs(Dialog.Content, { children: [
1602
- /* @__PURE__ */ jsx(Dialog.Header, { children: title }),
1603
- /* @__PURE__ */ jsx(Dialog.Body, { children: content }),
1604
- /* @__PURE__ */ jsxs(Dialog.Footer, { children: [
1605
- /* @__PURE__ */ jsx(Dialog.Cancel, { children: /* @__PURE__ */ jsx(Button, { variant: "tertiary", children: formatMessage({
1606
- id: "app.components.Button.cancel",
1607
- defaultMessage: "Cancel"
1608
- }) }) }),
1609
- /* @__PURE__ */ jsx(Button, { onClick: handleConfirm, variant, children: formatMessage({
1610
- id: "app.components.Button.confirm",
1611
- defaultMessage: "Confirm"
1612
- }) })
1613
- ] })
1614
- ] }) });
1615
- };
1616
- const DocumentActionModal = ({
1617
- isOpen,
1618
- title,
1619
- onClose,
1620
- footer: Footer,
1621
- content: Content,
1622
- onModalClose
1623
- }) => {
1624
- const handleClose = () => {
1625
- if (onClose) {
1626
- onClose();
1627
- }
1628
- onModalClose();
1629
- };
1630
- return /* @__PURE__ */ jsx(Modal.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxs(Modal.Content, { children: [
1631
- /* @__PURE__ */ jsx(Modal.Header, { children: /* @__PURE__ */ jsx(Modal.Title, { children: title }) }),
1632
- typeof Content === "function" ? /* @__PURE__ */ jsx(Content, { onClose: handleClose }) : /* @__PURE__ */ jsx(Modal.Body, { children: Content }),
1633
- typeof Footer === "function" ? /* @__PURE__ */ jsx(Footer, { onClose: handleClose }) : Footer
1634
- ] }) });
1635
- };
1636
- const PublishAction$1 = ({
1637
- activeTab,
1638
- documentId,
1639
- model,
1640
- collectionType,
1641
- meta,
1642
- document
1643
- }) => {
1644
- const { schema } = useDoc();
1645
- const navigate = useNavigate();
1646
- const { toggleNotification } = useNotification();
1647
- const { _unstableFormatValidationErrors: formatValidationErrors } = useAPIErrorHandler();
1648
- const isCloning = useMatch(CLONE_PATH) !== null;
1649
- const { formatMessage } = useIntl();
1650
- const { canPublish, canCreate, canUpdate } = useDocumentRBAC(
1651
- "PublishAction",
1652
- ({ canPublish: canPublish2, canCreate: canCreate2, canUpdate: canUpdate2 }) => ({ canPublish: canPublish2, canCreate: canCreate2, canUpdate: canUpdate2 })
1653
- );
1654
- const { publish } = useDocumentActions();
1655
- const [
1656
- countDraftRelations,
1657
- { isLoading: isLoadingDraftRelations, isError: isErrorDraftRelations }
1658
- ] = useLazyGetDraftRelationCountQuery();
1659
- const [localCountOfDraftRelations, setLocalCountOfDraftRelations] = React.useState(0);
1660
- const [serverCountOfDraftRelations, setServerCountOfDraftRelations] = React.useState(0);
1661
- const [{ query, rawQuery }] = useQueryParams();
1662
- const params = React.useMemo(() => buildValidParams(query), [query]);
1663
- const modified = useForm("PublishAction", ({ modified: modified2 }) => modified2);
1664
- const setSubmitting = useForm("PublishAction", ({ setSubmitting: setSubmitting2 }) => setSubmitting2);
1665
- const isSubmitting = useForm("PublishAction", ({ isSubmitting: isSubmitting2 }) => isSubmitting2);
1666
- const validate = useForm("PublishAction", (state) => state.validate);
1667
- const setErrors = useForm("PublishAction", (state) => state.setErrors);
1668
- const formValues = useForm("PublishAction", ({ values }) => values);
1669
- React.useEffect(() => {
1670
- if (isErrorDraftRelations) {
1671
- toggleNotification({
1672
- type: "danger",
1673
- message: formatMessage({
1674
- id: getTranslation("error.records.fetch-draft-relatons"),
1675
- defaultMessage: "An error occurred while fetching draft relations on this document."
1676
- })
1677
- });
1678
- }
1679
- }, [isErrorDraftRelations, toggleNotification, formatMessage]);
1680
- React.useEffect(() => {
1681
- const localDraftRelations = /* @__PURE__ */ new Set();
1682
- const extractDraftRelations = (data) => {
1683
- const relations = data.connect || [];
1684
- relations.forEach((relation) => {
1685
- if (relation.status === "draft") {
1686
- localDraftRelations.add(relation.id);
1687
- }
1688
- });
1689
- };
1690
- const traverseAndExtract = (data) => {
1691
- Object.entries(data).forEach(([key, value]) => {
1692
- if (key === "connect" && Array.isArray(value)) {
1693
- extractDraftRelations({ connect: value });
1694
- } else if (typeof value === "object" && value !== null) {
1695
- traverseAndExtract(value);
1696
- }
1697
- });
1698
- };
1699
- if (!documentId || modified) {
1700
- traverseAndExtract(formValues);
1701
- setLocalCountOfDraftRelations(localDraftRelations.size);
1702
- }
1703
- }, [documentId, modified, formValues, setLocalCountOfDraftRelations]);
1704
- React.useEffect(() => {
1705
- if (documentId) {
1706
- const fetchDraftRelationsCount = async () => {
1707
- const { data, error } = await countDraftRelations({
1708
- collectionType,
1709
- model,
1710
- documentId,
1711
- params
1712
- });
1713
- if (error) {
1714
- throw error;
1715
- }
1716
- if (data) {
1717
- setServerCountOfDraftRelations(data.data);
1718
- }
1719
- };
1720
- fetchDraftRelationsCount();
1736
+ path: "no-content-types",
1737
+ Component: NoContentType
1738
+ },
1739
+ ...routes$1
1740
+ ];
1741
+ const DocumentActions = ({ actions: actions2 }) => {
1742
+ const { formatMessage } = useIntl();
1743
+ const [primaryAction, secondaryAction, ...restActions] = actions2.filter((action) => {
1744
+ if (action.position === void 0) {
1745
+ return true;
1721
1746
  }
1722
- }, [documentId, countDraftRelations, collectionType, model, params]);
1723
- const isDocumentPublished = (document?.[PUBLISHED_AT_ATTRIBUTE_NAME] || meta?.availableStatus.some((doc) => doc[PUBLISHED_AT_ATTRIBUTE_NAME] !== null)) && document?.status !== "modified";
1724
- if (!schema?.options?.draftAndPublish) {
1747
+ const positions = Array.isArray(action.position) ? action.position : [action.position];
1748
+ return positions.includes("panel");
1749
+ });
1750
+ if (!primaryAction) {
1725
1751
  return null;
1726
1752
  }
1727
- const performPublish = async () => {
1728
- setSubmitting(true);
1729
- try {
1730
- const { errors } = await validate();
1731
- if (errors) {
1732
- toggleNotification({
1733
- type: "danger",
1734
- message: formatMessage({
1735
- id: "content-manager.validation.error",
1736
- defaultMessage: "There are validation errors in your document. Please fix them before saving."
1737
- })
1738
- });
1739
- return;
1740
- }
1741
- const res = await publish(
1742
- {
1743
- collectionType,
1744
- model,
1745
- documentId,
1746
- params
1747
- },
1748
- formValues
1749
- );
1750
- if ("data" in res && collectionType !== SINGLE_TYPES) {
1751
- navigate({
1752
- pathname: `../${collectionType}/${model}/${res.data.documentId}`,
1753
- search: rawQuery
1754
- });
1755
- } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1756
- setErrors(formatValidationErrors(res.error));
1757
- }
1758
- } finally {
1759
- setSubmitting(false);
1760
- }
1761
- };
1762
- const totalDraftRelations = localCountOfDraftRelations + serverCountOfDraftRelations;
1763
- const hasDraftRelations = totalDraftRelations > 0;
1764
- return {
1765
- /**
1766
- * Disabled when:
1767
- * - currently if you're cloning a document we don't support publish & clone at the same time.
1768
- * - the form is submitting
1769
- * - the active tab is the published tab
1770
- * - the document is already published & not modified
1771
- * - the document is being created & not modified
1772
- * - the user doesn't have the permission to publish
1773
- * - the user doesn't have the permission to create a new document
1774
- * - the user doesn't have the permission to update the document
1775
- */
1776
- disabled: isCloning || isSubmitting || isLoadingDraftRelations || activeTab === "published" || !modified && isDocumentPublished || !modified && !document?.documentId || !canPublish || Boolean(!document?.documentId && !canCreate || document?.documentId && !canUpdate),
1777
- label: formatMessage({
1778
- id: "app.utils.publish",
1779
- defaultMessage: "Publish"
1780
- }),
1781
- onClick: async () => {
1782
- if (hasDraftRelations) {
1783
- return;
1784
- }
1785
- await performPublish();
1786
- },
1787
- dialog: hasDraftRelations ? {
1788
- type: "dialog",
1789
- variant: "danger",
1790
- footer: null,
1791
- title: formatMessage({
1792
- id: getTranslation(`popUpwarning.warning.bulk-has-draft-relations.title`),
1793
- defaultMessage: "Confirmation"
1794
- }),
1795
- content: formatMessage(
1796
- {
1797
- id: getTranslation(`popUpwarning.warning.bulk-has-draft-relations.message`),
1798
- defaultMessage: "This entry is related to {count, plural, one {# draft entry} other {# draft entries}}. Publishing it could leave broken links in your app."
1799
- },
1753
+ return /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, alignItems: "stretch", width: "100%", children: [
1754
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
1755
+ /* @__PURE__ */ jsx(DocumentActionButton, { ...primaryAction, variant: primaryAction.variant || "default" }),
1756
+ restActions.length > 0 ? /* @__PURE__ */ jsx(
1757
+ DocumentActionsMenu,
1800
1758
  {
1801
- count: totalDraftRelations
1759
+ actions: restActions,
1760
+ label: formatMessage({
1761
+ id: "content-manager.containers.edit.panels.default.more-actions",
1762
+ defaultMessage: "More document actions"
1763
+ })
1802
1764
  }
1803
- ),
1804
- onConfirm: async () => {
1805
- await performPublish();
1765
+ ) : null
1766
+ ] }),
1767
+ secondaryAction ? /* @__PURE__ */ jsx(
1768
+ DocumentActionButton,
1769
+ {
1770
+ ...secondaryAction,
1771
+ variant: secondaryAction.variant || "secondary"
1806
1772
  }
1807
- } : void 0
1808
- };
1773
+ ) : null
1774
+ ] });
1809
1775
  };
1810
- PublishAction$1.type = "publish";
1811
- const UpdateAction = ({
1812
- activeTab,
1813
- documentId,
1814
- model,
1815
- collectionType
1816
- }) => {
1817
- const navigate = useNavigate();
1776
+ const DocumentActionButton = (action) => {
1777
+ const [dialogId, setDialogId] = React.useState(null);
1818
1778
  const { toggleNotification } = useNotification();
1819
- const { _unstableFormatValidationErrors: formatValidationErrors } = useAPIErrorHandler();
1820
- const cloneMatch = useMatch(CLONE_PATH);
1821
- const isCloning = cloneMatch !== null;
1822
- const { formatMessage } = useIntl();
1823
- const { canCreate, canUpdate } = useDocumentRBAC("UpdateAction", ({ canCreate: canCreate2, canUpdate: canUpdate2 }) => ({
1824
- canCreate: canCreate2,
1825
- canUpdate: canUpdate2
1826
- }));
1827
- const { create, update, clone } = useDocumentActions();
1828
- const [{ query, rawQuery }] = useQueryParams();
1829
- const params = React.useMemo(() => buildValidParams(query), [query]);
1830
- const isSubmitting = useForm("UpdateAction", ({ isSubmitting: isSubmitting2 }) => isSubmitting2);
1831
- const modified = useForm("UpdateAction", ({ modified: modified2 }) => modified2);
1832
- const setSubmitting = useForm("UpdateAction", ({ setSubmitting: setSubmitting2 }) => setSubmitting2);
1833
- const document = useForm("UpdateAction", ({ values }) => values);
1834
- const validate = useForm("UpdateAction", (state) => state.validate);
1835
- const setErrors = useForm("UpdateAction", (state) => state.setErrors);
1836
- const resetForm = useForm("PublishAction", ({ resetForm: resetForm2 }) => resetForm2);
1837
- return {
1838
- /**
1839
- * Disabled when:
1840
- * - the form is submitting
1841
- * - the document is not modified & we're not cloning (you can save a clone entity straight away)
1842
- * - the active tab is the published tab
1843
- * - the user doesn't have the permission to create a new document
1844
- * - the user doesn't have the permission to update the document
1845
- */
1846
- disabled: isSubmitting || !modified && !isCloning || activeTab === "published" || Boolean(!documentId && !canCreate || documentId && !canUpdate),
1847
- label: formatMessage({
1848
- id: "content-manager.containers.Edit.save",
1849
- defaultMessage: "Save"
1850
- }),
1851
- onClick: async () => {
1852
- setSubmitting(true);
1853
- try {
1854
- const { errors } = await validate();
1855
- if (errors) {
1779
+ const handleClick = (action2) => async (e) => {
1780
+ const { onClick = () => false, dialog, id } = action2;
1781
+ const muteDialog = await onClick(e);
1782
+ if (dialog && !muteDialog) {
1783
+ switch (dialog.type) {
1784
+ case "notification":
1856
1785
  toggleNotification({
1857
- type: "danger",
1858
- message: formatMessage({
1859
- id: "content-manager.validation.error",
1860
- defaultMessage: "There are validation errors in your document. Please fix them before saving."
1861
- })
1786
+ title: dialog.title,
1787
+ message: dialog.content,
1788
+ type: dialog.status,
1789
+ timeout: dialog.timeout,
1790
+ onClose: dialog.onClose
1862
1791
  });
1863
- return;
1864
- }
1865
- if (isCloning) {
1866
- const res = await clone(
1867
- {
1868
- model,
1869
- documentId: cloneMatch.params.origin,
1870
- params
1871
- },
1872
- document
1873
- );
1874
- if ("data" in res) {
1875
- navigate(
1876
- {
1877
- pathname: `../${res.data.documentId}`,
1878
- search: rawQuery
1879
- },
1880
- { relative: "path" }
1881
- );
1882
- } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1883
- setErrors(formatValidationErrors(res.error));
1884
- }
1885
- } else if (documentId || collectionType === SINGLE_TYPES) {
1886
- const res = await update(
1887
- {
1888
- collectionType,
1889
- model,
1890
- documentId,
1891
- params
1892
- },
1893
- document
1894
- );
1895
- if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1896
- setErrors(formatValidationErrors(res.error));
1897
- } else {
1898
- resetForm();
1899
- }
1900
- } else {
1901
- const res = await create(
1902
- {
1903
- model,
1904
- params
1905
- },
1906
- document
1907
- );
1908
- if ("data" in res && collectionType !== SINGLE_TYPES) {
1909
- navigate(
1910
- {
1911
- pathname: `../${res.data.documentId}`,
1912
- search: rawQuery
1913
- },
1914
- { replace: true, relative: "path" }
1915
- );
1916
- } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1917
- setErrors(formatValidationErrors(res.error));
1918
- }
1919
- }
1920
- } finally {
1921
- setSubmitting(false);
1792
+ break;
1793
+ case "dialog":
1794
+ case "modal":
1795
+ e.preventDefault();
1796
+ setDialogId(id);
1922
1797
  }
1923
1798
  }
1924
1799
  };
1800
+ const handleClose = () => {
1801
+ setDialogId(null);
1802
+ };
1803
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1804
+ /* @__PURE__ */ jsx(
1805
+ Button,
1806
+ {
1807
+ flex: "auto",
1808
+ startIcon: action.icon,
1809
+ disabled: action.disabled,
1810
+ onClick: handleClick(action),
1811
+ justifyContent: "center",
1812
+ variant: action.variant || "default",
1813
+ paddingTop: "7px",
1814
+ paddingBottom: "7px",
1815
+ children: action.label
1816
+ }
1817
+ ),
1818
+ action.dialog?.type === "dialog" ? /* @__PURE__ */ jsx(
1819
+ DocumentActionConfirmDialog,
1820
+ {
1821
+ ...action.dialog,
1822
+ variant: action.dialog?.variant ?? action.variant,
1823
+ isOpen: dialogId === action.id,
1824
+ onClose: handleClose
1825
+ }
1826
+ ) : null,
1827
+ action.dialog?.type === "modal" ? /* @__PURE__ */ jsx(
1828
+ DocumentActionModal,
1829
+ {
1830
+ ...action.dialog,
1831
+ onModalClose: handleClose,
1832
+ isOpen: dialogId === action.id
1833
+ }
1834
+ ) : null
1835
+ ] });
1925
1836
  };
1926
- UpdateAction.type = "update";
1927
- const UNPUBLISH_DRAFT_OPTIONS = {
1928
- KEEP: "keep",
1929
- DISCARD: "discard"
1930
- };
1931
- const UnpublishAction$1 = ({
1932
- activeTab,
1933
- documentId,
1934
- model,
1935
- collectionType,
1936
- document
1837
+ const DocumentActionsMenu = ({
1838
+ actions: actions2,
1839
+ children,
1840
+ label,
1841
+ variant = "tertiary"
1937
1842
  }) => {
1843
+ const [isOpen, setIsOpen] = React.useState(false);
1844
+ const [dialogId, setDialogId] = React.useState(null);
1938
1845
  const { formatMessage } = useIntl();
1939
- const { schema } = useDoc();
1940
- const canPublish = useDocumentRBAC("UnpublishAction", ({ canPublish: canPublish2 }) => canPublish2);
1941
- const { unpublish } = useDocumentActions();
1942
- const [{ query }] = useQueryParams();
1943
- const params = React.useMemo(() => buildValidParams(query), [query]);
1944
1846
  const { toggleNotification } = useNotification();
1945
- const [shouldKeepDraft, setShouldKeepDraft] = React.useState(true);
1946
- const isDocumentModified = document?.status === "modified";
1947
- const handleChange = (value) => {
1948
- setShouldKeepDraft(value === UNPUBLISH_DRAFT_OPTIONS.KEEP);
1949
- };
1950
- if (!schema?.options?.draftAndPublish) {
1951
- return null;
1952
- }
1953
- return {
1954
- disabled: !canPublish || activeTab === "published" || document?.status !== "published" && document?.status !== "modified",
1955
- label: formatMessage({
1956
- id: "app.utils.unpublish",
1957
- defaultMessage: "Unpublish"
1958
- }),
1959
- icon: /* @__PURE__ */ jsx(StyledCrossCircle, {}),
1960
- onClick: async () => {
1961
- if (!documentId && collectionType !== SINGLE_TYPES || isDocumentModified) {
1962
- if (!documentId) {
1963
- console.error(
1964
- "You're trying to unpublish a document without an id, this is likely a bug with Strapi. Please open an issue."
1965
- );
1847
+ const isDisabled = actions2.every((action) => action.disabled) || actions2.length === 0;
1848
+ const handleClick = (action) => async (e) => {
1849
+ const { onClick = () => false, dialog, id } = action;
1850
+ const muteDialog = await onClick(e);
1851
+ if (dialog && !muteDialog) {
1852
+ switch (dialog.type) {
1853
+ case "notification":
1966
1854
  toggleNotification({
1967
- message: formatMessage({
1968
- id: "content-manager.actions.unpublish.error",
1969
- defaultMessage: "An error occurred while trying to unpublish the document."
1970
- }),
1971
- type: "danger"
1855
+ title: dialog.title,
1856
+ message: dialog.content,
1857
+ type: dialog.status,
1858
+ timeout: dialog.timeout,
1859
+ onClose: dialog.onClose
1972
1860
  });
1973
- }
1974
- return;
1861
+ break;
1862
+ case "dialog":
1863
+ case "modal":
1864
+ setDialogId(id);
1975
1865
  }
1976
- await unpublish({
1977
- collectionType,
1978
- model,
1979
- documentId,
1980
- params
1981
- });
1982
- },
1983
- dialog: isDocumentModified ? {
1984
- type: "dialog",
1985
- title: formatMessage({
1986
- id: "app.components.ConfirmDialog.title",
1987
- defaultMessage: "Confirmation"
1988
- }),
1989
- content: /* @__PURE__ */ jsxs(Flex, { alignItems: "flex-start", direction: "column", gap: 6, children: [
1990
- /* @__PURE__ */ jsxs(Flex, { width: "100%", direction: "column", gap: 2, children: [
1991
- /* @__PURE__ */ jsx(WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
1992
- /* @__PURE__ */ jsx(Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
1993
- id: "content-manager.actions.unpublish.dialog.body",
1994
- defaultMessage: "Are you sure?"
1866
+ }
1867
+ };
1868
+ const handleClose = () => {
1869
+ setDialogId(null);
1870
+ setIsOpen(false);
1871
+ };
1872
+ return /* @__PURE__ */ jsxs(Menu.Root, { open: isOpen, onOpenChange: setIsOpen, children: [
1873
+ /* @__PURE__ */ jsxs(
1874
+ Menu.Trigger,
1875
+ {
1876
+ disabled: isDisabled,
1877
+ size: "S",
1878
+ endIcon: null,
1879
+ paddingTop: "4px",
1880
+ paddingLeft: "7px",
1881
+ paddingRight: "7px",
1882
+ variant,
1883
+ children: [
1884
+ /* @__PURE__ */ jsx(More, { "aria-hidden": true, focusable: false }),
1885
+ /* @__PURE__ */ jsx(VisuallyHidden, { tag: "span", children: label || formatMessage({
1886
+ id: "content-manager.containers.edit.panels.default.more-actions",
1887
+ defaultMessage: "More document actions"
1995
1888
  }) })
1996
- ] }),
1997
- /* @__PURE__ */ jsxs(
1998
- Radio.Group,
1999
- {
2000
- defaultValue: UNPUBLISH_DRAFT_OPTIONS.KEEP,
2001
- name: "discard-options",
2002
- "aria-label": formatMessage({
2003
- id: "content-manager.actions.unpublish.dialog.radio-label",
2004
- defaultMessage: "Choose an option to unpublish the document."
2005
- }),
2006
- onValueChange: handleChange,
2007
- children: [
2008
- /* @__PURE__ */ jsx(Radio.Item, { checked: shouldKeepDraft, value: UNPUBLISH_DRAFT_OPTIONS.KEEP, children: formatMessage({
2009
- id: "content-manager.actions.unpublish.dialog.option.keep-draft",
2010
- defaultMessage: "Keep draft"
2011
- }) }),
2012
- /* @__PURE__ */ jsx(Radio.Item, { checked: !shouldKeepDraft, value: UNPUBLISH_DRAFT_OPTIONS.DISCARD, children: formatMessage({
2013
- id: "content-manager.actions.unpublish.dialog.option.replace-draft",
2014
- defaultMessage: "Replace draft"
2015
- }) })
2016
- ]
2017
- }
2018
- )
2019
- ] }),
2020
- onConfirm: async () => {
2021
- if (!documentId && collectionType !== SINGLE_TYPES) {
2022
- console.error(
2023
- "You're trying to unpublish a document without an id, this is likely a bug with Strapi. Please open an issue."
2024
- );
2025
- toggleNotification({
2026
- message: formatMessage({
2027
- id: "content-manager.actions.unpublish.error",
2028
- defaultMessage: "An error occurred while trying to unpublish the document."
2029
- }),
2030
- type: "danger"
2031
- });
2032
- }
2033
- await unpublish(
1889
+ ]
1890
+ }
1891
+ ),
1892
+ /* @__PURE__ */ jsxs(Menu.Content, { maxHeight: void 0, popoverPlacement: "bottom-end", children: [
1893
+ actions2.map((action) => {
1894
+ return /* @__PURE__ */ jsx(
1895
+ Menu.Item,
2034
1896
  {
2035
- collectionType,
2036
- model,
2037
- documentId,
2038
- params
1897
+ disabled: action.disabled,
1898
+ onSelect: handleClick(action),
1899
+ display: "block",
1900
+ children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", gap: 4, children: [
1901
+ /* @__PURE__ */ jsxs(
1902
+ Flex,
1903
+ {
1904
+ color: !action.disabled ? convertActionVariantToColor(action.variant) : "inherit",
1905
+ gap: 2,
1906
+ tag: "span",
1907
+ children: [
1908
+ /* @__PURE__ */ jsx(
1909
+ Flex,
1910
+ {
1911
+ tag: "span",
1912
+ color: !action.disabled ? convertActionVariantToIconColor(action.variant) : "inherit",
1913
+ children: action.icon
1914
+ }
1915
+ ),
1916
+ action.label
1917
+ ]
1918
+ }
1919
+ ),
1920
+ action.id.startsWith("HistoryAction") && /* @__PURE__ */ jsx(
1921
+ Flex,
1922
+ {
1923
+ alignItems: "center",
1924
+ background: "alternative100",
1925
+ borderStyle: "solid",
1926
+ borderColor: "alternative200",
1927
+ borderWidth: "1px",
1928
+ height: 5,
1929
+ paddingLeft: 2,
1930
+ paddingRight: 2,
1931
+ hasRadius: true,
1932
+ color: "alternative600",
1933
+ children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", fontWeight: "bold", lineHeight: 1, children: formatMessage({ id: "global.new", defaultMessage: "New" }) })
1934
+ }
1935
+ )
1936
+ ] })
2039
1937
  },
2040
- !shouldKeepDraft
1938
+ action.id
2041
1939
  );
2042
- }
2043
- } : void 0,
2044
- variant: "danger",
2045
- position: ["panel", "table-row"]
1940
+ }),
1941
+ children
1942
+ ] }),
1943
+ actions2.map((action) => {
1944
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
1945
+ action.dialog?.type === "dialog" ? /* @__PURE__ */ jsx(
1946
+ DocumentActionConfirmDialog,
1947
+ {
1948
+ ...action.dialog,
1949
+ variant: action.variant,
1950
+ isOpen: dialogId === action.id,
1951
+ onClose: handleClose
1952
+ }
1953
+ ) : null,
1954
+ action.dialog?.type === "modal" ? /* @__PURE__ */ jsx(
1955
+ DocumentActionModal,
1956
+ {
1957
+ ...action.dialog,
1958
+ onModalClose: handleClose,
1959
+ isOpen: dialogId === action.id
1960
+ }
1961
+ ) : null
1962
+ ] }, action.id);
1963
+ })
1964
+ ] });
1965
+ };
1966
+ const convertActionVariantToColor = (variant = "secondary") => {
1967
+ switch (variant) {
1968
+ case "danger":
1969
+ return "danger600";
1970
+ case "secondary":
1971
+ return void 0;
1972
+ case "success":
1973
+ return "success600";
1974
+ default:
1975
+ return "primary600";
1976
+ }
1977
+ };
1978
+ const convertActionVariantToIconColor = (variant = "secondary") => {
1979
+ switch (variant) {
1980
+ case "danger":
1981
+ return "danger600";
1982
+ case "secondary":
1983
+ return "neutral500";
1984
+ case "success":
1985
+ return "success600";
1986
+ default:
1987
+ return "primary600";
1988
+ }
1989
+ };
1990
+ const DocumentActionConfirmDialog = ({
1991
+ onClose,
1992
+ onCancel,
1993
+ onConfirm,
1994
+ title,
1995
+ content,
1996
+ isOpen,
1997
+ variant = "secondary"
1998
+ }) => {
1999
+ const { formatMessage } = useIntl();
2000
+ const handleClose = async () => {
2001
+ if (onCancel) {
2002
+ await onCancel();
2003
+ }
2004
+ onClose();
2005
+ };
2006
+ const handleConfirm = async () => {
2007
+ if (onConfirm) {
2008
+ await onConfirm();
2009
+ }
2010
+ onClose();
2011
+ };
2012
+ return /* @__PURE__ */ jsx(Dialog.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxs(Dialog.Content, { children: [
2013
+ /* @__PURE__ */ jsx(Dialog.Header, { children: title }),
2014
+ /* @__PURE__ */ jsx(Dialog.Body, { children: content }),
2015
+ /* @__PURE__ */ jsxs(Dialog.Footer, { children: [
2016
+ /* @__PURE__ */ jsx(Dialog.Cancel, { children: /* @__PURE__ */ jsx(Button, { variant: "tertiary", fullWidth: true, children: formatMessage({
2017
+ id: "app.components.Button.cancel",
2018
+ defaultMessage: "Cancel"
2019
+ }) }) }),
2020
+ /* @__PURE__ */ jsx(Button, { onClick: handleConfirm, variant, fullWidth: true, children: formatMessage({
2021
+ id: "app.components.Button.confirm",
2022
+ defaultMessage: "Confirm"
2023
+ }) })
2024
+ ] })
2025
+ ] }) });
2026
+ };
2027
+ const DocumentActionModal = ({
2028
+ isOpen,
2029
+ title,
2030
+ onClose,
2031
+ footer: Footer,
2032
+ content: Content,
2033
+ onModalClose
2034
+ }) => {
2035
+ const handleClose = () => {
2036
+ if (onClose) {
2037
+ onClose();
2038
+ }
2039
+ onModalClose();
2046
2040
  };
2041
+ return /* @__PURE__ */ jsx(Modal.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxs(Modal.Content, { children: [
2042
+ /* @__PURE__ */ jsx(Modal.Header, { children: /* @__PURE__ */ jsx(Modal.Title, { children: title }) }),
2043
+ typeof Content === "function" ? /* @__PURE__ */ jsx(Content, { onClose: handleClose }) : /* @__PURE__ */ jsx(Modal.Body, { children: Content }),
2044
+ typeof Footer === "function" ? /* @__PURE__ */ jsx(Footer, { onClose: handleClose }) : Footer
2045
+ ] }) });
2047
2046
  };
2048
- UnpublishAction$1.type = "unpublish";
2049
- const DiscardAction = ({
2047
+ const PublishAction$1 = ({
2050
2048
  activeTab,
2051
2049
  documentId,
2052
2050
  model,
2053
2051
  collectionType,
2052
+ meta,
2054
2053
  document
2055
2054
  }) => {
2056
- const { formatMessage } = useIntl();
2057
2055
  const { schema } = useDoc();
2058
- const canUpdate = useDocumentRBAC("DiscardAction", ({ canUpdate: canUpdate2 }) => canUpdate2);
2059
- const { discard } = useDocumentActions();
2060
- const [{ query }] = useQueryParams();
2061
- const params = React.useMemo(() => buildValidParams(query), [query]);
2062
- if (!schema?.options?.draftAndPublish) {
2063
- return null;
2064
- }
2065
- return {
2066
- disabled: !canUpdate || activeTab === "published" || document?.status !== "modified",
2067
- label: formatMessage({
2068
- id: "content-manager.actions.discard.label",
2069
- defaultMessage: "Discard changes"
2070
- }),
2071
- icon: /* @__PURE__ */ jsx(StyledCrossCircle, {}),
2072
- position: ["panel", "table-row"],
2073
- variant: "danger",
2074
- dialog: {
2075
- type: "dialog",
2076
- title: formatMessage({
2077
- id: "app.components.ConfirmDialog.title",
2078
- defaultMessage: "Confirmation"
2079
- }),
2080
- content: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, children: [
2081
- /* @__PURE__ */ jsx(WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
2082
- /* @__PURE__ */ jsx(Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
2083
- id: "content-manager.actions.discard.dialog.body",
2084
- defaultMessage: "Are you sure?"
2085
- }) })
2086
- ] }),
2087
- onConfirm: async () => {
2088
- await discard({
2089
- collectionType,
2090
- model,
2091
- documentId,
2092
- params
2093
- });
2094
- }
2095
- }
2096
- };
2097
- };
2098
- DiscardAction.type = "discard";
2099
- const StyledCrossCircle = styled(CrossCircle)`
2100
- path {
2101
- fill: currentColor;
2102
- }
2103
- `;
2104
- const DEFAULT_ACTIONS = [PublishAction$1, UpdateAction, UnpublishAction$1, DiscardAction];
2105
- const intervals = ["years", "months", "days", "hours", "minutes", "seconds"];
2106
- const RelativeTime = React.forwardRef(
2107
- ({ timestamp, customIntervals = [], ...restProps }, forwardedRef) => {
2108
- const { formatRelativeTime, formatDate, formatTime } = useIntl();
2109
- const interval = intervalToDuration({
2110
- start: timestamp,
2111
- end: Date.now()
2112
- // see https://github.com/date-fns/date-fns/issues/2891 – No idea why it's all partial it returns it every time.
2113
- });
2114
- const unit = intervals.find((intervalUnit) => {
2115
- return interval[intervalUnit] > 0 && Object.keys(interval).includes(intervalUnit);
2116
- });
2117
- const relativeTime = isPast(timestamp) ? -interval[unit] : interval[unit];
2118
- const customInterval = customIntervals.find(
2119
- (custom) => interval[custom.unit] < custom.threshold
2120
- );
2121
- const displayText = customInterval ? customInterval.text : formatRelativeTime(relativeTime, unit, { numeric: "auto" });
2122
- return /* @__PURE__ */ jsx(
2123
- "time",
2124
- {
2125
- ref: forwardedRef,
2126
- dateTime: timestamp.toISOString(),
2127
- role: "time",
2128
- title: `${formatDate(timestamp)} ${formatTime(timestamp)}`,
2129
- ...restProps,
2130
- children: displayText
2131
- }
2132
- );
2133
- }
2134
- );
2135
- const getDisplayName = ({
2136
- firstname,
2137
- lastname,
2138
- username,
2139
- email
2140
- } = {}) => {
2141
- if (username) {
2142
- return username;
2143
- }
2144
- if (firstname) {
2145
- return `${firstname} ${lastname ?? ""}`.trim();
2146
- }
2147
- return email ?? "";
2148
- };
2149
- const capitalise = (str) => str.charAt(0).toUpperCase() + str.slice(1);
2150
- const DocumentStatus = ({ status = "draft", ...restProps }) => {
2151
- const statusVariant = status === "draft" ? "primary" : status === "published" ? "success" : "alternative";
2152
- return /* @__PURE__ */ jsx(Status, { ...restProps, showBullet: false, size: "S", variant: statusVariant, children: /* @__PURE__ */ jsx(Typography, { tag: "span", variant: "omega", fontWeight: "bold", children: capitalise(status) }) });
2153
- };
2154
- const Header = ({ isCreating, status, title: documentTitle = "Untitled" }) => {
2155
- const { formatMessage } = useIntl();
2056
+ const navigate = useNavigate();
2057
+ const { toggleNotification } = useNotification();
2058
+ const { _unstableFormatValidationErrors: formatValidationErrors } = useAPIErrorHandler();
2059
+ const isListView = useMatch(LIST_PATH) !== null;
2156
2060
  const isCloning = useMatch(CLONE_PATH) !== null;
2157
- const title = isCreating ? formatMessage({
2158
- id: "content-manager.containers.edit.title.new",
2159
- defaultMessage: "Create an entry"
2160
- }) : documentTitle;
2161
- return /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", paddingTop: 6, paddingBottom: 4, gap: 2, children: [
2162
- /* @__PURE__ */ jsx(BackButton, {}),
2163
- /* @__PURE__ */ jsxs(Flex, { width: "100%", justifyContent: "space-between", gap: "80px", alignItems: "flex-start", children: [
2164
- /* @__PURE__ */ jsx(Typography, { variant: "alpha", tag: "h1", children: title }),
2165
- /* @__PURE__ */ jsx(HeaderToolbar, {})
2166
- ] }),
2167
- status ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(DocumentStatus, { status: isCloning ? "draft" : status }) }) : null
2168
- ] });
2169
- };
2170
- const HeaderToolbar = () => {
2171
2061
  const { formatMessage } = useIntl();
2172
- const isCloning = useMatch(CLONE_PATH) !== null;
2062
+ const canPublish = useDocumentRBAC("PublishAction", ({ canPublish: canPublish2 }) => canPublish2);
2063
+ const { publish } = useDocumentActions();
2173
2064
  const [
2174
- {
2175
- query: { status = "draft" }
2065
+ countDraftRelations,
2066
+ { isLoading: isLoadingDraftRelations, isError: isErrorDraftRelations }
2067
+ ] = useLazyGetDraftRelationCountQuery();
2068
+ const [localCountOfDraftRelations, setLocalCountOfDraftRelations] = React.useState(0);
2069
+ const [serverCountOfDraftRelations, setServerCountOfDraftRelations] = React.useState(0);
2070
+ const [{ query, rawQuery }] = useQueryParams();
2071
+ const params = React.useMemo(() => buildValidParams(query), [query]);
2072
+ const modified = useForm("PublishAction", ({ modified: modified2 }) => modified2);
2073
+ const setSubmitting = useForm("PublishAction", ({ setSubmitting: setSubmitting2 }) => setSubmitting2);
2074
+ const isSubmitting = useForm("PublishAction", ({ isSubmitting: isSubmitting2 }) => isSubmitting2);
2075
+ const validate = useForm("PublishAction", (state) => state.validate);
2076
+ const setErrors = useForm("PublishAction", (state) => state.setErrors);
2077
+ const formValues = useForm("PublishAction", ({ values }) => values);
2078
+ React.useEffect(() => {
2079
+ if (isErrorDraftRelations) {
2080
+ toggleNotification({
2081
+ type: "danger",
2082
+ message: formatMessage({
2083
+ id: getTranslation("error.records.fetch-draft-relatons"),
2084
+ defaultMessage: "An error occurred while fetching draft relations on this document."
2085
+ })
2086
+ });
2176
2087
  }
2177
- ] = useQueryParams();
2178
- const { model, id, document, meta, collectionType } = useDoc();
2179
- const plugins = useStrapiApp("HeaderToolbar", (state) => state.plugins);
2180
- return /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
2181
- /* @__PURE__ */ jsx(
2182
- DescriptionComponentRenderer,
2183
- {
2184
- props: {
2185
- activeTab: status,
2186
- model,
2187
- documentId: id,
2188
- document: isCloning ? void 0 : document,
2189
- meta: isCloning ? void 0 : meta,
2190
- collectionType
2191
- },
2192
- descriptions: plugins["content-manager"].apis.getHeaderActions(),
2193
- children: (actions2) => {
2194
- if (actions2.length > 0) {
2195
- return /* @__PURE__ */ jsx(HeaderActions, { actions: actions2 });
2196
- } else {
2197
- return null;
2198
- }
2088
+ }, [isErrorDraftRelations, toggleNotification, formatMessage]);
2089
+ React.useEffect(() => {
2090
+ const localDraftRelations = /* @__PURE__ */ new Set();
2091
+ const extractDraftRelations = (data) => {
2092
+ const relations = data.connect || [];
2093
+ relations.forEach((relation) => {
2094
+ if (relation.status === "draft") {
2095
+ localDraftRelations.add(relation.id);
2199
2096
  }
2200
- }
2201
- ),
2202
- /* @__PURE__ */ jsx(
2203
- DescriptionComponentRenderer,
2204
- {
2205
- props: {
2206
- activeTab: status,
2207
- model,
2208
- documentId: id,
2209
- document: isCloning ? void 0 : document,
2210
- meta: isCloning ? void 0 : meta,
2211
- collectionType
2212
- },
2213
- descriptions: plugins["content-manager"].apis.getDocumentActions(),
2214
- children: (actions2) => {
2215
- const headerActions = actions2.filter((action) => {
2216
- const positions = Array.isArray(action.position) ? action.position : [action.position];
2217
- return positions.includes("header");
2218
- });
2219
- return /* @__PURE__ */ jsx(
2220
- DocumentActionsMenu,
2221
- {
2222
- actions: headerActions,
2223
- label: formatMessage({
2224
- id: "content-manager.containers.edit.header.more-actions",
2225
- defaultMessage: "More actions"
2226
- }),
2227
- children: /* @__PURE__ */ jsx(Information, { activeTab: status })
2228
- }
2229
- );
2097
+ });
2098
+ };
2099
+ const traverseAndExtract = (data) => {
2100
+ Object.entries(data).forEach(([key, value]) => {
2101
+ if (key === "connect" && Array.isArray(value)) {
2102
+ extractDraftRelations({ connect: value });
2103
+ } else if (typeof value === "object" && value !== null) {
2104
+ traverseAndExtract(value);
2230
2105
  }
2106
+ });
2107
+ };
2108
+ if (!documentId || modified) {
2109
+ traverseAndExtract(formValues);
2110
+ setLocalCountOfDraftRelations(localDraftRelations.size);
2111
+ }
2112
+ }, [documentId, modified, formValues, setLocalCountOfDraftRelations]);
2113
+ React.useEffect(() => {
2114
+ if (!document || !document.documentId || isListView) {
2115
+ return;
2116
+ }
2117
+ const fetchDraftRelationsCount = async () => {
2118
+ const { data, error } = await countDraftRelations({
2119
+ collectionType,
2120
+ model,
2121
+ documentId,
2122
+ params
2123
+ });
2124
+ if (error) {
2125
+ throw error;
2126
+ }
2127
+ if (data) {
2128
+ setServerCountOfDraftRelations(data.data);
2231
2129
  }
2232
- )
2233
- ] });
2234
- };
2235
- const Information = ({ activeTab }) => {
2236
- const { formatMessage } = useIntl();
2237
- const { document, meta } = useDoc();
2238
- if (!document || !document.id) {
2130
+ };
2131
+ fetchDraftRelationsCount();
2132
+ }, [isListView, document, documentId, countDraftRelations, collectionType, model, params]);
2133
+ const isDocumentPublished = (document?.[PUBLISHED_AT_ATTRIBUTE_NAME] || meta?.availableStatus.some((doc) => doc[PUBLISHED_AT_ATTRIBUTE_NAME] !== null)) && document?.status !== "modified";
2134
+ if (!schema?.options?.draftAndPublish) {
2239
2135
  return null;
2240
2136
  }
2241
- const createAndUpdateDocument = activeTab === "draft" ? document : meta?.availableStatus.find((status) => status.publishedAt === null);
2242
- const publishDocument = activeTab === "published" ? document : meta?.availableStatus.find((status) => status.publishedAt !== null);
2243
- const creator = createAndUpdateDocument?.[CREATED_BY_ATTRIBUTE_NAME] ? getDisplayName(createAndUpdateDocument[CREATED_BY_ATTRIBUTE_NAME]) : null;
2244
- const updator = createAndUpdateDocument?.[UPDATED_BY_ATTRIBUTE_NAME] ? getDisplayName(createAndUpdateDocument[UPDATED_BY_ATTRIBUTE_NAME]) : null;
2245
- const information = [
2246
- {
2247
- isDisplayed: !!publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME],
2248
- label: formatMessage({
2249
- id: "content-manager.containers.edit.information.last-published.label",
2250
- defaultMessage: "Last published"
2251
- }),
2252
- value: formatMessage(
2137
+ const performPublish = async () => {
2138
+ setSubmitting(true);
2139
+ try {
2140
+ const { errors } = await validate();
2141
+ if (errors) {
2142
+ toggleNotification({
2143
+ type: "danger",
2144
+ message: formatMessage({
2145
+ id: "content-manager.validation.error",
2146
+ defaultMessage: "There are validation errors in your document. Please fix them before saving."
2147
+ })
2148
+ });
2149
+ return;
2150
+ }
2151
+ const res = await publish(
2253
2152
  {
2254
- id: "content-manager.containers.edit.information.last-published.value",
2255
- defaultMessage: `Published {time}{isAnonymous, select, true {} other { by {author}}}`
2153
+ collectionType,
2154
+ model,
2155
+ documentId,
2156
+ params
2256
2157
  },
2257
- {
2258
- time: /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME]) }),
2259
- isAnonymous: !publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME],
2260
- author: publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME] ? getDisplayName(publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME]) : null
2261
- }
2262
- )
2158
+ formValues
2159
+ );
2160
+ if ("data" in res && collectionType !== SINGLE_TYPES) {
2161
+ navigate({
2162
+ pathname: `../${collectionType}/${model}/${res.data.documentId}`,
2163
+ search: rawQuery
2164
+ });
2165
+ } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
2166
+ setErrors(formatValidationErrors(res.error));
2167
+ }
2168
+ } finally {
2169
+ setSubmitting(false);
2170
+ }
2171
+ };
2172
+ const totalDraftRelations = localCountOfDraftRelations + serverCountOfDraftRelations;
2173
+ const enableDraftRelationsCount = false;
2174
+ const hasDraftRelations = enableDraftRelationsCount;
2175
+ return {
2176
+ /**
2177
+ * Disabled when:
2178
+ * - currently if you're cloning a document we don't support publish & clone at the same time.
2179
+ * - the form is submitting
2180
+ * - the active tab is the published tab
2181
+ * - the document is already published & not modified
2182
+ * - the document is being created & not modified
2183
+ * - the user doesn't have the permission to publish
2184
+ */
2185
+ disabled: isCloning || isSubmitting || isLoadingDraftRelations || activeTab === "published" || !modified && isDocumentPublished || !modified && !document?.documentId || !canPublish,
2186
+ label: formatMessage({
2187
+ id: "app.utils.publish",
2188
+ defaultMessage: "Publish"
2189
+ }),
2190
+ onClick: async () => {
2191
+ await performPublish();
2263
2192
  },
2264
- {
2265
- isDisplayed: !!createAndUpdateDocument?.[UPDATED_AT_ATTRIBUTE_NAME],
2266
- label: formatMessage({
2267
- id: "content-manager.containers.edit.information.last-draft.label",
2268
- defaultMessage: "Last draft"
2193
+ dialog: hasDraftRelations ? {
2194
+ type: "dialog",
2195
+ variant: "danger",
2196
+ footer: null,
2197
+ title: formatMessage({
2198
+ id: getTranslation(`popUpwarning.warning.bulk-has-draft-relations.title`),
2199
+ defaultMessage: "Confirmation"
2269
2200
  }),
2270
- value: formatMessage(
2201
+ content: formatMessage(
2271
2202
  {
2272
- id: "content-manager.containers.edit.information.last-draft.value",
2273
- defaultMessage: `Modified {time}{isAnonymous, select, true {} other { by {author}}}`
2203
+ id: getTranslation(`popUpwarning.warning.bulk-has-draft-relations.message`),
2204
+ defaultMessage: "This entry is related to {count, plural, one {# draft entry} other {# draft entries}}. Publishing it could leave broken links in your app."
2274
2205
  },
2275
2206
  {
2276
- time: /* @__PURE__ */ jsx(
2277
- RelativeTime,
2278
- {
2279
- timestamp: new Date(createAndUpdateDocument?.[UPDATED_AT_ATTRIBUTE_NAME])
2280
- }
2281
- ),
2282
- isAnonymous: !updator,
2283
- author: updator
2207
+ count: totalDraftRelations
2284
2208
  }
2285
- )
2286
- },
2287
- {
2288
- isDisplayed: !!createAndUpdateDocument?.[CREATED_AT_ATTRIBUTE_NAME],
2289
- label: formatMessage({
2290
- id: "content-manager.containers.edit.information.document.label",
2291
- defaultMessage: "Document"
2292
- }),
2293
- value: formatMessage(
2294
- {
2295
- id: "content-manager.containers.edit.information.document.value",
2296
- defaultMessage: `Created {time}{isAnonymous, select, true {} other { by {author}}}`
2297
- },
2298
- {
2299
- time: /* @__PURE__ */ jsx(
2300
- RelativeTime,
2209
+ ),
2210
+ onConfirm: async () => {
2211
+ await performPublish();
2212
+ }
2213
+ } : void 0
2214
+ };
2215
+ };
2216
+ PublishAction$1.type = "publish";
2217
+ const UpdateAction = ({
2218
+ activeTab,
2219
+ documentId,
2220
+ model,
2221
+ collectionType
2222
+ }) => {
2223
+ const navigate = useNavigate();
2224
+ const { toggleNotification } = useNotification();
2225
+ const { _unstableFormatValidationErrors: formatValidationErrors } = useAPIErrorHandler();
2226
+ const cloneMatch = useMatch(CLONE_PATH);
2227
+ const isCloning = cloneMatch !== null;
2228
+ const { formatMessage } = useIntl();
2229
+ const { create, update, clone } = useDocumentActions();
2230
+ const [{ query, rawQuery }] = useQueryParams();
2231
+ const params = React.useMemo(() => buildValidParams(query), [query]);
2232
+ const isSubmitting = useForm("UpdateAction", ({ isSubmitting: isSubmitting2 }) => isSubmitting2);
2233
+ const modified = useForm("UpdateAction", ({ modified: modified2 }) => modified2);
2234
+ const setSubmitting = useForm("UpdateAction", ({ setSubmitting: setSubmitting2 }) => setSubmitting2);
2235
+ const document = useForm("UpdateAction", ({ values }) => values);
2236
+ const validate = useForm("UpdateAction", (state) => state.validate);
2237
+ const setErrors = useForm("UpdateAction", (state) => state.setErrors);
2238
+ const resetForm = useForm("PublishAction", ({ resetForm: resetForm2 }) => resetForm2);
2239
+ return {
2240
+ /**
2241
+ * Disabled when:
2242
+ * - the form is submitting
2243
+ * - the document is not modified & we're not cloning (you can save a clone entity straight away)
2244
+ * - the active tab is the published tab
2245
+ */
2246
+ disabled: isSubmitting || !modified && !isCloning || activeTab === "published",
2247
+ label: formatMessage({
2248
+ id: "content-manager.containers.Edit.save",
2249
+ defaultMessage: "Save"
2250
+ }),
2251
+ onClick: async () => {
2252
+ setSubmitting(true);
2253
+ try {
2254
+ if (activeTab !== "draft") {
2255
+ const { errors } = await validate();
2256
+ if (errors) {
2257
+ toggleNotification({
2258
+ type: "danger",
2259
+ message: formatMessage({
2260
+ id: "content-manager.validation.error",
2261
+ defaultMessage: "There are validation errors in your document. Please fix them before saving."
2262
+ })
2263
+ });
2264
+ return;
2265
+ }
2266
+ }
2267
+ if (isCloning) {
2268
+ const res = await clone(
2301
2269
  {
2302
- timestamp: new Date(createAndUpdateDocument?.[CREATED_AT_ATTRIBUTE_NAME])
2303
- }
2304
- ),
2305
- isAnonymous: !creator,
2306
- author: creator
2270
+ model,
2271
+ documentId: cloneMatch.params.origin,
2272
+ params
2273
+ },
2274
+ document
2275
+ );
2276
+ if ("data" in res) {
2277
+ navigate(
2278
+ {
2279
+ pathname: `../${res.data.documentId}`,
2280
+ search: rawQuery
2281
+ },
2282
+ { relative: "path" }
2283
+ );
2284
+ } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
2285
+ setErrors(formatValidationErrors(res.error));
2286
+ }
2287
+ } else if (documentId || collectionType === SINGLE_TYPES) {
2288
+ const res = await update(
2289
+ {
2290
+ collectionType,
2291
+ model,
2292
+ documentId,
2293
+ params
2294
+ },
2295
+ document
2296
+ );
2297
+ if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
2298
+ setErrors(formatValidationErrors(res.error));
2299
+ } else {
2300
+ resetForm();
2301
+ }
2302
+ } else {
2303
+ const res = await create(
2304
+ {
2305
+ model,
2306
+ params
2307
+ },
2308
+ document
2309
+ );
2310
+ if ("data" in res && collectionType !== SINGLE_TYPES) {
2311
+ navigate(
2312
+ {
2313
+ pathname: `../${res.data.documentId}`,
2314
+ search: rawQuery
2315
+ },
2316
+ { replace: true, relative: "path" }
2317
+ );
2318
+ } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
2319
+ setErrors(formatValidationErrors(res.error));
2320
+ }
2307
2321
  }
2308
- )
2309
- }
2310
- ].filter((info) => info.isDisplayed);
2311
- return /* @__PURE__ */ jsx(
2312
- Flex,
2313
- {
2314
- borderWidth: "1px 0 0 0",
2315
- borderStyle: "solid",
2316
- borderColor: "neutral150",
2317
- direction: "column",
2318
- marginTop: 2,
2319
- tag: "dl",
2320
- padding: 5,
2321
- gap: 3,
2322
- alignItems: "flex-start",
2323
- marginLeft: "-0.4rem",
2324
- marginRight: "-0.4rem",
2325
- width: "calc(100% + 8px)",
2326
- children: information.map((info) => /* @__PURE__ */ jsxs(Flex, { gap: 1, direction: "column", alignItems: "flex-start", children: [
2327
- /* @__PURE__ */ jsx(Typography, { tag: "dt", variant: "pi", fontWeight: "bold", children: info.label }),
2328
- /* @__PURE__ */ jsx(Typography, { tag: "dd", variant: "pi", textColor: "neutral600", children: info.value })
2329
- ] }, info.label))
2322
+ } finally {
2323
+ setSubmitting(false);
2324
+ }
2330
2325
  }
2331
- );
2326
+ };
2332
2327
  };
2333
- const HeaderActions = ({ actions: actions2 }) => {
2334
- return /* @__PURE__ */ jsx(Flex, { children: actions2.map((action) => {
2335
- if ("options" in action) {
2336
- return /* @__PURE__ */ jsx(
2337
- SingleSelect,
2338
- {
2339
- size: "S",
2340
- disabled: action.disabled,
2341
- "aria-label": action.label,
2342
- onChange: action.onSelect,
2343
- value: action.value,
2344
- children: action.options.map(({ label, ...option }) => /* @__PURE__ */ jsx(SingleSelectOption, { ...option, children: label }, option.value))
2345
- },
2346
- action.id
2347
- );
2348
- } else {
2349
- return null;
2350
- }
2351
- }) });
2328
+ UpdateAction.type = "update";
2329
+ const UNPUBLISH_DRAFT_OPTIONS = {
2330
+ KEEP: "keep",
2331
+ DISCARD: "discard"
2352
2332
  };
2353
- const ConfigureTheViewAction = ({ collectionType, model }) => {
2354
- const navigate = useNavigate();
2333
+ const UnpublishAction$1 = ({
2334
+ activeTab,
2335
+ documentId,
2336
+ model,
2337
+ collectionType,
2338
+ document
2339
+ }) => {
2355
2340
  const { formatMessage } = useIntl();
2356
- return {
2357
- label: formatMessage({
2358
- id: "app.links.configure-view",
2359
- defaultMessage: "Configure the view"
2360
- }),
2361
- icon: /* @__PURE__ */ jsx(ListPlus, {}),
2362
- onClick: () => {
2363
- navigate(`../${collectionType}/${model}/configurations/edit`);
2364
- },
2365
- position: "header"
2341
+ const { schema } = useDoc();
2342
+ const canPublish = useDocumentRBAC("UnpublishAction", ({ canPublish: canPublish2 }) => canPublish2);
2343
+ const { unpublish } = useDocumentActions();
2344
+ const [{ query }] = useQueryParams();
2345
+ const params = React.useMemo(() => buildValidParams(query), [query]);
2346
+ const { toggleNotification } = useNotification();
2347
+ const [shouldKeepDraft, setShouldKeepDraft] = React.useState(true);
2348
+ const isDocumentModified = document?.status === "modified";
2349
+ const handleChange = (value) => {
2350
+ setShouldKeepDraft(value === UNPUBLISH_DRAFT_OPTIONS.KEEP);
2366
2351
  };
2367
- };
2368
- ConfigureTheViewAction.type = "configure-the-view";
2369
- const EditTheModelAction = ({ model }) => {
2370
- const navigate = useNavigate();
2371
- const { formatMessage } = useIntl();
2352
+ if (!schema?.options?.draftAndPublish) {
2353
+ return null;
2354
+ }
2372
2355
  return {
2356
+ disabled: !canPublish || activeTab === "published" || document?.status !== "published" && document?.status !== "modified",
2373
2357
  label: formatMessage({
2374
- id: "content-manager.link-to-ctb",
2375
- defaultMessage: "Edit the model"
2358
+ id: "app.utils.unpublish",
2359
+ defaultMessage: "Unpublish"
2376
2360
  }),
2377
- icon: /* @__PURE__ */ jsx(Pencil, {}),
2378
- onClick: () => {
2379
- navigate(`/plugins/content-type-builder/content-types/${model}`);
2361
+ icon: /* @__PURE__ */ jsx(Cross, {}),
2362
+ onClick: async () => {
2363
+ if (!documentId && collectionType !== SINGLE_TYPES || isDocumentModified) {
2364
+ if (!documentId) {
2365
+ console.error(
2366
+ "You're trying to unpublish a document without an id, this is likely a bug with Strapi. Please open an issue."
2367
+ );
2368
+ toggleNotification({
2369
+ message: formatMessage({
2370
+ id: "content-manager.actions.unpublish.error",
2371
+ defaultMessage: "An error occurred while trying to unpublish the document."
2372
+ }),
2373
+ type: "danger"
2374
+ });
2375
+ }
2376
+ return;
2377
+ }
2378
+ await unpublish({
2379
+ collectionType,
2380
+ model,
2381
+ documentId,
2382
+ params
2383
+ });
2380
2384
  },
2381
- position: "header"
2385
+ dialog: isDocumentModified ? {
2386
+ type: "dialog",
2387
+ title: formatMessage({
2388
+ id: "app.components.ConfirmDialog.title",
2389
+ defaultMessage: "Confirmation"
2390
+ }),
2391
+ content: /* @__PURE__ */ jsxs(Flex, { alignItems: "flex-start", direction: "column", gap: 6, children: [
2392
+ /* @__PURE__ */ jsxs(Flex, { width: "100%", direction: "column", gap: 2, children: [
2393
+ /* @__PURE__ */ jsx(WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
2394
+ /* @__PURE__ */ jsx(Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
2395
+ id: "content-manager.actions.unpublish.dialog.body",
2396
+ defaultMessage: "Are you sure?"
2397
+ }) })
2398
+ ] }),
2399
+ /* @__PURE__ */ jsxs(
2400
+ Radio.Group,
2401
+ {
2402
+ defaultValue: UNPUBLISH_DRAFT_OPTIONS.KEEP,
2403
+ name: "discard-options",
2404
+ "aria-label": formatMessage({
2405
+ id: "content-manager.actions.unpublish.dialog.radio-label",
2406
+ defaultMessage: "Choose an option to unpublish the document."
2407
+ }),
2408
+ onValueChange: handleChange,
2409
+ children: [
2410
+ /* @__PURE__ */ jsx(Radio.Item, { checked: shouldKeepDraft, value: UNPUBLISH_DRAFT_OPTIONS.KEEP, children: formatMessage({
2411
+ id: "content-manager.actions.unpublish.dialog.option.keep-draft",
2412
+ defaultMessage: "Keep draft"
2413
+ }) }),
2414
+ /* @__PURE__ */ jsx(Radio.Item, { checked: !shouldKeepDraft, value: UNPUBLISH_DRAFT_OPTIONS.DISCARD, children: formatMessage({
2415
+ id: "content-manager.actions.unpublish.dialog.option.replace-draft",
2416
+ defaultMessage: "Replace draft"
2417
+ }) })
2418
+ ]
2419
+ }
2420
+ )
2421
+ ] }),
2422
+ onConfirm: async () => {
2423
+ if (!documentId && collectionType !== SINGLE_TYPES) {
2424
+ console.error(
2425
+ "You're trying to unpublish a document without an id, this is likely a bug with Strapi. Please open an issue."
2426
+ );
2427
+ toggleNotification({
2428
+ message: formatMessage({
2429
+ id: "content-manager.actions.unpublish.error",
2430
+ defaultMessage: "An error occurred while trying to unpublish the document."
2431
+ }),
2432
+ type: "danger"
2433
+ });
2434
+ }
2435
+ await unpublish(
2436
+ {
2437
+ collectionType,
2438
+ model,
2439
+ documentId,
2440
+ params
2441
+ },
2442
+ !shouldKeepDraft
2443
+ );
2444
+ }
2445
+ } : void 0,
2446
+ variant: "danger",
2447
+ position: ["panel", "table-row"]
2382
2448
  };
2383
2449
  };
2384
- EditTheModelAction.type = "edit-the-model";
2385
- const DeleteAction$1 = ({ documentId, model, collectionType, document }) => {
2386
- const navigate = useNavigate();
2450
+ UnpublishAction$1.type = "unpublish";
2451
+ const DiscardAction = ({
2452
+ activeTab,
2453
+ documentId,
2454
+ model,
2455
+ collectionType,
2456
+ document
2457
+ }) => {
2387
2458
  const { formatMessage } = useIntl();
2388
- const listViewPathMatch = useMatch(LIST_PATH);
2389
- const canDelete = useDocumentRBAC("DeleteAction", (state) => state.canDelete);
2390
- const { delete: deleteAction } = useDocumentActions();
2391
- const { toggleNotification } = useNotification();
2392
- const setSubmitting = useForm("DeleteAction", (state) => state.setSubmitting);
2459
+ const { schema } = useDoc();
2460
+ const canUpdate = useDocumentRBAC("DiscardAction", ({ canUpdate: canUpdate2 }) => canUpdate2);
2461
+ const { discard } = useDocumentActions();
2462
+ const [{ query }] = useQueryParams();
2463
+ const params = React.useMemo(() => buildValidParams(query), [query]);
2464
+ if (!schema?.options?.draftAndPublish) {
2465
+ return null;
2466
+ }
2393
2467
  return {
2394
- disabled: !canDelete || !document,
2468
+ disabled: !canUpdate || activeTab === "published" || document?.status !== "modified",
2395
2469
  label: formatMessage({
2396
- id: "content-manager.actions.delete.label",
2397
- defaultMessage: "Delete document"
2470
+ id: "content-manager.actions.discard.label",
2471
+ defaultMessage: "Discard changes"
2398
2472
  }),
2399
- icon: /* @__PURE__ */ jsx(Trash, {}),
2473
+ icon: /* @__PURE__ */ jsx(Cross, {}),
2474
+ position: ["panel", "table-row"],
2475
+ variant: "danger",
2400
2476
  dialog: {
2401
2477
  type: "dialog",
2402
2478
  title: formatMessage({
@@ -2406,92 +2482,90 @@ const DeleteAction$1 = ({ documentId, model, collectionType, document }) => {
2406
2482
  content: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, children: [
2407
2483
  /* @__PURE__ */ jsx(WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
2408
2484
  /* @__PURE__ */ jsx(Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
2409
- id: "content-manager.actions.delete.dialog.body",
2485
+ id: "content-manager.actions.discard.dialog.body",
2410
2486
  defaultMessage: "Are you sure?"
2411
2487
  }) })
2412
2488
  ] }),
2413
2489
  onConfirm: async () => {
2414
- if (!listViewPathMatch) {
2415
- setSubmitting(true);
2416
- }
2417
- try {
2418
- if (!documentId && collectionType !== SINGLE_TYPES) {
2419
- console.error(
2420
- "You're trying to delete a document without an id, this is likely a bug with Strapi. Please open an issue."
2421
- );
2422
- toggleNotification({
2423
- message: formatMessage({
2424
- id: "content-manager.actions.delete.error",
2425
- defaultMessage: "An error occurred while trying to delete the document."
2426
- }),
2427
- type: "danger"
2428
- });
2429
- return;
2430
- }
2431
- const res = await deleteAction({
2432
- documentId,
2433
- model,
2434
- collectionType,
2435
- params: {
2436
- locale: "*"
2437
- }
2438
- });
2439
- if (!("error" in res)) {
2440
- navigate({ pathname: `../${collectionType}/${model}` }, { replace: true });
2441
- }
2442
- } finally {
2443
- if (!listViewPathMatch) {
2444
- setSubmitting(false);
2445
- }
2446
- }
2490
+ await discard({
2491
+ collectionType,
2492
+ model,
2493
+ documentId,
2494
+ params
2495
+ });
2496
+ }
2497
+ }
2498
+ };
2499
+ };
2500
+ DiscardAction.type = "discard";
2501
+ const DEFAULT_ACTIONS = [PublishAction$1, UpdateAction, UnpublishAction$1, DiscardAction];
2502
+ const intervals = ["years", "months", "days", "hours", "minutes", "seconds"];
2503
+ const RelativeTime = React.forwardRef(
2504
+ ({ timestamp, customIntervals = [], ...restProps }, forwardedRef) => {
2505
+ const { formatRelativeTime, formatDate, formatTime } = useIntl();
2506
+ const interval = intervalToDuration({
2507
+ start: timestamp,
2508
+ end: Date.now()
2509
+ // see https://github.com/date-fns/date-fns/issues/2891 – No idea why it's all partial it returns it every time.
2510
+ });
2511
+ const unit = intervals.find((intervalUnit) => {
2512
+ return interval[intervalUnit] > 0 && Object.keys(interval).includes(intervalUnit);
2513
+ });
2514
+ const relativeTime = isPast(timestamp) ? -interval[unit] : interval[unit];
2515
+ const customInterval = customIntervals.find(
2516
+ (custom) => interval[custom.unit] < custom.threshold
2517
+ );
2518
+ const displayText = customInterval ? customInterval.text : formatRelativeTime(relativeTime, unit, { numeric: "auto" });
2519
+ return /* @__PURE__ */ jsx(
2520
+ "time",
2521
+ {
2522
+ ref: forwardedRef,
2523
+ dateTime: timestamp.toISOString(),
2524
+ role: "time",
2525
+ title: `${formatDate(timestamp)} ${formatTime(timestamp)}`,
2526
+ ...restProps,
2527
+ children: displayText
2447
2528
  }
2448
- },
2449
- variant: "danger",
2450
- position: ["header", "table-row"]
2451
- };
2529
+ );
2530
+ }
2531
+ );
2532
+ const getDisplayName = ({
2533
+ firstname,
2534
+ lastname,
2535
+ username,
2536
+ email
2537
+ } = {}) => {
2538
+ if (username) {
2539
+ return username;
2540
+ }
2541
+ if (firstname) {
2542
+ return `${firstname} ${lastname ?? ""}`.trim();
2543
+ }
2544
+ return email ?? "";
2452
2545
  };
2453
- DeleteAction$1.type = "delete";
2454
- const DEFAULT_HEADER_ACTIONS = [EditTheModelAction, ConfigureTheViewAction, DeleteAction$1];
2455
- const Panels = () => {
2456
- const isCloning = useMatch(CLONE_PATH) !== null;
2457
- const [
2458
- {
2459
- query: { status }
2460
- }
2461
- ] = useQueryParams({
2462
- status: "draft"
2463
- });
2464
- const { model, id, document, meta, collectionType } = useDoc();
2465
- const plugins = useStrapiApp("Panels", (state) => state.plugins);
2466
- const props = {
2467
- activeTab: status,
2468
- model,
2469
- documentId: id,
2470
- document: isCloning ? void 0 : document,
2471
- meta: isCloning ? void 0 : meta,
2472
- collectionType
2473
- };
2474
- return /* @__PURE__ */ jsx(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: /* @__PURE__ */ jsx(
2475
- DescriptionComponentRenderer,
2476
- {
2477
- props,
2478
- descriptions: plugins["content-manager"].apis.getEditViewSidePanels(),
2479
- children: (panels) => panels.map(({ content, id: id2, ...description }) => /* @__PURE__ */ jsx(Panel, { ...description, children: content }, id2))
2480
- }
2481
- ) });
2546
+ const capitalise = (str) => str.charAt(0).toUpperCase() + str.slice(1);
2547
+ const DocumentStatus = ({ status = "draft", ...restProps }) => {
2548
+ const statusVariant = status === "draft" ? "secondary" : status === "published" ? "success" : "alternative";
2549
+ return /* @__PURE__ */ jsx(Status, { ...restProps, showBullet: false, size: "S", variant: statusVariant, children: /* @__PURE__ */ jsx(Typography, { tag: "span", variant: "omega", fontWeight: "bold", children: capitalise(status) }) });
2482
2550
  };
2483
- const ActionsPanel = () => {
2551
+ const Header = ({ isCreating, status, title: documentTitle = "Untitled" }) => {
2484
2552
  const { formatMessage } = useIntl();
2485
- return {
2486
- title: formatMessage({
2487
- id: "content-manager.containers.edit.panels.default.title",
2488
- defaultMessage: "Document"
2489
- }),
2490
- content: /* @__PURE__ */ jsx(ActionsPanelContent, {})
2491
- };
2553
+ const isCloning = useMatch(CLONE_PATH) !== null;
2554
+ const title = isCreating ? formatMessage({
2555
+ id: "content-manager.containers.edit.title.new",
2556
+ defaultMessage: "Create an entry"
2557
+ }) : documentTitle;
2558
+ return /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", paddingTop: 6, paddingBottom: 4, gap: 2, children: [
2559
+ /* @__PURE__ */ jsx(BackButton, {}),
2560
+ /* @__PURE__ */ jsxs(Flex, { width: "100%", justifyContent: "space-between", gap: "80px", alignItems: "flex-start", children: [
2561
+ /* @__PURE__ */ jsx(Typography, { variant: "alpha", tag: "h1", children: title }),
2562
+ /* @__PURE__ */ jsx(HeaderToolbar, {})
2563
+ ] }),
2564
+ status ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(DocumentStatus, { status: isCloning ? "draft" : status }) }) : null
2565
+ ] });
2492
2566
  };
2493
- ActionsPanel.type = "actions";
2494
- const ActionsPanelContent = () => {
2567
+ const HeaderToolbar = () => {
2568
+ const { formatMessage } = useIntl();
2495
2569
  const isCloning = useMatch(CLONE_PATH) !== null;
2496
2570
  const [
2497
2571
  {
@@ -2499,355 +2573,433 @@ const ActionsPanelContent = () => {
2499
2573
  }
2500
2574
  ] = useQueryParams();
2501
2575
  const { model, id, document, meta, collectionType } = useDoc();
2502
- const plugins = useStrapiApp("ActionsPanel", (state) => state.plugins);
2503
- const props = {
2504
- activeTab: status,
2505
- model,
2506
- documentId: id,
2507
- document: isCloning ? void 0 : document,
2508
- meta: isCloning ? void 0 : meta,
2509
- collectionType
2510
- };
2511
- return /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, width: "100%", children: [
2576
+ const plugins = useStrapiApp("HeaderToolbar", (state) => state.plugins);
2577
+ return /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
2512
2578
  /* @__PURE__ */ jsx(
2513
2579
  DescriptionComponentRenderer,
2514
2580
  {
2515
- props,
2516
- descriptions: plugins["content-manager"].apis.getDocumentActions(),
2517
- children: (actions2) => /* @__PURE__ */ jsx(DocumentActions, { actions: actions2 })
2581
+ props: {
2582
+ activeTab: status,
2583
+ model,
2584
+ documentId: id,
2585
+ document: isCloning ? void 0 : document,
2586
+ meta: isCloning ? void 0 : meta,
2587
+ collectionType
2588
+ },
2589
+ descriptions: plugins["content-manager"].apis.getHeaderActions(),
2590
+ children: (actions2) => {
2591
+ if (actions2.length > 0) {
2592
+ return /* @__PURE__ */ jsx(HeaderActions, { actions: actions2 });
2593
+ } else {
2594
+ return null;
2595
+ }
2596
+ }
2518
2597
  }
2519
2598
  ),
2520
- /* @__PURE__ */ jsx(InjectionZone, { area: "editView.right-links", slug: model })
2599
+ /* @__PURE__ */ jsx(
2600
+ DescriptionComponentRenderer,
2601
+ {
2602
+ props: {
2603
+ activeTab: status,
2604
+ model,
2605
+ documentId: id,
2606
+ document: isCloning ? void 0 : document,
2607
+ meta: isCloning ? void 0 : meta,
2608
+ collectionType
2609
+ },
2610
+ descriptions: plugins["content-manager"].apis.getDocumentActions(),
2611
+ children: (actions2) => {
2612
+ const headerActions = actions2.filter((action) => {
2613
+ const positions = Array.isArray(action.position) ? action.position : [action.position];
2614
+ return positions.includes("header");
2615
+ });
2616
+ return /* @__PURE__ */ jsx(
2617
+ DocumentActionsMenu,
2618
+ {
2619
+ actions: headerActions,
2620
+ label: formatMessage({
2621
+ id: "content-manager.containers.edit.header.more-actions",
2622
+ defaultMessage: "More actions"
2623
+ }),
2624
+ children: /* @__PURE__ */ jsx(Information, { activeTab: status })
2625
+ }
2626
+ );
2627
+ }
2628
+ }
2629
+ )
2521
2630
  ] });
2522
2631
  };
2523
- const Panel = React.forwardRef(({ children, title }, ref) => {
2524
- return /* @__PURE__ */ jsxs(
2525
- Flex,
2632
+ const Information = ({ activeTab }) => {
2633
+ const { formatMessage } = useIntl();
2634
+ const { document, meta } = useDoc();
2635
+ if (!document || !document.id) {
2636
+ return null;
2637
+ }
2638
+ const createAndUpdateDocument = activeTab === "draft" ? document : meta?.availableStatus.find((status) => status.publishedAt === null);
2639
+ const publishDocument = activeTab === "published" ? document : meta?.availableStatus.find((status) => status.publishedAt !== null);
2640
+ const creator = createAndUpdateDocument?.[CREATED_BY_ATTRIBUTE_NAME] ? getDisplayName(createAndUpdateDocument[CREATED_BY_ATTRIBUTE_NAME]) : null;
2641
+ const updator = createAndUpdateDocument?.[UPDATED_BY_ATTRIBUTE_NAME] ? getDisplayName(createAndUpdateDocument[UPDATED_BY_ATTRIBUTE_NAME]) : null;
2642
+ const information = [
2526
2643
  {
2527
- ref,
2528
- tag: "aside",
2529
- "aria-labelledby": "additional-information",
2530
- background: "neutral0",
2531
- borderColor: "neutral150",
2532
- hasRadius: true,
2533
- paddingBottom: 4,
2534
- paddingLeft: 4,
2535
- paddingRight: 4,
2536
- paddingTop: 4,
2537
- shadow: "tableShadow",
2538
- gap: 3,
2539
- direction: "column",
2540
- justifyContent: "stretch",
2541
- alignItems: "flex-start",
2542
- children: [
2543
- /* @__PURE__ */ jsx(Typography, { tag: "h2", variant: "sigma", textTransform: "uppercase", children: title }),
2544
- children
2545
- ]
2546
- }
2547
- );
2548
- });
2549
- const HOOKS = {
2550
- /**
2551
- * Hook that allows to mutate the displayed headers of the list view table
2552
- * @constant
2553
- * @type {string}
2554
- */
2555
- INJECT_COLUMN_IN_TABLE: "Admin/CM/pages/ListView/inject-column-in-table",
2556
- /**
2557
- * Hook that allows to mutate the CM's collection types links pre-set filters
2558
- * @constant
2559
- * @type {string}
2560
- */
2561
- MUTATE_COLLECTION_TYPES_LINKS: "Admin/CM/pages/App/mutate-collection-types-links",
2562
- /**
2563
- * Hook that allows to mutate the CM's edit view layout
2564
- * @constant
2565
- * @type {string}
2566
- */
2567
- MUTATE_EDIT_VIEW_LAYOUT: "Admin/CM/pages/EditView/mutate-edit-view-layout",
2568
- /**
2569
- * Hook that allows to mutate the CM's single types links pre-set filters
2570
- * @constant
2571
- * @type {string}
2572
- */
2573
- MUTATE_SINGLE_TYPES_LINKS: "Admin/CM/pages/App/mutate-single-types-links"
2574
- };
2575
- const contentTypesApi = contentManagerApi.injectEndpoints({
2576
- endpoints: (builder) => ({
2577
- getContentTypeConfiguration: builder.query({
2578
- query: (uid) => ({
2579
- url: `/content-manager/content-types/${uid}/configuration`,
2580
- method: "GET"
2644
+ isDisplayed: !!publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME],
2645
+ label: formatMessage({
2646
+ id: "content-manager.containers.edit.information.last-published.label",
2647
+ defaultMessage: "Last published"
2581
2648
  }),
2582
- transformResponse: (response) => response.data,
2583
- providesTags: (_result, _error, uid) => [
2584
- { type: "ContentTypesConfiguration", id: uid },
2585
- { type: "ContentTypeSettings", id: "LIST" }
2586
- ]
2587
- }),
2588
- getAllContentTypeSettings: builder.query({
2589
- query: () => "/content-manager/content-types-settings",
2590
- transformResponse: (response) => response.data,
2591
- providesTags: [{ type: "ContentTypeSettings", id: "LIST" }]
2592
- }),
2593
- updateContentTypeConfiguration: builder.mutation({
2594
- query: ({ uid, ...body }) => ({
2595
- url: `/content-manager/content-types/${uid}/configuration`,
2596
- method: "PUT",
2597
- data: body
2649
+ value: formatMessage(
2650
+ {
2651
+ id: "content-manager.containers.edit.information.last-published.value",
2652
+ defaultMessage: `Published {time}{isAnonymous, select, true {} other { by {author}}}`
2653
+ },
2654
+ {
2655
+ time: /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME]) }),
2656
+ isAnonymous: !publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME],
2657
+ author: publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME] ? getDisplayName(publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME]) : null
2658
+ }
2659
+ )
2660
+ },
2661
+ {
2662
+ isDisplayed: !!createAndUpdateDocument?.[UPDATED_AT_ATTRIBUTE_NAME],
2663
+ label: formatMessage({
2664
+ id: "content-manager.containers.edit.information.last-draft.label",
2665
+ defaultMessage: "Last draft"
2666
+ }),
2667
+ value: formatMessage(
2668
+ {
2669
+ id: "content-manager.containers.edit.information.last-draft.value",
2670
+ defaultMessage: `Modified {time}{isAnonymous, select, true {} other { by {author}}}`
2671
+ },
2672
+ {
2673
+ time: /* @__PURE__ */ jsx(
2674
+ RelativeTime,
2675
+ {
2676
+ timestamp: new Date(createAndUpdateDocument?.[UPDATED_AT_ATTRIBUTE_NAME])
2677
+ }
2678
+ ),
2679
+ isAnonymous: !updator,
2680
+ author: updator
2681
+ }
2682
+ )
2683
+ },
2684
+ {
2685
+ isDisplayed: !!createAndUpdateDocument?.[CREATED_AT_ATTRIBUTE_NAME],
2686
+ label: formatMessage({
2687
+ id: "content-manager.containers.edit.information.document.label",
2688
+ defaultMessage: "Document"
2598
2689
  }),
2599
- transformResponse: (response) => response.data,
2600
- invalidatesTags: (_result, _error, { uid }) => [
2601
- { type: "ContentTypesConfiguration", id: uid },
2602
- { type: "ContentTypeSettings", id: "LIST" },
2603
- // Is this necessary?
2604
- { type: "InitialData" }
2605
- ]
2606
- })
2607
- })
2608
- });
2609
- const {
2610
- useGetContentTypeConfigurationQuery,
2611
- useGetAllContentTypeSettingsQuery,
2612
- useUpdateContentTypeConfigurationMutation
2613
- } = contentTypesApi;
2614
- const checkIfAttributeIsDisplayable = (attribute) => {
2615
- const { type } = attribute;
2616
- if (type === "relation") {
2617
- return !attribute.relation.toLowerCase().includes("morph");
2618
- }
2619
- return !["json", "dynamiczone", "richtext", "password", "blocks"].includes(type) && !!type;
2620
- };
2621
- const getMainField = (attribute, mainFieldName, { schemas, components }) => {
2622
- if (!mainFieldName) {
2623
- return void 0;
2624
- }
2625
- const mainFieldType = attribute.type === "component" ? components[attribute.component].attributes[mainFieldName].type : (
2626
- // @ts-expect-error – `targetModel` does exist on the attribute for a relation.
2627
- schemas.find((schema) => schema.uid === attribute.targetModel)?.attributes[mainFieldName].type
2690
+ value: formatMessage(
2691
+ {
2692
+ id: "content-manager.containers.edit.information.document.value",
2693
+ defaultMessage: `Created {time}{isAnonymous, select, true {} other { by {author}}}`
2694
+ },
2695
+ {
2696
+ time: /* @__PURE__ */ jsx(
2697
+ RelativeTime,
2698
+ {
2699
+ timestamp: new Date(createAndUpdateDocument?.[CREATED_AT_ATTRIBUTE_NAME])
2700
+ }
2701
+ ),
2702
+ isAnonymous: !creator,
2703
+ author: creator
2704
+ }
2705
+ )
2706
+ }
2707
+ ].filter((info) => info.isDisplayed);
2708
+ return /* @__PURE__ */ jsx(
2709
+ Flex,
2710
+ {
2711
+ borderWidth: "1px 0 0 0",
2712
+ borderStyle: "solid",
2713
+ borderColor: "neutral150",
2714
+ direction: "column",
2715
+ marginTop: 2,
2716
+ tag: "dl",
2717
+ padding: 5,
2718
+ gap: 3,
2719
+ alignItems: "flex-start",
2720
+ marginLeft: "-0.4rem",
2721
+ marginRight: "-0.4rem",
2722
+ width: "calc(100% + 8px)",
2723
+ children: information.map((info) => /* @__PURE__ */ jsxs(Flex, { gap: 1, direction: "column", alignItems: "flex-start", children: [
2724
+ /* @__PURE__ */ jsx(Typography, { tag: "dt", variant: "pi", fontWeight: "bold", children: info.label }),
2725
+ /* @__PURE__ */ jsx(Typography, { tag: "dd", variant: "pi", textColor: "neutral600", children: info.value })
2726
+ ] }, info.label))
2727
+ }
2628
2728
  );
2629
- return {
2630
- name: mainFieldName,
2631
- type: mainFieldType ?? "string"
2632
- };
2633
- };
2634
- const DEFAULT_SETTINGS = {
2635
- bulkable: false,
2636
- filterable: false,
2637
- searchable: false,
2638
- pagination: false,
2639
- defaultSortBy: "",
2640
- defaultSortOrder: "asc",
2641
- mainField: "id",
2642
- pageSize: 10
2643
2729
  };
2644
- const useDocumentLayout = (model) => {
2645
- const { schema, components } = useDocument({ model, collectionType: "" }, { skip: true });
2646
- const [{ query }] = useQueryParams();
2647
- const runHookWaterfall = useStrapiApp("useDocumentLayout", (state) => state.runHookWaterfall);
2648
- const { toggleNotification } = useNotification();
2649
- const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
2650
- const { isLoading: isLoadingSchemas, schemas } = useContentTypeSchema();
2651
- const {
2652
- data,
2653
- isLoading: isLoadingConfigs,
2654
- error,
2655
- isFetching: isFetchingConfigs
2656
- } = useGetContentTypeConfigurationQuery(model);
2657
- const isLoading = isLoadingSchemas || isFetchingConfigs || isLoadingConfigs;
2658
- React.useEffect(() => {
2659
- if (error) {
2660
- toggleNotification({
2661
- type: "danger",
2662
- message: formatAPIError(error)
2663
- });
2730
+ const HeaderActions = ({ actions: actions2 }) => {
2731
+ const [dialogId, setDialogId] = React.useState(null);
2732
+ const handleClick = (action) => async (e) => {
2733
+ if (!("options" in action)) {
2734
+ const { onClick = () => false, dialog, id } = action;
2735
+ const muteDialog = await onClick(e);
2736
+ if (dialog && !muteDialog) {
2737
+ e.preventDefault();
2738
+ setDialogId(id);
2739
+ }
2664
2740
  }
2665
- }, [error, formatAPIError, toggleNotification]);
2666
- const editLayout = React.useMemo(
2667
- () => data && !isLoading ? formatEditLayout(data, { schemas, schema, components }) : {
2668
- layout: [],
2669
- components: {},
2670
- metadatas: {},
2671
- options: {},
2672
- settings: DEFAULT_SETTINGS
2673
- },
2674
- [data, isLoading, schemas, schema, components]
2675
- );
2676
- const listLayout = React.useMemo(() => {
2677
- return data && !isLoading ? formatListLayout(data, { schemas, schema, components }) : {
2678
- layout: [],
2679
- metadatas: {},
2680
- options: {},
2681
- settings: DEFAULT_SETTINGS
2682
- };
2683
- }, [data, isLoading, schemas, schema, components]);
2684
- const { layout: edit } = React.useMemo(
2685
- () => runHookWaterfall(HOOKS.MUTATE_EDIT_VIEW_LAYOUT, {
2686
- layout: editLayout,
2687
- query
2688
- }),
2689
- [editLayout, query, runHookWaterfall]
2690
- );
2691
- return {
2692
- error,
2693
- isLoading,
2694
- edit,
2695
- list: listLayout
2696
2741
  };
2697
- };
2698
- const useDocLayout = () => {
2699
- const { model } = useDoc();
2700
- return useDocumentLayout(model);
2701
- };
2702
- const formatEditLayout = (data, {
2703
- schemas,
2704
- schema,
2705
- components
2706
- }) => {
2707
- let currentPanelIndex = 0;
2708
- const panelledEditAttributes = convertEditLayoutToFieldLayouts(
2709
- data.contentType.layouts.edit,
2710
- schema?.attributes,
2711
- data.contentType.metadatas,
2712
- { configurations: data.components, schemas: components },
2713
- schemas
2714
- ).reduce((panels, row) => {
2715
- if (row.some((field) => field.type === "dynamiczone")) {
2716
- panels.push([row]);
2717
- currentPanelIndex += 2;
2742
+ const handleClose = () => {
2743
+ setDialogId(null);
2744
+ };
2745
+ return /* @__PURE__ */ jsx(Flex, { gap: 1, children: actions2.map((action) => {
2746
+ if (action.options) {
2747
+ return /* @__PURE__ */ jsx(
2748
+ SingleSelect,
2749
+ {
2750
+ size: "S",
2751
+ disabled: action.disabled,
2752
+ "aria-label": action.label,
2753
+ onChange: action.onSelect,
2754
+ value: action.value,
2755
+ children: action.options.map(({ label, ...option }) => /* @__PURE__ */ jsx(SingleSelectOption, { ...option, children: label }, option.value))
2756
+ },
2757
+ action.id
2758
+ );
2718
2759
  } else {
2719
- if (!panels[currentPanelIndex]) {
2720
- panels.push([]);
2760
+ if (action.type === "icon") {
2761
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
2762
+ /* @__PURE__ */ jsx(
2763
+ IconButton,
2764
+ {
2765
+ disabled: action.disabled,
2766
+ label: action.label,
2767
+ size: "S",
2768
+ onClick: handleClick(action),
2769
+ children: action.icon
2770
+ }
2771
+ ),
2772
+ action.dialog ? /* @__PURE__ */ jsx(
2773
+ HeaderActionDialog,
2774
+ {
2775
+ ...action.dialog,
2776
+ isOpen: dialogId === action.id,
2777
+ onClose: handleClose
2778
+ }
2779
+ ) : null
2780
+ ] }, action.id);
2721
2781
  }
2722
- panels[currentPanelIndex].push(row);
2723
2782
  }
2724
- return panels;
2725
- }, []);
2726
- const componentEditAttributes = Object.entries(data.components).reduce(
2727
- (acc, [uid, configuration]) => {
2728
- acc[uid] = {
2729
- layout: convertEditLayoutToFieldLayouts(
2730
- configuration.layouts.edit,
2731
- components[uid].attributes,
2732
- configuration.metadatas
2733
- ),
2734
- settings: {
2735
- ...configuration.settings,
2736
- icon: components[uid].info.icon,
2737
- displayName: components[uid].info.displayName
2738
- }
2739
- };
2740
- return acc;
2741
- },
2742
- {}
2743
- );
2744
- const editMetadatas = Object.entries(data.contentType.metadatas).reduce(
2745
- (acc, [attribute, metadata]) => {
2746
- return {
2747
- ...acc,
2748
- [attribute]: metadata.edit
2749
- };
2783
+ }) });
2784
+ };
2785
+ const HeaderActionDialog = ({
2786
+ onClose,
2787
+ onCancel,
2788
+ title,
2789
+ content: Content,
2790
+ isOpen
2791
+ }) => {
2792
+ const handleClose = async () => {
2793
+ if (onCancel) {
2794
+ await onCancel();
2795
+ }
2796
+ onClose();
2797
+ };
2798
+ return /* @__PURE__ */ jsx(Dialog.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxs(Dialog.Content, { children: [
2799
+ /* @__PURE__ */ jsx(Dialog.Header, { children: title }),
2800
+ typeof Content === "function" ? /* @__PURE__ */ jsx(Content, { onClose: handleClose }) : Content
2801
+ ] }) });
2802
+ };
2803
+ const ConfigureTheViewAction = ({ collectionType, model }) => {
2804
+ const navigate = useNavigate();
2805
+ const { formatMessage } = useIntl();
2806
+ return {
2807
+ label: formatMessage({
2808
+ id: "app.links.configure-view",
2809
+ defaultMessage: "Configure the view"
2810
+ }),
2811
+ icon: /* @__PURE__ */ jsx(ListPlus, {}),
2812
+ onClick: () => {
2813
+ navigate(`../${collectionType}/${model}/configurations/edit`);
2750
2814
  },
2751
- {}
2752
- );
2815
+ position: "header"
2816
+ };
2817
+ };
2818
+ ConfigureTheViewAction.type = "configure-the-view";
2819
+ const EditTheModelAction = ({ model }) => {
2820
+ const navigate = useNavigate();
2821
+ const { formatMessage } = useIntl();
2753
2822
  return {
2754
- layout: panelledEditAttributes,
2755
- components: componentEditAttributes,
2756
- metadatas: editMetadatas,
2757
- settings: {
2758
- ...data.contentType.settings,
2759
- displayName: schema?.info.displayName
2823
+ label: formatMessage({
2824
+ id: "content-manager.link-to-ctb",
2825
+ defaultMessage: "Edit the model"
2826
+ }),
2827
+ icon: /* @__PURE__ */ jsx(Pencil, {}),
2828
+ onClick: () => {
2829
+ navigate(`/plugins/content-type-builder/content-types/${model}`);
2760
2830
  },
2761
- options: {
2762
- ...schema?.options,
2763
- ...schema?.pluginOptions,
2764
- ...data.contentType.options
2765
- }
2831
+ position: "header"
2766
2832
  };
2767
2833
  };
2768
- const convertEditLayoutToFieldLayouts = (rows, attributes = {}, metadatas, components, schemas = []) => {
2769
- return rows.map(
2770
- (row) => row.map((field) => {
2771
- const attribute = attributes[field.name];
2772
- if (!attribute) {
2773
- return null;
2834
+ EditTheModelAction.type = "edit-the-model";
2835
+ const DeleteAction$1 = ({ documentId, model, collectionType, document }) => {
2836
+ const navigate = useNavigate();
2837
+ const { formatMessage } = useIntl();
2838
+ const listViewPathMatch = useMatch(LIST_PATH);
2839
+ const canDelete = useDocumentRBAC("DeleteAction", (state) => state.canDelete);
2840
+ const { delete: deleteAction } = useDocumentActions();
2841
+ const { toggleNotification } = useNotification();
2842
+ const setSubmitting = useForm("DeleteAction", (state) => state.setSubmitting);
2843
+ const isLocalized = document?.locale != null;
2844
+ return {
2845
+ disabled: !canDelete || !document,
2846
+ label: formatMessage(
2847
+ {
2848
+ id: "content-manager.actions.delete.label",
2849
+ defaultMessage: "Delete entry{isLocalized, select, true { (all locales)} other {}}"
2850
+ },
2851
+ { isLocalized }
2852
+ ),
2853
+ icon: /* @__PURE__ */ jsx(Trash, {}),
2854
+ dialog: {
2855
+ type: "dialog",
2856
+ title: formatMessage({
2857
+ id: "app.components.ConfirmDialog.title",
2858
+ defaultMessage: "Confirmation"
2859
+ }),
2860
+ content: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, children: [
2861
+ /* @__PURE__ */ jsx(WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
2862
+ /* @__PURE__ */ jsx(Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
2863
+ id: "content-manager.actions.delete.dialog.body",
2864
+ defaultMessage: "Are you sure?"
2865
+ }) })
2866
+ ] }),
2867
+ onConfirm: async () => {
2868
+ if (!listViewPathMatch) {
2869
+ setSubmitting(true);
2870
+ }
2871
+ try {
2872
+ if (!documentId && collectionType !== SINGLE_TYPES) {
2873
+ console.error(
2874
+ "You're trying to delete a document without an id, this is likely a bug with Strapi. Please open an issue."
2875
+ );
2876
+ toggleNotification({
2877
+ message: formatMessage({
2878
+ id: "content-manager.actions.delete.error",
2879
+ defaultMessage: "An error occurred while trying to delete the document."
2880
+ }),
2881
+ type: "danger"
2882
+ });
2883
+ return;
2884
+ }
2885
+ const res = await deleteAction({
2886
+ documentId,
2887
+ model,
2888
+ collectionType,
2889
+ params: {
2890
+ locale: "*"
2891
+ }
2892
+ });
2893
+ if (!("error" in res)) {
2894
+ navigate({ pathname: `../${collectionType}/${model}` }, { replace: true });
2895
+ }
2896
+ } finally {
2897
+ if (!listViewPathMatch) {
2898
+ setSubmitting(false);
2899
+ }
2900
+ }
2774
2901
  }
2775
- const { edit: metadata } = metadatas[field.name];
2776
- const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
2777
- return {
2778
- attribute,
2779
- disabled: !metadata.editable,
2780
- hint: metadata.description,
2781
- label: metadata.label ?? "",
2782
- name: field.name,
2783
- // @ts-expect-error – mainField does exist on the metadata for a relation.
2784
- mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
2785
- schemas,
2786
- components: components?.schemas ?? {}
2787
- }),
2788
- placeholder: metadata.placeholder ?? "",
2789
- required: attribute.required ?? false,
2790
- size: field.size,
2791
- unique: "unique" in attribute ? attribute.unique : false,
2792
- visible: metadata.visible ?? true,
2793
- type: attribute.type
2794
- };
2795
- }).filter((field) => field !== null)
2796
- );
2797
- };
2798
- const formatListLayout = (data, {
2799
- schemas,
2800
- schema,
2801
- components
2802
- }) => {
2803
- const listMetadatas = Object.entries(data.contentType.metadatas).reduce(
2804
- (acc, [attribute, metadata]) => {
2805
- return {
2806
- ...acc,
2807
- [attribute]: metadata.list
2808
- };
2809
2902
  },
2810
- {}
2811
- );
2812
- const listAttributes = convertListLayoutToFieldLayouts(
2813
- data.contentType.layouts.list,
2814
- schema?.attributes,
2815
- listMetadatas,
2816
- { configurations: data.components, schemas: components },
2817
- schemas
2818
- );
2819
- return {
2820
- layout: listAttributes,
2821
- settings: { ...data.contentType.settings, displayName: schema?.info.displayName },
2822
- metadatas: listMetadatas,
2823
- options: {
2824
- ...schema?.options,
2825
- ...schema?.pluginOptions,
2826
- ...data.contentType.options
2903
+ variant: "danger",
2904
+ position: ["header", "table-row"]
2905
+ };
2906
+ };
2907
+ DeleteAction$1.type = "delete";
2908
+ const DEFAULT_HEADER_ACTIONS = [EditTheModelAction, ConfigureTheViewAction, DeleteAction$1];
2909
+ const Panels = () => {
2910
+ const isCloning = useMatch(CLONE_PATH) !== null;
2911
+ const [
2912
+ {
2913
+ query: { status }
2914
+ }
2915
+ ] = useQueryParams({
2916
+ status: "draft"
2917
+ });
2918
+ const { model, id, document, meta, collectionType } = useDoc();
2919
+ const plugins = useStrapiApp("Panels", (state) => state.plugins);
2920
+ const props = {
2921
+ activeTab: status,
2922
+ model,
2923
+ documentId: id,
2924
+ document: isCloning ? void 0 : document,
2925
+ meta: isCloning ? void 0 : meta,
2926
+ collectionType
2927
+ };
2928
+ return /* @__PURE__ */ jsx(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: /* @__PURE__ */ jsx(
2929
+ DescriptionComponentRenderer,
2930
+ {
2931
+ props,
2932
+ descriptions: plugins["content-manager"].apis.getEditViewSidePanels(),
2933
+ children: (panels) => panels.map(({ content, id: id2, ...description }) => /* @__PURE__ */ jsx(Panel, { ...description, children: content }, id2))
2827
2934
  }
2935
+ ) });
2936
+ };
2937
+ const ActionsPanel = () => {
2938
+ const { formatMessage } = useIntl();
2939
+ return {
2940
+ title: formatMessage({
2941
+ id: "content-manager.containers.edit.panels.default.title",
2942
+ defaultMessage: "Entry"
2943
+ }),
2944
+ content: /* @__PURE__ */ jsx(ActionsPanelContent, {})
2828
2945
  };
2829
2946
  };
2830
- const convertListLayoutToFieldLayouts = (columns, attributes = {}, metadatas, components, schemas = []) => {
2831
- return columns.map((name) => {
2832
- const attribute = attributes[name];
2833
- if (!attribute) {
2834
- return null;
2947
+ ActionsPanel.type = "actions";
2948
+ const ActionsPanelContent = () => {
2949
+ const isCloning = useMatch(CLONE_PATH) !== null;
2950
+ const [
2951
+ {
2952
+ query: { status = "draft" }
2835
2953
  }
2836
- const metadata = metadatas[name];
2837
- const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
2838
- return {
2839
- attribute,
2840
- label: metadata.label ?? "",
2841
- mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
2842
- schemas,
2843
- components: components?.schemas ?? {}
2844
- }),
2845
- name,
2846
- searchable: metadata.searchable ?? true,
2847
- sortable: metadata.sortable ?? true
2848
- };
2849
- }).filter((field) => field !== null);
2954
+ ] = useQueryParams();
2955
+ const { model, id, document, meta, collectionType } = useDoc();
2956
+ const plugins = useStrapiApp("ActionsPanel", (state) => state.plugins);
2957
+ const props = {
2958
+ activeTab: status,
2959
+ model,
2960
+ documentId: id,
2961
+ document: isCloning ? void 0 : document,
2962
+ meta: isCloning ? void 0 : meta,
2963
+ collectionType
2964
+ };
2965
+ return /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, width: "100%", children: [
2966
+ /* @__PURE__ */ jsx(
2967
+ DescriptionComponentRenderer,
2968
+ {
2969
+ props,
2970
+ descriptions: plugins["content-manager"].apis.getDocumentActions(),
2971
+ children: (actions2) => /* @__PURE__ */ jsx(DocumentActions, { actions: actions2 })
2972
+ }
2973
+ ),
2974
+ /* @__PURE__ */ jsx(InjectionZone, { area: "editView.right-links", slug: model })
2975
+ ] });
2850
2976
  };
2977
+ const Panel = React.forwardRef(({ children, title }, ref) => {
2978
+ return /* @__PURE__ */ jsxs(
2979
+ Flex,
2980
+ {
2981
+ ref,
2982
+ tag: "aside",
2983
+ "aria-labelledby": "additional-information",
2984
+ background: "neutral0",
2985
+ borderColor: "neutral150",
2986
+ hasRadius: true,
2987
+ paddingBottom: 4,
2988
+ paddingLeft: 4,
2989
+ paddingRight: 4,
2990
+ paddingTop: 4,
2991
+ shadow: "tableShadow",
2992
+ gap: 3,
2993
+ direction: "column",
2994
+ justifyContent: "stretch",
2995
+ alignItems: "flex-start",
2996
+ children: [
2997
+ /* @__PURE__ */ jsx(Typography, { tag: "h2", variant: "sigma", textTransform: "uppercase", children: title }),
2998
+ children
2999
+ ]
3000
+ }
3001
+ );
3002
+ });
2851
3003
  const ConfirmBulkActionDialog = ({
2852
3004
  onToggleDialog,
2853
3005
  isOpen = false,
@@ -2886,6 +3038,7 @@ const ConfirmDialogPublishAll = ({
2886
3038
  const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler(getTranslation);
2887
3039
  const { model, schema } = useDoc();
2888
3040
  const [{ query }] = useQueryParams();
3041
+ const enableDraftRelationsCount = false;
2889
3042
  const {
2890
3043
  data: countDraftRelations = 0,
2891
3044
  isLoading,
@@ -2897,7 +3050,7 @@ const ConfirmDialogPublishAll = ({
2897
3050
  locale: query?.plugins?.i18n?.locale
2898
3051
  },
2899
3052
  {
2900
- skip: selectedEntries.length === 0
3053
+ skip: !enableDraftRelationsCount
2901
3054
  }
2902
3055
  );
2903
3056
  React.useEffect(() => {
@@ -3082,7 +3235,7 @@ const SelectedEntriesTableContent = ({
3082
3235
  status: row.status
3083
3236
  }
3084
3237
  ) }),
3085
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
3238
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Flex, { children: /* @__PURE__ */ jsx(
3086
3239
  IconButton,
3087
3240
  {
3088
3241
  tag: Link,
@@ -3105,9 +3258,10 @@ const SelectedEntriesTableContent = ({
3105
3258
  ),
3106
3259
  target: "_blank",
3107
3260
  marginLeft: "auto",
3108
- children: /* @__PURE__ */ jsx(Pencil, {})
3261
+ variant: "ghost",
3262
+ children: /* @__PURE__ */ jsx(Pencil, { width: "1.6rem", height: "1.6rem" })
3109
3263
  }
3110
- ) })
3264
+ ) }) })
3111
3265
  ] }, row.id)) })
3112
3266
  ] });
3113
3267
  };
@@ -3144,7 +3298,13 @@ const SelectedEntriesModalContent = ({
3144
3298
  );
3145
3299
  const { rows, validationErrors } = React.useMemo(() => {
3146
3300
  if (data.length > 0 && schema) {
3147
- const validate = createYupSchema(schema.attributes, components);
3301
+ const validate = createYupSchema(
3302
+ schema.attributes,
3303
+ components,
3304
+ // Since this is the "Publish" action, the validation
3305
+ // schema must enforce the rules for published entities
3306
+ { status: "published" }
3307
+ );
3148
3308
  const validationErrors2 = {};
3149
3309
  const rows2 = data.map((entry) => {
3150
3310
  try {
@@ -3494,7 +3654,7 @@ const TableActions = ({ document }) => {
3494
3654
  DescriptionComponentRenderer,
3495
3655
  {
3496
3656
  props,
3497
- descriptions: plugins["content-manager"].apis.getDocumentActions(),
3657
+ descriptions: plugins["content-manager"].apis.getDocumentActions().filter((action) => action.name !== "PublishAction"),
3498
3658
  children: (actions2) => {
3499
3659
  const tableRowActions = actions2.filter((action) => {
3500
3660
  const positions = Array.isArray(action.position) ? action.position : [action.position];
@@ -3605,7 +3765,7 @@ const CloneAction = ({ model, documentId }) => {
3605
3765
  }),
3606
3766
  content: /* @__PURE__ */ jsx(AutoCloneFailureModalBody, { prohibitedFields }),
3607
3767
  footer: ({ onClose }) => {
3608
- return /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", children: [
3768
+ return /* @__PURE__ */ jsxs(Modal.Footer, { children: [
3609
3769
  /* @__PURE__ */ jsx(Button, { onClick: onClose, variant: "tertiary", children: formatMessage({
3610
3770
  id: "cancel",
3611
3771
  defaultMessage: "Cancel"
@@ -3836,7 +3996,7 @@ const index = {
3836
3996
  app.router.addRoute({
3837
3997
  path: "content-manager/*",
3838
3998
  lazy: async () => {
3839
- const { Layout } = await import("./layout-Jl9mJFJZ.mjs");
3999
+ const { Layout } = await import("./layout-B4UhJ8MJ.mjs");
3840
4000
  return {
3841
4001
  Component: Layout
3842
4002
  };
@@ -3853,7 +4013,7 @@ const index = {
3853
4013
  async registerTrads({ locales }) {
3854
4014
  const importedTrads = await Promise.all(
3855
4015
  locales.map((locale) => {
3856
- return __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/ar.json": () => import("./ar-CCEVvqGG.mjs"), "./translations/ca.json": () => import("./ca-5U32ON2v.mjs"), "./translations/cs.json": () => import("./cs-CM2aBUar.mjs"), "./translations/de.json": () => import("./de-C72KDNOl.mjs"), "./translations/en.json": () => import("./en-BrCTWlZv.mjs"), "./translations/es.json": () => import("./es-CeXiYflN.mjs"), "./translations/eu.json": () => import("./eu-CdALomew.mjs"), "./translations/fr.json": () => import("./fr-CD9VFbPM.mjs"), "./translations/gu.json": () => import("./gu-CNpaMDpH.mjs"), "./translations/hi.json": () => import("./hi-Dwvd04m3.mjs"), "./translations/hu.json": () => import("./hu-CeYvaaO0.mjs"), "./translations/id.json": () => import("./id-BtwA9WJT.mjs"), "./translations/it.json": () => import("./it-BrVPqaf1.mjs"), "./translations/ja.json": () => import("./ja-CtsUxOvk.mjs"), "./translations/ko.json": () => import("./ko-HVQRlfUI.mjs"), "./translations/ml.json": () => import("./ml-BihZwQit.mjs"), "./translations/ms.json": () => import("./ms-m_WjyWx7.mjs"), "./translations/nl.json": () => import("./nl-D4R9gHx5.mjs"), "./translations/pl.json": () => import("./pl-sbx9mSt_.mjs"), "./translations/pt-BR.json": () => import("./pt-BR-C71iDxnh.mjs"), "./translations/pt.json": () => import("./pt-BsaFvS8-.mjs"), "./translations/ru.json": () => import("./ru-BE6A4Exp.mjs"), "./translations/sa.json": () => import("./sa-Dag0k-Z8.mjs"), "./translations/sk.json": () => import("./sk-BFg-R8qJ.mjs"), "./translations/sv.json": () => import("./sv-CUnfWGsh.mjs"), "./translations/th.json": () => import("./th-BqbI8lIT.mjs"), "./translations/tr.json": () => import("./tr-CgeK3wJM.mjs"), "./translations/uk.json": () => import("./uk-CR-zDhAY.mjs"), "./translations/vi.json": () => import("./vi-DUXIk_fw.mjs"), "./translations/zh-Hans.json": () => import("./zh-Hans-BPQcRIyH.mjs"), "./translations/zh.json": () => import("./zh-BWZspA60.mjs") }), `./translations/${locale}.json`).then(({ default: data }) => {
4016
+ return __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/ar.json": () => import("./ar-CCEVvqGG.mjs"), "./translations/ca.json": () => import("./ca-5U32ON2v.mjs"), "./translations/cs.json": () => import("./cs-CM2aBUar.mjs"), "./translations/de.json": () => import("./de-C72KDNOl.mjs"), "./translations/en.json": () => import("./en-C8YBvRrK.mjs"), "./translations/es.json": () => import("./es-CeXiYflN.mjs"), "./translations/eu.json": () => import("./eu-CdALomew.mjs"), "./translations/fr.json": () => import("./fr-CD9VFbPM.mjs"), "./translations/gu.json": () => import("./gu-CNpaMDpH.mjs"), "./translations/hi.json": () => import("./hi-Dwvd04m3.mjs"), "./translations/hu.json": () => import("./hu-CeYvaaO0.mjs"), "./translations/id.json": () => import("./id-BtwA9WJT.mjs"), "./translations/it.json": () => import("./it-BrVPqaf1.mjs"), "./translations/ja.json": () => import("./ja-CtsUxOvk.mjs"), "./translations/ko.json": () => import("./ko-HVQRlfUI.mjs"), "./translations/ml.json": () => import("./ml-BihZwQit.mjs"), "./translations/ms.json": () => import("./ms-m_WjyWx7.mjs"), "./translations/nl.json": () => import("./nl-D4R9gHx5.mjs"), "./translations/pl.json": () => import("./pl-sbx9mSt_.mjs"), "./translations/pt-BR.json": () => import("./pt-BR-C71iDxnh.mjs"), "./translations/pt.json": () => import("./pt-BsaFvS8-.mjs"), "./translations/ru.json": () => import("./ru-BE6A4Exp.mjs"), "./translations/sa.json": () => import("./sa-Dag0k-Z8.mjs"), "./translations/sk.json": () => import("./sk-BFg-R8qJ.mjs"), "./translations/sv.json": () => import("./sv-CUnfWGsh.mjs"), "./translations/th.json": () => import("./th-BqbI8lIT.mjs"), "./translations/tr.json": () => import("./tr-CgeK3wJM.mjs"), "./translations/uk.json": () => import("./uk-CR-zDhAY.mjs"), "./translations/vi.json": () => import("./vi-DUXIk_fw.mjs"), "./translations/zh-Hans.json": () => import("./zh-Hans-BPQcRIyH.mjs"), "./translations/zh.json": () => import("./zh-BWZspA60.mjs") }), `./translations/${locale}.json`).then(({ default: data }) => {
3857
4017
  return {
3858
4018
  data: prefixPluginTranslations(data, PLUGIN_ID),
3859
4019
  locale
@@ -3874,13 +4034,15 @@ export {
3874
4034
  BulkActionsRenderer as B,
3875
4035
  COLLECTION_TYPES as C,
3876
4036
  DocumentStatus as D,
3877
- DEFAULT_SETTINGS as E,
3878
- convertEditLayoutToFieldLayouts as F,
3879
- useDocument as G,
4037
+ extractContentTypeComponents as E,
4038
+ DEFAULT_SETTINGS as F,
4039
+ convertEditLayoutToFieldLayouts as G,
3880
4040
  HOOKS as H,
3881
4041
  InjectionZone as I,
3882
- index as J,
3883
- useDocumentActions as K,
4042
+ useDocument as J,
4043
+ index as K,
4044
+ useContentManagerContext as L,
4045
+ useDocumentActions as M,
3884
4046
  Panels as P,
3885
4047
  RelativeTime as R,
3886
4048
  SINGLE_TYPES as S,
@@ -3898,18 +4060,18 @@ export {
3898
4060
  PERMISSIONS as k,
3899
4061
  DocumentRBAC as l,
3900
4062
  DOCUMENT_META_FIELDS as m,
3901
- useDocLayout as n,
3902
- useGetContentTypeConfigurationQuery as o,
3903
- CREATOR_FIELDS as p,
3904
- getMainField as q,
3905
- getDisplayName as r,
4063
+ CLONE_PATH as n,
4064
+ useDocLayout as o,
4065
+ useGetContentTypeConfigurationQuery as p,
4066
+ CREATOR_FIELDS as q,
4067
+ getMainField as r,
3906
4068
  setInitialData as s,
3907
- checkIfAttributeIsDisplayable as t,
4069
+ getDisplayName as t,
3908
4070
  useContentTypeSchema as u,
3909
- useGetAllDocumentsQuery as v,
3910
- convertListLayoutToFieldLayouts as w,
3911
- capitalise as x,
3912
- useUpdateContentTypeConfigurationMutation as y,
3913
- extractContentTypeComponents as z
4071
+ checkIfAttributeIsDisplayable as v,
4072
+ useGetAllDocumentsQuery as w,
4073
+ convertListLayoutToFieldLayouts as x,
4074
+ capitalise as y,
4075
+ useUpdateContentTypeConfigurationMutation as z
3914
4076
  };
3915
- //# sourceMappingURL=index-BcQ8cRyl.mjs.map
4077
+ //# sourceMappingURL=index-CPCHQ3X_.mjs.map