@strapi/content-manager 0.0.0-experimental.779667bd163026468f566293decf331a0246fff9 → 0.0.0-experimental.7b750d18de359d0a42233cb8707e3c31c5983345

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