@strapi/content-manager 0.0.0-experimental.62ce06180fe9a772eaeb3d43d238b26644f39f7c → 0.0.0-experimental.65b9961ce81496e349024ceb95be1d5946f2c429

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 (135) hide show
  1. package/LICENSE +18 -3
  2. package/dist/_chunks/{ComponentConfigurationPage-Cl7eB3s4.js → ComponentConfigurationPage-CO977CPh.js} +4 -4
  3. package/dist/_chunks/{ComponentConfigurationPage-Cl7eB3s4.js.map → ComponentConfigurationPage-CO977CPh.js.map} +1 -1
  4. package/dist/_chunks/{ComponentConfigurationPage-DErJQEVW.mjs → ComponentConfigurationPage-CQroR9Qk.mjs} +4 -4
  5. package/dist/_chunks/{ComponentConfigurationPage-DErJQEVW.mjs.map → ComponentConfigurationPage-CQroR9Qk.mjs.map} +1 -1
  6. package/dist/_chunks/{EditConfigurationPage-CyfFvH6-.js → EditConfigurationPage-BPgoE-kf.js} +4 -4
  7. package/dist/_chunks/{EditConfigurationPage-CyfFvH6-.js.map → EditConfigurationPage-BPgoE-kf.js.map} +1 -1
  8. package/dist/_chunks/{EditConfigurationPage-CBosWqQ7.mjs → EditConfigurationPage-tVCJ5vWC.mjs} +4 -4
  9. package/dist/_chunks/{EditConfigurationPage-CBosWqQ7.mjs.map → EditConfigurationPage-tVCJ5vWC.mjs.map} +1 -1
  10. package/dist/_chunks/{EditViewPage-ClIueJnM.mjs → EditViewPage-8mOu02ji.mjs} +30 -9
  11. package/dist/_chunks/EditViewPage-8mOu02ji.mjs.map +1 -0
  12. package/dist/_chunks/{EditViewPage-DxyAOItK.js → EditViewPage-BMVgUNOX.js} +30 -9
  13. package/dist/_chunks/EditViewPage-BMVgUNOX.js.map +1 -0
  14. package/dist/_chunks/{Field-BZBYmvaf.mjs → Field-CJPYzwD7.mjs} +518 -154
  15. package/dist/_chunks/Field-CJPYzwD7.mjs.map +1 -0
  16. package/dist/_chunks/{Field-C0Y_SR9e.js → Field-CdSLKFQk.js} +520 -156
  17. package/dist/_chunks/Field-CdSLKFQk.js.map +1 -0
  18. package/dist/_chunks/{Form-jwRSC2kV.mjs → Form-DJOJ-GF1.mjs} +36 -17
  19. package/dist/_chunks/Form-DJOJ-GF1.mjs.map +1 -0
  20. package/dist/_chunks/{Form-DwvGnISS.js → Form-eP5bZwap.js} +36 -17
  21. package/dist/_chunks/Form-eP5bZwap.js.map +1 -0
  22. package/dist/_chunks/{History-Cda0Yjzz.js → History-B-Mrquzu.js} +63 -25
  23. package/dist/_chunks/History-B-Mrquzu.js.map +1 -0
  24. package/dist/_chunks/{History-BgzAIj0G.mjs → History-MnQLtk1g.mjs} +64 -26
  25. package/dist/_chunks/History-MnQLtk1g.mjs.map +1 -0
  26. package/dist/_chunks/{ListConfigurationPage-GH55qfoT.mjs → ListConfigurationPage-BcycI8Lw.mjs} +21 -9
  27. package/dist/_chunks/ListConfigurationPage-BcycI8Lw.mjs.map +1 -0
  28. package/dist/_chunks/{ListConfigurationPage-C29EF97r.js → ListConfigurationPage-C0n4rUzH.js} +21 -9
  29. package/dist/_chunks/ListConfigurationPage-C0n4rUzH.js.map +1 -0
  30. package/dist/_chunks/{ListViewPage-QU03PFj1.mjs → ListViewPage-CRXONXwZ.mjs} +59 -41
  31. package/dist/_chunks/ListViewPage-CRXONXwZ.mjs.map +1 -0
  32. package/dist/_chunks/{ListViewPage-CnRt0UT7.js → ListViewPage-q0SHVPUS.js} +61 -43
  33. package/dist/_chunks/ListViewPage-q0SHVPUS.js.map +1 -0
  34. package/dist/_chunks/{NoContentTypePage-DFDjxByI.js → NoContentTypePage-Bh3komDV.js} +2 -2
  35. package/dist/_chunks/{NoContentTypePage-DFDjxByI.js.map → NoContentTypePage-Bh3komDV.js.map} +1 -1
  36. package/dist/_chunks/{NoContentTypePage-CPs2CnzH.mjs → NoContentTypePage-ukzFRF3z.mjs} +2 -2
  37. package/dist/_chunks/{NoContentTypePage-CPs2CnzH.mjs.map → NoContentTypePage-ukzFRF3z.mjs.map} +1 -1
  38. package/dist/_chunks/{NoPermissionsPage-ct58lcY0.mjs → NoPermissionsPage-B4sD7Ble.mjs} +2 -2
  39. package/dist/_chunks/{NoPermissionsPage-ct58lcY0.mjs.map → NoPermissionsPage-B4sD7Ble.mjs.map} +1 -1
  40. package/dist/_chunks/{NoPermissionsPage-BVHI-jv5.js → NoPermissionsPage-BGBpj_Y1.js} +2 -2
  41. package/dist/_chunks/{NoPermissionsPage-BVHI-jv5.js.map → NoPermissionsPage-BGBpj_Y1.js.map} +1 -1
  42. package/dist/_chunks/{Relations-BjpPPCKp.js → Relations-B53wYe8g.js} +33 -24
  43. package/dist/_chunks/Relations-B53wYe8g.js.map +1 -0
  44. package/dist/_chunks/{Relations-KMf5qEN0.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-6kKXK7y8.mjs → index-CJ2vYwuT.mjs} +992 -689
  51. package/dist/_chunks/index-CJ2vYwuT.mjs.map +1 -0
  52. package/dist/_chunks/{index-D9ZwczCV.js → index-DbT2sx-Q.js} +984 -681
  53. package/dist/_chunks/index-DbT2sx-Q.js.map +1 -0
  54. package/dist/_chunks/{layout-BJfBoBiF.js → layout-CeBSIkmP.js} +25 -12
  55. package/dist/_chunks/layout-CeBSIkmP.js.map +1 -0
  56. package/dist/_chunks/{layout-B1Z-9koY.mjs → layout-vzKSrr7p.mjs} +27 -14
  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-CMvjzyU3.js → relations-Cl-6t9iz.js} +2 -2
  63. package/dist/_chunks/{relations-CMvjzyU3.js.map → relations-Cl-6t9iz.js.map} +1 -1
  64. package/dist/_chunks/{relations-CgZg7Pyx.mjs → relations-DI0lguF0.mjs} +2 -2
  65. package/dist/_chunks/{relations-CgZg7Pyx.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 +3 -2
  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/BlocksInput/utils/constants.d.ts +4 -0
  80. package/dist/admin/src/pages/EditView/components/FormInputs/Relations.d.ts +20 -0
  81. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/EditorLayout.d.ts +2 -2
  82. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/WysiwygFooter.d.ts +2 -2
  83. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/WysiwygStyles.d.ts +4 -48
  84. package/dist/admin/src/pages/EditView/components/Header.d.ts +11 -11
  85. package/dist/admin/src/services/api.d.ts +1 -1
  86. package/dist/admin/src/services/components.d.ts +2 -2
  87. package/dist/admin/src/services/contentTypes.d.ts +3 -3
  88. package/dist/admin/src/services/documents.d.ts +19 -17
  89. package/dist/admin/src/services/init.d.ts +1 -1
  90. package/dist/admin/src/services/relations.d.ts +2 -2
  91. package/dist/admin/src/services/uid.d.ts +3 -3
  92. package/dist/admin/src/utils/validation.d.ts +4 -1
  93. package/dist/server/index.js +205 -118
  94. package/dist/server/index.js.map +1 -1
  95. package/dist/server/index.mjs +206 -119
  96. package/dist/server/index.mjs.map +1 -1
  97. package/dist/server/src/controllers/collection-types.d.ts.map +1 -1
  98. package/dist/server/src/controllers/relations.d.ts.map +1 -1
  99. package/dist/server/src/controllers/uid.d.ts.map +1 -1
  100. package/dist/server/src/controllers/validation/dimensions.d.ts +4 -2
  101. package/dist/server/src/controllers/validation/dimensions.d.ts.map +1 -1
  102. package/dist/server/src/history/services/history.d.ts.map +1 -1
  103. package/dist/server/src/history/services/lifecycles.d.ts.map +1 -1
  104. package/dist/server/src/history/services/utils.d.ts +2 -1
  105. package/dist/server/src/history/services/utils.d.ts.map +1 -1
  106. package/dist/server/src/policies/hasPermissions.d.ts.map +1 -1
  107. package/dist/server/src/services/document-manager.d.ts.map +1 -1
  108. package/dist/server/src/services/document-metadata.d.ts.map +1 -1
  109. package/dist/server/src/services/permission-checker.d.ts.map +1 -1
  110. package/dist/server/src/services/utils/populate.d.ts.map +1 -1
  111. package/dist/shared/contracts/collection-types.d.ts +3 -1
  112. package/dist/shared/contracts/collection-types.d.ts.map +1 -1
  113. package/package.json +12 -12
  114. package/dist/_chunks/EditViewPage-ClIueJnM.mjs.map +0 -1
  115. package/dist/_chunks/EditViewPage-DxyAOItK.js.map +0 -1
  116. package/dist/_chunks/Field-BZBYmvaf.mjs.map +0 -1
  117. package/dist/_chunks/Field-C0Y_SR9e.js.map +0 -1
  118. package/dist/_chunks/Form-DwvGnISS.js.map +0 -1
  119. package/dist/_chunks/Form-jwRSC2kV.mjs.map +0 -1
  120. package/dist/_chunks/History-BgzAIj0G.mjs.map +0 -1
  121. package/dist/_chunks/History-Cda0Yjzz.js.map +0 -1
  122. package/dist/_chunks/ListConfigurationPage-C29EF97r.js.map +0 -1
  123. package/dist/_chunks/ListConfigurationPage-GH55qfoT.mjs.map +0 -1
  124. package/dist/_chunks/ListViewPage-CnRt0UT7.js.map +0 -1
  125. package/dist/_chunks/ListViewPage-QU03PFj1.mjs.map +0 -1
  126. package/dist/_chunks/Relations-BjpPPCKp.js.map +0 -1
  127. package/dist/_chunks/Relations-KMf5qEN0.mjs.map +0 -1
  128. package/dist/_chunks/index-6kKXK7y8.mjs.map +0 -1
  129. package/dist/_chunks/index-D9ZwczCV.js.map +0 -1
  130. package/dist/_chunks/layout-B1Z-9koY.mjs.map +0 -1
  131. package/dist/_chunks/layout-BJfBoBiF.js.map +0 -1
  132. package/dist/_chunks/usePrev-B9w_-eYc.js.map +0 -1
  133. package/dist/_chunks/usePrev-DH6iah0A.mjs +0 -16
  134. package/dist/_chunks/usePrev-DH6iah0A.mjs.map +0 -1
  135. 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) : [];
@@ -195,8 +167,7 @@ const contentManagerApi = adminApi.enhanceEndpoints({
195
167
  "InitialData",
196
168
  "HistoryVersion",
197
169
  "Relations",
198
- "Release",
199
- "ReleaseAction"
170
+ "UidAvailability"
200
171
  ]
201
172
  });
202
173
  const documentApi = contentManagerApi.injectEndpoints({
@@ -210,7 +181,12 @@ const documentApi = contentManagerApi.injectEndpoints({
210
181
  params: query
211
182
  }
212
183
  }),
213
- 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
+ }
214
190
  }),
215
191
  cloneDocument: builder.mutation({
216
192
  query: ({ model, sourceId, data, params }) => ({
@@ -221,7 +197,10 @@ const documentApi = contentManagerApi.injectEndpoints({
221
197
  params
222
198
  }
223
199
  }),
224
- 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
+ ]
225
204
  }),
226
205
  /**
227
206
  * Creates a new collection-type document. This should ONLY be used for collection-types.
@@ -238,7 +217,8 @@ const documentApi = contentManagerApi.injectEndpoints({
238
217
  }),
239
218
  invalidatesTags: (result, _error, { model }) => [
240
219
  { type: "Document", id: `${model}_LIST` },
241
- "Relations"
220
+ "Relations",
221
+ { type: "UidAvailability", id: model }
242
222
  ]
243
223
  }),
244
224
  deleteDocument: builder.mutation({
@@ -250,9 +230,7 @@ const documentApi = contentManagerApi.injectEndpoints({
250
230
  }
251
231
  }),
252
232
  invalidatesTags: (_result, _error, { collectionType, model }) => [
253
- { type: "Document", id: collectionType !== SINGLE_TYPES ? `${model}_LIST` : model },
254
- { type: "Release", id: "LIST" },
255
- { type: "ReleaseAction", id: "LIST" }
233
+ { type: "Document", id: collectionType !== SINGLE_TYPES ? `${model}_LIST` : model }
256
234
  ]
257
235
  }),
258
236
  deleteManyDocuments: builder.mutation({
@@ -264,11 +242,7 @@ const documentApi = contentManagerApi.injectEndpoints({
264
242
  params
265
243
  }
266
244
  }),
267
- invalidatesTags: (_res, _error, { model }) => [
268
- { type: "Document", id: `${model}_LIST` },
269
- { type: "Release", id: "LIST" },
270
- { type: "ReleaseAction", id: "LIST" }
271
- ]
245
+ invalidatesTags: (_res, _error, { model }) => [{ type: "Document", id: `${model}_LIST` }]
272
246
  }),
273
247
  discardDocument: builder.mutation({
274
248
  query: ({ collectionType, model, documentId, params }) => ({
@@ -286,8 +260,7 @@ const documentApi = contentManagerApi.injectEndpoints({
286
260
  },
287
261
  { type: "Document", id: `${model}_LIST` },
288
262
  "Relations",
289
- { type: "Release", id: "LIST" },
290
- { type: "ReleaseAction", id: "LIST" }
263
+ { type: "UidAvailability", id: model }
291
264
  ];
292
265
  }
293
266
  }),
@@ -305,6 +278,7 @@ const documentApi = contentManagerApi.injectEndpoints({
305
278
  }),
306
279
  providesTags: (result, _error, arg) => {
307
280
  return [
281
+ { type: "Document", id: `ALL_LIST` },
308
282
  { type: "Document", id: `${arg.model}_LIST` },
309
283
  ...result?.results.map(({ documentId }) => ({
310
284
  type: "Document",
@@ -343,6 +317,11 @@ const documentApi = contentManagerApi.injectEndpoints({
343
317
  {
344
318
  type: "Document",
345
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`
346
325
  }
347
326
  ];
348
327
  }
@@ -407,9 +386,20 @@ const documentApi = contentManagerApi.injectEndpoints({
407
386
  id: collectionType !== SINGLE_TYPES ? `${model}_${documentId}` : model
408
387
  },
409
388
  "Relations",
410
- { type: "Release", id: "LIST" },
411
- { type: "ReleaseAction", id: "LIST" }
389
+ { type: "UidAvailability", id: model }
412
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
+ }
413
403
  }
414
404
  }),
415
405
  unpublishDocument: builder.mutation({
@@ -479,20 +469,39 @@ const buildValidParams = (query) => {
479
469
  const isBaseQueryError = (error) => {
480
470
  return error.name !== void 0;
481
471
  };
482
- 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 }) => {
483
491
  const createModelSchema = (attributes2) => yup.object().shape(
484
492
  Object.entries(attributes2).reduce((acc, [name, attribute]) => {
485
493
  if (DOCUMENT_META_FIELDS.includes(name)) {
486
494
  return acc;
487
495
  }
488
496
  const validations = [
497
+ addNullableValidation,
489
498
  addRequiredValidation,
490
499
  addMinLengthValidation,
491
500
  addMaxLengthValidation,
492
501
  addMinValidation,
493
502
  addMaxValidation,
494
503
  addRegexValidation
495
- ].map((fn) => fn(attribute));
504
+ ].map((fn) => fn(attribute, options));
496
505
  const transformSchema = pipe(...validations);
497
506
  switch (attribute.type) {
498
507
  case "component": {
@@ -502,12 +511,12 @@ const createYupSchema = (attributes = {}, components = {}) => {
502
511
  ...acc,
503
512
  [name]: transformSchema(
504
513
  yup.array().of(createModelSchema(attributes3).nullable(false))
505
- )
514
+ ).test(arrayValidator(attribute, options))
506
515
  };
507
516
  } else {
508
517
  return {
509
518
  ...acc,
510
- [name]: transformSchema(createModelSchema(attributes3))
519
+ [name]: transformSchema(createModelSchema(attributes3).nullable())
511
520
  };
512
521
  }
513
522
  }
@@ -529,7 +538,7 @@ const createYupSchema = (attributes = {}, components = {}) => {
529
538
  }
530
539
  )
531
540
  )
532
- )
541
+ ).test(arrayValidator(attribute, options))
533
542
  };
534
543
  case "relation":
535
544
  return {
@@ -541,7 +550,7 @@ const createYupSchema = (attributes = {}, components = {}) => {
541
550
  } else if (Array.isArray(value)) {
542
551
  return yup.array().of(
543
552
  yup.object().shape({
544
- id: yup.string().required()
553
+ id: yup.number().required()
545
554
  })
546
555
  );
547
556
  } else if (typeof value === "object") {
@@ -593,6 +602,14 @@ const createAttributeSchema = (attribute) => {
593
602
  if (!value || typeof value === "string" && value.length === 0) {
594
603
  return true;
595
604
  }
605
+ if (typeof value === "object") {
606
+ try {
607
+ JSON.stringify(value);
608
+ return true;
609
+ } catch (err) {
610
+ return false;
611
+ }
612
+ }
596
613
  try {
597
614
  JSON.parse(value);
598
615
  return true;
@@ -611,13 +628,7 @@ const createAttributeSchema = (attribute) => {
611
628
  return yup.mixed();
612
629
  }
613
630
  };
614
- const addRequiredValidation = (attribute) => (schema) => {
615
- if (attribute.required) {
616
- return schema.required({
617
- id: translatedErrors.required.id,
618
- defaultMessage: "This field is required."
619
- });
620
- }
631
+ const nullableSchema = (schema) => {
621
632
  return schema?.nullable ? schema.nullable() : (
622
633
  // In some cases '.nullable' will not be available on the schema.
623
634
  // e.g. when the schema has been built using yup.lazy (e.g. for relations).
@@ -625,7 +636,22 @@ const addRequiredValidation = (attribute) => (schema) => {
625
636
  schema
626
637
  );
627
638
  };
628
- 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
+ }
629
655
  if ("minLength" in attribute && attribute.minLength && Number.isInteger(attribute.minLength) && "min" in schema) {
630
656
  return schema.min(attribute.minLength, {
631
657
  ...translatedErrors.minLength,
@@ -647,10 +673,13 @@ const addMaxLengthValidation = (attribute) => (schema) => {
647
673
  }
648
674
  return schema;
649
675
  };
650
- const addMinValidation = (attribute) => (schema) => {
651
- 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) {
652
681
  const min = toInteger(attribute.min);
653
- if ("min" in schema && min) {
682
+ if (min) {
654
683
  return schema.min(min, {
655
684
  ...translatedErrors.min,
656
685
  values: {
@@ -768,16 +797,115 @@ const extractContentTypeComponents = (attributes = {}, allComponents = {}) => {
768
797
  }, {});
769
798
  return componentsByKey;
770
799
  };
771
- 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);
772
899
  const { toggleNotification } = useNotification();
773
900
  const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
901
+ const { isLoading: isLoadingSchemas, schemas } = useContentTypeSchema();
774
902
  const {
775
- currentData: data,
776
- isLoading: isLoadingDocument,
777
- isFetching: isFetchingDocument,
778
- error
779
- } = useGetDocumentQuery(args, opts);
780
- 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;
781
909
  React.useEffect(() => {
782
910
  if (error) {
783
911
  toggleNotification({
@@ -785,68 +913,321 @@ const useDocument = (args, opts) => {
785
913
  message: formatAPIError(error)
786
914
  });
787
915
  }
788
- }, [toggleNotification, error, formatAPIError, args.collectionType]);
789
- const validationSchema = React.useMemo(() => {
790
- if (!schema) {
791
- return null;
792
- }
793
- return createYupSchema(schema.attributes, components);
794
- }, [schema, components]);
795
- const validate = React.useCallback(
796
- (document) => {
797
- if (!validationSchema) {
798
- throw new Error(
799
- "There is no validation schema generated, this is likely due to the schema not being loaded yet."
800
- );
801
- }
802
- try {
803
- validationSchema.validateSync(document, { abortEarly: false, strict: true });
804
- return null;
805
- } catch (error2) {
806
- if (error2 instanceof ValidationError) {
807
- return getYupValidationErrors(error2);
808
- }
809
- throw error2;
810
- }
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
811
924
  },
812
- [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]
813
941
  );
814
- const isLoading = isLoadingDocument || isFetchingDocument || isLoadingSchema;
815
942
  return {
816
- components,
817
- document: data?.data,
818
- meta: data?.meta,
943
+ error,
819
944
  isLoading,
820
- schema,
821
- validate
945
+ edit,
946
+ list: listLayout
822
947
  };
823
948
  };
824
- const useDoc = () => {
825
- const { id, slug, collectionType, origin } = useParams();
826
- const [{ query }] = useQueryParams();
827
- const params = React.useMemo(() => buildValidParams(query), [query]);
828
- if (!collectionType) {
829
- throw new Error("Could not find collectionType in url params");
830
- }
831
- if (!slug) {
832
- throw new Error("Could not find model in url params");
833
- }
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;
834
1184
  return {
835
1185
  collectionType,
836
1186
  model: slug,
837
- id: origin || id === "create" ? void 0 : id,
838
- ...useDocument(
839
- { documentId: origin || id, model: slug, collectionType, params },
840
- {
841
- skip: id === "create" || !origin && !id && collectionType !== SINGLE_TYPES
842
- }
843
- )
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
844
1228
  };
845
1229
  };
846
1230
  const prefixPluginTranslations = (trad, pluginId) => {
847
- if (!pluginId) {
848
- throw new TypeError("pluginId can't be empty");
849
- }
850
1231
  return Object.keys(trad).reduce((acc, current) => {
851
1232
  acc[`${pluginId}.${current}`] = trad[current];
852
1233
  return acc;
@@ -862,6 +1243,8 @@ const useDocumentActions = () => {
862
1243
  const { formatMessage } = useIntl();
863
1244
  const { trackUsage } = useTracking();
864
1245
  const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
1246
+ const navigate = useNavigate();
1247
+ const setCurrentStep = useGuidedTour("useDocumentActions", (state) => state.setCurrentStep);
865
1248
  const [deleteDocument] = useDeleteDocumentMutation();
866
1249
  const _delete = React.useCallback(
867
1250
  async ({ collectionType, model, documentId, params }, trackerProperty) => {
@@ -1176,6 +1559,7 @@ const useDocumentActions = () => {
1176
1559
  defaultMessage: "Saved document"
1177
1560
  })
1178
1561
  });
1562
+ setCurrentStep("contentManager.success");
1179
1563
  return res.data;
1180
1564
  } catch (err) {
1181
1565
  toggleNotification({
@@ -1197,7 +1581,6 @@ const useDocumentActions = () => {
1197
1581
  sourceId
1198
1582
  });
1199
1583
  if ("error" in res) {
1200
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1201
1584
  return { error: res.error };
1202
1585
  }
1203
1586
  toggleNotification({
@@ -1216,7 +1599,7 @@ const useDocumentActions = () => {
1216
1599
  throw err;
1217
1600
  }
1218
1601
  },
1219
- [autoCloneDocument, formatAPIError, formatMessage, toggleNotification]
1602
+ [autoCloneDocument, formatMessage, toggleNotification]
1220
1603
  );
1221
1604
  const [cloneDocument] = useCloneDocumentMutation();
1222
1605
  const clone = React.useCallback(
@@ -1242,6 +1625,7 @@ const useDocumentActions = () => {
1242
1625
  defaultMessage: "Cloned document"
1243
1626
  })
1244
1627
  });
1628
+ navigate(`../../${res.data.data.documentId}`, { relative: "path" });
1245
1629
  return res.data;
1246
1630
  } catch (err) {
1247
1631
  toggleNotification({
@@ -1252,7 +1636,7 @@ const useDocumentActions = () => {
1252
1636
  throw err;
1253
1637
  }
1254
1638
  },
1255
- [cloneDocument, trackUsage, toggleNotification, formatMessage, formatAPIError]
1639
+ [cloneDocument, trackUsage, toggleNotification, formatMessage, formatAPIError, navigate]
1256
1640
  );
1257
1641
  const [getDoc] = useLazyGetDocumentQuery();
1258
1642
  const getDocument = React.useCallback(
@@ -1278,7 +1662,7 @@ const useDocumentActions = () => {
1278
1662
  };
1279
1663
  };
1280
1664
  const ProtectedHistoryPage = lazy(
1281
- () => import("./History-BgzAIj0G.mjs").then((mod) => ({ default: mod.ProtectedHistoryPage }))
1665
+ () => import("./History-MnQLtk1g.mjs").then((mod) => ({ default: mod.ProtectedHistoryPage }))
1282
1666
  );
1283
1667
  const routes$1 = [
1284
1668
  {
@@ -1291,31 +1675,31 @@ const routes$1 = [
1291
1675
  }
1292
1676
  ];
1293
1677
  const ProtectedEditViewPage = lazy(
1294
- () => import("./EditViewPage-ClIueJnM.mjs").then((mod) => ({ default: mod.ProtectedEditViewPage }))
1678
+ () => import("./EditViewPage-8mOu02ji.mjs").then((mod) => ({ default: mod.ProtectedEditViewPage }))
1295
1679
  );
1296
1680
  const ProtectedListViewPage = lazy(
1297
- () => import("./ListViewPage-QU03PFj1.mjs").then((mod) => ({ default: mod.ProtectedListViewPage }))
1681
+ () => import("./ListViewPage-CRXONXwZ.mjs").then((mod) => ({ default: mod.ProtectedListViewPage }))
1298
1682
  );
1299
1683
  const ProtectedListConfiguration = lazy(
1300
- () => import("./ListConfigurationPage-GH55qfoT.mjs").then((mod) => ({
1684
+ () => import("./ListConfigurationPage-BcycI8Lw.mjs").then((mod) => ({
1301
1685
  default: mod.ProtectedListConfiguration
1302
1686
  }))
1303
1687
  );
1304
1688
  const ProtectedEditConfigurationPage = lazy(
1305
- () => import("./EditConfigurationPage-CBosWqQ7.mjs").then((mod) => ({
1689
+ () => import("./EditConfigurationPage-tVCJ5vWC.mjs").then((mod) => ({
1306
1690
  default: mod.ProtectedEditConfigurationPage
1307
1691
  }))
1308
1692
  );
1309
1693
  const ProtectedComponentConfigurationPage = lazy(
1310
- () => import("./ComponentConfigurationPage-DErJQEVW.mjs").then((mod) => ({
1694
+ () => import("./ComponentConfigurationPage-CQroR9Qk.mjs").then((mod) => ({
1311
1695
  default: mod.ProtectedComponentConfigurationPage
1312
1696
  }))
1313
1697
  );
1314
1698
  const NoPermissions = lazy(
1315
- () => import("./NoPermissionsPage-ct58lcY0.mjs").then((mod) => ({ default: mod.NoPermissions }))
1699
+ () => import("./NoPermissionsPage-B4sD7Ble.mjs").then((mod) => ({ default: mod.NoPermissions }))
1316
1700
  );
1317
1701
  const NoContentType = lazy(
1318
- () => import("./NoContentTypePage-CPs2CnzH.mjs").then((mod) => ({ default: mod.NoContentType }))
1702
+ () => import("./NoContentTypePage-ukzFRF3z.mjs").then((mod) => ({ default: mod.NoContentType }))
1319
1703
  );
1320
1704
  const CollectionTypePages = () => {
1321
1705
  const { collectionType } = useParams();
@@ -1429,12 +1813,14 @@ const DocumentActionButton = (action) => {
1429
1813
  /* @__PURE__ */ jsx(
1430
1814
  Button,
1431
1815
  {
1432
- flex: 1,
1816
+ flex: "auto",
1433
1817
  startIcon: action.icon,
1434
1818
  disabled: action.disabled,
1435
1819
  onClick: handleClick(action),
1436
1820
  justifyContent: "center",
1437
1821
  variant: action.variant || "default",
1822
+ paddingTop: "7px",
1823
+ paddingBottom: "7px",
1438
1824
  children: action.label
1439
1825
  }
1440
1826
  ),
@@ -1442,7 +1828,7 @@ const DocumentActionButton = (action) => {
1442
1828
  DocumentActionConfirmDialog,
1443
1829
  {
1444
1830
  ...action.dialog,
1445
- variant: action.variant,
1831
+ variant: action.dialog?.variant ?? action.variant,
1446
1832
  isOpen: dialogId === action.id,
1447
1833
  onClose: handleClose
1448
1834
  }
@@ -1499,9 +1885,9 @@ const DocumentActionsMenu = ({
1499
1885
  disabled: isDisabled,
1500
1886
  size: "S",
1501
1887
  endIcon: null,
1502
- paddingTop: "7px",
1503
- paddingLeft: "9px",
1504
- paddingRight: "9px",
1888
+ paddingTop: "4px",
1889
+ paddingLeft: "7px",
1890
+ paddingRight: "7px",
1505
1891
  variant,
1506
1892
  children: [
1507
1893
  /* @__PURE__ */ jsx(More, { "aria-hidden": true, focusable: false }),
@@ -1512,7 +1898,7 @@ const DocumentActionsMenu = ({
1512
1898
  ]
1513
1899
  }
1514
1900
  ),
1515
- /* @__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: [
1516
1902
  actions2.map((action) => {
1517
1903
  return /* @__PURE__ */ jsx(
1518
1904
  Menu.Item,
@@ -1521,10 +1907,25 @@ const DocumentActionsMenu = ({
1521
1907
  onSelect: handleClick(action),
1522
1908
  display: "block",
1523
1909
  children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", gap: 4, children: [
1524
- /* @__PURE__ */ jsxs(Flex, { color: convertActionVariantToColor(action.variant), gap: 2, tag: "span", children: [
1525
- /* @__PURE__ */ jsx(Box, { tag: "span", color: convertActionVariantToIconColor(action.variant), children: action.icon }),
1526
- action.label
1527
- ] }),
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
+ ),
1528
1929
  action.id.startsWith("HistoryAction") && /* @__PURE__ */ jsx(
1529
1930
  Flex,
1530
1931
  {
@@ -1621,11 +2022,11 @@ const DocumentActionConfirmDialog = ({
1621
2022
  /* @__PURE__ */ jsx(Dialog.Header, { children: title }),
1622
2023
  /* @__PURE__ */ jsx(Dialog.Body, { children: content }),
1623
2024
  /* @__PURE__ */ jsxs(Dialog.Footer, { children: [
1624
- /* @__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({
1625
2026
  id: "app.components.Button.cancel",
1626
2027
  defaultMessage: "Cancel"
1627
2028
  }) }) }),
1628
- /* @__PURE__ */ jsx(Button, { onClick: handleConfirm, variant, children: formatMessage({
2029
+ /* @__PURE__ */ jsx(Button, { onClick: handleConfirm, variant, fullWidth: true, children: formatMessage({
1629
2030
  id: "app.components.Button.confirm",
1630
2031
  defaultMessage: "Confirm"
1631
2032
  }) })
@@ -1664,13 +2065,17 @@ const PublishAction$1 = ({
1664
2065
  const navigate = useNavigate();
1665
2066
  const { toggleNotification } = useNotification();
1666
2067
  const { _unstableFormatValidationErrors: formatValidationErrors } = useAPIErrorHandler();
2068
+ const isListView = useMatch(LIST_PATH) !== null;
1667
2069
  const isCloning = useMatch(CLONE_PATH) !== null;
1668
2070
  const { formatMessage } = useIntl();
1669
- const { canPublish, canCreate, canUpdate } = useDocumentRBAC(
1670
- "PublishAction",
1671
- ({ canPublish: canPublish2, canCreate: canCreate2, canUpdate: canUpdate2 }) => ({ canPublish: canPublish2, canCreate: canCreate2, canUpdate: canUpdate2 })
1672
- );
2071
+ const canPublish = useDocumentRBAC("PublishAction", ({ canPublish: canPublish2 }) => canPublish2);
1673
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);
1674
2079
  const [{ query, rawQuery }] = useQueryParams();
1675
2080
  const params = React.useMemo(() => buildValidParams(query), [query]);
1676
2081
  const modified = useForm("PublishAction", ({ modified: modified2 }) => modified2);
@@ -1679,62 +2084,144 @@ const PublishAction$1 = ({
1679
2084
  const validate = useForm("PublishAction", (state) => state.validate);
1680
2085
  const setErrors = useForm("PublishAction", (state) => state.setErrors);
1681
2086
  const formValues = useForm("PublishAction", ({ values }) => values);
1682
- const isDocumentPublished = (document?.[PUBLISHED_AT_ATTRIBUTE_NAME] || meta?.availableStatus.some((doc) => doc[PUBLISHED_AT_ATTRIBUTE_NAME] !== null)) && document?.status !== "modified";
1683
- if (!schema?.options?.draftAndPublish) {
1684
- return null;
1685
- }
1686
- return {
1687
- /**
1688
- * Disabled when:
1689
- * - currently if you're cloning a document we don't support publish & clone at the same time.
1690
- * - 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
1691
2191
  * - the active tab is the published tab
1692
2192
  * - the document is already published & not modified
1693
2193
  * - the document is being created & not modified
1694
2194
  * - the user doesn't have the permission to publish
1695
- * - the user doesn't have the permission to create a new document
1696
- * - the user doesn't have the permission to update the document
1697
2195
  */
1698
- 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,
1699
2197
  label: formatMessage({
1700
2198
  id: "app.utils.publish",
1701
2199
  defaultMessage: "Publish"
1702
2200
  }),
1703
2201
  onClick: async () => {
1704
- setSubmitting(true);
1705
- try {
1706
- const { errors } = await validate();
1707
- if (errors) {
1708
- toggleNotification({
1709
- type: "danger",
1710
- message: formatMessage({
1711
- id: "content-manager.validation.error",
1712
- defaultMessage: "There are validation errors in your document. Please fix them before saving."
1713
- })
1714
- });
1715
- return;
1716
- }
1717
- const res = await publish(
1718
- {
1719
- collectionType,
1720
- model,
1721
- documentId,
1722
- params
1723
- },
1724
- formValues
1725
- );
1726
- if ("data" in res && collectionType !== SINGLE_TYPES) {
1727
- navigate({
1728
- pathname: `../${collectionType}/${model}/${res.data.documentId}`,
1729
- search: rawQuery
1730
- });
1731
- } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1732
- 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
1733
2219
  }
1734
- } finally {
1735
- setSubmitting(false);
2220
+ ),
2221
+ onConfirm: async () => {
2222
+ await performPublish();
1736
2223
  }
1737
- }
2224
+ } : void 0
1738
2225
  };
1739
2226
  };
1740
2227
  PublishAction$1.type = "publish";
@@ -1750,10 +2237,6 @@ const UpdateAction = ({
1750
2237
  const cloneMatch = useMatch(CLONE_PATH);
1751
2238
  const isCloning = cloneMatch !== null;
1752
2239
  const { formatMessage } = useIntl();
1753
- const { canCreate, canUpdate } = useDocumentRBAC("UpdateAction", ({ canCreate: canCreate2, canUpdate: canUpdate2 }) => ({
1754
- canCreate: canCreate2,
1755
- canUpdate: canUpdate2
1756
- }));
1757
2240
  const { create, update, clone } = useDocumentActions();
1758
2241
  const [{ query, rawQuery }] = useQueryParams();
1759
2242
  const params = React.useMemo(() => buildValidParams(query), [query]);
@@ -1770,10 +2253,8 @@ const UpdateAction = ({
1770
2253
  * - the form is submitting
1771
2254
  * - the document is not modified & we're not cloning (you can save a clone entity straight away)
1772
2255
  * - the active tab is the published tab
1773
- * - the user doesn't have the permission to create a new document
1774
- * - the user doesn't have the permission to update the document
1775
2256
  */
1776
- disabled: isSubmitting || !modified && !isCloning || activeTab === "published" || Boolean(!documentId && !canCreate || documentId && !canUpdate),
2257
+ disabled: isSubmitting || !modified && !isCloning || activeTab === "published",
1777
2258
  label: formatMessage({
1778
2259
  id: "content-manager.containers.Edit.save",
1779
2260
  defaultMessage: "Save"
@@ -1781,7 +2262,9 @@ const UpdateAction = ({
1781
2262
  onClick: async () => {
1782
2263
  setSubmitting(true);
1783
2264
  try {
1784
- const { errors } = await validate();
2265
+ const { errors } = await validate(true, {
2266
+ status: "draft"
2267
+ });
1785
2268
  if (errors) {
1786
2269
  toggleNotification({
1787
2270
  type: "danger",
@@ -1802,10 +2285,13 @@ const UpdateAction = ({
1802
2285
  document
1803
2286
  );
1804
2287
  if ("data" in res) {
1805
- navigate({
1806
- pathname: `../${collectionType}/${model}/${res.data.documentId}`,
1807
- search: rawQuery
1808
- });
2288
+ navigate(
2289
+ {
2290
+ pathname: `../${res.data.documentId}`,
2291
+ search: rawQuery
2292
+ },
2293
+ { relative: "path" }
2294
+ );
1809
2295
  } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1810
2296
  setErrors(formatValidationErrors(res.error));
1811
2297
  }
@@ -1833,10 +2319,13 @@ const UpdateAction = ({
1833
2319
  document
1834
2320
  );
1835
2321
  if ("data" in res && collectionType !== SINGLE_TYPES) {
1836
- navigate({
1837
- pathname: `../${collectionType}/${model}/${res.data.documentId}`,
1838
- search: rawQuery
1839
- });
2322
+ navigate(
2323
+ {
2324
+ pathname: `../${res.data.documentId}`,
2325
+ search: rawQuery
2326
+ },
2327
+ { replace: true, relative: "path" }
2328
+ );
1840
2329
  } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1841
2330
  setErrors(formatValidationErrors(res.error));
1842
2331
  }
@@ -1880,7 +2369,7 @@ const UnpublishAction$1 = ({
1880
2369
  id: "app.utils.unpublish",
1881
2370
  defaultMessage: "Unpublish"
1882
2371
  }),
1883
- icon: /* @__PURE__ */ jsx(StyledCrossCircle, {}),
2372
+ icon: /* @__PURE__ */ jsx(Cross, {}),
1884
2373
  onClick: async () => {
1885
2374
  if (!documentId && collectionType !== SINGLE_TYPES || isDocumentModified) {
1886
2375
  if (!documentId) {
@@ -1992,7 +2481,7 @@ const DiscardAction = ({
1992
2481
  id: "content-manager.actions.discard.label",
1993
2482
  defaultMessage: "Discard changes"
1994
2483
  }),
1995
- icon: /* @__PURE__ */ jsx(StyledCrossCircle, {}),
2484
+ icon: /* @__PURE__ */ jsx(Cross, {}),
1996
2485
  position: ["panel", "table-row"],
1997
2486
  variant: "danger",
1998
2487
  dialog: {
@@ -2020,11 +2509,6 @@ const DiscardAction = ({
2020
2509
  };
2021
2510
  };
2022
2511
  DiscardAction.type = "discard";
2023
- const StyledCrossCircle = styled(CrossCircle)`
2024
- path {
2025
- fill: currentColor;
2026
- }
2027
- `;
2028
2512
  const DEFAULT_ACTIONS = [PublishAction$1, UpdateAction, UnpublishAction$1, DiscardAction];
2029
2513
  const intervals = ["years", "months", "days", "hours", "minutes", "seconds"];
2030
2514
  const RelativeTime = React.forwardRef(
@@ -2072,7 +2556,7 @@ const getDisplayName = ({
2072
2556
  };
2073
2557
  const capitalise = (str) => str.charAt(0).toUpperCase() + str.slice(1);
2074
2558
  const DocumentStatus = ({ status = "draft", ...restProps }) => {
2075
- const statusVariant = status === "draft" ? "primary" : status === "published" ? "success" : "alternative";
2559
+ const statusVariant = status === "draft" ? "secondary" : status === "published" ? "success" : "alternative";
2076
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) }) });
2077
2561
  };
2078
2562
  const Header = ({ isCreating, status, title: documentTitle = "Untitled" }) => {
@@ -2082,23 +2566,13 @@ const Header = ({ isCreating, status, title: documentTitle = "Untitled" }) => {
2082
2566
  id: "content-manager.containers.edit.title.new",
2083
2567
  defaultMessage: "Create an entry"
2084
2568
  }) : documentTitle;
2085
- 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: [
2086
2570
  /* @__PURE__ */ jsx(BackButton, {}),
2087
- /* @__PURE__ */ jsxs(
2088
- Flex,
2089
- {
2090
- width: "100%",
2091
- justifyContent: "space-between",
2092
- paddingTop: 1,
2093
- gap: "80px",
2094
- alignItems: "flex-start",
2095
- children: [
2096
- /* @__PURE__ */ jsx(Typography, { variant: "alpha", tag: "h1", children: title }),
2097
- /* @__PURE__ */ jsx(HeaderToolbar, {})
2098
- ]
2099
- }
2100
- ),
2101
- 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
2102
2576
  ] });
2103
2577
  };
2104
2578
  const HeaderToolbar = () => {
@@ -2181,12 +2655,12 @@ const Information = ({ activeTab }) => {
2181
2655
  isDisplayed: !!publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME],
2182
2656
  label: formatMessage({
2183
2657
  id: "content-manager.containers.edit.information.last-published.label",
2184
- defaultMessage: "Last published"
2658
+ defaultMessage: "Published"
2185
2659
  }),
2186
2660
  value: formatMessage(
2187
2661
  {
2188
2662
  id: "content-manager.containers.edit.information.last-published.value",
2189
- defaultMessage: `Published {time}{isAnonymous, select, true {} other { by {author}}}`
2663
+ defaultMessage: `{time}{isAnonymous, select, true {} other { by {author}}}`
2190
2664
  },
2191
2665
  {
2192
2666
  time: /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME]) }),
@@ -2199,12 +2673,12 @@ const Information = ({ activeTab }) => {
2199
2673
  isDisplayed: !!createAndUpdateDocument?.[UPDATED_AT_ATTRIBUTE_NAME],
2200
2674
  label: formatMessage({
2201
2675
  id: "content-manager.containers.edit.information.last-draft.label",
2202
- defaultMessage: "Last draft"
2676
+ defaultMessage: "Updated"
2203
2677
  }),
2204
2678
  value: formatMessage(
2205
2679
  {
2206
2680
  id: "content-manager.containers.edit.information.last-draft.value",
2207
- defaultMessage: `Modified {time}{isAnonymous, select, true {} other { by {author}}}`
2681
+ defaultMessage: `{time}{isAnonymous, select, true {} other { by {author}}}`
2208
2682
  },
2209
2683
  {
2210
2684
  time: /* @__PURE__ */ jsx(
@@ -2222,12 +2696,12 @@ const Information = ({ activeTab }) => {
2222
2696
  isDisplayed: !!createAndUpdateDocument?.[CREATED_AT_ATTRIBUTE_NAME],
2223
2697
  label: formatMessage({
2224
2698
  id: "content-manager.containers.edit.information.document.label",
2225
- defaultMessage: "Document"
2699
+ defaultMessage: "Created"
2226
2700
  }),
2227
2701
  value: formatMessage(
2228
2702
  {
2229
2703
  id: "content-manager.containers.edit.information.document.value",
2230
- defaultMessage: `Created {time}{isAnonymous, select, true {} other { by {author}}}`
2704
+ defaultMessage: `{time}{isAnonymous, select, true {} other { by {author}}}`
2231
2705
  },
2232
2706
  {
2233
2707
  time: /* @__PURE__ */ jsx(
@@ -2265,25 +2739,77 @@ const Information = ({ activeTab }) => {
2265
2739
  );
2266
2740
  };
2267
2741
  const HeaderActions = ({ actions: actions2 }) => {
2268
- return /* @__PURE__ */ jsx(Flex, { children: actions2.map((action) => {
2269
- 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) {
2270
2758
  return /* @__PURE__ */ jsx(
2271
2759
  SingleSelect,
2272
2760
  {
2273
2761
  size: "S",
2274
- disabled: action.disabled,
2275
- "aria-label": action.label,
2276
2762
  onChange: action.onSelect,
2277
- value: action.value,
2763
+ "aria-label": action.label,
2764
+ ...action,
2278
2765
  children: action.options.map(({ label, ...option }) => /* @__PURE__ */ jsx(SingleSelectOption, { ...option, children: label }, option.value))
2279
2766
  },
2280
2767
  action.id
2281
2768
  );
2282
2769
  } else {
2283
- 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
+ }
2284
2792
  }
2285
2793
  }) });
2286
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
+ };
2287
2813
  const ConfigureTheViewAction = ({ collectionType, model }) => {
2288
2814
  const navigate = useNavigate();
2289
2815
  const { formatMessage } = useIntl();
@@ -2324,12 +2850,16 @@ const DeleteAction$1 = ({ documentId, model, collectionType, document }) => {
2324
2850
  const { delete: deleteAction } = useDocumentActions();
2325
2851
  const { toggleNotification } = useNotification();
2326
2852
  const setSubmitting = useForm("DeleteAction", (state) => state.setSubmitting);
2853
+ const isLocalized = document?.locale != null;
2327
2854
  return {
2328
2855
  disabled: !canDelete || !document,
2329
- label: formatMessage({
2330
- id: "content-manager.actions.delete.label",
2331
- defaultMessage: "Delete document"
2332
- }),
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
+ ),
2333
2863
  icon: /* @__PURE__ */ jsx(Trash, {}),
2334
2864
  dialog: {
2335
2865
  type: "dialog",
@@ -2365,423 +2895,121 @@ const DeleteAction$1 = ({ documentId, model, collectionType, document }) => {
2365
2895
  const res = await deleteAction({
2366
2896
  documentId,
2367
2897
  model,
2368
- collectionType,
2369
- params: {
2370
- locale: "*"
2371
- }
2372
- });
2373
- if (!("error" in res)) {
2374
- navigate({ pathname: `../${collectionType}/${model}` }, { replace: true });
2375
- }
2376
- } finally {
2377
- if (!listViewPathMatch) {
2378
- setSubmitting(false);
2379
- }
2380
- }
2381
- }
2382
- },
2383
- variant: "danger",
2384
- position: ["header", "table-row"]
2385
- };
2386
- };
2387
- DeleteAction$1.type = "delete";
2388
- const DEFAULT_HEADER_ACTIONS = [EditTheModelAction, ConfigureTheViewAction, DeleteAction$1];
2389
- const Panels = () => {
2390
- const isCloning = useMatch(CLONE_PATH) !== null;
2391
- const [
2392
- {
2393
- query: { status }
2394
- }
2395
- ] = useQueryParams({
2396
- status: "draft"
2397
- });
2398
- const { model, id, document, meta, collectionType } = useDoc();
2399
- const plugins = useStrapiApp("Panels", (state) => state.plugins);
2400
- const props = {
2401
- activeTab: status,
2402
- model,
2403
- documentId: id,
2404
- document: isCloning ? void 0 : document,
2405
- meta: isCloning ? void 0 : meta,
2406
- collectionType
2407
- };
2408
- return /* @__PURE__ */ jsx(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: /* @__PURE__ */ jsx(
2409
- DescriptionComponentRenderer,
2410
- {
2411
- props,
2412
- descriptions: plugins["content-manager"].apis.getEditViewSidePanels(),
2413
- children: (panels) => panels.map(({ content, id: id2, ...description }) => /* @__PURE__ */ jsx(Panel, { ...description, children: content }, id2))
2414
- }
2415
- ) });
2416
- };
2417
- const ActionsPanel = () => {
2418
- const { formatMessage } = useIntl();
2419
- return {
2420
- title: formatMessage({
2421
- id: "content-manager.containers.edit.panels.default.title",
2422
- defaultMessage: "Document"
2423
- }),
2424
- content: /* @__PURE__ */ jsx(ActionsPanelContent, {})
2425
- };
2426
- };
2427
- ActionsPanel.type = "actions";
2428
- const ActionsPanelContent = () => {
2429
- const isCloning = useMatch(CLONE_PATH) !== null;
2430
- const [
2431
- {
2432
- query: { status = "draft" }
2433
- }
2434
- ] = useQueryParams();
2435
- const { model, id, document, meta, collectionType } = useDoc();
2436
- const plugins = useStrapiApp("ActionsPanel", (state) => state.plugins);
2437
- const props = {
2438
- activeTab: status,
2439
- model,
2440
- documentId: id,
2441
- document: isCloning ? void 0 : document,
2442
- meta: isCloning ? void 0 : meta,
2443
- collectionType
2444
- };
2445
- return /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, width: "100%", children: [
2446
- /* @__PURE__ */ jsx(
2447
- DescriptionComponentRenderer,
2448
- {
2449
- props,
2450
- descriptions: plugins["content-manager"].apis.getDocumentActions(),
2451
- children: (actions2) => /* @__PURE__ */ jsx(DocumentActions, { actions: actions2 })
2452
- }
2453
- ),
2454
- /* @__PURE__ */ jsx(InjectionZone, { area: "editView.right-links", slug: model })
2455
- ] });
2456
- };
2457
- const Panel = React.forwardRef(({ children, title }, ref) => {
2458
- return /* @__PURE__ */ jsxs(
2459
- Flex,
2460
- {
2461
- ref,
2462
- tag: "aside",
2463
- "aria-labelledby": "additional-information",
2464
- background: "neutral0",
2465
- borderColor: "neutral150",
2466
- hasRadius: true,
2467
- paddingBottom: 4,
2468
- paddingLeft: 4,
2469
- paddingRight: 4,
2470
- paddingTop: 4,
2471
- shadow: "tableShadow",
2472
- gap: 3,
2473
- direction: "column",
2474
- justifyContent: "stretch",
2475
- alignItems: "flex-start",
2476
- children: [
2477
- /* @__PURE__ */ jsx(Typography, { tag: "h2", variant: "sigma", textTransform: "uppercase", children: title }),
2478
- children
2479
- ]
2480
- }
2481
- );
2482
- });
2483
- const HOOKS = {
2484
- /**
2485
- * Hook that allows to mutate the displayed headers of the list view table
2486
- * @constant
2487
- * @type {string}
2488
- */
2489
- INJECT_COLUMN_IN_TABLE: "Admin/CM/pages/ListView/inject-column-in-table",
2490
- /**
2491
- * Hook that allows to mutate the CM's collection types links pre-set filters
2492
- * @constant
2493
- * @type {string}
2494
- */
2495
- MUTATE_COLLECTION_TYPES_LINKS: "Admin/CM/pages/App/mutate-collection-types-links",
2496
- /**
2497
- * Hook that allows to mutate the CM's edit view layout
2498
- * @constant
2499
- * @type {string}
2500
- */
2501
- MUTATE_EDIT_VIEW_LAYOUT: "Admin/CM/pages/EditView/mutate-edit-view-layout",
2502
- /**
2503
- * Hook that allows to mutate the CM's single types links pre-set filters
2504
- * @constant
2505
- * @type {string}
2506
- */
2507
- MUTATE_SINGLE_TYPES_LINKS: "Admin/CM/pages/App/mutate-single-types-links"
2508
- };
2509
- const contentTypesApi = contentManagerApi.injectEndpoints({
2510
- endpoints: (builder) => ({
2511
- getContentTypeConfiguration: builder.query({
2512
- query: (uid) => ({
2513
- url: `/content-manager/content-types/${uid}/configuration`,
2514
- method: "GET"
2515
- }),
2516
- transformResponse: (response) => response.data,
2517
- providesTags: (_result, _error, uid) => [
2518
- { type: "ContentTypesConfiguration", id: uid },
2519
- { type: "ContentTypeSettings", id: "LIST" }
2520
- ]
2521
- }),
2522
- getAllContentTypeSettings: builder.query({
2523
- query: () => "/content-manager/content-types-settings",
2524
- transformResponse: (response) => response.data,
2525
- providesTags: [{ type: "ContentTypeSettings", id: "LIST" }]
2526
- }),
2527
- updateContentTypeConfiguration: builder.mutation({
2528
- query: ({ uid, ...body }) => ({
2529
- url: `/content-manager/content-types/${uid}/configuration`,
2530
- method: "PUT",
2531
- data: body
2532
- }),
2533
- transformResponse: (response) => response.data,
2534
- invalidatesTags: (_result, _error, { uid }) => [
2535
- { type: "ContentTypesConfiguration", id: uid },
2536
- { type: "ContentTypeSettings", id: "LIST" },
2537
- // Is this necessary?
2538
- { type: "InitialData" }
2539
- ]
2540
- })
2541
- })
2542
- });
2543
- const {
2544
- useGetContentTypeConfigurationQuery,
2545
- useGetAllContentTypeSettingsQuery,
2546
- useUpdateContentTypeConfigurationMutation
2547
- } = contentTypesApi;
2548
- const checkIfAttributeIsDisplayable = (attribute) => {
2549
- const { type } = attribute;
2550
- if (type === "relation") {
2551
- return !attribute.relation.toLowerCase().includes("morph");
2552
- }
2553
- return !["json", "dynamiczone", "richtext", "password", "blocks"].includes(type) && !!type;
2554
- };
2555
- const getMainField = (attribute, mainFieldName, { schemas, components }) => {
2556
- if (!mainFieldName) {
2557
- return void 0;
2558
- }
2559
- const mainFieldType = attribute.type === "component" ? components[attribute.component].attributes[mainFieldName].type : (
2560
- // @ts-expect-error – `targetModel` does exist on the attribute for a relation.
2561
- schemas.find((schema) => schema.uid === attribute.targetModel)?.attributes[mainFieldName].type
2562
- );
2563
- return {
2564
- name: mainFieldName,
2565
- type: mainFieldType ?? "string"
2566
- };
2567
- };
2568
- const DEFAULT_SETTINGS = {
2569
- bulkable: false,
2570
- filterable: false,
2571
- searchable: false,
2572
- pagination: false,
2573
- defaultSortBy: "",
2574
- defaultSortOrder: "asc",
2575
- mainField: "id",
2576
- pageSize: 10
2577
- };
2578
- const useDocumentLayout = (model) => {
2579
- const { schema, components } = useDocument({ model, collectionType: "" }, { skip: true });
2580
- const [{ query }] = useQueryParams();
2581
- const runHookWaterfall = useStrapiApp("useDocumentLayout", (state) => state.runHookWaterfall);
2582
- const { toggleNotification } = useNotification();
2583
- const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
2584
- const { isLoading: isLoadingSchemas, schemas } = useContentTypeSchema();
2585
- const {
2586
- data,
2587
- isLoading: isLoadingConfigs,
2588
- error,
2589
- isFetching: isFetchingConfigs
2590
- } = useGetContentTypeConfigurationQuery(model);
2591
- const isLoading = isLoadingSchemas || isFetchingConfigs || isLoadingConfigs;
2592
- React.useEffect(() => {
2593
- if (error) {
2594
- toggleNotification({
2595
- type: "danger",
2596
- message: formatAPIError(error)
2597
- });
2598
- }
2599
- }, [error, formatAPIError, toggleNotification]);
2600
- const editLayout = React.useMemo(
2601
- () => data && !isLoading ? formatEditLayout(data, { schemas, schema, components }) : {
2602
- layout: [],
2603
- components: {},
2604
- metadatas: {},
2605
- options: {},
2606
- settings: DEFAULT_SETTINGS
2607
- },
2608
- [data, isLoading, schemas, schema, components]
2609
- );
2610
- const listLayout = React.useMemo(() => {
2611
- return data && !isLoading ? formatListLayout(data, { schemas, schema, components }) : {
2612
- layout: [],
2613
- metadatas: {},
2614
- options: {},
2615
- settings: DEFAULT_SETTINGS
2616
- };
2617
- }, [data, isLoading, schemas, schema, components]);
2618
- const { layout: edit } = React.useMemo(
2619
- () => runHookWaterfall(HOOKS.MUTATE_EDIT_VIEW_LAYOUT, {
2620
- layout: editLayout,
2621
- query
2622
- }),
2623
- [editLayout, query, runHookWaterfall]
2624
- );
2625
- return {
2626
- error,
2627
- isLoading,
2628
- edit,
2629
- list: listLayout
2630
- };
2631
- };
2632
- const useDocLayout = () => {
2633
- const { model } = useDoc();
2634
- return useDocumentLayout(model);
2635
- };
2636
- const formatEditLayout = (data, {
2637
- schemas,
2638
- schema,
2639
- components
2640
- }) => {
2641
- let currentPanelIndex = 0;
2642
- const panelledEditAttributes = convertEditLayoutToFieldLayouts(
2643
- data.contentType.layouts.edit,
2644
- schema?.attributes,
2645
- data.contentType.metadatas,
2646
- { configurations: data.components, schemas: components },
2647
- schemas
2648
- ).reduce((panels, row) => {
2649
- if (row.some((field) => field.type === "dynamiczone")) {
2650
- panels.push([row]);
2651
- currentPanelIndex += 2;
2652
- } else {
2653
- if (!panels[currentPanelIndex]) {
2654
- panels.push([]);
2655
- }
2656
- panels[currentPanelIndex].push(row);
2657
- }
2658
- return panels;
2659
- }, []);
2660
- const componentEditAttributes = Object.entries(data.components).reduce(
2661
- (acc, [uid, configuration]) => {
2662
- acc[uid] = {
2663
- layout: convertEditLayoutToFieldLayouts(
2664
- configuration.layouts.edit,
2665
- components[uid].attributes,
2666
- configuration.metadatas
2667
- ),
2668
- settings: {
2669
- ...configuration.settings,
2670
- icon: components[uid].info.icon,
2671
- displayName: components[uid].info.displayName
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
+ }
2672
2910
  }
2673
- };
2674
- return acc;
2675
- },
2676
- {}
2677
- );
2678
- const editMetadatas = Object.entries(data.contentType.metadatas).reduce(
2679
- (acc, [attribute, metadata]) => {
2680
- return {
2681
- ...acc,
2682
- [attribute]: metadata.edit
2683
- };
2684
- },
2685
- {}
2686
- );
2687
- return {
2688
- layout: panelledEditAttributes,
2689
- components: componentEditAttributes,
2690
- metadatas: editMetadatas,
2691
- settings: {
2692
- ...data.contentType.settings,
2693
- displayName: schema?.info.displayName
2911
+ }
2694
2912
  },
2695
- options: {
2696
- ...schema?.options,
2697
- ...schema?.pluginOptions,
2698
- ...data.contentType.options
2699
- }
2913
+ variant: "danger",
2914
+ position: ["header", "table-row"]
2700
2915
  };
2701
2916
  };
2702
- const convertEditLayoutToFieldLayouts = (rows, attributes = {}, metadatas, components, schemas = []) => {
2703
- return rows.map(
2704
- (row) => row.map((field) => {
2705
- const attribute = attributes[field.name];
2706
- if (!attribute) {
2707
- return null;
2708
- }
2709
- const { edit: metadata } = metadatas[field.name];
2710
- const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
2711
- return {
2712
- attribute,
2713
- disabled: !metadata.editable,
2714
- hint: metadata.description,
2715
- label: metadata.label ?? "",
2716
- name: field.name,
2717
- // @ts-expect-error – mainField does exist on the metadata for a relation.
2718
- mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
2719
- schemas,
2720
- components: components?.schemas ?? {}
2721
- }),
2722
- placeholder: metadata.placeholder ?? "",
2723
- required: attribute.required ?? false,
2724
- size: field.size,
2725
- unique: "unique" in attribute ? attribute.unique : false,
2726
- visible: metadata.visible ?? true,
2727
- type: attribute.type
2728
- };
2729
- }).filter((field) => field !== null)
2730
- );
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
+ ) });
2731
2946
  };
2732
- const formatListLayout = (data, {
2733
- schemas,
2734
- schema,
2735
- components
2736
- }) => {
2737
- const listMetadatas = Object.entries(data.contentType.metadatas).reduce(
2738
- (acc, [attribute, metadata]) => {
2739
- return {
2740
- ...acc,
2741
- [attribute]: metadata.list
2742
- };
2743
- },
2744
- {}
2745
- );
2746
- const listAttributes = convertListLayoutToFieldLayouts(
2747
- data.contentType.layouts.list,
2748
- schema?.attributes,
2749
- listMetadatas,
2750
- { configurations: data.components, schemas: components },
2751
- schemas
2752
- );
2947
+ const ActionsPanel = () => {
2948
+ const { formatMessage } = useIntl();
2753
2949
  return {
2754
- layout: listAttributes,
2755
- settings: { ...data.contentType.settings, displayName: schema?.info.displayName },
2756
- metadatas: listMetadatas,
2757
- options: {
2758
- ...schema?.options,
2759
- ...schema?.pluginOptions,
2760
- ...data.contentType.options
2761
- }
2950
+ title: formatMessage({
2951
+ id: "content-manager.containers.edit.panels.default.title",
2952
+ defaultMessage: "Entry"
2953
+ }),
2954
+ content: /* @__PURE__ */ jsx(ActionsPanelContent, {})
2762
2955
  };
2763
2956
  };
2764
- const convertListLayoutToFieldLayouts = (columns, attributes = {}, metadatas, components, schemas = []) => {
2765
- return columns.map((name) => {
2766
- const attribute = attributes[name];
2767
- if (!attribute) {
2768
- return null;
2957
+ ActionsPanel.type = "actions";
2958
+ const ActionsPanelContent = () => {
2959
+ const isCloning = useMatch(CLONE_PATH) !== null;
2960
+ const [
2961
+ {
2962
+ query: { status = "draft" }
2769
2963
  }
2770
- const metadata = metadatas[name];
2771
- const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
2772
- return {
2773
- attribute,
2774
- label: metadata.label ?? "",
2775
- mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
2776
- schemas,
2777
- components: components?.schemas ?? {}
2778
- }),
2779
- name,
2780
- searchable: metadata.searchable ?? true,
2781
- sortable: metadata.sortable ?? true
2782
- };
2783
- }).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
+ ] });
2784
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
+ });
2785
3013
  const ConfirmBulkActionDialog = ({
2786
3014
  onToggleDialog,
2787
3015
  isOpen = false,
@@ -2789,7 +3017,7 @@ const ConfirmBulkActionDialog = ({
2789
3017
  endAction
2790
3018
  }) => {
2791
3019
  const { formatMessage } = useIntl();
2792
- 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: [
2793
3021
  /* @__PURE__ */ jsx(Dialog.Header, { children: formatMessage({
2794
3022
  id: "app.components.ConfirmDialog.title",
2795
3023
  defaultMessage: "Confirmation"
@@ -2820,6 +3048,7 @@ const ConfirmDialogPublishAll = ({
2820
3048
  const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler(getTranslation);
2821
3049
  const { model, schema } = useDoc();
2822
3050
  const [{ query }] = useQueryParams();
3051
+ const enableDraftRelationsCount = false;
2823
3052
  const {
2824
3053
  data: countDraftRelations = 0,
2825
3054
  isLoading,
@@ -2831,7 +3060,7 @@ const ConfirmDialogPublishAll = ({
2831
3060
  locale: query?.plugins?.i18n?.locale
2832
3061
  },
2833
3062
  {
2834
- skip: selectedEntries.length === 0
3063
+ skip: !enableDraftRelationsCount
2835
3064
  }
2836
3065
  );
2837
3066
  React.useEffect(() => {
@@ -2910,7 +3139,14 @@ const formatErrorMessages = (errors, parentKey, formatMessage) => {
2910
3139
  )
2911
3140
  );
2912
3141
  } else {
2913
- 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
+ );
2914
3150
  }
2915
3151
  } else {
2916
3152
  messages.push(
@@ -3009,7 +3245,7 @@ const SelectedEntriesTableContent = ({
3009
3245
  status: row.status
3010
3246
  }
3011
3247
  ) }),
3012
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
3248
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Flex, { children: /* @__PURE__ */ jsx(
3013
3249
  IconButton,
3014
3250
  {
3015
3251
  tag: Link,
@@ -3032,9 +3268,10 @@ const SelectedEntriesTableContent = ({
3032
3268
  ),
3033
3269
  target: "_blank",
3034
3270
  marginLeft: "auto",
3035
- children: /* @__PURE__ */ jsx(Pencil, {})
3271
+ variant: "ghost",
3272
+ children: /* @__PURE__ */ jsx(Pencil, { width: "1.6rem", height: "1.6rem" })
3036
3273
  }
3037
- ) })
3274
+ ) }) })
3038
3275
  ] }, row.id)) })
3039
3276
  ] });
3040
3277
  };
@@ -3071,7 +3308,13 @@ const SelectedEntriesModalContent = ({
3071
3308
  );
3072
3309
  const { rows, validationErrors } = React.useMemo(() => {
3073
3310
  if (data.length > 0 && schema) {
3074
- 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
+ );
3075
3318
  const validationErrors2 = {};
3076
3319
  const rows2 = data.map((entry) => {
3077
3320
  try {
@@ -3421,7 +3664,7 @@ const TableActions = ({ document }) => {
3421
3664
  DescriptionComponentRenderer,
3422
3665
  {
3423
3666
  props,
3424
- descriptions: plugins["content-manager"].apis.getDocumentActions(),
3667
+ descriptions: plugins["content-manager"].apis.getDocumentActions().filter((action) => action.name !== "PublishAction"),
3425
3668
  children: (actions2) => {
3426
3669
  const tableRowActions = actions2.filter((action) => {
3427
3670
  const positions = Array.isArray(action.position) ? action.position : [action.position];
@@ -3532,7 +3775,7 @@ const CloneAction = ({ model, documentId }) => {
3532
3775
  }),
3533
3776
  content: /* @__PURE__ */ jsx(AutoCloneFailureModalBody, { prohibitedFields }),
3534
3777
  footer: ({ onClose }) => {
3535
- return /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", children: [
3778
+ return /* @__PURE__ */ jsxs(Modal.Footer, { children: [
3536
3779
  /* @__PURE__ */ jsx(Button, { onClick: onClose, variant: "tertiary", children: formatMessage({
3537
3780
  id: "cancel",
3538
3781
  defaultMessage: "Cancel"
@@ -3573,8 +3816,7 @@ class ContentManagerPlugin {
3573
3816
  documentActions = [
3574
3817
  ...DEFAULT_ACTIONS,
3575
3818
  ...DEFAULT_TABLE_ROW_ACTIONS,
3576
- ...DEFAULT_HEADER_ACTIONS,
3577
- HistoryAction
3819
+ ...DEFAULT_HEADER_ACTIONS
3578
3820
  ];
3579
3821
  editViewSidePanels = [ActionsPanel];
3580
3822
  headerActions = [];
@@ -3663,6 +3905,52 @@ const getPrintableType = (value) => {
3663
3905
  }
3664
3906
  return nativeType;
3665
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
+ };
3666
3954
  const initialState = {
3667
3955
  collectionTypeLinks: [],
3668
3956
  components: [],
@@ -3713,15 +4001,29 @@ const index = {
3713
4001
  defaultMessage: "Content Manager"
3714
4002
  },
3715
4003
  permissions: [],
3716
- Component: () => import("./layout-B1Z-9koY.mjs").then((mod) => ({ default: mod.Layout })),
3717
4004
  position: 1
3718
4005
  });
4006
+ app.router.addRoute({
4007
+ path: "content-manager/*",
4008
+ lazy: async () => {
4009
+ const { Layout } = await import("./layout-vzKSrr7p.mjs");
4010
+ return {
4011
+ Component: Layout
4012
+ };
4013
+ },
4014
+ children: routes
4015
+ });
3719
4016
  app.registerPlugin(cm.config);
3720
4017
  },
4018
+ bootstrap(app) {
4019
+ if (typeof historyAdmin.bootstrap === "function") {
4020
+ historyAdmin.bootstrap(app);
4021
+ }
4022
+ },
3721
4023
  async registerTrads({ locales }) {
3722
4024
  const importedTrads = await Promise.all(
3723
4025
  locales.map((locale) => {
3724
- 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 }) => {
3725
4027
  return {
3726
4028
  data: prefixPluginTranslations(data, PLUGIN_ID),
3727
4029
  locale
@@ -3749,7 +4051,8 @@ export {
3749
4051
  InjectionZone as I,
3750
4052
  useDocument as J,
3751
4053
  index as K,
3752
- useDocumentActions as L,
4054
+ useContentManagerContext as L,
4055
+ useDocumentActions as M,
3753
4056
  Panels as P,
3754
4057
  RelativeTime as R,
3755
4058
  SINGLE_TYPES as S,
@@ -3767,11 +4070,11 @@ export {
3767
4070
  PERMISSIONS as k,
3768
4071
  DocumentRBAC as l,
3769
4072
  DOCUMENT_META_FIELDS as m,
3770
- useDocLayout as n,
3771
- useGetContentTypeConfigurationQuery as o,
3772
- CREATOR_FIELDS as p,
3773
- getMainField as q,
3774
- routes as r,
4073
+ CLONE_PATH as n,
4074
+ useDocLayout as o,
4075
+ useGetContentTypeConfigurationQuery as p,
4076
+ CREATOR_FIELDS as q,
4077
+ getMainField as r,
3775
4078
  setInitialData as s,
3776
4079
  getDisplayName as t,
3777
4080
  useContentTypeSchema as u,
@@ -3781,4 +4084,4 @@ export {
3781
4084
  capitalise as y,
3782
4085
  useUpdateContentTypeConfigurationMutation as z
3783
4086
  };
3784
- //# sourceMappingURL=index-6kKXK7y8.mjs.map
4087
+ //# sourceMappingURL=index-CJ2vYwuT.mjs.map