@strapi/content-manager 0.0.0-experimental.d53e940834bf72ddc725f1d2fd36dac9abec30cb → 0.0.0-experimental.d65615a2b9130dd742d3c396674457d7971da928

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 (134) hide show
  1. package/LICENSE +18 -3
  2. package/dist/_chunks/{ComponentConfigurationPage-C-49MccQ.js → ComponentConfigurationPage-CO977CPh.js} +4 -4
  3. package/dist/_chunks/{ComponentConfigurationPage-C-49MccQ.js.map → ComponentConfigurationPage-CO977CPh.js.map} +1 -1
  4. package/dist/_chunks/{ComponentConfigurationPage-DmwmiFQy.mjs → ComponentConfigurationPage-CQroR9Qk.mjs} +4 -4
  5. package/dist/_chunks/{ComponentConfigurationPage-DmwmiFQy.mjs.map → ComponentConfigurationPage-CQroR9Qk.mjs.map} +1 -1
  6. package/dist/_chunks/{EditConfigurationPage-DjFJw56M.js → EditConfigurationPage-BPgoE-kf.js} +4 -4
  7. package/dist/_chunks/{EditConfigurationPage-DjFJw56M.js.map → EditConfigurationPage-BPgoE-kf.js.map} +1 -1
  8. package/dist/_chunks/{EditConfigurationPage-JT3E7NZy.mjs → EditConfigurationPage-tVCJ5vWC.mjs} +4 -4
  9. package/dist/_chunks/{EditConfigurationPage-JT3E7NZy.mjs.map → EditConfigurationPage-tVCJ5vWC.mjs.map} +1 -1
  10. package/dist/_chunks/{EditViewPage-CPj61RMh.mjs → EditViewPage-8mOu02ji.mjs} +30 -9
  11. package/dist/_chunks/EditViewPage-8mOu02ji.mjs.map +1 -0
  12. package/dist/_chunks/{EditViewPage-zT3fBr4Y.js → EditViewPage-BMVgUNOX.js} +30 -9
  13. package/dist/_chunks/EditViewPage-BMVgUNOX.js.map +1 -0
  14. package/dist/_chunks/{Field-dha5VnIQ.mjs → Field-CJPYzwD7.mjs} +249 -152
  15. package/dist/_chunks/Field-CJPYzwD7.mjs.map +1 -0
  16. package/dist/_chunks/{Field-Boxf9Ajp.js → Field-CdSLKFQk.js} +251 -154
  17. package/dist/_chunks/Field-CdSLKFQk.js.map +1 -0
  18. package/dist/_chunks/{Form-DHrru2AV.mjs → Form-DJOJ-GF1.mjs} +36 -17
  19. package/dist/_chunks/Form-DJOJ-GF1.mjs.map +1 -0
  20. package/dist/_chunks/{Form-y5g1SRsh.js → Form-eP5bZwap.js} +36 -17
  21. package/dist/_chunks/Form-eP5bZwap.js.map +1 -0
  22. package/dist/_chunks/{History-CqN6K7SX.js → History-B-Mrquzu.js} +63 -25
  23. package/dist/_chunks/History-B-Mrquzu.js.map +1 -0
  24. package/dist/_chunks/{History-Bru_KoeP.mjs → History-MnQLtk1g.mjs} +64 -26
  25. package/dist/_chunks/History-MnQLtk1g.mjs.map +1 -0
  26. package/dist/_chunks/{ListConfigurationPage-D8wGABj0.mjs → ListConfigurationPage-BcycI8Lw.mjs} +21 -9
  27. package/dist/_chunks/ListConfigurationPage-BcycI8Lw.mjs.map +1 -0
  28. package/dist/_chunks/{ListConfigurationPage-R_p-SbHZ.js → ListConfigurationPage-C0n4rUzH.js} +21 -9
  29. package/dist/_chunks/ListConfigurationPage-C0n4rUzH.js.map +1 -0
  30. package/dist/_chunks/{ListViewPage-SID6TRb9.mjs → ListViewPage-CRXONXwZ.mjs} +59 -41
  31. package/dist/_chunks/ListViewPage-CRXONXwZ.mjs.map +1 -0
  32. package/dist/_chunks/{ListViewPage-pEw_zug9.js → ListViewPage-q0SHVPUS.js} +61 -43
  33. package/dist/_chunks/ListViewPage-q0SHVPUS.js.map +1 -0
  34. package/dist/_chunks/{NoContentTypePage-C5dcQojD.js → NoContentTypePage-Bh3komDV.js} +2 -2
  35. package/dist/_chunks/{NoContentTypePage-C5dcQojD.js.map → NoContentTypePage-Bh3komDV.js.map} +1 -1
  36. package/dist/_chunks/{NoContentTypePage-CJ7UXwrQ.mjs → NoContentTypePage-ukzFRF3z.mjs} +2 -2
  37. package/dist/_chunks/{NoContentTypePage-CJ7UXwrQ.mjs.map → NoContentTypePage-ukzFRF3z.mjs.map} +1 -1
  38. package/dist/_chunks/{NoPermissionsPage-B7syEq5E.mjs → NoPermissionsPage-B4sD7Ble.mjs} +2 -2
  39. package/dist/_chunks/{NoPermissionsPage-B7syEq5E.mjs.map → NoPermissionsPage-B4sD7Ble.mjs.map} +1 -1
  40. package/dist/_chunks/{NoPermissionsPage-BtPrImPP.js → NoPermissionsPage-BGBpj_Y1.js} +2 -2
  41. package/dist/_chunks/{NoPermissionsPage-BtPrImPP.js.map → NoPermissionsPage-BGBpj_Y1.js.map} +1 -1
  42. package/dist/_chunks/{Relations-DjTQ5kGB.js → Relations-B53wYe8g.js} +33 -24
  43. package/dist/_chunks/Relations-B53wYe8g.js.map +1 -0
  44. package/dist/_chunks/{Relations-B9Crnhnn.mjs → Relations-CIexb8gr.mjs} +33 -24
  45. package/dist/_chunks/Relations-CIexb8gr.mjs.map +1 -0
  46. package/dist/_chunks/{en-fbKQxLGn.js → en-Bm0D0IWz.js} +17 -15
  47. package/dist/_chunks/{en-fbKQxLGn.js.map → en-Bm0D0IWz.js.map} +1 -1
  48. package/dist/_chunks/{en-Ux26r5pl.mjs → en-DKV44jRb.mjs} +17 -15
  49. package/dist/_chunks/{en-Ux26r5pl.mjs.map → en-DKV44jRb.mjs.map} +1 -1
  50. package/dist/_chunks/{index-DJXJw9V5.mjs → index-CJ2vYwuT.mjs} +997 -690
  51. package/dist/_chunks/index-CJ2vYwuT.mjs.map +1 -0
  52. package/dist/_chunks/{index-DVPWZkbS.js → index-DbT2sx-Q.js} +978 -671
  53. package/dist/_chunks/index-DbT2sx-Q.js.map +1 -0
  54. package/dist/_chunks/{layout-Dm6fbiQj.js → layout-CeBSIkmP.js} +24 -11
  55. package/dist/_chunks/layout-CeBSIkmP.js.map +1 -0
  56. package/dist/_chunks/{layout-Bau7ZfLV.mjs → layout-vzKSrr7p.mjs} +25 -12
  57. package/dist/_chunks/layout-vzKSrr7p.mjs.map +1 -0
  58. package/dist/_chunks/{objects-gigeqt7s.js → objects-BcXOv6_9.js} +2 -4
  59. package/dist/_chunks/{objects-gigeqt7s.js.map → objects-BcXOv6_9.js.map} +1 -1
  60. package/dist/_chunks/{objects-mKMAmfec.mjs → objects-D6yBsdmx.mjs} +2 -4
  61. package/dist/_chunks/{objects-mKMAmfec.mjs.map → objects-D6yBsdmx.mjs.map} +1 -1
  62. package/dist/_chunks/{relations-CKnpRgrN.js → relations-Cl-6t9iz.js} +2 -2
  63. package/dist/_chunks/{relations-CKnpRgrN.js.map → relations-Cl-6t9iz.js.map} +1 -1
  64. package/dist/_chunks/{relations-BH_kBSJ0.mjs → relations-DI0lguF0.mjs} +2 -2
  65. package/dist/_chunks/{relations-BH_kBSJ0.mjs.map → relations-DI0lguF0.mjs.map} +1 -1
  66. package/dist/_chunks/{usePrev-B9w_-eYc.js → useDebounce-CtcjDB3L.js} +14 -1
  67. package/dist/_chunks/useDebounce-CtcjDB3L.js.map +1 -0
  68. package/dist/_chunks/useDebounce-DmuSJIF3.mjs +29 -0
  69. package/dist/_chunks/useDebounce-DmuSJIF3.mjs.map +1 -0
  70. package/dist/admin/index.js +2 -1
  71. package/dist/admin/index.js.map +1 -1
  72. package/dist/admin/index.mjs +5 -4
  73. package/dist/admin/src/exports.d.ts +1 -1
  74. package/dist/admin/src/history/index.d.ts +3 -0
  75. package/dist/admin/src/history/services/historyVersion.d.ts +1 -1
  76. package/dist/admin/src/hooks/useDocument.d.ts +32 -1
  77. package/dist/admin/src/index.d.ts +1 -0
  78. package/dist/admin/src/pages/EditView/components/DocumentActions.d.ts +1 -0
  79. package/dist/admin/src/pages/EditView/components/FormInputs/Relations.d.ts +20 -0
  80. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/EditorLayout.d.ts +2 -2
  81. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/WysiwygFooter.d.ts +2 -2
  82. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/WysiwygStyles.d.ts +4 -48
  83. package/dist/admin/src/pages/EditView/components/Header.d.ts +11 -11
  84. package/dist/admin/src/services/api.d.ts +1 -1
  85. package/dist/admin/src/services/components.d.ts +2 -2
  86. package/dist/admin/src/services/contentTypes.d.ts +3 -3
  87. package/dist/admin/src/services/documents.d.ts +19 -17
  88. package/dist/admin/src/services/init.d.ts +1 -1
  89. package/dist/admin/src/services/relations.d.ts +2 -2
  90. package/dist/admin/src/services/uid.d.ts +3 -3
  91. package/dist/admin/src/utils/validation.d.ts +4 -1
  92. package/dist/server/index.js +207 -120
  93. package/dist/server/index.js.map +1 -1
  94. package/dist/server/index.mjs +208 -121
  95. package/dist/server/index.mjs.map +1 -1
  96. package/dist/server/src/controllers/collection-types.d.ts.map +1 -1
  97. package/dist/server/src/controllers/relations.d.ts.map +1 -1
  98. package/dist/server/src/controllers/uid.d.ts.map +1 -1
  99. package/dist/server/src/controllers/validation/dimensions.d.ts +4 -2
  100. package/dist/server/src/controllers/validation/dimensions.d.ts.map +1 -1
  101. package/dist/server/src/history/services/history.d.ts.map +1 -1
  102. package/dist/server/src/history/services/lifecycles.d.ts.map +1 -1
  103. package/dist/server/src/history/services/utils.d.ts +2 -1
  104. package/dist/server/src/history/services/utils.d.ts.map +1 -1
  105. package/dist/server/src/policies/hasPermissions.d.ts.map +1 -1
  106. package/dist/server/src/services/document-manager.d.ts.map +1 -1
  107. package/dist/server/src/services/document-metadata.d.ts.map +1 -1
  108. package/dist/server/src/services/permission-checker.d.ts.map +1 -1
  109. package/dist/server/src/services/utils/populate.d.ts.map +1 -1
  110. package/dist/shared/contracts/collection-types.d.ts +3 -1
  111. package/dist/shared/contracts/collection-types.d.ts.map +1 -1
  112. package/package.json +12 -12
  113. package/dist/_chunks/EditViewPage-CPj61RMh.mjs.map +0 -1
  114. package/dist/_chunks/EditViewPage-zT3fBr4Y.js.map +0 -1
  115. package/dist/_chunks/Field-Boxf9Ajp.js.map +0 -1
  116. package/dist/_chunks/Field-dha5VnIQ.mjs.map +0 -1
  117. package/dist/_chunks/Form-DHrru2AV.mjs.map +0 -1
  118. package/dist/_chunks/Form-y5g1SRsh.js.map +0 -1
  119. package/dist/_chunks/History-Bru_KoeP.mjs.map +0 -1
  120. package/dist/_chunks/History-CqN6K7SX.js.map +0 -1
  121. package/dist/_chunks/ListConfigurationPage-D8wGABj0.mjs.map +0 -1
  122. package/dist/_chunks/ListConfigurationPage-R_p-SbHZ.js.map +0 -1
  123. package/dist/_chunks/ListViewPage-SID6TRb9.mjs.map +0 -1
  124. package/dist/_chunks/ListViewPage-pEw_zug9.js.map +0 -1
  125. package/dist/_chunks/Relations-B9Crnhnn.mjs.map +0 -1
  126. package/dist/_chunks/Relations-DjTQ5kGB.js.map +0 -1
  127. package/dist/_chunks/index-DJXJw9V5.mjs.map +0 -1
  128. package/dist/_chunks/index-DVPWZkbS.js.map +0 -1
  129. package/dist/_chunks/layout-Bau7ZfLV.mjs.map +0 -1
  130. package/dist/_chunks/layout-Dm6fbiQj.js.map +0 -1
  131. package/dist/_chunks/usePrev-B9w_-eYc.js.map +0 -1
  132. package/dist/_chunks/usePrev-DH6iah0A.mjs +0 -16
  133. package/dist/_chunks/usePrev-DH6iah0A.mjs.map +0 -1
  134. package/strapi-server.js +0 -3
@@ -1,17 +1,17 @@
1
- import { ClockCounterClockwise, CrossCircle, More, WarningCircle, ListPlus, Pencil, Trash, Check, CheckCircle, ArrowsCounterClockwise, ChevronRight, Duplicate, 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, useQueryParams, createContext, useAuth, useRBAC, Page, adminApi, translatedErrors, useNotification, useAPIErrorHandler, getYupValidationErrors, useTracking, useForm, BackButton, DescriptionComponentRenderer, useTable, Table } from "@strapi/admin/strapi-admin";
4
- import { stringify } from "qs";
5
- import { useIntl } from "react-intl";
6
- import { useNavigate, useParams, Navigate, useMatch, useLocation, Link, NavLink } from "react-router-dom";
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";
7
4
  import * as React from "react";
8
5
  import { lazy } from "react";
9
- import { Button, Menu, VisuallyHidden, Flex, Box, Typography, Dialog, Modal, Radio, Status, SingleSelect, SingleSelectOption, Loader, IconButton, Tooltip, LinkButton } from "@strapi/design-system";
10
- import { styled } from "styled-components";
6
+ import { Button, Menu, VisuallyHidden, Flex, Typography, Dialog, Modal, Radio, Status, Box, SingleSelect, SingleSelectOption, IconButton, Loader, Tooltip, LinkButton } from "@strapi/design-system";
7
+ import { useIntl } from "react-intl";
8
+ import { useParams, useNavigate, Navigate, useMatch, useLocation, Link, NavLink } from "react-router-dom";
11
9
  import * as yup from "yup";
12
10
  import { ValidationError } from "yup";
13
11
  import pipe from "lodash/fp/pipe";
14
12
  import { intervalToDuration, isPast } from "date-fns";
13
+ import { styled } from "styled-components";
14
+ import { stringify } from "qs";
15
15
  import { createSlice, combineReducers } from "@reduxjs/toolkit";
16
16
  const __variableDynamicImportRuntimeHelper = (glob, path) => {
17
17
  const v = glob[path];
@@ -49,42 +49,6 @@ const useInjectionZone = (area) => {
49
49
  const [page, position] = area.split(".");
50
50
  return contentManagerPlugin.getInjectedComponents(page, position);
51
51
  };
52
- const HistoryAction = ({ model, document }) => {
53
- const { formatMessage } = useIntl();
54
- const [{ query }] = useQueryParams();
55
- const navigate = useNavigate();
56
- const pluginsQueryParams = stringify({ plugins: query.plugins }, { encode: false });
57
- if (!window.strapi.features.isEnabled("cms-content-history")) {
58
- return null;
59
- }
60
- return {
61
- icon: /* @__PURE__ */ jsx(ClockCounterClockwise, {}),
62
- label: formatMessage({
63
- id: "content-manager.history.document-action",
64
- defaultMessage: "Content History"
65
- }),
66
- onClick: () => navigate({ pathname: "history", search: pluginsQueryParams }),
67
- disabled: (
68
- /**
69
- * The user is creating a new document.
70
- * It hasn't been saved yet, so there's no history to go to
71
- */
72
- !document || /**
73
- * The document has been created but the current dimension has never been saved.
74
- * For example, the user is creating a new locale in an existing document,
75
- * so there's no history for the document in that locale
76
- */
77
- !document.id || /**
78
- * History is only available for content types created by the user.
79
- * These have the `api::` prefix, as opposed to the ones created by Strapi or plugins,
80
- * which start with `admin::` or `plugin::`
81
- */
82
- !model.startsWith("api::")
83
- ),
84
- position: "header"
85
- };
86
- };
87
- HistoryAction.type = "history";
88
52
  const ID = "id";
89
53
  const CREATED_BY_ATTRIBUTE_NAME = "createdBy";
90
54
  const UPDATED_BY_ATTRIBUTE_NAME = "updatedBy";
@@ -136,6 +100,7 @@ const DocumentRBAC = ({ children, permissions }) => {
136
100
  if (!slug) {
137
101
  throw new Error("Cannot find the slug param in the URL");
138
102
  }
103
+ const [{ rawQuery }] = useQueryParams();
139
104
  const userPermissions = useAuth("DocumentRBAC", (state) => state.permissions);
140
105
  const contentTypePermissions = React.useMemo(() => {
141
106
  const contentTypePermissions2 = userPermissions.filter(
@@ -146,7 +111,14 @@ const DocumentRBAC = ({ children, permissions }) => {
146
111
  return { ...acc, [action]: [permission] };
147
112
  }, {});
148
113
  }, [slug, userPermissions]);
149
- 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
+ );
150
122
  const canCreateFields = !isLoading && allowedActions.canCreate ? extractAndDedupeFields(contentTypePermissions.create) : [];
151
123
  const canReadFields = !isLoading && allowedActions.canRead ? extractAndDedupeFields(contentTypePermissions.read) : [];
152
124
  const canUpdateFields = !isLoading && allowedActions.canUpdate ? extractAndDedupeFields(contentTypePermissions.update) : [];
@@ -194,7 +166,8 @@ const contentManagerApi = adminApi.enhanceEndpoints({
194
166
  "Document",
195
167
  "InitialData",
196
168
  "HistoryVersion",
197
- "Relations"
169
+ "Relations",
170
+ "UidAvailability"
198
171
  ]
199
172
  });
200
173
  const documentApi = contentManagerApi.injectEndpoints({
@@ -208,7 +181,12 @@ const documentApi = contentManagerApi.injectEndpoints({
208
181
  params: query
209
182
  }
210
183
  }),
211
- invalidatesTags: (_result, _error, { model }) => [{ type: "Document", id: `${model}_LIST` }]
184
+ invalidatesTags: (_result, error, { model }) => {
185
+ if (error) {
186
+ return [];
187
+ }
188
+ return [{ type: "Document", id: `${model}_LIST` }];
189
+ }
212
190
  }),
213
191
  cloneDocument: builder.mutation({
214
192
  query: ({ model, sourceId, data, params }) => ({
@@ -219,7 +197,10 @@ const documentApi = contentManagerApi.injectEndpoints({
219
197
  params
220
198
  }
221
199
  }),
222
- 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
+ ]
223
204
  }),
224
205
  /**
225
206
  * Creates a new collection-type document. This should ONLY be used for collection-types.
@@ -236,7 +217,8 @@ const documentApi = contentManagerApi.injectEndpoints({
236
217
  }),
237
218
  invalidatesTags: (result, _error, { model }) => [
238
219
  { type: "Document", id: `${model}_LIST` },
239
- "Relations"
220
+ "Relations",
221
+ { type: "UidAvailability", id: model }
240
222
  ]
241
223
  }),
242
224
  deleteDocument: builder.mutation({
@@ -277,7 +259,8 @@ const documentApi = contentManagerApi.injectEndpoints({
277
259
  id: collectionType !== SINGLE_TYPES ? `${model}_${documentId}` : model
278
260
  },
279
261
  { type: "Document", id: `${model}_LIST` },
280
- "Relations"
262
+ "Relations",
263
+ { type: "UidAvailability", id: model }
281
264
  ];
282
265
  }
283
266
  }),
@@ -295,6 +278,7 @@ const documentApi = contentManagerApi.injectEndpoints({
295
278
  }),
296
279
  providesTags: (result, _error, arg) => {
297
280
  return [
281
+ { type: "Document", id: `ALL_LIST` },
298
282
  { type: "Document", id: `${arg.model}_LIST` },
299
283
  ...result?.results.map(({ documentId }) => ({
300
284
  type: "Document",
@@ -333,6 +317,11 @@ const documentApi = contentManagerApi.injectEndpoints({
333
317
  {
334
318
  type: "Document",
335
319
  id: collectionType !== SINGLE_TYPES ? `${model}_${result && "documentId" in result ? result.documentId : documentId}` : model
320
+ },
321
+ // Make it easy to invalidate all individual documents queries for a model
322
+ {
323
+ type: "Document",
324
+ id: `${model}_ALL_ITEMS`
336
325
  }
337
326
  ];
338
327
  }
@@ -396,8 +385,21 @@ const documentApi = contentManagerApi.injectEndpoints({
396
385
  type: "Document",
397
386
  id: collectionType !== SINGLE_TYPES ? `${model}_${documentId}` : model
398
387
  },
399
- "Relations"
388
+ "Relations",
389
+ { type: "UidAvailability", id: model }
400
390
  ];
391
+ },
392
+ async onQueryStarted({ data, ...patch }, { dispatch, queryFulfilled }) {
393
+ const patchResult = dispatch(
394
+ documentApi.util.updateQueryData("getDocument", patch, (draft) => {
395
+ Object.assign(draft.data, data);
396
+ })
397
+ );
398
+ try {
399
+ await queryFulfilled;
400
+ } catch {
401
+ patchResult.undo();
402
+ }
401
403
  }
402
404
  }),
403
405
  unpublishDocument: builder.mutation({
@@ -467,20 +469,39 @@ const buildValidParams = (query) => {
467
469
  const isBaseQueryError = (error) => {
468
470
  return error.name !== void 0;
469
471
  };
470
- 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 }) => {
471
491
  const createModelSchema = (attributes2) => yup.object().shape(
472
492
  Object.entries(attributes2).reduce((acc, [name, attribute]) => {
473
493
  if (DOCUMENT_META_FIELDS.includes(name)) {
474
494
  return acc;
475
495
  }
476
496
  const validations = [
497
+ addNullableValidation,
477
498
  addRequiredValidation,
478
499
  addMinLengthValidation,
479
500
  addMaxLengthValidation,
480
501
  addMinValidation,
481
502
  addMaxValidation,
482
503
  addRegexValidation
483
- ].map((fn) => fn(attribute));
504
+ ].map((fn) => fn(attribute, options));
484
505
  const transformSchema = pipe(...validations);
485
506
  switch (attribute.type) {
486
507
  case "component": {
@@ -490,12 +511,12 @@ const createYupSchema = (attributes = {}, components = {}) => {
490
511
  ...acc,
491
512
  [name]: transformSchema(
492
513
  yup.array().of(createModelSchema(attributes3).nullable(false))
493
- )
514
+ ).test(arrayValidator(attribute, options))
494
515
  };
495
516
  } else {
496
517
  return {
497
518
  ...acc,
498
- [name]: transformSchema(createModelSchema(attributes3))
519
+ [name]: transformSchema(createModelSchema(attributes3).nullable())
499
520
  };
500
521
  }
501
522
  }
@@ -517,7 +538,7 @@ const createYupSchema = (attributes = {}, components = {}) => {
517
538
  }
518
539
  )
519
540
  )
520
- )
541
+ ).test(arrayValidator(attribute, options))
521
542
  };
522
543
  case "relation":
523
544
  return {
@@ -529,7 +550,7 @@ const createYupSchema = (attributes = {}, components = {}) => {
529
550
  } else if (Array.isArray(value)) {
530
551
  return yup.array().of(
531
552
  yup.object().shape({
532
- id: yup.string().required()
553
+ id: yup.number().required()
533
554
  })
534
555
  );
535
556
  } else if (typeof value === "object") {
@@ -581,6 +602,14 @@ const createAttributeSchema = (attribute) => {
581
602
  if (!value || typeof value === "string" && value.length === 0) {
582
603
  return true;
583
604
  }
605
+ if (typeof value === "object") {
606
+ try {
607
+ JSON.stringify(value);
608
+ return true;
609
+ } catch (err) {
610
+ return false;
611
+ }
612
+ }
584
613
  try {
585
614
  JSON.parse(value);
586
615
  return true;
@@ -599,13 +628,7 @@ const createAttributeSchema = (attribute) => {
599
628
  return yup.mixed();
600
629
  }
601
630
  };
602
- const addRequiredValidation = (attribute) => (schema) => {
603
- if (attribute.required) {
604
- return schema.required({
605
- id: translatedErrors.required.id,
606
- defaultMessage: "This field is required."
607
- });
608
- }
631
+ const nullableSchema = (schema) => {
609
632
  return schema?.nullable ? schema.nullable() : (
610
633
  // In some cases '.nullable' will not be available on the schema.
611
634
  // e.g. when the schema has been built using yup.lazy (e.g. for relations).
@@ -613,7 +636,22 @@ const addRequiredValidation = (attribute) => (schema) => {
613
636
  schema
614
637
  );
615
638
  };
616
- 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
+ }
617
655
  if ("minLength" in attribute && attribute.minLength && Number.isInteger(attribute.minLength) && "min" in schema) {
618
656
  return schema.min(attribute.minLength, {
619
657
  ...translatedErrors.minLength,
@@ -635,10 +673,13 @@ const addMaxLengthValidation = (attribute) => (schema) => {
635
673
  }
636
674
  return schema;
637
675
  };
638
- const addMinValidation = (attribute) => (schema) => {
639
- 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) {
640
681
  const min = toInteger(attribute.min);
641
- if ("min" in schema && min) {
682
+ if (min) {
642
683
  return schema.min(min, {
643
684
  ...translatedErrors.min,
644
685
  values: {
@@ -756,16 +797,115 @@ const extractContentTypeComponents = (attributes = {}, allComponents = {}) => {
756
797
  }, {});
757
798
  return componentsByKey;
758
799
  };
759
- 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);
760
899
  const { toggleNotification } = useNotification();
761
900
  const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
901
+ const { isLoading: isLoadingSchemas, schemas } = useContentTypeSchema();
762
902
  const {
763
- currentData: data,
764
- isLoading: isLoadingDocument,
765
- isFetching: isFetchingDocument,
766
- error
767
- } = useGetDocumentQuery(args, opts);
768
- 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;
769
909
  React.useEffect(() => {
770
910
  if (error) {
771
911
  toggleNotification({
@@ -773,68 +913,321 @@ const useDocument = (args, opts) => {
773
913
  message: formatAPIError(error)
774
914
  });
775
915
  }
776
- }, [toggleNotification, error, formatAPIError, args.collectionType]);
777
- const validationSchema = React.useMemo(() => {
778
- if (!schema) {
779
- return null;
780
- }
781
- return createYupSchema(schema.attributes, components);
782
- }, [schema, components]);
783
- const validate = React.useCallback(
784
- (document) => {
785
- if (!validationSchema) {
786
- throw new Error(
787
- "There is no validation schema generated, this is likely due to the schema not being loaded yet."
788
- );
789
- }
790
- try {
791
- validationSchema.validateSync(document, { abortEarly: false, strict: true });
792
- return null;
793
- } catch (error2) {
794
- if (error2 instanceof ValidationError) {
795
- return getYupValidationErrors(error2);
796
- }
797
- throw error2;
798
- }
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
799
924
  },
800
- [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]
801
941
  );
802
- const isLoading = isLoadingDocument || isFetchingDocument || isLoadingSchema;
803
942
  return {
804
- components,
805
- document: data?.data,
806
- meta: data?.meta,
943
+ error,
807
944
  isLoading,
808
- schema,
809
- validate
945
+ edit,
946
+ list: listLayout
810
947
  };
811
948
  };
812
- const useDoc = () => {
813
- const { id, slug, collectionType, origin } = useParams();
814
- const [{ query }] = useQueryParams();
815
- const params = React.useMemo(() => buildValidParams(query), [query]);
816
- if (!collectionType) {
817
- throw new Error("Could not find collectionType in url params");
818
- }
819
- if (!slug) {
820
- throw new Error("Could not find model in url params");
821
- }
949
+ const useDocLayout = () => {
950
+ const { model } = useDoc();
951
+ return useDocumentLayout(model);
952
+ };
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([]);
972
+ }
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
990
+ }
991
+ };
992
+ return acc;
993
+ },
994
+ {}
995
+ );
996
+ const editMetadatas = Object.entries(data.contentType.metadatas).reduce(
997
+ (acc, [attribute, metadata]) => {
998
+ return {
999
+ ...acc,
1000
+ [attribute]: metadata.edit
1001
+ };
1002
+ },
1003
+ {}
1004
+ );
1005
+ return {
1006
+ layout: panelledEditAttributes,
1007
+ components: componentEditAttributes,
1008
+ metadatas: editMetadatas,
1009
+ settings: {
1010
+ ...data.contentType.settings,
1011
+ displayName: schema?.info.displayName
1012
+ },
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;
1026
+ }
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)
1048
+ );
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;
822
1184
  return {
823
1185
  collectionType,
824
1186
  model: slug,
825
- id: origin || id === "create" ? void 0 : id,
826
- ...useDocument(
827
- { documentId: origin || id, model: slug, collectionType, params },
828
- {
829
- skip: id === "create" || !origin && !id && collectionType !== SINGLE_TYPES
830
- }
831
- )
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
832
1228
  };
833
1229
  };
834
1230
  const prefixPluginTranslations = (trad, pluginId) => {
835
- if (!pluginId) {
836
- throw new TypeError("pluginId can't be empty");
837
- }
838
1231
  return Object.keys(trad).reduce((acc, current) => {
839
1232
  acc[`${pluginId}.${current}`] = trad[current];
840
1233
  return acc;
@@ -850,6 +1243,8 @@ const useDocumentActions = () => {
850
1243
  const { formatMessage } = useIntl();
851
1244
  const { trackUsage } = useTracking();
852
1245
  const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
1246
+ const navigate = useNavigate();
1247
+ const setCurrentStep = useGuidedTour("useDocumentActions", (state) => state.setCurrentStep);
853
1248
  const [deleteDocument] = useDeleteDocumentMutation();
854
1249
  const _delete = React.useCallback(
855
1250
  async ({ collectionType, model, documentId, params }, trackerProperty) => {
@@ -1164,6 +1559,7 @@ const useDocumentActions = () => {
1164
1559
  defaultMessage: "Saved document"
1165
1560
  })
1166
1561
  });
1562
+ setCurrentStep("contentManager.success");
1167
1563
  return res.data;
1168
1564
  } catch (err) {
1169
1565
  toggleNotification({
@@ -1185,7 +1581,6 @@ const useDocumentActions = () => {
1185
1581
  sourceId
1186
1582
  });
1187
1583
  if ("error" in res) {
1188
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1189
1584
  return { error: res.error };
1190
1585
  }
1191
1586
  toggleNotification({
@@ -1204,7 +1599,7 @@ const useDocumentActions = () => {
1204
1599
  throw err;
1205
1600
  }
1206
1601
  },
1207
- [autoCloneDocument, formatAPIError, formatMessage, toggleNotification]
1602
+ [autoCloneDocument, formatMessage, toggleNotification]
1208
1603
  );
1209
1604
  const [cloneDocument] = useCloneDocumentMutation();
1210
1605
  const clone = React.useCallback(
@@ -1230,6 +1625,7 @@ const useDocumentActions = () => {
1230
1625
  defaultMessage: "Cloned document"
1231
1626
  })
1232
1627
  });
1628
+ navigate(`../../${res.data.data.documentId}`, { relative: "path" });
1233
1629
  return res.data;
1234
1630
  } catch (err) {
1235
1631
  toggleNotification({
@@ -1240,7 +1636,7 @@ const useDocumentActions = () => {
1240
1636
  throw err;
1241
1637
  }
1242
1638
  },
1243
- [cloneDocument, trackUsage, toggleNotification, formatMessage, formatAPIError]
1639
+ [cloneDocument, trackUsage, toggleNotification, formatMessage, formatAPIError, navigate]
1244
1640
  );
1245
1641
  const [getDoc] = useLazyGetDocumentQuery();
1246
1642
  const getDocument = React.useCallback(
@@ -1266,7 +1662,7 @@ const useDocumentActions = () => {
1266
1662
  };
1267
1663
  };
1268
1664
  const ProtectedHistoryPage = lazy(
1269
- () => import("./History-Bru_KoeP.mjs").then((mod) => ({ default: mod.ProtectedHistoryPage }))
1665
+ () => import("./History-MnQLtk1g.mjs").then((mod) => ({ default: mod.ProtectedHistoryPage }))
1270
1666
  );
1271
1667
  const routes$1 = [
1272
1668
  {
@@ -1279,31 +1675,31 @@ const routes$1 = [
1279
1675
  }
1280
1676
  ];
1281
1677
  const ProtectedEditViewPage = lazy(
1282
- () => import("./EditViewPage-CPj61RMh.mjs").then((mod) => ({ default: mod.ProtectedEditViewPage }))
1678
+ () => import("./EditViewPage-8mOu02ji.mjs").then((mod) => ({ default: mod.ProtectedEditViewPage }))
1283
1679
  );
1284
1680
  const ProtectedListViewPage = lazy(
1285
- () => import("./ListViewPage-SID6TRb9.mjs").then((mod) => ({ default: mod.ProtectedListViewPage }))
1681
+ () => import("./ListViewPage-CRXONXwZ.mjs").then((mod) => ({ default: mod.ProtectedListViewPage }))
1286
1682
  );
1287
1683
  const ProtectedListConfiguration = lazy(
1288
- () => import("./ListConfigurationPage-D8wGABj0.mjs").then((mod) => ({
1684
+ () => import("./ListConfigurationPage-BcycI8Lw.mjs").then((mod) => ({
1289
1685
  default: mod.ProtectedListConfiguration
1290
1686
  }))
1291
1687
  );
1292
1688
  const ProtectedEditConfigurationPage = lazy(
1293
- () => import("./EditConfigurationPage-JT3E7NZy.mjs").then((mod) => ({
1689
+ () => import("./EditConfigurationPage-tVCJ5vWC.mjs").then((mod) => ({
1294
1690
  default: mod.ProtectedEditConfigurationPage
1295
1691
  }))
1296
1692
  );
1297
1693
  const ProtectedComponentConfigurationPage = lazy(
1298
- () => import("./ComponentConfigurationPage-DmwmiFQy.mjs").then((mod) => ({
1694
+ () => import("./ComponentConfigurationPage-CQroR9Qk.mjs").then((mod) => ({
1299
1695
  default: mod.ProtectedComponentConfigurationPage
1300
1696
  }))
1301
1697
  );
1302
1698
  const NoPermissions = lazy(
1303
- () => import("./NoPermissionsPage-B7syEq5E.mjs").then((mod) => ({ default: mod.NoPermissions }))
1699
+ () => import("./NoPermissionsPage-B4sD7Ble.mjs").then((mod) => ({ default: mod.NoPermissions }))
1304
1700
  );
1305
1701
  const NoContentType = lazy(
1306
- () => import("./NoContentTypePage-CJ7UXwrQ.mjs").then((mod) => ({ default: mod.NoContentType }))
1702
+ () => import("./NoContentTypePage-ukzFRF3z.mjs").then((mod) => ({ default: mod.NoContentType }))
1307
1703
  );
1308
1704
  const CollectionTypePages = () => {
1309
1705
  const { collectionType } = useParams();
@@ -1417,12 +1813,14 @@ const DocumentActionButton = (action) => {
1417
1813
  /* @__PURE__ */ jsx(
1418
1814
  Button,
1419
1815
  {
1420
- flex: 1,
1816
+ flex: "auto",
1421
1817
  startIcon: action.icon,
1422
1818
  disabled: action.disabled,
1423
1819
  onClick: handleClick(action),
1424
1820
  justifyContent: "center",
1425
1821
  variant: action.variant || "default",
1822
+ paddingTop: "7px",
1823
+ paddingBottom: "7px",
1426
1824
  children: action.label
1427
1825
  }
1428
1826
  ),
@@ -1430,7 +1828,7 @@ const DocumentActionButton = (action) => {
1430
1828
  DocumentActionConfirmDialog,
1431
1829
  {
1432
1830
  ...action.dialog,
1433
- variant: action.variant,
1831
+ variant: action.dialog?.variant ?? action.variant,
1434
1832
  isOpen: dialogId === action.id,
1435
1833
  onClose: handleClose
1436
1834
  }
@@ -1487,9 +1885,9 @@ const DocumentActionsMenu = ({
1487
1885
  disabled: isDisabled,
1488
1886
  size: "S",
1489
1887
  endIcon: null,
1490
- paddingTop: "7px",
1491
- paddingLeft: "9px",
1492
- paddingRight: "9px",
1888
+ paddingTop: "4px",
1889
+ paddingLeft: "7px",
1890
+ paddingRight: "7px",
1493
1891
  variant,
1494
1892
  children: [
1495
1893
  /* @__PURE__ */ jsx(More, { "aria-hidden": true, focusable: false }),
@@ -1500,7 +1898,7 @@ const DocumentActionsMenu = ({
1500
1898
  ]
1501
1899
  }
1502
1900
  ),
1503
- /* @__PURE__ */ jsxs(Menu.Content, { top: "4px", maxHeight: void 0, popoverPlacement: "bottom-end", children: [
1901
+ /* @__PURE__ */ jsxs(Menu.Content, { maxHeight: void 0, popoverPlacement: "bottom-end", children: [
1504
1902
  actions2.map((action) => {
1505
1903
  return /* @__PURE__ */ jsx(
1506
1904
  Menu.Item,
@@ -1509,10 +1907,25 @@ const DocumentActionsMenu = ({
1509
1907
  onSelect: handleClick(action),
1510
1908
  display: "block",
1511
1909
  children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", gap: 4, children: [
1512
- /* @__PURE__ */ jsxs(Flex, { color: convertActionVariantToColor(action.variant), gap: 2, tag: "span", children: [
1513
- /* @__PURE__ */ jsx(Box, { tag: "span", color: convertActionVariantToIconColor(action.variant), children: action.icon }),
1514
- action.label
1515
- ] }),
1910
+ /* @__PURE__ */ jsxs(
1911
+ Flex,
1912
+ {
1913
+ color: !action.disabled ? convertActionVariantToColor(action.variant) : "inherit",
1914
+ gap: 2,
1915
+ tag: "span",
1916
+ children: [
1917
+ /* @__PURE__ */ jsx(
1918
+ Flex,
1919
+ {
1920
+ tag: "span",
1921
+ color: !action.disabled ? convertActionVariantToIconColor(action.variant) : "inherit",
1922
+ children: action.icon
1923
+ }
1924
+ ),
1925
+ action.label
1926
+ ]
1927
+ }
1928
+ ),
1516
1929
  action.id.startsWith("HistoryAction") && /* @__PURE__ */ jsx(
1517
1930
  Flex,
1518
1931
  {
@@ -1609,11 +2022,11 @@ const DocumentActionConfirmDialog = ({
1609
2022
  /* @__PURE__ */ jsx(Dialog.Header, { children: title }),
1610
2023
  /* @__PURE__ */ jsx(Dialog.Body, { children: content }),
1611
2024
  /* @__PURE__ */ jsxs(Dialog.Footer, { children: [
1612
- /* @__PURE__ */ jsx(Dialog.Cancel, { children: /* @__PURE__ */ jsx(Button, { variant: "tertiary", children: formatMessage({
2025
+ /* @__PURE__ */ jsx(Dialog.Cancel, { children: /* @__PURE__ */ jsx(Button, { variant: "tertiary", fullWidth: true, children: formatMessage({
1613
2026
  id: "app.components.Button.cancel",
1614
2027
  defaultMessage: "Cancel"
1615
2028
  }) }) }),
1616
- /* @__PURE__ */ jsx(Button, { onClick: handleConfirm, variant, children: formatMessage({
2029
+ /* @__PURE__ */ jsx(Button, { onClick: handleConfirm, variant, fullWidth: true, children: formatMessage({
1617
2030
  id: "app.components.Button.confirm",
1618
2031
  defaultMessage: "Confirm"
1619
2032
  }) })
@@ -1652,13 +2065,17 @@ const PublishAction$1 = ({
1652
2065
  const navigate = useNavigate();
1653
2066
  const { toggleNotification } = useNotification();
1654
2067
  const { _unstableFormatValidationErrors: formatValidationErrors } = useAPIErrorHandler();
2068
+ const isListView = useMatch(LIST_PATH) !== null;
1655
2069
  const isCloning = useMatch(CLONE_PATH) !== null;
1656
2070
  const { formatMessage } = useIntl();
1657
- const { canPublish, canCreate, canUpdate } = useDocumentRBAC(
1658
- "PublishAction",
1659
- ({ canPublish: canPublish2, canCreate: canCreate2, canUpdate: canUpdate2 }) => ({ canPublish: canPublish2, canCreate: canCreate2, canUpdate: canUpdate2 })
1660
- );
2071
+ const canPublish = useDocumentRBAC("PublishAction", ({ canPublish: canPublish2 }) => canPublish2);
1661
2072
  const { publish } = useDocumentActions();
2073
+ const [
2074
+ countDraftRelations,
2075
+ { isLoading: isLoadingDraftRelations, isError: isErrorDraftRelations }
2076
+ ] = useLazyGetDraftRelationCountQuery();
2077
+ const [localCountOfDraftRelations, setLocalCountOfDraftRelations] = React.useState(0);
2078
+ const [serverCountOfDraftRelations, setServerCountOfDraftRelations] = React.useState(0);
1662
2079
  const [{ query, rawQuery }] = useQueryParams();
1663
2080
  const params = React.useMemo(() => buildValidParams(query), [query]);
1664
2081
  const modified = useForm("PublishAction", ({ modified: modified2 }) => modified2);
@@ -1667,62 +2084,144 @@ const PublishAction$1 = ({
1667
2084
  const validate = useForm("PublishAction", (state) => state.validate);
1668
2085
  const setErrors = useForm("PublishAction", (state) => state.setErrors);
1669
2086
  const formValues = useForm("PublishAction", ({ values }) => values);
1670
- const isDocumentPublished = (document?.[PUBLISHED_AT_ATTRIBUTE_NAME] || meta?.availableStatus.some((doc) => doc[PUBLISHED_AT_ATTRIBUTE_NAME] !== null)) && document?.status !== "modified";
1671
- if (!schema?.options?.draftAndPublish) {
1672
- return null;
1673
- }
1674
- return {
1675
- /**
1676
- * Disabled when:
1677
- * - currently if you're cloning a document we don't support publish & clone at the same time.
1678
- * - the form is submitting
2087
+ React.useEffect(() => {
2088
+ if (isErrorDraftRelations) {
2089
+ toggleNotification({
2090
+ type: "danger",
2091
+ message: formatMessage({
2092
+ id: getTranslation("error.records.fetch-draft-relatons"),
2093
+ defaultMessage: "An error occurred while fetching draft relations on this document."
2094
+ })
2095
+ });
2096
+ }
2097
+ }, [isErrorDraftRelations, toggleNotification, formatMessage]);
2098
+ React.useEffect(() => {
2099
+ const localDraftRelations = /* @__PURE__ */ new Set();
2100
+ const extractDraftRelations = (data) => {
2101
+ const relations = data.connect || [];
2102
+ relations.forEach((relation) => {
2103
+ if (relation.status === "draft") {
2104
+ localDraftRelations.add(relation.id);
2105
+ }
2106
+ });
2107
+ };
2108
+ const traverseAndExtract = (data) => {
2109
+ Object.entries(data).forEach(([key, value]) => {
2110
+ if (key === "connect" && Array.isArray(value)) {
2111
+ extractDraftRelations({ connect: value });
2112
+ } else if (typeof value === "object" && value !== null) {
2113
+ traverseAndExtract(value);
2114
+ }
2115
+ });
2116
+ };
2117
+ if (!documentId || modified) {
2118
+ traverseAndExtract(formValues);
2119
+ setLocalCountOfDraftRelations(localDraftRelations.size);
2120
+ }
2121
+ }, [documentId, modified, formValues, setLocalCountOfDraftRelations]);
2122
+ React.useEffect(() => {
2123
+ if (!document || !document.documentId || isListView) {
2124
+ return;
2125
+ }
2126
+ const fetchDraftRelationsCount = async () => {
2127
+ const { data, error } = await countDraftRelations({
2128
+ collectionType,
2129
+ model,
2130
+ documentId,
2131
+ params
2132
+ });
2133
+ if (error) {
2134
+ throw error;
2135
+ }
2136
+ if (data) {
2137
+ setServerCountOfDraftRelations(data.data);
2138
+ }
2139
+ };
2140
+ fetchDraftRelationsCount();
2141
+ }, [isListView, document, documentId, countDraftRelations, collectionType, model, params]);
2142
+ const isDocumentPublished = (document?.[PUBLISHED_AT_ATTRIBUTE_NAME] || meta?.availableStatus.some((doc) => doc[PUBLISHED_AT_ATTRIBUTE_NAME] !== null)) && document?.status !== "modified";
2143
+ if (!schema?.options?.draftAndPublish) {
2144
+ return null;
2145
+ }
2146
+ const performPublish = async () => {
2147
+ setSubmitting(true);
2148
+ try {
2149
+ const { errors } = await validate(true, {
2150
+ status: "published"
2151
+ });
2152
+ if (errors) {
2153
+ toggleNotification({
2154
+ type: "danger",
2155
+ message: formatMessage({
2156
+ id: "content-manager.validation.error",
2157
+ defaultMessage: "There are validation errors in your document. Please fix them before saving."
2158
+ })
2159
+ });
2160
+ return;
2161
+ }
2162
+ const res = await publish(
2163
+ {
2164
+ collectionType,
2165
+ model,
2166
+ documentId,
2167
+ params
2168
+ },
2169
+ formValues
2170
+ );
2171
+ if ("data" in res && collectionType !== SINGLE_TYPES) {
2172
+ navigate({
2173
+ pathname: `../${collectionType}/${model}/${res.data.documentId}`,
2174
+ search: rawQuery
2175
+ });
2176
+ } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
2177
+ setErrors(formatValidationErrors(res.error));
2178
+ }
2179
+ } finally {
2180
+ setSubmitting(false);
2181
+ }
2182
+ };
2183
+ const totalDraftRelations = localCountOfDraftRelations + serverCountOfDraftRelations;
2184
+ const enableDraftRelationsCount = false;
2185
+ const hasDraftRelations = enableDraftRelationsCount;
2186
+ return {
2187
+ /**
2188
+ * Disabled when:
2189
+ * - currently if you're cloning a document we don't support publish & clone at the same time.
2190
+ * - the form is submitting
1679
2191
  * - the active tab is the published tab
1680
2192
  * - the document is already published & not modified
1681
2193
  * - the document is being created & not modified
1682
2194
  * - the user doesn't have the permission to publish
1683
- * - the user doesn't have the permission to create a new document
1684
- * - the user doesn't have the permission to update the document
1685
2195
  */
1686
- disabled: isCloning || isSubmitting || activeTab === "published" || !modified && isDocumentPublished || !modified && !document?.documentId || !canPublish || Boolean(!document?.documentId && !canCreate || document?.documentId && !canUpdate),
2196
+ disabled: isCloning || isSubmitting || isLoadingDraftRelations || activeTab === "published" || !modified && isDocumentPublished || !modified && !document?.documentId || !canPublish,
1687
2197
  label: formatMessage({
1688
2198
  id: "app.utils.publish",
1689
2199
  defaultMessage: "Publish"
1690
2200
  }),
1691
2201
  onClick: async () => {
1692
- setSubmitting(true);
1693
- try {
1694
- const { errors } = await validate();
1695
- if (errors) {
1696
- toggleNotification({
1697
- type: "danger",
1698
- message: formatMessage({
1699
- id: "content-manager.validation.error",
1700
- defaultMessage: "There are validation errors in your document. Please fix them before saving."
1701
- })
1702
- });
1703
- return;
1704
- }
1705
- const res = await publish(
1706
- {
1707
- collectionType,
1708
- model,
1709
- documentId,
1710
- params
1711
- },
1712
- formValues
1713
- );
1714
- if ("data" in res && collectionType !== SINGLE_TYPES) {
1715
- navigate({
1716
- pathname: `../${collectionType}/${model}/${res.data.documentId}`,
1717
- search: rawQuery
1718
- });
1719
- } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1720
- setErrors(formatValidationErrors(res.error));
2202
+ await performPublish();
2203
+ },
2204
+ dialog: hasDraftRelations ? {
2205
+ type: "dialog",
2206
+ variant: "danger",
2207
+ footer: null,
2208
+ title: formatMessage({
2209
+ id: getTranslation(`popUpwarning.warning.bulk-has-draft-relations.title`),
2210
+ defaultMessage: "Confirmation"
2211
+ }),
2212
+ content: formatMessage(
2213
+ {
2214
+ id: getTranslation(`popUpwarning.warning.bulk-has-draft-relations.message`),
2215
+ defaultMessage: "This entry is related to {count, plural, one {# draft entry} other {# draft entries}}. Publishing it could leave broken links in your app."
2216
+ },
2217
+ {
2218
+ count: totalDraftRelations
1721
2219
  }
1722
- } finally {
1723
- setSubmitting(false);
2220
+ ),
2221
+ onConfirm: async () => {
2222
+ await performPublish();
1724
2223
  }
1725
- }
2224
+ } : void 0
1726
2225
  };
1727
2226
  };
1728
2227
  PublishAction$1.type = "publish";
@@ -1738,10 +2237,6 @@ const UpdateAction = ({
1738
2237
  const cloneMatch = useMatch(CLONE_PATH);
1739
2238
  const isCloning = cloneMatch !== null;
1740
2239
  const { formatMessage } = useIntl();
1741
- const { canCreate, canUpdate } = useDocumentRBAC("UpdateAction", ({ canCreate: canCreate2, canUpdate: canUpdate2 }) => ({
1742
- canCreate: canCreate2,
1743
- canUpdate: canUpdate2
1744
- }));
1745
2240
  const { create, update, clone } = useDocumentActions();
1746
2241
  const [{ query, rawQuery }] = useQueryParams();
1747
2242
  const params = React.useMemo(() => buildValidParams(query), [query]);
@@ -1758,10 +2253,8 @@ const UpdateAction = ({
1758
2253
  * - the form is submitting
1759
2254
  * - the document is not modified & we're not cloning (you can save a clone entity straight away)
1760
2255
  * - the active tab is the published tab
1761
- * - the user doesn't have the permission to create a new document
1762
- * - the user doesn't have the permission to update the document
1763
2256
  */
1764
- disabled: isSubmitting || !modified && !isCloning || activeTab === "published" || Boolean(!documentId && !canCreate || documentId && !canUpdate),
2257
+ disabled: isSubmitting || !modified && !isCloning || activeTab === "published",
1765
2258
  label: formatMessage({
1766
2259
  id: "content-manager.containers.Edit.save",
1767
2260
  defaultMessage: "Save"
@@ -1769,7 +2262,9 @@ const UpdateAction = ({
1769
2262
  onClick: async () => {
1770
2263
  setSubmitting(true);
1771
2264
  try {
1772
- const { errors } = await validate();
2265
+ const { errors } = await validate(true, {
2266
+ status: "draft"
2267
+ });
1773
2268
  if (errors) {
1774
2269
  toggleNotification({
1775
2270
  type: "danger",
@@ -1790,10 +2285,13 @@ const UpdateAction = ({
1790
2285
  document
1791
2286
  );
1792
2287
  if ("data" in res) {
1793
- navigate({
1794
- pathname: `../${collectionType}/${model}/${res.data.documentId}`,
1795
- search: rawQuery
1796
- });
2288
+ navigate(
2289
+ {
2290
+ pathname: `../${res.data.documentId}`,
2291
+ search: rawQuery
2292
+ },
2293
+ { relative: "path" }
2294
+ );
1797
2295
  } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1798
2296
  setErrors(formatValidationErrors(res.error));
1799
2297
  }
@@ -1821,10 +2319,13 @@ const UpdateAction = ({
1821
2319
  document
1822
2320
  );
1823
2321
  if ("data" in res && collectionType !== SINGLE_TYPES) {
1824
- navigate({
1825
- pathname: `../${collectionType}/${model}/${res.data.documentId}`,
1826
- search: rawQuery
1827
- });
2322
+ navigate(
2323
+ {
2324
+ pathname: `../${res.data.documentId}`,
2325
+ search: rawQuery
2326
+ },
2327
+ { replace: true, relative: "path" }
2328
+ );
1828
2329
  } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1829
2330
  setErrors(formatValidationErrors(res.error));
1830
2331
  }
@@ -1868,7 +2369,7 @@ const UnpublishAction$1 = ({
1868
2369
  id: "app.utils.unpublish",
1869
2370
  defaultMessage: "Unpublish"
1870
2371
  }),
1871
- icon: /* @__PURE__ */ jsx(StyledCrossCircle, {}),
2372
+ icon: /* @__PURE__ */ jsx(Cross, {}),
1872
2373
  onClick: async () => {
1873
2374
  if (!documentId && collectionType !== SINGLE_TYPES || isDocumentModified) {
1874
2375
  if (!documentId) {
@@ -1980,7 +2481,7 @@ const DiscardAction = ({
1980
2481
  id: "content-manager.actions.discard.label",
1981
2482
  defaultMessage: "Discard changes"
1982
2483
  }),
1983
- icon: /* @__PURE__ */ jsx(StyledCrossCircle, {}),
2484
+ icon: /* @__PURE__ */ jsx(Cross, {}),
1984
2485
  position: ["panel", "table-row"],
1985
2486
  variant: "danger",
1986
2487
  dialog: {
@@ -2008,11 +2509,6 @@ const DiscardAction = ({
2008
2509
  };
2009
2510
  };
2010
2511
  DiscardAction.type = "discard";
2011
- const StyledCrossCircle = styled(CrossCircle)`
2012
- path {
2013
- fill: currentColor;
2014
- }
2015
- `;
2016
2512
  const DEFAULT_ACTIONS = [PublishAction$1, UpdateAction, UnpublishAction$1, DiscardAction];
2017
2513
  const intervals = ["years", "months", "days", "hours", "minutes", "seconds"];
2018
2514
  const RelativeTime = React.forwardRef(
@@ -2060,7 +2556,7 @@ const getDisplayName = ({
2060
2556
  };
2061
2557
  const capitalise = (str) => str.charAt(0).toUpperCase() + str.slice(1);
2062
2558
  const DocumentStatus = ({ status = "draft", ...restProps }) => {
2063
- const statusVariant = status === "draft" ? "primary" : status === "published" ? "success" : "alternative";
2559
+ const statusVariant = status === "draft" ? "secondary" : status === "published" ? "success" : "alternative";
2064
2560
  return /* @__PURE__ */ jsx(Status, { ...restProps, showBullet: false, size: "S", variant: statusVariant, children: /* @__PURE__ */ jsx(Typography, { tag: "span", variant: "omega", fontWeight: "bold", children: capitalise(status) }) });
2065
2561
  };
2066
2562
  const Header = ({ isCreating, status, title: documentTitle = "Untitled" }) => {
@@ -2070,23 +2566,13 @@ const Header = ({ isCreating, status, title: documentTitle = "Untitled" }) => {
2070
2566
  id: "content-manager.containers.edit.title.new",
2071
2567
  defaultMessage: "Create an entry"
2072
2568
  }) : documentTitle;
2073
- return /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", paddingTop: 8, paddingBottom: 4, gap: 3, children: [
2569
+ return /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", paddingTop: 6, paddingBottom: 4, gap: 2, children: [
2074
2570
  /* @__PURE__ */ jsx(BackButton, {}),
2075
- /* @__PURE__ */ jsxs(
2076
- Flex,
2077
- {
2078
- width: "100%",
2079
- justifyContent: "space-between",
2080
- paddingTop: 1,
2081
- gap: "80px",
2082
- alignItems: "flex-start",
2083
- children: [
2084
- /* @__PURE__ */ jsx(Typography, { variant: "alpha", tag: "h1", children: title }),
2085
- /* @__PURE__ */ jsx(HeaderToolbar, {})
2086
- ]
2087
- }
2088
- ),
2089
- status ? /* @__PURE__ */ jsx(DocumentStatus, { status: isCloning ? "draft" : status }) : null
2571
+ /* @__PURE__ */ jsxs(Flex, { width: "100%", justifyContent: "space-between", gap: "80px", alignItems: "flex-start", children: [
2572
+ /* @__PURE__ */ jsx(Typography, { variant: "alpha", tag: "h1", children: title }),
2573
+ /* @__PURE__ */ jsx(HeaderToolbar, {})
2574
+ ] }),
2575
+ status ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(DocumentStatus, { status: isCloning ? "draft" : status }) }) : null
2090
2576
  ] });
2091
2577
  };
2092
2578
  const HeaderToolbar = () => {
@@ -2169,12 +2655,12 @@ const Information = ({ activeTab }) => {
2169
2655
  isDisplayed: !!publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME],
2170
2656
  label: formatMessage({
2171
2657
  id: "content-manager.containers.edit.information.last-published.label",
2172
- defaultMessage: "Last published"
2658
+ defaultMessage: "Published"
2173
2659
  }),
2174
2660
  value: formatMessage(
2175
2661
  {
2176
2662
  id: "content-manager.containers.edit.information.last-published.value",
2177
- defaultMessage: `Published {time}{isAnonymous, select, true {} other { by {author}}}`
2663
+ defaultMessage: `{time}{isAnonymous, select, true {} other { by {author}}}`
2178
2664
  },
2179
2665
  {
2180
2666
  time: /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME]) }),
@@ -2187,12 +2673,12 @@ const Information = ({ activeTab }) => {
2187
2673
  isDisplayed: !!createAndUpdateDocument?.[UPDATED_AT_ATTRIBUTE_NAME],
2188
2674
  label: formatMessage({
2189
2675
  id: "content-manager.containers.edit.information.last-draft.label",
2190
- defaultMessage: "Last draft"
2676
+ defaultMessage: "Updated"
2191
2677
  }),
2192
2678
  value: formatMessage(
2193
2679
  {
2194
2680
  id: "content-manager.containers.edit.information.last-draft.value",
2195
- defaultMessage: `Modified {time}{isAnonymous, select, true {} other { by {author}}}`
2681
+ defaultMessage: `{time}{isAnonymous, select, true {} other { by {author}}}`
2196
2682
  },
2197
2683
  {
2198
2684
  time: /* @__PURE__ */ jsx(
@@ -2210,12 +2696,12 @@ const Information = ({ activeTab }) => {
2210
2696
  isDisplayed: !!createAndUpdateDocument?.[CREATED_AT_ATTRIBUTE_NAME],
2211
2697
  label: formatMessage({
2212
2698
  id: "content-manager.containers.edit.information.document.label",
2213
- defaultMessage: "Document"
2699
+ defaultMessage: "Created"
2214
2700
  }),
2215
2701
  value: formatMessage(
2216
2702
  {
2217
2703
  id: "content-manager.containers.edit.information.document.value",
2218
- defaultMessage: `Created {time}{isAnonymous, select, true {} other { by {author}}}`
2704
+ defaultMessage: `{time}{isAnonymous, select, true {} other { by {author}}}`
2219
2705
  },
2220
2706
  {
2221
2707
  time: /* @__PURE__ */ jsx(
@@ -2253,25 +2739,77 @@ const Information = ({ activeTab }) => {
2253
2739
  );
2254
2740
  };
2255
2741
  const HeaderActions = ({ actions: actions2 }) => {
2256
- return /* @__PURE__ */ jsx(Flex, { children: actions2.map((action) => {
2257
- if ("options" in action) {
2742
+ const [dialogId, setDialogId] = React.useState(null);
2743
+ const handleClick = (action) => async (e) => {
2744
+ if (!("options" in action)) {
2745
+ const { onClick = () => false, dialog, id } = action;
2746
+ const muteDialog = await onClick(e);
2747
+ if (dialog && !muteDialog) {
2748
+ e.preventDefault();
2749
+ setDialogId(id);
2750
+ }
2751
+ }
2752
+ };
2753
+ const handleClose = () => {
2754
+ setDialogId(null);
2755
+ };
2756
+ return /* @__PURE__ */ jsx(Flex, { gap: 1, children: actions2.map((action) => {
2757
+ if (action.options) {
2258
2758
  return /* @__PURE__ */ jsx(
2259
2759
  SingleSelect,
2260
2760
  {
2261
2761
  size: "S",
2262
- disabled: action.disabled,
2263
- "aria-label": action.label,
2264
2762
  onChange: action.onSelect,
2265
- value: action.value,
2763
+ "aria-label": action.label,
2764
+ ...action,
2266
2765
  children: action.options.map(({ label, ...option }) => /* @__PURE__ */ jsx(SingleSelectOption, { ...option, children: label }, option.value))
2267
2766
  },
2268
2767
  action.id
2269
2768
  );
2270
2769
  } else {
2271
- return null;
2770
+ if (action.type === "icon") {
2771
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
2772
+ /* @__PURE__ */ jsx(
2773
+ IconButton,
2774
+ {
2775
+ disabled: action.disabled,
2776
+ label: action.label,
2777
+ size: "S",
2778
+ onClick: handleClick(action),
2779
+ children: action.icon
2780
+ }
2781
+ ),
2782
+ action.dialog ? /* @__PURE__ */ jsx(
2783
+ HeaderActionDialog,
2784
+ {
2785
+ ...action.dialog,
2786
+ isOpen: dialogId === action.id,
2787
+ onClose: handleClose
2788
+ }
2789
+ ) : null
2790
+ ] }, action.id);
2791
+ }
2272
2792
  }
2273
2793
  }) });
2274
2794
  };
2795
+ const HeaderActionDialog = ({
2796
+ onClose,
2797
+ onCancel,
2798
+ title,
2799
+ content: Content,
2800
+ isOpen
2801
+ }) => {
2802
+ const handleClose = async () => {
2803
+ if (onCancel) {
2804
+ await onCancel();
2805
+ }
2806
+ onClose();
2807
+ };
2808
+ return /* @__PURE__ */ jsx(Dialog.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxs(Dialog.Content, { children: [
2809
+ /* @__PURE__ */ jsx(Dialog.Header, { children: title }),
2810
+ typeof Content === "function" ? /* @__PURE__ */ jsx(Content, { onClose: handleClose }) : Content
2811
+ ] }) });
2812
+ };
2275
2813
  const ConfigureTheViewAction = ({ collectionType, model }) => {
2276
2814
  const navigate = useNavigate();
2277
2815
  const { formatMessage } = useIntl();
@@ -2312,12 +2850,16 @@ const DeleteAction$1 = ({ documentId, model, collectionType, document }) => {
2312
2850
  const { delete: deleteAction } = useDocumentActions();
2313
2851
  const { toggleNotification } = useNotification();
2314
2852
  const setSubmitting = useForm("DeleteAction", (state) => state.setSubmitting);
2853
+ const isLocalized = document?.locale != null;
2315
2854
  return {
2316
2855
  disabled: !canDelete || !document,
2317
- label: formatMessage({
2318
- id: "content-manager.actions.delete.label",
2319
- defaultMessage: "Delete document"
2320
- }),
2856
+ label: formatMessage(
2857
+ {
2858
+ id: "content-manager.actions.delete.label",
2859
+ defaultMessage: "Delete entry{isLocalized, select, true { (all locales)} other {}}"
2860
+ },
2861
+ { isLocalized }
2862
+ ),
2321
2863
  icon: /* @__PURE__ */ jsx(Trash, {}),
2322
2864
  dialog: {
2323
2865
  type: "dialog",
@@ -2351,425 +2893,123 @@ const DeleteAction$1 = ({ documentId, model, collectionType, document }) => {
2351
2893
  return;
2352
2894
  }
2353
2895
  const res = await deleteAction({
2354
- documentId,
2355
- model,
2356
- collectionType,
2357
- params: {
2358
- locale: "*"
2359
- }
2360
- });
2361
- if (!("error" in res)) {
2362
- navigate({ pathname: `../${collectionType}/${model}` }, { replace: true });
2363
- }
2364
- } finally {
2365
- if (!listViewPathMatch) {
2366
- setSubmitting(false);
2367
- }
2368
- }
2369
- }
2370
- },
2371
- variant: "danger",
2372
- position: ["header", "table-row"]
2373
- };
2374
- };
2375
- DeleteAction$1.type = "delete";
2376
- const DEFAULT_HEADER_ACTIONS = [EditTheModelAction, ConfigureTheViewAction, DeleteAction$1];
2377
- const Panels = () => {
2378
- const isCloning = useMatch(CLONE_PATH) !== null;
2379
- const [
2380
- {
2381
- query: { status }
2382
- }
2383
- ] = useQueryParams({
2384
- status: "draft"
2385
- });
2386
- const { model, id, document, meta, collectionType } = useDoc();
2387
- const plugins = useStrapiApp("Panels", (state) => state.plugins);
2388
- const props = {
2389
- activeTab: status,
2390
- model,
2391
- documentId: id,
2392
- document: isCloning ? void 0 : document,
2393
- meta: isCloning ? void 0 : meta,
2394
- collectionType
2395
- };
2396
- return /* @__PURE__ */ jsx(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: /* @__PURE__ */ jsx(
2397
- DescriptionComponentRenderer,
2398
- {
2399
- props,
2400
- descriptions: plugins["content-manager"].apis.getEditViewSidePanels(),
2401
- children: (panels) => panels.map(({ content, id: id2, ...description }) => /* @__PURE__ */ jsx(Panel, { ...description, children: content }, id2))
2402
- }
2403
- ) });
2404
- };
2405
- const ActionsPanel = () => {
2406
- const { formatMessage } = useIntl();
2407
- return {
2408
- title: formatMessage({
2409
- id: "content-manager.containers.edit.panels.default.title",
2410
- defaultMessage: "Document"
2411
- }),
2412
- content: /* @__PURE__ */ jsx(ActionsPanelContent, {})
2413
- };
2414
- };
2415
- ActionsPanel.type = "actions";
2416
- const ActionsPanelContent = () => {
2417
- const isCloning = useMatch(CLONE_PATH) !== null;
2418
- const [
2419
- {
2420
- query: { status = "draft" }
2421
- }
2422
- ] = useQueryParams();
2423
- const { model, id, document, meta, collectionType } = useDoc();
2424
- const plugins = useStrapiApp("ActionsPanel", (state) => state.plugins);
2425
- const props = {
2426
- activeTab: status,
2427
- model,
2428
- documentId: id,
2429
- document: isCloning ? void 0 : document,
2430
- meta: isCloning ? void 0 : meta,
2431
- collectionType
2432
- };
2433
- return /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, width: "100%", children: [
2434
- /* @__PURE__ */ jsx(
2435
- DescriptionComponentRenderer,
2436
- {
2437
- props,
2438
- descriptions: plugins["content-manager"].apis.getDocumentActions(),
2439
- children: (actions2) => /* @__PURE__ */ jsx(DocumentActions, { actions: actions2 })
2440
- }
2441
- ),
2442
- /* @__PURE__ */ jsx(InjectionZone, { area: "editView.right-links", slug: model })
2443
- ] });
2444
- };
2445
- const Panel = React.forwardRef(({ children, title }, ref) => {
2446
- return /* @__PURE__ */ jsxs(
2447
- Flex,
2448
- {
2449
- ref,
2450
- tag: "aside",
2451
- "aria-labelledby": "additional-information",
2452
- background: "neutral0",
2453
- borderColor: "neutral150",
2454
- hasRadius: true,
2455
- paddingBottom: 4,
2456
- paddingLeft: 4,
2457
- paddingRight: 4,
2458
- paddingTop: 4,
2459
- shadow: "tableShadow",
2460
- gap: 3,
2461
- direction: "column",
2462
- justifyContent: "stretch",
2463
- alignItems: "flex-start",
2464
- children: [
2465
- /* @__PURE__ */ jsx(Typography, { tag: "h2", variant: "sigma", textTransform: "uppercase", children: title }),
2466
- children
2467
- ]
2468
- }
2469
- );
2470
- });
2471
- const HOOKS = {
2472
- /**
2473
- * Hook that allows to mutate the displayed headers of the list view table
2474
- * @constant
2475
- * @type {string}
2476
- */
2477
- INJECT_COLUMN_IN_TABLE: "Admin/CM/pages/ListView/inject-column-in-table",
2478
- /**
2479
- * Hook that allows to mutate the CM's collection types links pre-set filters
2480
- * @constant
2481
- * @type {string}
2482
- */
2483
- MUTATE_COLLECTION_TYPES_LINKS: "Admin/CM/pages/App/mutate-collection-types-links",
2484
- /**
2485
- * Hook that allows to mutate the CM's edit view layout
2486
- * @constant
2487
- * @type {string}
2488
- */
2489
- MUTATE_EDIT_VIEW_LAYOUT: "Admin/CM/pages/EditView/mutate-edit-view-layout",
2490
- /**
2491
- * Hook that allows to mutate the CM's single types links pre-set filters
2492
- * @constant
2493
- * @type {string}
2494
- */
2495
- MUTATE_SINGLE_TYPES_LINKS: "Admin/CM/pages/App/mutate-single-types-links"
2496
- };
2497
- const contentTypesApi = contentManagerApi.injectEndpoints({
2498
- endpoints: (builder) => ({
2499
- getContentTypeConfiguration: builder.query({
2500
- query: (uid) => ({
2501
- url: `/content-manager/content-types/${uid}/configuration`,
2502
- method: "GET"
2503
- }),
2504
- transformResponse: (response) => response.data,
2505
- providesTags: (_result, _error, uid) => [
2506
- { type: "ContentTypesConfiguration", id: uid },
2507
- { type: "ContentTypeSettings", id: "LIST" }
2508
- ]
2509
- }),
2510
- getAllContentTypeSettings: builder.query({
2511
- query: () => "/content-manager/content-types-settings",
2512
- transformResponse: (response) => response.data,
2513
- providesTags: [{ type: "ContentTypeSettings", id: "LIST" }]
2514
- }),
2515
- updateContentTypeConfiguration: builder.mutation({
2516
- query: ({ uid, ...body }) => ({
2517
- url: `/content-manager/content-types/${uid}/configuration`,
2518
- method: "PUT",
2519
- data: body
2520
- }),
2521
- transformResponse: (response) => response.data,
2522
- invalidatesTags: (_result, _error, { uid }) => [
2523
- { type: "ContentTypesConfiguration", id: uid },
2524
- { type: "ContentTypeSettings", id: "LIST" },
2525
- // Is this necessary?
2526
- { type: "InitialData" }
2527
- ]
2528
- })
2529
- })
2530
- });
2531
- const {
2532
- useGetContentTypeConfigurationQuery,
2533
- useGetAllContentTypeSettingsQuery,
2534
- useUpdateContentTypeConfigurationMutation
2535
- } = contentTypesApi;
2536
- const checkIfAttributeIsDisplayable = (attribute) => {
2537
- const { type } = attribute;
2538
- if (type === "relation") {
2539
- return !attribute.relation.toLowerCase().includes("morph");
2540
- }
2541
- return !["json", "dynamiczone", "richtext", "password", "blocks"].includes(type) && !!type;
2542
- };
2543
- const getMainField = (attribute, mainFieldName, { schemas, components }) => {
2544
- if (!mainFieldName) {
2545
- return void 0;
2546
- }
2547
- const mainFieldType = attribute.type === "component" ? components[attribute.component].attributes[mainFieldName].type : (
2548
- // @ts-expect-error – `targetModel` does exist on the attribute for a relation.
2549
- schemas.find((schema) => schema.uid === attribute.targetModel)?.attributes[mainFieldName].type
2550
- );
2551
- return {
2552
- name: mainFieldName,
2553
- type: mainFieldType ?? "string"
2554
- };
2555
- };
2556
- const DEFAULT_SETTINGS = {
2557
- bulkable: false,
2558
- filterable: false,
2559
- searchable: false,
2560
- pagination: false,
2561
- defaultSortBy: "",
2562
- defaultSortOrder: "asc",
2563
- mainField: "id",
2564
- pageSize: 10
2565
- };
2566
- const useDocumentLayout = (model) => {
2567
- const { schema, components } = useDocument({ model, collectionType: "" }, { skip: true });
2568
- const [{ query }] = useQueryParams();
2569
- const runHookWaterfall = useStrapiApp("useDocumentLayout", (state) => state.runHookWaterfall);
2570
- const { toggleNotification } = useNotification();
2571
- const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
2572
- const { isLoading: isLoadingSchemas, schemas } = useContentTypeSchema();
2573
- const {
2574
- data,
2575
- isLoading: isLoadingConfigs,
2576
- error,
2577
- isFetching: isFetchingConfigs
2578
- } = useGetContentTypeConfigurationQuery(model);
2579
- const isLoading = isLoadingSchemas || isFetchingConfigs || isLoadingConfigs;
2580
- React.useEffect(() => {
2581
- if (error) {
2582
- toggleNotification({
2583
- type: "danger",
2584
- message: formatAPIError(error)
2585
- });
2586
- }
2587
- }, [error, formatAPIError, toggleNotification]);
2588
- const editLayout = React.useMemo(
2589
- () => data && !isLoading ? formatEditLayout(data, { schemas, schema, components }) : {
2590
- layout: [],
2591
- components: {},
2592
- metadatas: {},
2593
- options: {},
2594
- settings: DEFAULT_SETTINGS
2595
- },
2596
- [data, isLoading, schemas, schema, components]
2597
- );
2598
- const listLayout = React.useMemo(() => {
2599
- return data && !isLoading ? formatListLayout(data, { schemas, schema, components }) : {
2600
- layout: [],
2601
- metadatas: {},
2602
- options: {},
2603
- settings: DEFAULT_SETTINGS
2604
- };
2605
- }, [data, isLoading, schemas, schema, components]);
2606
- const { layout: edit } = React.useMemo(
2607
- () => runHookWaterfall(HOOKS.MUTATE_EDIT_VIEW_LAYOUT, {
2608
- layout: editLayout,
2609
- query
2610
- }),
2611
- [editLayout, query, runHookWaterfall]
2612
- );
2613
- return {
2614
- error,
2615
- isLoading,
2616
- edit,
2617
- list: listLayout
2618
- };
2619
- };
2620
- const useDocLayout = () => {
2621
- const { model } = useDoc();
2622
- return useDocumentLayout(model);
2623
- };
2624
- const formatEditLayout = (data, {
2625
- schemas,
2626
- schema,
2627
- components
2628
- }) => {
2629
- let currentPanelIndex = 0;
2630
- const panelledEditAttributes = convertEditLayoutToFieldLayouts(
2631
- data.contentType.layouts.edit,
2632
- schema?.attributes,
2633
- data.contentType.metadatas,
2634
- { configurations: data.components, schemas: components },
2635
- schemas
2636
- ).reduce((panels, row) => {
2637
- if (row.some((field) => field.type === "dynamiczone")) {
2638
- panels.push([row]);
2639
- currentPanelIndex += 2;
2640
- } else {
2641
- if (!panels[currentPanelIndex]) {
2642
- panels.push([]);
2643
- }
2644
- panels[currentPanelIndex].push(row);
2645
- }
2646
- return panels;
2647
- }, []);
2648
- const componentEditAttributes = Object.entries(data.components).reduce(
2649
- (acc, [uid, configuration]) => {
2650
- acc[uid] = {
2651
- layout: convertEditLayoutToFieldLayouts(
2652
- configuration.layouts.edit,
2653
- components[uid].attributes,
2654
- configuration.metadatas
2655
- ),
2656
- settings: {
2657
- ...configuration.settings,
2658
- icon: components[uid].info.icon,
2659
- displayName: components[uid].info.displayName
2896
+ documentId,
2897
+ model,
2898
+ collectionType,
2899
+ params: {
2900
+ locale: "*"
2901
+ }
2902
+ });
2903
+ if (!("error" in res)) {
2904
+ navigate({ pathname: `../${collectionType}/${model}` }, { replace: true });
2905
+ }
2906
+ } finally {
2907
+ if (!listViewPathMatch) {
2908
+ setSubmitting(false);
2909
+ }
2660
2910
  }
2661
- };
2662
- return acc;
2663
- },
2664
- {}
2665
- );
2666
- const editMetadatas = Object.entries(data.contentType.metadatas).reduce(
2667
- (acc, [attribute, metadata]) => {
2668
- return {
2669
- ...acc,
2670
- [attribute]: metadata.edit
2671
- };
2672
- },
2673
- {}
2674
- );
2675
- return {
2676
- layout: panelledEditAttributes,
2677
- components: componentEditAttributes,
2678
- metadatas: editMetadatas,
2679
- settings: {
2680
- ...data.contentType.settings,
2681
- displayName: schema?.info.displayName
2911
+ }
2682
2912
  },
2683
- options: {
2684
- ...schema?.options,
2685
- ...schema?.pluginOptions,
2686
- ...data.contentType.options
2687
- }
2913
+ variant: "danger",
2914
+ position: ["header", "table-row"]
2688
2915
  };
2689
2916
  };
2690
- const convertEditLayoutToFieldLayouts = (rows, attributes = {}, metadatas, components, schemas = []) => {
2691
- return rows.map(
2692
- (row) => row.map((field) => {
2693
- const attribute = attributes[field.name];
2694
- if (!attribute) {
2695
- return null;
2696
- }
2697
- const { edit: metadata } = metadatas[field.name];
2698
- const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
2699
- return {
2700
- attribute,
2701
- disabled: !metadata.editable,
2702
- hint: metadata.description,
2703
- label: metadata.label ?? "",
2704
- name: field.name,
2705
- // @ts-expect-error – mainField does exist on the metadata for a relation.
2706
- mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
2707
- schemas,
2708
- components: components?.schemas ?? {}
2709
- }),
2710
- placeholder: metadata.placeholder ?? "",
2711
- required: attribute.required ?? false,
2712
- size: field.size,
2713
- unique: "unique" in attribute ? attribute.unique : false,
2714
- visible: metadata.visible ?? true,
2715
- type: attribute.type
2716
- };
2717
- }).filter((field) => field !== null)
2718
- );
2917
+ DeleteAction$1.type = "delete";
2918
+ const DEFAULT_HEADER_ACTIONS = [EditTheModelAction, ConfigureTheViewAction, DeleteAction$1];
2919
+ const Panels = () => {
2920
+ const isCloning = useMatch(CLONE_PATH) !== null;
2921
+ const [
2922
+ {
2923
+ query: { status }
2924
+ }
2925
+ ] = useQueryParams({
2926
+ status: "draft"
2927
+ });
2928
+ const { model, id, document, meta, collectionType } = useDoc();
2929
+ const plugins = useStrapiApp("Panels", (state) => state.plugins);
2930
+ const props = {
2931
+ activeTab: status,
2932
+ model,
2933
+ documentId: id,
2934
+ document: isCloning ? void 0 : document,
2935
+ meta: isCloning ? void 0 : meta,
2936
+ collectionType
2937
+ };
2938
+ return /* @__PURE__ */ jsx(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: /* @__PURE__ */ jsx(
2939
+ DescriptionComponentRenderer,
2940
+ {
2941
+ props,
2942
+ descriptions: plugins["content-manager"].apis.getEditViewSidePanels(),
2943
+ children: (panels) => panels.map(({ content, id: id2, ...description }) => /* @__PURE__ */ jsx(Panel, { ...description, children: content }, id2))
2944
+ }
2945
+ ) });
2719
2946
  };
2720
- const formatListLayout = (data, {
2721
- schemas,
2722
- schema,
2723
- components
2724
- }) => {
2725
- const listMetadatas = Object.entries(data.contentType.metadatas).reduce(
2726
- (acc, [attribute, metadata]) => {
2727
- return {
2728
- ...acc,
2729
- [attribute]: metadata.list
2730
- };
2731
- },
2732
- {}
2733
- );
2734
- const listAttributes = convertListLayoutToFieldLayouts(
2735
- data.contentType.layouts.list,
2736
- schema?.attributes,
2737
- listMetadatas,
2738
- { configurations: data.components, schemas: components },
2739
- schemas
2740
- );
2947
+ const ActionsPanel = () => {
2948
+ const { formatMessage } = useIntl();
2741
2949
  return {
2742
- layout: listAttributes,
2743
- settings: { ...data.contentType.settings, displayName: schema?.info.displayName },
2744
- metadatas: listMetadatas,
2745
- options: {
2746
- ...schema?.options,
2747
- ...schema?.pluginOptions,
2748
- ...data.contentType.options
2749
- }
2950
+ title: formatMessage({
2951
+ id: "content-manager.containers.edit.panels.default.title",
2952
+ defaultMessage: "Entry"
2953
+ }),
2954
+ content: /* @__PURE__ */ jsx(ActionsPanelContent, {})
2750
2955
  };
2751
2956
  };
2752
- const convertListLayoutToFieldLayouts = (columns, attributes = {}, metadatas, components, schemas = []) => {
2753
- return columns.map((name) => {
2754
- const attribute = attributes[name];
2755
- if (!attribute) {
2756
- return null;
2957
+ ActionsPanel.type = "actions";
2958
+ const ActionsPanelContent = () => {
2959
+ const isCloning = useMatch(CLONE_PATH) !== null;
2960
+ const [
2961
+ {
2962
+ query: { status = "draft" }
2757
2963
  }
2758
- const metadata = metadatas[name];
2759
- const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
2760
- return {
2761
- attribute,
2762
- label: metadata.label ?? "",
2763
- mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
2764
- schemas,
2765
- components: components?.schemas ?? {}
2766
- }),
2767
- name,
2768
- searchable: metadata.searchable ?? true,
2769
- sortable: metadata.sortable ?? true
2770
- };
2771
- }).filter((field) => field !== null);
2964
+ ] = useQueryParams();
2965
+ const { model, id, document, meta, collectionType } = useDoc();
2966
+ const plugins = useStrapiApp("ActionsPanel", (state) => state.plugins);
2967
+ const props = {
2968
+ activeTab: status,
2969
+ model,
2970
+ documentId: id,
2971
+ document: isCloning ? void 0 : document,
2972
+ meta: isCloning ? void 0 : meta,
2973
+ collectionType
2974
+ };
2975
+ return /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, width: "100%", children: [
2976
+ /* @__PURE__ */ jsx(
2977
+ DescriptionComponentRenderer,
2978
+ {
2979
+ props,
2980
+ descriptions: plugins["content-manager"].apis.getDocumentActions(),
2981
+ children: (actions2) => /* @__PURE__ */ jsx(DocumentActions, { actions: actions2 })
2982
+ }
2983
+ ),
2984
+ /* @__PURE__ */ jsx(InjectionZone, { area: "editView.right-links", slug: model })
2985
+ ] });
2772
2986
  };
2987
+ const Panel = React.forwardRef(({ children, title }, ref) => {
2988
+ return /* @__PURE__ */ jsxs(
2989
+ Flex,
2990
+ {
2991
+ ref,
2992
+ tag: "aside",
2993
+ "aria-labelledby": "additional-information",
2994
+ background: "neutral0",
2995
+ borderColor: "neutral150",
2996
+ hasRadius: true,
2997
+ paddingBottom: 4,
2998
+ paddingLeft: 4,
2999
+ paddingRight: 4,
3000
+ paddingTop: 4,
3001
+ shadow: "tableShadow",
3002
+ gap: 3,
3003
+ direction: "column",
3004
+ justifyContent: "stretch",
3005
+ alignItems: "flex-start",
3006
+ children: [
3007
+ /* @__PURE__ */ jsx(Typography, { tag: "h2", variant: "sigma", textTransform: "uppercase", children: title }),
3008
+ children
3009
+ ]
3010
+ }
3011
+ );
3012
+ });
2773
3013
  const ConfirmBulkActionDialog = ({
2774
3014
  onToggleDialog,
2775
3015
  isOpen = false,
@@ -2777,7 +3017,7 @@ const ConfirmBulkActionDialog = ({
2777
3017
  endAction
2778
3018
  }) => {
2779
3019
  const { formatMessage } = useIntl();
2780
- return /* @__PURE__ */ jsx(Dialog.Root, { onOpenChange: onToggleDialog, open: isOpen, children: /* @__PURE__ */ jsxs(Dialog.Content, { children: [
3020
+ return /* @__PURE__ */ jsx(Dialog.Root, { open: isOpen, children: /* @__PURE__ */ jsxs(Dialog.Content, { children: [
2781
3021
  /* @__PURE__ */ jsx(Dialog.Header, { children: formatMessage({
2782
3022
  id: "app.components.ConfirmDialog.title",
2783
3023
  defaultMessage: "Confirmation"
@@ -2808,6 +3048,7 @@ const ConfirmDialogPublishAll = ({
2808
3048
  const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler(getTranslation);
2809
3049
  const { model, schema } = useDoc();
2810
3050
  const [{ query }] = useQueryParams();
3051
+ const enableDraftRelationsCount = false;
2811
3052
  const {
2812
3053
  data: countDraftRelations = 0,
2813
3054
  isLoading,
@@ -2819,7 +3060,7 @@ const ConfirmDialogPublishAll = ({
2819
3060
  locale: query?.plugins?.i18n?.locale
2820
3061
  },
2821
3062
  {
2822
- skip: selectedEntries.length === 0
3063
+ skip: !enableDraftRelationsCount
2823
3064
  }
2824
3065
  );
2825
3066
  React.useEffect(() => {
@@ -2898,7 +3139,14 @@ const formatErrorMessages = (errors, parentKey, formatMessage) => {
2898
3139
  )
2899
3140
  );
2900
3141
  } else {
2901
- messages.push(...formatErrorMessages(value, currentKey, formatMessage));
3142
+ messages.push(
3143
+ ...formatErrorMessages(
3144
+ // @ts-expect-error TODO: check why value is not compatible with FormErrors
3145
+ value,
3146
+ currentKey,
3147
+ formatMessage
3148
+ )
3149
+ );
2902
3150
  }
2903
3151
  } else {
2904
3152
  messages.push(
@@ -2997,7 +3245,7 @@ const SelectedEntriesTableContent = ({
2997
3245
  status: row.status
2998
3246
  }
2999
3247
  ) }),
3000
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
3248
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Flex, { children: /* @__PURE__ */ jsx(
3001
3249
  IconButton,
3002
3250
  {
3003
3251
  tag: Link,
@@ -3020,9 +3268,10 @@ const SelectedEntriesTableContent = ({
3020
3268
  ),
3021
3269
  target: "_blank",
3022
3270
  marginLeft: "auto",
3023
- children: /* @__PURE__ */ jsx(Pencil, {})
3271
+ variant: "ghost",
3272
+ children: /* @__PURE__ */ jsx(Pencil, { width: "1.6rem", height: "1.6rem" })
3024
3273
  }
3025
- ) })
3274
+ ) }) })
3026
3275
  ] }, row.id)) })
3027
3276
  ] });
3028
3277
  };
@@ -3059,7 +3308,13 @@ const SelectedEntriesModalContent = ({
3059
3308
  );
3060
3309
  const { rows, validationErrors } = React.useMemo(() => {
3061
3310
  if (data.length > 0 && schema) {
3062
- const validate = createYupSchema(schema.attributes, components);
3311
+ const validate = createYupSchema(
3312
+ schema.attributes,
3313
+ components,
3314
+ // Since this is the "Publish" action, the validation
3315
+ // schema must enforce the rules for published entities
3316
+ { status: "published" }
3317
+ );
3063
3318
  const validationErrors2 = {};
3064
3319
  const rows2 = data.map((entry) => {
3065
3320
  try {
@@ -3409,7 +3664,7 @@ const TableActions = ({ document }) => {
3409
3664
  DescriptionComponentRenderer,
3410
3665
  {
3411
3666
  props,
3412
- descriptions: plugins["content-manager"].apis.getDocumentActions(),
3667
+ descriptions: plugins["content-manager"].apis.getDocumentActions().filter((action) => action.name !== "PublishAction"),
3413
3668
  children: (actions2) => {
3414
3669
  const tableRowActions = actions2.filter((action) => {
3415
3670
  const positions = Array.isArray(action.position) ? action.position : [action.position];
@@ -3520,7 +3775,7 @@ const CloneAction = ({ model, documentId }) => {
3520
3775
  }),
3521
3776
  content: /* @__PURE__ */ jsx(AutoCloneFailureModalBody, { prohibitedFields }),
3522
3777
  footer: ({ onClose }) => {
3523
- return /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", children: [
3778
+ return /* @__PURE__ */ jsxs(Modal.Footer, { children: [
3524
3779
  /* @__PURE__ */ jsx(Button, { onClick: onClose, variant: "tertiary", children: formatMessage({
3525
3780
  id: "cancel",
3526
3781
  defaultMessage: "Cancel"
@@ -3561,8 +3816,7 @@ class ContentManagerPlugin {
3561
3816
  documentActions = [
3562
3817
  ...DEFAULT_ACTIONS,
3563
3818
  ...DEFAULT_TABLE_ROW_ACTIONS,
3564
- ...DEFAULT_HEADER_ACTIONS,
3565
- HistoryAction
3819
+ ...DEFAULT_HEADER_ACTIONS
3566
3820
  ];
3567
3821
  editViewSidePanels = [ActionsPanel];
3568
3822
  headerActions = [];
@@ -3651,6 +3905,52 @@ const getPrintableType = (value) => {
3651
3905
  }
3652
3906
  return nativeType;
3653
3907
  };
3908
+ const HistoryAction = ({ model, document }) => {
3909
+ const { formatMessage } = useIntl();
3910
+ const [{ query }] = useQueryParams();
3911
+ const navigate = useNavigate();
3912
+ const pluginsQueryParams = stringify({ plugins: query.plugins }, { encode: false });
3913
+ if (!window.strapi.features.isEnabled("cms-content-history")) {
3914
+ return null;
3915
+ }
3916
+ return {
3917
+ icon: /* @__PURE__ */ jsx(ClockCounterClockwise, {}),
3918
+ label: formatMessage({
3919
+ id: "content-manager.history.document-action",
3920
+ defaultMessage: "Content History"
3921
+ }),
3922
+ onClick: () => navigate({ pathname: "history", search: pluginsQueryParams }),
3923
+ disabled: (
3924
+ /**
3925
+ * The user is creating a new document.
3926
+ * It hasn't been saved yet, so there's no history to go to
3927
+ */
3928
+ !document || /**
3929
+ * The document has been created but the current dimension has never been saved.
3930
+ * For example, the user is creating a new locale in an existing document,
3931
+ * so there's no history for the document in that locale
3932
+ */
3933
+ !document.id || /**
3934
+ * History is only available for content types created by the user.
3935
+ * These have the `api::` prefix, as opposed to the ones created by Strapi or plugins,
3936
+ * which start with `admin::` or `plugin::`
3937
+ */
3938
+ !model.startsWith("api::")
3939
+ ),
3940
+ position: "header"
3941
+ };
3942
+ };
3943
+ HistoryAction.type = "history";
3944
+ const historyAdmin = {
3945
+ bootstrap(app) {
3946
+ const { addDocumentAction } = app.getPlugin("content-manager").apis;
3947
+ addDocumentAction((actions2) => {
3948
+ const indexOfDeleteAction = actions2.findIndex((action) => action.type === "delete");
3949
+ actions2.splice(indexOfDeleteAction, 0, HistoryAction);
3950
+ return actions2;
3951
+ });
3952
+ }
3953
+ };
3654
3954
  const initialState = {
3655
3955
  collectionTypeLinks: [],
3656
3956
  components: [],
@@ -3706,7 +4006,7 @@ const index = {
3706
4006
  app.router.addRoute({
3707
4007
  path: "content-manager/*",
3708
4008
  lazy: async () => {
3709
- const { Layout } = await import("./layout-Bau7ZfLV.mjs");
4009
+ const { Layout } = await import("./layout-vzKSrr7p.mjs");
3710
4010
  return {
3711
4011
  Component: Layout
3712
4012
  };
@@ -3715,10 +4015,15 @@ const index = {
3715
4015
  });
3716
4016
  app.registerPlugin(cm.config);
3717
4017
  },
4018
+ bootstrap(app) {
4019
+ if (typeof historyAdmin.bootstrap === "function") {
4020
+ historyAdmin.bootstrap(app);
4021
+ }
4022
+ },
3718
4023
  async registerTrads({ locales }) {
3719
4024
  const importedTrads = await Promise.all(
3720
4025
  locales.map((locale) => {
3721
- 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-Ux26r5pl.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 }) => {
4026
+ 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 }) => {
3722
4027
  return {
3723
4028
  data: prefixPluginTranslations(data, PLUGIN_ID),
3724
4029
  locale
@@ -3739,13 +4044,15 @@ export {
3739
4044
  BulkActionsRenderer as B,
3740
4045
  COLLECTION_TYPES as C,
3741
4046
  DocumentStatus as D,
3742
- DEFAULT_SETTINGS as E,
3743
- convertEditLayoutToFieldLayouts as F,
3744
- useDocument as G,
4047
+ extractContentTypeComponents as E,
4048
+ DEFAULT_SETTINGS as F,
4049
+ convertEditLayoutToFieldLayouts as G,
3745
4050
  HOOKS as H,
3746
4051
  InjectionZone as I,
3747
- index as J,
3748
- useDocumentActions as K,
4052
+ useDocument as J,
4053
+ index as K,
4054
+ useContentManagerContext as L,
4055
+ useDocumentActions as M,
3749
4056
  Panels as P,
3750
4057
  RelativeTime as R,
3751
4058
  SINGLE_TYPES as S,
@@ -3763,18 +4070,18 @@ export {
3763
4070
  PERMISSIONS as k,
3764
4071
  DocumentRBAC as l,
3765
4072
  DOCUMENT_META_FIELDS as m,
3766
- useDocLayout as n,
3767
- useGetContentTypeConfigurationQuery as o,
3768
- CREATOR_FIELDS as p,
3769
- getMainField as q,
3770
- getDisplayName as r,
4073
+ CLONE_PATH as n,
4074
+ useDocLayout as o,
4075
+ useGetContentTypeConfigurationQuery as p,
4076
+ CREATOR_FIELDS as q,
4077
+ getMainField as r,
3771
4078
  setInitialData as s,
3772
- checkIfAttributeIsDisplayable as t,
4079
+ getDisplayName as t,
3773
4080
  useContentTypeSchema as u,
3774
- useGetAllDocumentsQuery as v,
3775
- convertListLayoutToFieldLayouts as w,
3776
- capitalise as x,
3777
- useUpdateContentTypeConfigurationMutation as y,
3778
- extractContentTypeComponents as z
4081
+ checkIfAttributeIsDisplayable as v,
4082
+ useGetAllDocumentsQuery as w,
4083
+ convertListLayoutToFieldLayouts as x,
4084
+ capitalise as y,
4085
+ useUpdateContentTypeConfigurationMutation as z
3779
4086
  };
3780
- //# sourceMappingURL=index-DJXJw9V5.mjs.map
4087
+ //# sourceMappingURL=index-CJ2vYwuT.mjs.map