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

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