@strapi/content-manager 0.0.0-experimental.62ce06180fe9a772eaeb3d43d238b26644f39f7c → 0.0.0-experimental.646ad2aaf2b8f9970409242af8d77b0512d19bd1

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 (142) hide show
  1. package/LICENSE +18 -3
  2. package/dist/_chunks/{ComponentConfigurationPage-Cl7eB3s4.js → ComponentConfigurationPage-D1SEOQBu.js} +3 -3
  3. package/dist/_chunks/{ComponentConfigurationPage-Cl7eB3s4.js.map → ComponentConfigurationPage-D1SEOQBu.js.map} +1 -1
  4. package/dist/_chunks/{ComponentConfigurationPage-DErJQEVW.mjs → ComponentConfigurationPage-oqdZo6l8.mjs} +3 -3
  5. package/dist/_chunks/{ComponentConfigurationPage-DErJQEVW.mjs.map → ComponentConfigurationPage-oqdZo6l8.mjs.map} +1 -1
  6. package/dist/_chunks/{EditConfigurationPage-CyfFvH6-.js → EditConfigurationPage-BP94U6vG.js} +3 -3
  7. package/dist/_chunks/{EditConfigurationPage-CyfFvH6-.js.map → EditConfigurationPage-BP94U6vG.js.map} +1 -1
  8. package/dist/_chunks/{EditConfigurationPage-CBosWqQ7.mjs → EditConfigurationPage-DVLmpXPs.mjs} +3 -3
  9. package/dist/_chunks/{EditConfigurationPage-CBosWqQ7.mjs.map → EditConfigurationPage-DVLmpXPs.mjs.map} +1 -1
  10. package/dist/_chunks/{EditViewPage-DxyAOItK.js → EditViewPage-BfAywbBE.js} +30 -9
  11. package/dist/_chunks/EditViewPage-BfAywbBE.js.map +1 -0
  12. package/dist/_chunks/{EditViewPage-ClIueJnM.mjs → EditViewPage-Cvjs7D6M.mjs} +30 -9
  13. package/dist/_chunks/EditViewPage-Cvjs7D6M.mjs.map +1 -0
  14. package/dist/_chunks/{Field-BZBYmvaf.mjs → Field-CJrfStLX.mjs} +517 -153
  15. package/dist/_chunks/Field-CJrfStLX.mjs.map +1 -0
  16. package/dist/_chunks/{Field-C0Y_SR9e.js → Field-DC7FM64m.js} +519 -155
  17. package/dist/_chunks/Field-DC7FM64m.js.map +1 -0
  18. package/dist/_chunks/{Form-DwvGnISS.js → Form-Ahp2hi7E.js} +36 -17
  19. package/dist/_chunks/Form-Ahp2hi7E.js.map +1 -0
  20. package/dist/_chunks/{Form-jwRSC2kV.mjs → Form-BTgUlCEm.mjs} +36 -17
  21. package/dist/_chunks/Form-BTgUlCEm.mjs.map +1 -0
  22. package/dist/_chunks/{History-Cda0Yjzz.js → History-DZ9T1ZL6.js} +63 -25
  23. package/dist/_chunks/History-DZ9T1ZL6.js.map +1 -0
  24. package/dist/_chunks/{History-BgzAIj0G.mjs → History-Drr6mxnK.mjs} +64 -26
  25. package/dist/_chunks/History-Drr6mxnK.mjs.map +1 -0
  26. package/dist/_chunks/{ListConfigurationPage-C29EF97r.js → ListConfigurationPage-B8bYMcVE.js} +20 -8
  27. package/dist/_chunks/ListConfigurationPage-B8bYMcVE.js.map +1 -0
  28. package/dist/_chunks/{ListConfigurationPage-GH55qfoT.mjs → ListConfigurationPage-C6calJtW.mjs} +20 -8
  29. package/dist/_chunks/ListConfigurationPage-C6calJtW.mjs.map +1 -0
  30. package/dist/_chunks/{ListViewPage-CnRt0UT7.js → ListViewPage-BfiTNTUl.js} +61 -43
  31. package/dist/_chunks/ListViewPage-BfiTNTUl.js.map +1 -0
  32. package/dist/_chunks/{ListViewPage-QU03PFj1.mjs → ListViewPage-CQb0CL40.mjs} +59 -41
  33. package/dist/_chunks/ListViewPage-CQb0CL40.mjs.map +1 -0
  34. package/dist/_chunks/{NoContentTypePage-CPs2CnzH.mjs → NoContentTypePage-C-BK38Ai.mjs} +2 -2
  35. package/dist/_chunks/{NoContentTypePage-CPs2CnzH.mjs.map → NoContentTypePage-C-BK38Ai.mjs.map} +1 -1
  36. package/dist/_chunks/{NoContentTypePage-DFDjxByI.js → NoContentTypePage-wJwVyqoZ.js} +2 -2
  37. package/dist/_chunks/{NoContentTypePage-DFDjxByI.js.map → NoContentTypePage-wJwVyqoZ.js.map} +1 -1
  38. package/dist/_chunks/{NoPermissionsPage-ct58lcY0.mjs → NoPermissionsPage-BBdxJ-4m.mjs} +2 -2
  39. package/dist/_chunks/{NoPermissionsPage-ct58lcY0.mjs.map → NoPermissionsPage-BBdxJ-4m.mjs.map} +1 -1
  40. package/dist/_chunks/{NoPermissionsPage-BVHI-jv5.js → NoPermissionsPage-DKaXyuK9.js} +2 -2
  41. package/dist/_chunks/{NoPermissionsPage-BVHI-jv5.js.map → NoPermissionsPage-DKaXyuK9.js.map} +1 -1
  42. package/dist/_chunks/{Relations-BjpPPCKp.js → Relations-D7NskJzt.js} +69 -36
  43. package/dist/_chunks/Relations-D7NskJzt.js.map +1 -0
  44. package/dist/_chunks/{Relations-KMf5qEN0.mjs → Relations-DApDLUXv.mjs} +70 -37
  45. package/dist/_chunks/Relations-DApDLUXv.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-Bz1SCyIj.mjs} +1005 -686
  51. package/dist/_chunks/index-Bz1SCyIj.mjs.map +1 -0
  52. package/dist/_chunks/{index-D9ZwczCV.js → index-D3u7haqj.js} +998 -678
  53. package/dist/_chunks/index-D3u7haqj.js.map +1 -0
  54. package/dist/_chunks/{layout-BJfBoBiF.js → layout-C_0aK53L.js} +25 -12
  55. package/dist/_chunks/layout-C_0aK53L.js.map +1 -0
  56. package/dist/_chunks/{layout-B1Z-9koY.mjs → layout-PNlIceEV.mjs} +27 -14
  57. package/dist/_chunks/layout-PNlIceEV.mjs.map +1 -0
  58. package/dist/_chunks/{relations-CgZg7Pyx.mjs → relations-BAIQsBLx.mjs} +3 -7
  59. package/dist/_chunks/relations-BAIQsBLx.mjs.map +1 -0
  60. package/dist/_chunks/{relations-CMvjzyU3.js → relations-ClRXiXcM.js} +3 -7
  61. package/dist/_chunks/relations-ClRXiXcM.js.map +1 -0
  62. package/dist/_chunks/{usePrev-B9w_-eYc.js → useDebounce-CtcjDB3L.js} +14 -1
  63. package/dist/_chunks/useDebounce-CtcjDB3L.js.map +1 -0
  64. package/dist/_chunks/useDebounce-DmuSJIF3.mjs +29 -0
  65. package/dist/_chunks/useDebounce-DmuSJIF3.mjs.map +1 -0
  66. package/dist/admin/index.js +2 -1
  67. package/dist/admin/index.js.map +1 -1
  68. package/dist/admin/index.mjs +3 -2
  69. package/dist/admin/src/exports.d.ts +1 -1
  70. package/dist/admin/src/history/index.d.ts +3 -0
  71. package/dist/admin/src/history/services/historyVersion.d.ts +1 -1
  72. package/dist/admin/src/hooks/useDocument.d.ts +32 -1
  73. package/dist/admin/src/index.d.ts +1 -0
  74. package/dist/admin/src/pages/EditView/components/DocumentActions.d.ts +1 -0
  75. package/dist/admin/src/pages/EditView/components/FormInputs/BlocksInput/utils/constants.d.ts +4 -0
  76. package/dist/admin/src/pages/EditView/components/FormInputs/Relations.d.ts +20 -0
  77. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/EditorLayout.d.ts +2 -2
  78. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/WysiwygFooter.d.ts +2 -2
  79. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/WysiwygStyles.d.ts +4 -48
  80. package/dist/admin/src/pages/EditView/components/Header.d.ts +11 -11
  81. package/dist/admin/src/services/api.d.ts +1 -1
  82. package/dist/admin/src/services/components.d.ts +2 -2
  83. package/dist/admin/src/services/contentTypes.d.ts +3 -3
  84. package/dist/admin/src/services/documents.d.ts +19 -17
  85. package/dist/admin/src/services/init.d.ts +1 -1
  86. package/dist/admin/src/services/relations.d.ts +2 -2
  87. package/dist/admin/src/services/uid.d.ts +3 -3
  88. package/dist/admin/src/utils/validation.d.ts +4 -1
  89. package/dist/server/index.js +245 -132
  90. package/dist/server/index.js.map +1 -1
  91. package/dist/server/index.mjs +246 -133
  92. package/dist/server/index.mjs.map +1 -1
  93. package/dist/server/src/controllers/collection-types.d.ts.map +1 -1
  94. package/dist/server/src/controllers/relations.d.ts.map +1 -1
  95. package/dist/server/src/controllers/uid.d.ts.map +1 -1
  96. package/dist/server/src/controllers/utils/metadata.d.ts +15 -1
  97. package/dist/server/src/controllers/utils/metadata.d.ts.map +1 -1
  98. package/dist/server/src/controllers/validation/dimensions.d.ts +4 -2
  99. package/dist/server/src/controllers/validation/dimensions.d.ts.map +1 -1
  100. package/dist/server/src/history/services/history.d.ts.map +1 -1
  101. package/dist/server/src/history/services/lifecycles.d.ts.map +1 -1
  102. package/dist/server/src/history/services/utils.d.ts +2 -1
  103. package/dist/server/src/history/services/utils.d.ts.map +1 -1
  104. package/dist/server/src/index.d.ts +4 -4
  105. package/dist/server/src/policies/hasPermissions.d.ts.map +1 -1
  106. package/dist/server/src/services/document-manager.d.ts.map +1 -1
  107. package/dist/server/src/services/document-metadata.d.ts +8 -8
  108. package/dist/server/src/services/document-metadata.d.ts.map +1 -1
  109. package/dist/server/src/services/index.d.ts +4 -4
  110. package/dist/server/src/services/permission-checker.d.ts.map +1 -1
  111. package/dist/server/src/services/utils/configuration/index.d.ts +2 -2
  112. package/dist/server/src/services/utils/configuration/layouts.d.ts +2 -2
  113. package/dist/server/src/services/utils/populate.d.ts.map +1 -1
  114. package/dist/server/src/utils/index.d.ts +2 -0
  115. package/dist/server/src/utils/index.d.ts.map +1 -1
  116. package/dist/shared/contracts/collection-types.d.ts +3 -1
  117. package/dist/shared/contracts/collection-types.d.ts.map +1 -1
  118. package/package.json +12 -12
  119. package/dist/_chunks/EditViewPage-ClIueJnM.mjs.map +0 -1
  120. package/dist/_chunks/EditViewPage-DxyAOItK.js.map +0 -1
  121. package/dist/_chunks/Field-BZBYmvaf.mjs.map +0 -1
  122. package/dist/_chunks/Field-C0Y_SR9e.js.map +0 -1
  123. package/dist/_chunks/Form-DwvGnISS.js.map +0 -1
  124. package/dist/_chunks/Form-jwRSC2kV.mjs.map +0 -1
  125. package/dist/_chunks/History-BgzAIj0G.mjs.map +0 -1
  126. package/dist/_chunks/History-Cda0Yjzz.js.map +0 -1
  127. package/dist/_chunks/ListConfigurationPage-C29EF97r.js.map +0 -1
  128. package/dist/_chunks/ListConfigurationPage-GH55qfoT.mjs.map +0 -1
  129. package/dist/_chunks/ListViewPage-CnRt0UT7.js.map +0 -1
  130. package/dist/_chunks/ListViewPage-QU03PFj1.mjs.map +0 -1
  131. package/dist/_chunks/Relations-BjpPPCKp.js.map +0 -1
  132. package/dist/_chunks/Relations-KMf5qEN0.mjs.map +0 -1
  133. package/dist/_chunks/index-6kKXK7y8.mjs.map +0 -1
  134. package/dist/_chunks/index-D9ZwczCV.js.map +0 -1
  135. package/dist/_chunks/layout-B1Z-9koY.mjs.map +0 -1
  136. package/dist/_chunks/layout-BJfBoBiF.js.map +0 -1
  137. package/dist/_chunks/relations-CMvjzyU3.js.map +0 -1
  138. package/dist/_chunks/relations-CgZg7Pyx.mjs.map +0 -1
  139. package/dist/_chunks/usePrev-B9w_-eYc.js.map +0 -1
  140. package/dist/_chunks/usePrev-DH6iah0A.mjs +0 -16
  141. package/dist/_chunks/usePrev-DH6iah0A.mjs.map +0 -1
  142. package/strapi-server.js +0 -3
@@ -1,17 +1,18 @@
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 mapValues from "lodash/fp/mapValues";
8
+ import { useIntl } from "react-intl";
9
+ import { useParams, useNavigate, Navigate, useMatch, useLocation, Link, NavLink } from "react-router-dom";
11
10
  import * as yup from "yup";
12
11
  import { ValidationError } from "yup";
13
12
  import pipe from "lodash/fp/pipe";
14
13
  import { intervalToDuration, isPast } from "date-fns";
14
+ import { styled } from "styled-components";
15
+ import { stringify } from "qs";
15
16
  import { createSlice, combineReducers } from "@reduxjs/toolkit";
16
17
  const __variableDynamicImportRuntimeHelper = (glob, path) => {
17
18
  const v = glob[path];
@@ -49,42 +50,6 @@ const useInjectionZone = (area) => {
49
50
  const [page, position] = area.split(".");
50
51
  return contentManagerPlugin.getInjectedComponents(page, position);
51
52
  };
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
53
  const ID = "id";
89
54
  const CREATED_BY_ATTRIBUTE_NAME = "createdBy";
90
55
  const UPDATED_BY_ATTRIBUTE_NAME = "updatedBy";
@@ -136,6 +101,7 @@ const DocumentRBAC = ({ children, permissions }) => {
136
101
  if (!slug) {
137
102
  throw new Error("Cannot find the slug param in the URL");
138
103
  }
104
+ const [{ rawQuery }] = useQueryParams();
139
105
  const userPermissions = useAuth("DocumentRBAC", (state) => state.permissions);
140
106
  const contentTypePermissions = React.useMemo(() => {
141
107
  const contentTypePermissions2 = userPermissions.filter(
@@ -146,7 +112,14 @@ const DocumentRBAC = ({ children, permissions }) => {
146
112
  return { ...acc, [action]: [permission] };
147
113
  }, {});
148
114
  }, [slug, userPermissions]);
149
- const { isLoading, allowedActions } = useRBAC(contentTypePermissions, permissions ?? void 0);
115
+ const { isLoading, allowedActions } = useRBAC(
116
+ contentTypePermissions,
117
+ permissions ?? void 0,
118
+ // TODO: useRBAC context should be typed and built differently
119
+ // We are passing raw query as context to the hook so that it can
120
+ // rely on the locale provided from DocumentRBAC for its permission calculations.
121
+ rawQuery
122
+ );
150
123
  const canCreateFields = !isLoading && allowedActions.canCreate ? extractAndDedupeFields(contentTypePermissions.create) : [];
151
124
  const canReadFields = !isLoading && allowedActions.canRead ? extractAndDedupeFields(contentTypePermissions.read) : [];
152
125
  const canUpdateFields = !isLoading && allowedActions.canUpdate ? extractAndDedupeFields(contentTypePermissions.update) : [];
@@ -195,8 +168,7 @@ const contentManagerApi = adminApi.enhanceEndpoints({
195
168
  "InitialData",
196
169
  "HistoryVersion",
197
170
  "Relations",
198
- "Release",
199
- "ReleaseAction"
171
+ "UidAvailability"
200
172
  ]
201
173
  });
202
174
  const documentApi = contentManagerApi.injectEndpoints({
@@ -210,7 +182,12 @@ const documentApi = contentManagerApi.injectEndpoints({
210
182
  params: query
211
183
  }
212
184
  }),
213
- invalidatesTags: (_result, _error, { model }) => [{ type: "Document", id: `${model}_LIST` }]
185
+ invalidatesTags: (_result, error, { model }) => {
186
+ if (error) {
187
+ return [];
188
+ }
189
+ return [{ type: "Document", id: `${model}_LIST` }];
190
+ }
214
191
  }),
215
192
  cloneDocument: builder.mutation({
216
193
  query: ({ model, sourceId, data, params }) => ({
@@ -221,7 +198,10 @@ const documentApi = contentManagerApi.injectEndpoints({
221
198
  params
222
199
  }
223
200
  }),
224
- invalidatesTags: (_result, _error, { model }) => [{ type: "Document", id: `${model}_LIST` }]
201
+ invalidatesTags: (_result, _error, { model }) => [
202
+ { type: "Document", id: `${model}_LIST` },
203
+ { type: "UidAvailability", id: model }
204
+ ]
225
205
  }),
226
206
  /**
227
207
  * Creates a new collection-type document. This should ONLY be used for collection-types.
@@ -238,7 +218,8 @@ const documentApi = contentManagerApi.injectEndpoints({
238
218
  }),
239
219
  invalidatesTags: (result, _error, { model }) => [
240
220
  { type: "Document", id: `${model}_LIST` },
241
- "Relations"
221
+ "Relations",
222
+ { type: "UidAvailability", id: model }
242
223
  ]
243
224
  }),
244
225
  deleteDocument: builder.mutation({
@@ -250,9 +231,7 @@ const documentApi = contentManagerApi.injectEndpoints({
250
231
  }
251
232
  }),
252
233
  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" }
234
+ { type: "Document", id: collectionType !== SINGLE_TYPES ? `${model}_LIST` : model }
256
235
  ]
257
236
  }),
258
237
  deleteManyDocuments: builder.mutation({
@@ -264,11 +243,7 @@ const documentApi = contentManagerApi.injectEndpoints({
264
243
  params
265
244
  }
266
245
  }),
267
- invalidatesTags: (_res, _error, { model }) => [
268
- { type: "Document", id: `${model}_LIST` },
269
- { type: "Release", id: "LIST" },
270
- { type: "ReleaseAction", id: "LIST" }
271
- ]
246
+ invalidatesTags: (_res, _error, { model }) => [{ type: "Document", id: `${model}_LIST` }]
272
247
  }),
273
248
  discardDocument: builder.mutation({
274
249
  query: ({ collectionType, model, documentId, params }) => ({
@@ -286,8 +261,7 @@ const documentApi = contentManagerApi.injectEndpoints({
286
261
  },
287
262
  { type: "Document", id: `${model}_LIST` },
288
263
  "Relations",
289
- { type: "Release", id: "LIST" },
290
- { type: "ReleaseAction", id: "LIST" }
264
+ { type: "UidAvailability", id: model }
291
265
  ];
292
266
  }
293
267
  }),
@@ -305,6 +279,7 @@ const documentApi = contentManagerApi.injectEndpoints({
305
279
  }),
306
280
  providesTags: (result, _error, arg) => {
307
281
  return [
282
+ { type: "Document", id: `ALL_LIST` },
308
283
  { type: "Document", id: `${arg.model}_LIST` },
309
284
  ...result?.results.map(({ documentId }) => ({
310
285
  type: "Document",
@@ -343,6 +318,11 @@ const documentApi = contentManagerApi.injectEndpoints({
343
318
  {
344
319
  type: "Document",
345
320
  id: collectionType !== SINGLE_TYPES ? `${model}_${result && "documentId" in result ? result.documentId : documentId}` : model
321
+ },
322
+ // Make it easy to invalidate all individual documents queries for a model
323
+ {
324
+ type: "Document",
325
+ id: `${model}_ALL_ITEMS`
346
326
  }
347
327
  ];
348
328
  }
@@ -407,9 +387,20 @@ const documentApi = contentManagerApi.injectEndpoints({
407
387
  id: collectionType !== SINGLE_TYPES ? `${model}_${documentId}` : model
408
388
  },
409
389
  "Relations",
410
- { type: "Release", id: "LIST" },
411
- { type: "ReleaseAction", id: "LIST" }
390
+ { type: "UidAvailability", id: model }
412
391
  ];
392
+ },
393
+ async onQueryStarted({ data, ...patch }, { dispatch, queryFulfilled }) {
394
+ const patchResult = dispatch(
395
+ documentApi.util.updateQueryData("getDocument", patch, (draft) => {
396
+ Object.assign(draft.data, data);
397
+ })
398
+ );
399
+ try {
400
+ await queryFulfilled;
401
+ } catch {
402
+ patchResult.undo();
403
+ }
413
404
  }
414
405
  }),
415
406
  unpublishDocument: builder.mutation({
@@ -479,20 +470,39 @@ const buildValidParams = (query) => {
479
470
  const isBaseQueryError = (error) => {
480
471
  return error.name !== void 0;
481
472
  };
482
- const createYupSchema = (attributes = {}, components = {}) => {
473
+ const arrayValidator = (attribute, options) => ({
474
+ message: translatedErrors.required,
475
+ test(value) {
476
+ if (options.status === "draft") {
477
+ return true;
478
+ }
479
+ if (!attribute.required) {
480
+ return true;
481
+ }
482
+ if (!value) {
483
+ return false;
484
+ }
485
+ if (Array.isArray(value) && value.length === 0) {
486
+ return false;
487
+ }
488
+ return true;
489
+ }
490
+ });
491
+ const createYupSchema = (attributes = {}, components = {}, options = { status: null }) => {
483
492
  const createModelSchema = (attributes2) => yup.object().shape(
484
493
  Object.entries(attributes2).reduce((acc, [name, attribute]) => {
485
494
  if (DOCUMENT_META_FIELDS.includes(name)) {
486
495
  return acc;
487
496
  }
488
497
  const validations = [
498
+ addNullableValidation,
489
499
  addRequiredValidation,
490
500
  addMinLengthValidation,
491
501
  addMaxLengthValidation,
492
502
  addMinValidation,
493
503
  addMaxValidation,
494
504
  addRegexValidation
495
- ].map((fn) => fn(attribute));
505
+ ].map((fn) => fn(attribute, options));
496
506
  const transformSchema = pipe(...validations);
497
507
  switch (attribute.type) {
498
508
  case "component": {
@@ -502,12 +512,12 @@ const createYupSchema = (attributes = {}, components = {}) => {
502
512
  ...acc,
503
513
  [name]: transformSchema(
504
514
  yup.array().of(createModelSchema(attributes3).nullable(false))
505
- )
515
+ ).test(arrayValidator(attribute, options))
506
516
  };
507
517
  } else {
508
518
  return {
509
519
  ...acc,
510
- [name]: transformSchema(createModelSchema(attributes3))
520
+ [name]: transformSchema(createModelSchema(attributes3).nullable())
511
521
  };
512
522
  }
513
523
  }
@@ -529,7 +539,7 @@ const createYupSchema = (attributes = {}, components = {}) => {
529
539
  }
530
540
  )
531
541
  )
532
- )
542
+ ).test(arrayValidator(attribute, options))
533
543
  };
534
544
  case "relation":
535
545
  return {
@@ -541,7 +551,7 @@ const createYupSchema = (attributes = {}, components = {}) => {
541
551
  } else if (Array.isArray(value)) {
542
552
  return yup.array().of(
543
553
  yup.object().shape({
544
- id: yup.string().required()
554
+ id: yup.number().required()
545
555
  })
546
556
  );
547
557
  } else if (typeof value === "object") {
@@ -593,6 +603,14 @@ const createAttributeSchema = (attribute) => {
593
603
  if (!value || typeof value === "string" && value.length === 0) {
594
604
  return true;
595
605
  }
606
+ if (typeof value === "object") {
607
+ try {
608
+ JSON.stringify(value);
609
+ return true;
610
+ } catch (err) {
611
+ return false;
612
+ }
613
+ }
596
614
  try {
597
615
  JSON.parse(value);
598
616
  return true;
@@ -611,13 +629,7 @@ const createAttributeSchema = (attribute) => {
611
629
  return yup.mixed();
612
630
  }
613
631
  };
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
- }
632
+ const nullableSchema = (schema) => {
621
633
  return schema?.nullable ? schema.nullable() : (
622
634
  // In some cases '.nullable' will not be available on the schema.
623
635
  // e.g. when the schema has been built using yup.lazy (e.g. for relations).
@@ -625,7 +637,22 @@ const addRequiredValidation = (attribute) => (schema) => {
625
637
  schema
626
638
  );
627
639
  };
628
- const addMinLengthValidation = (attribute) => (schema) => {
640
+ const addNullableValidation = () => (schema) => {
641
+ return nullableSchema(schema);
642
+ };
643
+ const addRequiredValidation = (attribute, options) => (schema) => {
644
+ if (options.status === "draft" || !attribute.required) {
645
+ return schema;
646
+ }
647
+ if (attribute.required && "required" in schema) {
648
+ return schema.required(translatedErrors.required);
649
+ }
650
+ return schema;
651
+ };
652
+ const addMinLengthValidation = (attribute, options) => (schema) => {
653
+ if (options.status === "draft") {
654
+ return schema;
655
+ }
629
656
  if ("minLength" in attribute && attribute.minLength && Number.isInteger(attribute.minLength) && "min" in schema) {
630
657
  return schema.min(attribute.minLength, {
631
658
  ...translatedErrors.minLength,
@@ -647,10 +674,13 @@ const addMaxLengthValidation = (attribute) => (schema) => {
647
674
  }
648
675
  return schema;
649
676
  };
650
- const addMinValidation = (attribute) => (schema) => {
651
- if ("min" in attribute) {
677
+ const addMinValidation = (attribute, options) => (schema) => {
678
+ if (options.status === "draft") {
679
+ return schema;
680
+ }
681
+ if ("min" in attribute && "min" in schema) {
652
682
  const min = toInteger(attribute.min);
653
- if ("min" in schema && min) {
683
+ if (min) {
654
684
  return schema.min(min, {
655
685
  ...translatedErrors.min,
656
686
  values: {
@@ -768,16 +798,115 @@ const extractContentTypeComponents = (attributes = {}, allComponents = {}) => {
768
798
  }, {});
769
799
  return componentsByKey;
770
800
  };
771
- const useDocument = (args, opts) => {
801
+ const HOOKS = {
802
+ /**
803
+ * Hook that allows to mutate the displayed headers of the list view table
804
+ * @constant
805
+ * @type {string}
806
+ */
807
+ INJECT_COLUMN_IN_TABLE: "Admin/CM/pages/ListView/inject-column-in-table",
808
+ /**
809
+ * Hook that allows to mutate the CM's collection types links pre-set filters
810
+ * @constant
811
+ * @type {string}
812
+ */
813
+ MUTATE_COLLECTION_TYPES_LINKS: "Admin/CM/pages/App/mutate-collection-types-links",
814
+ /**
815
+ * Hook that allows to mutate the CM's edit view layout
816
+ * @constant
817
+ * @type {string}
818
+ */
819
+ MUTATE_EDIT_VIEW_LAYOUT: "Admin/CM/pages/EditView/mutate-edit-view-layout",
820
+ /**
821
+ * Hook that allows to mutate the CM's single types links pre-set filters
822
+ * @constant
823
+ * @type {string}
824
+ */
825
+ MUTATE_SINGLE_TYPES_LINKS: "Admin/CM/pages/App/mutate-single-types-links"
826
+ };
827
+ const contentTypesApi = contentManagerApi.injectEndpoints({
828
+ endpoints: (builder) => ({
829
+ getContentTypeConfiguration: builder.query({
830
+ query: (uid) => ({
831
+ url: `/content-manager/content-types/${uid}/configuration`,
832
+ method: "GET"
833
+ }),
834
+ transformResponse: (response) => response.data,
835
+ providesTags: (_result, _error, uid) => [
836
+ { type: "ContentTypesConfiguration", id: uid },
837
+ { type: "ContentTypeSettings", id: "LIST" }
838
+ ]
839
+ }),
840
+ getAllContentTypeSettings: builder.query({
841
+ query: () => "/content-manager/content-types-settings",
842
+ transformResponse: (response) => response.data,
843
+ providesTags: [{ type: "ContentTypeSettings", id: "LIST" }]
844
+ }),
845
+ updateContentTypeConfiguration: builder.mutation({
846
+ query: ({ uid, ...body }) => ({
847
+ url: `/content-manager/content-types/${uid}/configuration`,
848
+ method: "PUT",
849
+ data: body
850
+ }),
851
+ transformResponse: (response) => response.data,
852
+ invalidatesTags: (_result, _error, { uid }) => [
853
+ { type: "ContentTypesConfiguration", id: uid },
854
+ { type: "ContentTypeSettings", id: "LIST" },
855
+ // Is this necessary?
856
+ { type: "InitialData" }
857
+ ]
858
+ })
859
+ })
860
+ });
861
+ const {
862
+ useGetContentTypeConfigurationQuery,
863
+ useGetAllContentTypeSettingsQuery,
864
+ useUpdateContentTypeConfigurationMutation
865
+ } = contentTypesApi;
866
+ const checkIfAttributeIsDisplayable = (attribute) => {
867
+ const { type } = attribute;
868
+ if (type === "relation") {
869
+ return !attribute.relation.toLowerCase().includes("morph");
870
+ }
871
+ return !["json", "dynamiczone", "richtext", "password", "blocks"].includes(type) && !!type;
872
+ };
873
+ const getMainField = (attribute, mainFieldName, { schemas, components }) => {
874
+ if (!mainFieldName) {
875
+ return void 0;
876
+ }
877
+ const mainFieldType = attribute.type === "component" ? components[attribute.component].attributes[mainFieldName].type : (
878
+ // @ts-expect-error – `targetModel` does exist on the attribute for a relation.
879
+ schemas.find((schema) => schema.uid === attribute.targetModel)?.attributes[mainFieldName].type
880
+ );
881
+ return {
882
+ name: mainFieldName,
883
+ type: mainFieldType ?? "string"
884
+ };
885
+ };
886
+ const DEFAULT_SETTINGS = {
887
+ bulkable: false,
888
+ filterable: false,
889
+ searchable: false,
890
+ pagination: false,
891
+ defaultSortBy: "",
892
+ defaultSortOrder: "asc",
893
+ mainField: "id",
894
+ pageSize: 10
895
+ };
896
+ const useDocumentLayout = (model) => {
897
+ const { schema, components } = useDocument({ model, collectionType: "" }, { skip: true });
898
+ const [{ query }] = useQueryParams();
899
+ const runHookWaterfall = useStrapiApp("useDocumentLayout", (state) => state.runHookWaterfall);
772
900
  const { toggleNotification } = useNotification();
773
901
  const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
902
+ const { isLoading: isLoadingSchemas, schemas } = useContentTypeSchema();
774
903
  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);
904
+ data,
905
+ isLoading: isLoadingConfigs,
906
+ error,
907
+ isFetching: isFetchingConfigs
908
+ } = useGetContentTypeConfigurationQuery(model);
909
+ const isLoading = isLoadingSchemas || isFetchingConfigs || isLoadingConfigs;
781
910
  React.useEffect(() => {
782
911
  if (error) {
783
912
  toggleNotification({
@@ -785,62 +914,318 @@ const useDocument = (args, opts) => {
785
914
  message: formatAPIError(error)
786
915
  });
787
916
  }
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
- }
917
+ }, [error, formatAPIError, toggleNotification]);
918
+ const editLayout = React.useMemo(
919
+ () => data && !isLoading ? formatEditLayout(data, { schemas, schema, components }) : {
920
+ layout: [],
921
+ components: {},
922
+ metadatas: {},
923
+ options: {},
924
+ settings: DEFAULT_SETTINGS
811
925
  },
812
- [validationSchema]
926
+ [data, isLoading, schemas, schema, components]
927
+ );
928
+ const listLayout = React.useMemo(() => {
929
+ return data && !isLoading ? formatListLayout(data, { schemas, schema, components }) : {
930
+ layout: [],
931
+ metadatas: {},
932
+ options: {},
933
+ settings: DEFAULT_SETTINGS
934
+ };
935
+ }, [data, isLoading, schemas, schema, components]);
936
+ const { layout: edit } = React.useMemo(
937
+ () => runHookWaterfall(HOOKS.MUTATE_EDIT_VIEW_LAYOUT, {
938
+ layout: editLayout,
939
+ query
940
+ }),
941
+ [editLayout, query, runHookWaterfall]
813
942
  );
814
- const isLoading = isLoadingDocument || isFetchingDocument || isLoadingSchema;
815
943
  return {
816
- components,
817
- document: data?.data,
818
- meta: data?.meta,
944
+ error,
819
945
  isLoading,
820
- schema,
821
- validate
946
+ edit,
947
+ list: listLayout
822
948
  };
823
949
  };
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
- }
950
+ const useDocLayout = () => {
951
+ const { model } = useDoc();
952
+ return useDocumentLayout(model);
953
+ };
954
+ const formatEditLayout = (data, {
955
+ schemas,
956
+ schema,
957
+ components
958
+ }) => {
959
+ let currentPanelIndex = 0;
960
+ const panelledEditAttributes = convertEditLayoutToFieldLayouts(
961
+ data.contentType.layouts.edit,
962
+ schema?.attributes,
963
+ data.contentType.metadatas,
964
+ { configurations: data.components, schemas: components },
965
+ schemas
966
+ ).reduce((panels, row) => {
967
+ if (row.some((field) => field.type === "dynamiczone")) {
968
+ panels.push([row]);
969
+ currentPanelIndex += 2;
970
+ } else {
971
+ if (!panels[currentPanelIndex]) {
972
+ panels.push([]);
973
+ }
974
+ panels[currentPanelIndex].push(row);
975
+ }
976
+ return panels;
977
+ }, []);
978
+ const componentEditAttributes = Object.entries(data.components).reduce(
979
+ (acc, [uid, configuration]) => {
980
+ acc[uid] = {
981
+ layout: convertEditLayoutToFieldLayouts(
982
+ configuration.layouts.edit,
983
+ components[uid].attributes,
984
+ configuration.metadatas,
985
+ { configurations: data.components, schemas: components }
986
+ ),
987
+ settings: {
988
+ ...configuration.settings,
989
+ icon: components[uid].info.icon,
990
+ displayName: components[uid].info.displayName
991
+ }
992
+ };
993
+ return acc;
994
+ },
995
+ {}
996
+ );
997
+ const editMetadatas = Object.entries(data.contentType.metadatas).reduce(
998
+ (acc, [attribute, metadata]) => {
999
+ return {
1000
+ ...acc,
1001
+ [attribute]: metadata.edit
1002
+ };
1003
+ },
1004
+ {}
1005
+ );
1006
+ return {
1007
+ layout: panelledEditAttributes,
1008
+ components: componentEditAttributes,
1009
+ metadatas: editMetadatas,
1010
+ settings: {
1011
+ ...data.contentType.settings,
1012
+ displayName: schema?.info.displayName
1013
+ },
1014
+ options: {
1015
+ ...schema?.options,
1016
+ ...schema?.pluginOptions,
1017
+ ...data.contentType.options
1018
+ }
1019
+ };
1020
+ };
1021
+ const convertEditLayoutToFieldLayouts = (rows, attributes = {}, metadatas, components, schemas = []) => {
1022
+ return rows.map(
1023
+ (row) => row.map((field) => {
1024
+ const attribute = attributes[field.name];
1025
+ if (!attribute) {
1026
+ return null;
1027
+ }
1028
+ const { edit: metadata } = metadatas[field.name];
1029
+ const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
1030
+ return {
1031
+ attribute,
1032
+ disabled: !metadata.editable,
1033
+ hint: metadata.description,
1034
+ label: metadata.label ?? "",
1035
+ name: field.name,
1036
+ // @ts-expect-error – mainField does exist on the metadata for a relation.
1037
+ mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
1038
+ schemas,
1039
+ components: components?.schemas ?? {}
1040
+ }),
1041
+ placeholder: metadata.placeholder ?? "",
1042
+ required: attribute.required ?? false,
1043
+ size: field.size,
1044
+ unique: "unique" in attribute ? attribute.unique : false,
1045
+ visible: metadata.visible ?? true,
1046
+ type: attribute.type
1047
+ };
1048
+ }).filter((field) => field !== null)
1049
+ );
1050
+ };
1051
+ const formatListLayout = (data, {
1052
+ schemas,
1053
+ schema,
1054
+ components
1055
+ }) => {
1056
+ const listMetadatas = Object.entries(data.contentType.metadatas).reduce(
1057
+ (acc, [attribute, metadata]) => {
1058
+ return {
1059
+ ...acc,
1060
+ [attribute]: metadata.list
1061
+ };
1062
+ },
1063
+ {}
1064
+ );
1065
+ const listAttributes = convertListLayoutToFieldLayouts(
1066
+ data.contentType.layouts.list,
1067
+ schema?.attributes,
1068
+ listMetadatas,
1069
+ { configurations: data.components, schemas: components },
1070
+ schemas
1071
+ );
1072
+ return {
1073
+ layout: listAttributes,
1074
+ settings: { ...data.contentType.settings, displayName: schema?.info.displayName },
1075
+ metadatas: listMetadatas,
1076
+ options: {
1077
+ ...schema?.options,
1078
+ ...schema?.pluginOptions,
1079
+ ...data.contentType.options
1080
+ }
1081
+ };
1082
+ };
1083
+ const convertListLayoutToFieldLayouts = (columns, attributes = {}, metadatas, components, schemas = []) => {
1084
+ return columns.map((name) => {
1085
+ const attribute = attributes[name];
1086
+ if (!attribute) {
1087
+ return null;
1088
+ }
1089
+ const metadata = metadatas[name];
1090
+ const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
1091
+ return {
1092
+ attribute,
1093
+ label: metadata.label ?? "",
1094
+ mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
1095
+ schemas,
1096
+ components: components?.schemas ?? {}
1097
+ }),
1098
+ name,
1099
+ searchable: metadata.searchable ?? true,
1100
+ sortable: metadata.sortable ?? true
1101
+ };
1102
+ }).filter((field) => field !== null);
1103
+ };
1104
+ const useDocument = (args, opts) => {
1105
+ const { toggleNotification } = useNotification();
1106
+ const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
1107
+ const {
1108
+ currentData: data,
1109
+ isLoading: isLoadingDocument,
1110
+ isFetching: isFetchingDocument,
1111
+ error
1112
+ } = useGetDocumentQuery(args, {
1113
+ ...opts,
1114
+ skip: !args.documentId && args.collectionType !== SINGLE_TYPES || opts?.skip
1115
+ });
1116
+ const {
1117
+ components,
1118
+ schema,
1119
+ schemas,
1120
+ isLoading: isLoadingSchema
1121
+ } = useContentTypeSchema(args.model);
1122
+ React.useEffect(() => {
1123
+ if (error) {
1124
+ toggleNotification({
1125
+ type: "danger",
1126
+ message: formatAPIError(error)
1127
+ });
1128
+ }
1129
+ }, [toggleNotification, error, formatAPIError, args.collectionType]);
1130
+ const validationSchema = React.useMemo(() => {
1131
+ if (!schema) {
1132
+ return null;
1133
+ }
1134
+ return createYupSchema(schema.attributes, components);
1135
+ }, [schema, components]);
1136
+ const validate = React.useCallback(
1137
+ (document) => {
1138
+ if (!validationSchema) {
1139
+ throw new Error(
1140
+ "There is no validation schema generated, this is likely due to the schema not being loaded yet."
1141
+ );
1142
+ }
1143
+ try {
1144
+ validationSchema.validateSync(document, { abortEarly: false, strict: true });
1145
+ return null;
1146
+ } catch (error2) {
1147
+ if (error2 instanceof ValidationError) {
1148
+ return getYupValidationErrors(error2);
1149
+ }
1150
+ throw error2;
1151
+ }
1152
+ },
1153
+ [validationSchema]
1154
+ );
1155
+ const isLoading = isLoadingDocument || isFetchingDocument || isLoadingSchema;
1156
+ const hasError = !!error;
1157
+ return {
1158
+ components,
1159
+ document: data?.data,
1160
+ meta: data?.meta,
1161
+ isLoading,
1162
+ hasError,
1163
+ schema,
1164
+ schemas,
1165
+ validate
1166
+ };
1167
+ };
1168
+ const useDoc = () => {
1169
+ const { id, slug, collectionType, origin } = useParams();
1170
+ const [{ query }] = useQueryParams();
1171
+ const params = React.useMemo(() => buildValidParams(query), [query]);
1172
+ if (!collectionType) {
1173
+ throw new Error("Could not find collectionType in url params");
1174
+ }
1175
+ if (!slug) {
1176
+ throw new Error("Could not find model in url params");
1177
+ }
1178
+ const document = useDocument(
1179
+ { documentId: origin || id, model: slug, collectionType, params },
1180
+ {
1181
+ skip: id === "create" || !origin && !id && collectionType !== SINGLE_TYPES
1182
+ }
1183
+ );
1184
+ const returnId = origin || id === "create" ? void 0 : id;
834
1185
  return {
835
1186
  collectionType,
836
1187
  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
- )
1188
+ id: returnId,
1189
+ ...document
1190
+ };
1191
+ };
1192
+ const useContentManagerContext = () => {
1193
+ const {
1194
+ collectionType,
1195
+ model,
1196
+ id,
1197
+ components,
1198
+ isLoading: isLoadingDoc,
1199
+ schema,
1200
+ schemas
1201
+ } = useDoc();
1202
+ const layout = useDocumentLayout(model);
1203
+ const form = useForm("useContentManagerContext", (state) => state);
1204
+ const isSingleType = collectionType === SINGLE_TYPES;
1205
+ const slug = model;
1206
+ const isCreatingEntry = id === "create";
1207
+ useContentTypeSchema();
1208
+ const isLoading = isLoadingDoc || layout.isLoading;
1209
+ const error = layout.error;
1210
+ return {
1211
+ error,
1212
+ isLoading,
1213
+ // Base metadata
1214
+ model,
1215
+ collectionType,
1216
+ id,
1217
+ slug,
1218
+ isCreatingEntry,
1219
+ isSingleType,
1220
+ hasDraftAndPublish: schema?.options?.draftAndPublish ?? false,
1221
+ // All schema infos
1222
+ components,
1223
+ contentType: schema,
1224
+ contentTypes: schemas,
1225
+ // Form state
1226
+ form,
1227
+ // layout infos
1228
+ layout
844
1229
  };
845
1230
  };
846
1231
  const prefixPluginTranslations = (trad, pluginId) => {
@@ -862,6 +1247,8 @@ const useDocumentActions = () => {
862
1247
  const { formatMessage } = useIntl();
863
1248
  const { trackUsage } = useTracking();
864
1249
  const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
1250
+ const navigate = useNavigate();
1251
+ const setCurrentStep = useGuidedTour("useDocumentActions", (state) => state.setCurrentStep);
865
1252
  const [deleteDocument] = useDeleteDocumentMutation();
866
1253
  const _delete = React.useCallback(
867
1254
  async ({ collectionType, model, documentId, params }, trackerProperty) => {
@@ -1176,6 +1563,7 @@ const useDocumentActions = () => {
1176
1563
  defaultMessage: "Saved document"
1177
1564
  })
1178
1565
  });
1566
+ setCurrentStep("contentManager.success");
1179
1567
  return res.data;
1180
1568
  } catch (err) {
1181
1569
  toggleNotification({
@@ -1197,7 +1585,6 @@ const useDocumentActions = () => {
1197
1585
  sourceId
1198
1586
  });
1199
1587
  if ("error" in res) {
1200
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1201
1588
  return { error: res.error };
1202
1589
  }
1203
1590
  toggleNotification({
@@ -1216,7 +1603,7 @@ const useDocumentActions = () => {
1216
1603
  throw err;
1217
1604
  }
1218
1605
  },
1219
- [autoCloneDocument, formatAPIError, formatMessage, toggleNotification]
1606
+ [autoCloneDocument, formatMessage, toggleNotification]
1220
1607
  );
1221
1608
  const [cloneDocument] = useCloneDocumentMutation();
1222
1609
  const clone = React.useCallback(
@@ -1242,6 +1629,7 @@ const useDocumentActions = () => {
1242
1629
  defaultMessage: "Cloned document"
1243
1630
  })
1244
1631
  });
1632
+ navigate(`../../${res.data.data.documentId}`, { relative: "path" });
1245
1633
  return res.data;
1246
1634
  } catch (err) {
1247
1635
  toggleNotification({
@@ -1252,7 +1640,7 @@ const useDocumentActions = () => {
1252
1640
  throw err;
1253
1641
  }
1254
1642
  },
1255
- [cloneDocument, trackUsage, toggleNotification, formatMessage, formatAPIError]
1643
+ [cloneDocument, trackUsage, toggleNotification, formatMessage, formatAPIError, navigate]
1256
1644
  );
1257
1645
  const [getDoc] = useLazyGetDocumentQuery();
1258
1646
  const getDocument = React.useCallback(
@@ -1278,7 +1666,7 @@ const useDocumentActions = () => {
1278
1666
  };
1279
1667
  };
1280
1668
  const ProtectedHistoryPage = lazy(
1281
- () => import("./History-BgzAIj0G.mjs").then((mod) => ({ default: mod.ProtectedHistoryPage }))
1669
+ () => import("./History-Drr6mxnK.mjs").then((mod) => ({ default: mod.ProtectedHistoryPage }))
1282
1670
  );
1283
1671
  const routes$1 = [
1284
1672
  {
@@ -1291,31 +1679,31 @@ const routes$1 = [
1291
1679
  }
1292
1680
  ];
1293
1681
  const ProtectedEditViewPage = lazy(
1294
- () => import("./EditViewPage-ClIueJnM.mjs").then((mod) => ({ default: mod.ProtectedEditViewPage }))
1682
+ () => import("./EditViewPage-Cvjs7D6M.mjs").then((mod) => ({ default: mod.ProtectedEditViewPage }))
1295
1683
  );
1296
1684
  const ProtectedListViewPage = lazy(
1297
- () => import("./ListViewPage-QU03PFj1.mjs").then((mod) => ({ default: mod.ProtectedListViewPage }))
1685
+ () => import("./ListViewPage-CQb0CL40.mjs").then((mod) => ({ default: mod.ProtectedListViewPage }))
1298
1686
  );
1299
1687
  const ProtectedListConfiguration = lazy(
1300
- () => import("./ListConfigurationPage-GH55qfoT.mjs").then((mod) => ({
1688
+ () => import("./ListConfigurationPage-C6calJtW.mjs").then((mod) => ({
1301
1689
  default: mod.ProtectedListConfiguration
1302
1690
  }))
1303
1691
  );
1304
1692
  const ProtectedEditConfigurationPage = lazy(
1305
- () => import("./EditConfigurationPage-CBosWqQ7.mjs").then((mod) => ({
1693
+ () => import("./EditConfigurationPage-DVLmpXPs.mjs").then((mod) => ({
1306
1694
  default: mod.ProtectedEditConfigurationPage
1307
1695
  }))
1308
1696
  );
1309
1697
  const ProtectedComponentConfigurationPage = lazy(
1310
- () => import("./ComponentConfigurationPage-DErJQEVW.mjs").then((mod) => ({
1698
+ () => import("./ComponentConfigurationPage-oqdZo6l8.mjs").then((mod) => ({
1311
1699
  default: mod.ProtectedComponentConfigurationPage
1312
1700
  }))
1313
1701
  );
1314
1702
  const NoPermissions = lazy(
1315
- () => import("./NoPermissionsPage-ct58lcY0.mjs").then((mod) => ({ default: mod.NoPermissions }))
1703
+ () => import("./NoPermissionsPage-BBdxJ-4m.mjs").then((mod) => ({ default: mod.NoPermissions }))
1316
1704
  );
1317
1705
  const NoContentType = lazy(
1318
- () => import("./NoContentTypePage-CPs2CnzH.mjs").then((mod) => ({ default: mod.NoContentType }))
1706
+ () => import("./NoContentTypePage-C-BK38Ai.mjs").then((mod) => ({ default: mod.NoContentType }))
1319
1707
  );
1320
1708
  const CollectionTypePages = () => {
1321
1709
  const { collectionType } = useParams();
@@ -1429,12 +1817,14 @@ const DocumentActionButton = (action) => {
1429
1817
  /* @__PURE__ */ jsx(
1430
1818
  Button,
1431
1819
  {
1432
- flex: 1,
1820
+ flex: "auto",
1433
1821
  startIcon: action.icon,
1434
1822
  disabled: action.disabled,
1435
1823
  onClick: handleClick(action),
1436
1824
  justifyContent: "center",
1437
1825
  variant: action.variant || "default",
1826
+ paddingTop: "7px",
1827
+ paddingBottom: "7px",
1438
1828
  children: action.label
1439
1829
  }
1440
1830
  ),
@@ -1442,7 +1832,7 @@ const DocumentActionButton = (action) => {
1442
1832
  DocumentActionConfirmDialog,
1443
1833
  {
1444
1834
  ...action.dialog,
1445
- variant: action.variant,
1835
+ variant: action.dialog?.variant ?? action.variant,
1446
1836
  isOpen: dialogId === action.id,
1447
1837
  onClose: handleClose
1448
1838
  }
@@ -1499,9 +1889,9 @@ const DocumentActionsMenu = ({
1499
1889
  disabled: isDisabled,
1500
1890
  size: "S",
1501
1891
  endIcon: null,
1502
- paddingTop: "7px",
1503
- paddingLeft: "9px",
1504
- paddingRight: "9px",
1892
+ paddingTop: "4px",
1893
+ paddingLeft: "7px",
1894
+ paddingRight: "7px",
1505
1895
  variant,
1506
1896
  children: [
1507
1897
  /* @__PURE__ */ jsx(More, { "aria-hidden": true, focusable: false }),
@@ -1512,7 +1902,7 @@ const DocumentActionsMenu = ({
1512
1902
  ]
1513
1903
  }
1514
1904
  ),
1515
- /* @__PURE__ */ jsxs(Menu.Content, { top: "4px", maxHeight: void 0, popoverPlacement: "bottom-end", children: [
1905
+ /* @__PURE__ */ jsxs(Menu.Content, { maxHeight: void 0, popoverPlacement: "bottom-end", children: [
1516
1906
  actions2.map((action) => {
1517
1907
  return /* @__PURE__ */ jsx(
1518
1908
  Menu.Item,
@@ -1521,10 +1911,25 @@ const DocumentActionsMenu = ({
1521
1911
  onSelect: handleClick(action),
1522
1912
  display: "block",
1523
1913
  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
- ] }),
1914
+ /* @__PURE__ */ jsxs(
1915
+ Flex,
1916
+ {
1917
+ color: !action.disabled ? convertActionVariantToColor(action.variant) : "inherit",
1918
+ gap: 2,
1919
+ tag: "span",
1920
+ children: [
1921
+ /* @__PURE__ */ jsx(
1922
+ Flex,
1923
+ {
1924
+ tag: "span",
1925
+ color: !action.disabled ? convertActionVariantToIconColor(action.variant) : "inherit",
1926
+ children: action.icon
1927
+ }
1928
+ ),
1929
+ action.label
1930
+ ]
1931
+ }
1932
+ ),
1528
1933
  action.id.startsWith("HistoryAction") && /* @__PURE__ */ jsx(
1529
1934
  Flex,
1530
1935
  {
@@ -1621,11 +2026,11 @@ const DocumentActionConfirmDialog = ({
1621
2026
  /* @__PURE__ */ jsx(Dialog.Header, { children: title }),
1622
2027
  /* @__PURE__ */ jsx(Dialog.Body, { children: content }),
1623
2028
  /* @__PURE__ */ jsxs(Dialog.Footer, { children: [
1624
- /* @__PURE__ */ jsx(Dialog.Cancel, { children: /* @__PURE__ */ jsx(Button, { variant: "tertiary", children: formatMessage({
2029
+ /* @__PURE__ */ jsx(Dialog.Cancel, { children: /* @__PURE__ */ jsx(Button, { variant: "tertiary", fullWidth: true, children: formatMessage({
1625
2030
  id: "app.components.Button.cancel",
1626
2031
  defaultMessage: "Cancel"
1627
2032
  }) }) }),
1628
- /* @__PURE__ */ jsx(Button, { onClick: handleConfirm, variant, children: formatMessage({
2033
+ /* @__PURE__ */ jsx(Button, { onClick: handleConfirm, variant, fullWidth: true, children: formatMessage({
1629
2034
  id: "app.components.Button.confirm",
1630
2035
  defaultMessage: "Confirm"
1631
2036
  }) })
@@ -1652,6 +2057,18 @@ const DocumentActionModal = ({
1652
2057
  typeof Footer === "function" ? /* @__PURE__ */ jsx(Footer, { onClose: handleClose }) : Footer
1653
2058
  ] }) });
1654
2059
  };
2060
+ const transformData = (data) => {
2061
+ if (Array.isArray(data)) {
2062
+ return data.map(transformData);
2063
+ }
2064
+ if (typeof data === "object" && data !== null) {
2065
+ if ("apiData" in data) {
2066
+ return data.apiData;
2067
+ }
2068
+ return mapValues(transformData)(data);
2069
+ }
2070
+ return data;
2071
+ };
1655
2072
  const PublishAction$1 = ({
1656
2073
  activeTab,
1657
2074
  documentId,
@@ -1664,13 +2081,17 @@ const PublishAction$1 = ({
1664
2081
  const navigate = useNavigate();
1665
2082
  const { toggleNotification } = useNotification();
1666
2083
  const { _unstableFormatValidationErrors: formatValidationErrors } = useAPIErrorHandler();
2084
+ const isListView = useMatch(LIST_PATH) !== null;
1667
2085
  const isCloning = useMatch(CLONE_PATH) !== null;
1668
2086
  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
- );
2087
+ const canPublish = useDocumentRBAC("PublishAction", ({ canPublish: canPublish2 }) => canPublish2);
1673
2088
  const { publish } = useDocumentActions();
2089
+ const [
2090
+ countDraftRelations,
2091
+ { isLoading: isLoadingDraftRelations, isError: isErrorDraftRelations }
2092
+ ] = useLazyGetDraftRelationCountQuery();
2093
+ const [localCountOfDraftRelations, setLocalCountOfDraftRelations] = React.useState(0);
2094
+ const [serverCountOfDraftRelations, setServerCountOfDraftRelations] = React.useState(0);
1674
2095
  const [{ query, rawQuery }] = useQueryParams();
1675
2096
  const params = React.useMemo(() => buildValidParams(query), [query]);
1676
2097
  const modified = useForm("PublishAction", ({ modified: modified2 }) => modified2);
@@ -1679,12 +2100,107 @@ const PublishAction$1 = ({
1679
2100
  const validate = useForm("PublishAction", (state) => state.validate);
1680
2101
  const setErrors = useForm("PublishAction", (state) => state.setErrors);
1681
2102
  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
- /**
2103
+ React.useEffect(() => {
2104
+ if (isErrorDraftRelations) {
2105
+ toggleNotification({
2106
+ type: "danger",
2107
+ message: formatMessage({
2108
+ id: getTranslation("error.records.fetch-draft-relatons"),
2109
+ defaultMessage: "An error occurred while fetching draft relations on this document."
2110
+ })
2111
+ });
2112
+ }
2113
+ }, [isErrorDraftRelations, toggleNotification, formatMessage]);
2114
+ React.useEffect(() => {
2115
+ const localDraftRelations = /* @__PURE__ */ new Set();
2116
+ const extractDraftRelations = (data) => {
2117
+ const relations = data.connect || [];
2118
+ relations.forEach((relation) => {
2119
+ if (relation.status === "draft") {
2120
+ localDraftRelations.add(relation.id);
2121
+ }
2122
+ });
2123
+ };
2124
+ const traverseAndExtract = (data) => {
2125
+ Object.entries(data).forEach(([key, value]) => {
2126
+ if (key === "connect" && Array.isArray(value)) {
2127
+ extractDraftRelations({ connect: value });
2128
+ } else if (typeof value === "object" && value !== null) {
2129
+ traverseAndExtract(value);
2130
+ }
2131
+ });
2132
+ };
2133
+ if (!documentId || modified) {
2134
+ traverseAndExtract(formValues);
2135
+ setLocalCountOfDraftRelations(localDraftRelations.size);
2136
+ }
2137
+ }, [documentId, modified, formValues, setLocalCountOfDraftRelations]);
2138
+ React.useEffect(() => {
2139
+ if (!document || !document.documentId || isListView) {
2140
+ return;
2141
+ }
2142
+ const fetchDraftRelationsCount = async () => {
2143
+ const { data, error } = await countDraftRelations({
2144
+ collectionType,
2145
+ model,
2146
+ documentId,
2147
+ params
2148
+ });
2149
+ if (error) {
2150
+ throw error;
2151
+ }
2152
+ if (data) {
2153
+ setServerCountOfDraftRelations(data.data);
2154
+ }
2155
+ };
2156
+ fetchDraftRelationsCount();
2157
+ }, [isListView, document, documentId, countDraftRelations, collectionType, model, params]);
2158
+ const isDocumentPublished = (document?.[PUBLISHED_AT_ATTRIBUTE_NAME] || meta?.availableStatus.some((doc) => doc[PUBLISHED_AT_ATTRIBUTE_NAME] !== null)) && document?.status !== "modified";
2159
+ if (!schema?.options?.draftAndPublish) {
2160
+ return null;
2161
+ }
2162
+ const performPublish = async () => {
2163
+ setSubmitting(true);
2164
+ try {
2165
+ const { errors } = await validate(true, {
2166
+ status: "published"
2167
+ });
2168
+ if (errors) {
2169
+ toggleNotification({
2170
+ type: "danger",
2171
+ message: formatMessage({
2172
+ id: "content-manager.validation.error",
2173
+ defaultMessage: "There are validation errors in your document. Please fix them before saving."
2174
+ })
2175
+ });
2176
+ return;
2177
+ }
2178
+ const res = await publish(
2179
+ {
2180
+ collectionType,
2181
+ model,
2182
+ documentId,
2183
+ params
2184
+ },
2185
+ transformData(formValues)
2186
+ );
2187
+ if ("data" in res && collectionType !== SINGLE_TYPES) {
2188
+ navigate({
2189
+ pathname: `../${collectionType}/${model}/${res.data.documentId}`,
2190
+ search: rawQuery
2191
+ });
2192
+ } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
2193
+ setErrors(formatValidationErrors(res.error));
2194
+ }
2195
+ } finally {
2196
+ setSubmitting(false);
2197
+ }
2198
+ };
2199
+ const totalDraftRelations = localCountOfDraftRelations + serverCountOfDraftRelations;
2200
+ const enableDraftRelationsCount = false;
2201
+ const hasDraftRelations = enableDraftRelationsCount;
2202
+ return {
2203
+ /**
1688
2204
  * Disabled when:
1689
2205
  * - currently if you're cloning a document we don't support publish & clone at the same time.
1690
2206
  * - the form is submitting
@@ -1692,49 +2208,36 @@ const PublishAction$1 = ({
1692
2208
  * - the document is already published & not modified
1693
2209
  * - the document is being created & not modified
1694
2210
  * - 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
2211
  */
1698
- disabled: isCloning || isSubmitting || activeTab === "published" || !modified && isDocumentPublished || !modified && !document?.documentId || !canPublish || Boolean(!document?.documentId && !canCreate || document?.documentId && !canUpdate),
2212
+ disabled: isCloning || isSubmitting || isLoadingDraftRelations || activeTab === "published" || !modified && isDocumentPublished || !modified && !document?.documentId || !canPublish,
1699
2213
  label: formatMessage({
1700
2214
  id: "app.utils.publish",
1701
2215
  defaultMessage: "Publish"
1702
2216
  }),
1703
2217
  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));
2218
+ await performPublish();
2219
+ },
2220
+ dialog: hasDraftRelations ? {
2221
+ type: "dialog",
2222
+ variant: "danger",
2223
+ footer: null,
2224
+ title: formatMessage({
2225
+ id: getTranslation(`popUpwarning.warning.bulk-has-draft-relations.title`),
2226
+ defaultMessage: "Confirmation"
2227
+ }),
2228
+ content: formatMessage(
2229
+ {
2230
+ id: getTranslation(`popUpwarning.warning.bulk-has-draft-relations.message`),
2231
+ defaultMessage: "This entry is related to {count, plural, one {# draft entry} other {# draft entries}}. Publishing it could leave broken links in your app."
2232
+ },
2233
+ {
2234
+ count: totalDraftRelations
1733
2235
  }
1734
- } finally {
1735
- setSubmitting(false);
2236
+ ),
2237
+ onConfirm: async () => {
2238
+ await performPublish();
1736
2239
  }
1737
- }
2240
+ } : void 0
1738
2241
  };
1739
2242
  };
1740
2243
  PublishAction$1.type = "publish";
@@ -1750,10 +2253,6 @@ const UpdateAction = ({
1750
2253
  const cloneMatch = useMatch(CLONE_PATH);
1751
2254
  const isCloning = cloneMatch !== null;
1752
2255
  const { formatMessage } = useIntl();
1753
- const { canCreate, canUpdate } = useDocumentRBAC("UpdateAction", ({ canCreate: canCreate2, canUpdate: canUpdate2 }) => ({
1754
- canCreate: canCreate2,
1755
- canUpdate: canUpdate2
1756
- }));
1757
2256
  const { create, update, clone } = useDocumentActions();
1758
2257
  const [{ query, rawQuery }] = useQueryParams();
1759
2258
  const params = React.useMemo(() => buildValidParams(query), [query]);
@@ -1770,10 +2269,8 @@ const UpdateAction = ({
1770
2269
  * - the form is submitting
1771
2270
  * - the document is not modified & we're not cloning (you can save a clone entity straight away)
1772
2271
  * - 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
2272
  */
1776
- disabled: isSubmitting || !modified && !isCloning || activeTab === "published" || Boolean(!documentId && !canCreate || documentId && !canUpdate),
2273
+ disabled: isSubmitting || !modified && !isCloning || activeTab === "published",
1777
2274
  label: formatMessage({
1778
2275
  id: "content-manager.containers.Edit.save",
1779
2276
  defaultMessage: "Save"
@@ -1781,7 +2278,9 @@ const UpdateAction = ({
1781
2278
  onClick: async () => {
1782
2279
  setSubmitting(true);
1783
2280
  try {
1784
- const { errors } = await validate();
2281
+ const { errors } = await validate(true, {
2282
+ status: "draft"
2283
+ });
1785
2284
  if (errors) {
1786
2285
  toggleNotification({
1787
2286
  type: "danger",
@@ -1799,13 +2298,16 @@ const UpdateAction = ({
1799
2298
  documentId: cloneMatch.params.origin,
1800
2299
  params
1801
2300
  },
1802
- document
2301
+ transformData(document)
1803
2302
  );
1804
2303
  if ("data" in res) {
1805
- navigate({
1806
- pathname: `../${collectionType}/${model}/${res.data.documentId}`,
1807
- search: rawQuery
1808
- });
2304
+ navigate(
2305
+ {
2306
+ pathname: `../${res.data.documentId}`,
2307
+ search: rawQuery
2308
+ },
2309
+ { relative: "path" }
2310
+ );
1809
2311
  } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1810
2312
  setErrors(formatValidationErrors(res.error));
1811
2313
  }
@@ -1817,7 +2319,7 @@ const UpdateAction = ({
1817
2319
  documentId,
1818
2320
  params
1819
2321
  },
1820
- document
2322
+ transformData(document)
1821
2323
  );
1822
2324
  if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1823
2325
  setErrors(formatValidationErrors(res.error));
@@ -1830,13 +2332,16 @@ const UpdateAction = ({
1830
2332
  model,
1831
2333
  params
1832
2334
  },
1833
- document
2335
+ transformData(document)
1834
2336
  );
1835
2337
  if ("data" in res && collectionType !== SINGLE_TYPES) {
1836
- navigate({
1837
- pathname: `../${collectionType}/${model}/${res.data.documentId}`,
1838
- search: rawQuery
1839
- });
2338
+ navigate(
2339
+ {
2340
+ pathname: `../${res.data.documentId}`,
2341
+ search: rawQuery
2342
+ },
2343
+ { replace: true, relative: "path" }
2344
+ );
1840
2345
  } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1841
2346
  setErrors(formatValidationErrors(res.error));
1842
2347
  }
@@ -1880,7 +2385,7 @@ const UnpublishAction$1 = ({
1880
2385
  id: "app.utils.unpublish",
1881
2386
  defaultMessage: "Unpublish"
1882
2387
  }),
1883
- icon: /* @__PURE__ */ jsx(StyledCrossCircle, {}),
2388
+ icon: /* @__PURE__ */ jsx(Cross, {}),
1884
2389
  onClick: async () => {
1885
2390
  if (!documentId && collectionType !== SINGLE_TYPES || isDocumentModified) {
1886
2391
  if (!documentId) {
@@ -1992,7 +2497,7 @@ const DiscardAction = ({
1992
2497
  id: "content-manager.actions.discard.label",
1993
2498
  defaultMessage: "Discard changes"
1994
2499
  }),
1995
- icon: /* @__PURE__ */ jsx(StyledCrossCircle, {}),
2500
+ icon: /* @__PURE__ */ jsx(Cross, {}),
1996
2501
  position: ["panel", "table-row"],
1997
2502
  variant: "danger",
1998
2503
  dialog: {
@@ -2020,11 +2525,6 @@ const DiscardAction = ({
2020
2525
  };
2021
2526
  };
2022
2527
  DiscardAction.type = "discard";
2023
- const StyledCrossCircle = styled(CrossCircle)`
2024
- path {
2025
- fill: currentColor;
2026
- }
2027
- `;
2028
2528
  const DEFAULT_ACTIONS = [PublishAction$1, UpdateAction, UnpublishAction$1, DiscardAction];
2029
2529
  const intervals = ["years", "months", "days", "hours", "minutes", "seconds"];
2030
2530
  const RelativeTime = React.forwardRef(
@@ -2072,7 +2572,7 @@ const getDisplayName = ({
2072
2572
  };
2073
2573
  const capitalise = (str) => str.charAt(0).toUpperCase() + str.slice(1);
2074
2574
  const DocumentStatus = ({ status = "draft", ...restProps }) => {
2075
- const statusVariant = status === "draft" ? "primary" : status === "published" ? "success" : "alternative";
2575
+ const statusVariant = status === "draft" ? "secondary" : status === "published" ? "success" : "alternative";
2076
2576
  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
2577
  };
2078
2578
  const Header = ({ isCreating, status, title: documentTitle = "Untitled" }) => {
@@ -2082,23 +2582,13 @@ const Header = ({ isCreating, status, title: documentTitle = "Untitled" }) => {
2082
2582
  id: "content-manager.containers.edit.title.new",
2083
2583
  defaultMessage: "Create an entry"
2084
2584
  }) : documentTitle;
2085
- return /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", paddingTop: 8, paddingBottom: 4, gap: 3, children: [
2585
+ return /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", paddingTop: 6, paddingBottom: 4, gap: 2, children: [
2086
2586
  /* @__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
2587
+ /* @__PURE__ */ jsxs(Flex, { width: "100%", justifyContent: "space-between", gap: "80px", alignItems: "flex-start", children: [
2588
+ /* @__PURE__ */ jsx(Typography, { variant: "alpha", tag: "h1", children: title }),
2589
+ /* @__PURE__ */ jsx(HeaderToolbar, {})
2590
+ ] }),
2591
+ status ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(DocumentStatus, { status: isCloning ? "draft" : status }) }) : null
2102
2592
  ] });
2103
2593
  };
2104
2594
  const HeaderToolbar = () => {
@@ -2181,12 +2671,12 @@ const Information = ({ activeTab }) => {
2181
2671
  isDisplayed: !!publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME],
2182
2672
  label: formatMessage({
2183
2673
  id: "content-manager.containers.edit.information.last-published.label",
2184
- defaultMessage: "Last published"
2674
+ defaultMessage: "Published"
2185
2675
  }),
2186
2676
  value: formatMessage(
2187
2677
  {
2188
2678
  id: "content-manager.containers.edit.information.last-published.value",
2189
- defaultMessage: `Published {time}{isAnonymous, select, true {} other { by {author}}}`
2679
+ defaultMessage: `{time}{isAnonymous, select, true {} other { by {author}}}`
2190
2680
  },
2191
2681
  {
2192
2682
  time: /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME]) }),
@@ -2199,12 +2689,12 @@ const Information = ({ activeTab }) => {
2199
2689
  isDisplayed: !!createAndUpdateDocument?.[UPDATED_AT_ATTRIBUTE_NAME],
2200
2690
  label: formatMessage({
2201
2691
  id: "content-manager.containers.edit.information.last-draft.label",
2202
- defaultMessage: "Last draft"
2692
+ defaultMessage: "Updated"
2203
2693
  }),
2204
2694
  value: formatMessage(
2205
2695
  {
2206
2696
  id: "content-manager.containers.edit.information.last-draft.value",
2207
- defaultMessage: `Modified {time}{isAnonymous, select, true {} other { by {author}}}`
2697
+ defaultMessage: `{time}{isAnonymous, select, true {} other { by {author}}}`
2208
2698
  },
2209
2699
  {
2210
2700
  time: /* @__PURE__ */ jsx(
@@ -2222,12 +2712,12 @@ const Information = ({ activeTab }) => {
2222
2712
  isDisplayed: !!createAndUpdateDocument?.[CREATED_AT_ATTRIBUTE_NAME],
2223
2713
  label: formatMessage({
2224
2714
  id: "content-manager.containers.edit.information.document.label",
2225
- defaultMessage: "Document"
2715
+ defaultMessage: "Created"
2226
2716
  }),
2227
2717
  value: formatMessage(
2228
2718
  {
2229
2719
  id: "content-manager.containers.edit.information.document.value",
2230
- defaultMessage: `Created {time}{isAnonymous, select, true {} other { by {author}}}`
2720
+ defaultMessage: `{time}{isAnonymous, select, true {} other { by {author}}}`
2231
2721
  },
2232
2722
  {
2233
2723
  time: /* @__PURE__ */ jsx(
@@ -2265,25 +2755,77 @@ const Information = ({ activeTab }) => {
2265
2755
  );
2266
2756
  };
2267
2757
  const HeaderActions = ({ actions: actions2 }) => {
2268
- return /* @__PURE__ */ jsx(Flex, { children: actions2.map((action) => {
2269
- if ("options" in action) {
2758
+ const [dialogId, setDialogId] = React.useState(null);
2759
+ const handleClick = (action) => async (e) => {
2760
+ if (!("options" in action)) {
2761
+ const { onClick = () => false, dialog, id } = action;
2762
+ const muteDialog = await onClick(e);
2763
+ if (dialog && !muteDialog) {
2764
+ e.preventDefault();
2765
+ setDialogId(id);
2766
+ }
2767
+ }
2768
+ };
2769
+ const handleClose = () => {
2770
+ setDialogId(null);
2771
+ };
2772
+ return /* @__PURE__ */ jsx(Flex, { gap: 1, children: actions2.map((action) => {
2773
+ if (action.options) {
2270
2774
  return /* @__PURE__ */ jsx(
2271
2775
  SingleSelect,
2272
2776
  {
2273
2777
  size: "S",
2274
- disabled: action.disabled,
2275
- "aria-label": action.label,
2276
2778
  onChange: action.onSelect,
2277
- value: action.value,
2779
+ "aria-label": action.label,
2780
+ ...action,
2278
2781
  children: action.options.map(({ label, ...option }) => /* @__PURE__ */ jsx(SingleSelectOption, { ...option, children: label }, option.value))
2279
2782
  },
2280
2783
  action.id
2281
2784
  );
2282
2785
  } else {
2283
- return null;
2786
+ if (action.type === "icon") {
2787
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
2788
+ /* @__PURE__ */ jsx(
2789
+ IconButton,
2790
+ {
2791
+ disabled: action.disabled,
2792
+ label: action.label,
2793
+ size: "S",
2794
+ onClick: handleClick(action),
2795
+ children: action.icon
2796
+ }
2797
+ ),
2798
+ action.dialog ? /* @__PURE__ */ jsx(
2799
+ HeaderActionDialog,
2800
+ {
2801
+ ...action.dialog,
2802
+ isOpen: dialogId === action.id,
2803
+ onClose: handleClose
2804
+ }
2805
+ ) : null
2806
+ ] }, action.id);
2807
+ }
2284
2808
  }
2285
2809
  }) });
2286
2810
  };
2811
+ const HeaderActionDialog = ({
2812
+ onClose,
2813
+ onCancel,
2814
+ title,
2815
+ content: Content,
2816
+ isOpen
2817
+ }) => {
2818
+ const handleClose = async () => {
2819
+ if (onCancel) {
2820
+ await onCancel();
2821
+ }
2822
+ onClose();
2823
+ };
2824
+ return /* @__PURE__ */ jsx(Dialog.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxs(Dialog.Content, { children: [
2825
+ /* @__PURE__ */ jsx(Dialog.Header, { children: title }),
2826
+ typeof Content === "function" ? /* @__PURE__ */ jsx(Content, { onClose: handleClose }) : Content
2827
+ ] }) });
2828
+ };
2287
2829
  const ConfigureTheViewAction = ({ collectionType, model }) => {
2288
2830
  const navigate = useNavigate();
2289
2831
  const { formatMessage } = useIntl();
@@ -2324,12 +2866,16 @@ const DeleteAction$1 = ({ documentId, model, collectionType, document }) => {
2324
2866
  const { delete: deleteAction } = useDocumentActions();
2325
2867
  const { toggleNotification } = useNotification();
2326
2868
  const setSubmitting = useForm("DeleteAction", (state) => state.setSubmitting);
2869
+ const isLocalized = document?.locale != null;
2327
2870
  return {
2328
2871
  disabled: !canDelete || !document,
2329
- label: formatMessage({
2330
- id: "content-manager.actions.delete.label",
2331
- defaultMessage: "Delete document"
2332
- }),
2872
+ label: formatMessage(
2873
+ {
2874
+ id: "content-manager.actions.delete.label",
2875
+ defaultMessage: "Delete entry{isLocalized, select, true { (all locales)} other {}}"
2876
+ },
2877
+ { isLocalized }
2878
+ ),
2333
2879
  icon: /* @__PURE__ */ jsx(Trash, {}),
2334
2880
  dialog: {
2335
2881
  type: "dialog",
@@ -2365,423 +2911,121 @@ const DeleteAction$1 = ({ documentId, model, collectionType, document }) => {
2365
2911
  const res = await deleteAction({
2366
2912
  documentId,
2367
2913
  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
2914
+ collectionType,
2915
+ params: {
2916
+ locale: "*"
2917
+ }
2918
+ });
2919
+ if (!("error" in res)) {
2920
+ navigate({ pathname: `../${collectionType}/${model}` }, { replace: true });
2921
+ }
2922
+ } finally {
2923
+ if (!listViewPathMatch) {
2924
+ setSubmitting(false);
2925
+ }
2672
2926
  }
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
2927
+ }
2694
2928
  },
2695
- options: {
2696
- ...schema?.options,
2697
- ...schema?.pluginOptions,
2698
- ...data.contentType.options
2699
- }
2929
+ variant: "danger",
2930
+ position: ["header", "table-row"]
2700
2931
  };
2701
2932
  };
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
- );
2933
+ DeleteAction$1.type = "delete";
2934
+ const DEFAULT_HEADER_ACTIONS = [EditTheModelAction, ConfigureTheViewAction, DeleteAction$1];
2935
+ const Panels = () => {
2936
+ const isCloning = useMatch(CLONE_PATH) !== null;
2937
+ const [
2938
+ {
2939
+ query: { status }
2940
+ }
2941
+ ] = useQueryParams({
2942
+ status: "draft"
2943
+ });
2944
+ const { model, id, document, meta, collectionType } = useDoc();
2945
+ const plugins = useStrapiApp("Panels", (state) => state.plugins);
2946
+ const props = {
2947
+ activeTab: status,
2948
+ model,
2949
+ documentId: id,
2950
+ document: isCloning ? void 0 : document,
2951
+ meta: isCloning ? void 0 : meta,
2952
+ collectionType
2953
+ };
2954
+ return /* @__PURE__ */ jsx(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: /* @__PURE__ */ jsx(
2955
+ DescriptionComponentRenderer,
2956
+ {
2957
+ props,
2958
+ descriptions: plugins["content-manager"].apis.getEditViewSidePanels(),
2959
+ children: (panels) => panels.map(({ content, id: id2, ...description }) => /* @__PURE__ */ jsx(Panel, { ...description, children: content }, id2))
2960
+ }
2961
+ ) });
2731
2962
  };
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
- );
2963
+ const ActionsPanel = () => {
2964
+ const { formatMessage } = useIntl();
2753
2965
  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
- }
2966
+ title: formatMessage({
2967
+ id: "content-manager.containers.edit.panels.default.title",
2968
+ defaultMessage: "Entry"
2969
+ }),
2970
+ content: /* @__PURE__ */ jsx(ActionsPanelContent, {})
2762
2971
  };
2763
2972
  };
2764
- const convertListLayoutToFieldLayouts = (columns, attributes = {}, metadatas, components, schemas = []) => {
2765
- return columns.map((name) => {
2766
- const attribute = attributes[name];
2767
- if (!attribute) {
2768
- return null;
2973
+ ActionsPanel.type = "actions";
2974
+ const ActionsPanelContent = () => {
2975
+ const isCloning = useMatch(CLONE_PATH) !== null;
2976
+ const [
2977
+ {
2978
+ query: { status = "draft" }
2769
2979
  }
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);
2980
+ ] = useQueryParams();
2981
+ const { model, id, document, meta, collectionType } = useDoc();
2982
+ const plugins = useStrapiApp("ActionsPanel", (state) => state.plugins);
2983
+ const props = {
2984
+ activeTab: status,
2985
+ model,
2986
+ documentId: id,
2987
+ document: isCloning ? void 0 : document,
2988
+ meta: isCloning ? void 0 : meta,
2989
+ collectionType
2990
+ };
2991
+ return /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, width: "100%", children: [
2992
+ /* @__PURE__ */ jsx(
2993
+ DescriptionComponentRenderer,
2994
+ {
2995
+ props,
2996
+ descriptions: plugins["content-manager"].apis.getDocumentActions(),
2997
+ children: (actions2) => /* @__PURE__ */ jsx(DocumentActions, { actions: actions2 })
2998
+ }
2999
+ ),
3000
+ /* @__PURE__ */ jsx(InjectionZone, { area: "editView.right-links", slug: model })
3001
+ ] });
2784
3002
  };
3003
+ const Panel = React.forwardRef(({ children, title }, ref) => {
3004
+ return /* @__PURE__ */ jsxs(
3005
+ Flex,
3006
+ {
3007
+ ref,
3008
+ tag: "aside",
3009
+ "aria-labelledby": "additional-information",
3010
+ background: "neutral0",
3011
+ borderColor: "neutral150",
3012
+ hasRadius: true,
3013
+ paddingBottom: 4,
3014
+ paddingLeft: 4,
3015
+ paddingRight: 4,
3016
+ paddingTop: 4,
3017
+ shadow: "tableShadow",
3018
+ gap: 3,
3019
+ direction: "column",
3020
+ justifyContent: "stretch",
3021
+ alignItems: "flex-start",
3022
+ children: [
3023
+ /* @__PURE__ */ jsx(Typography, { tag: "h2", variant: "sigma", textTransform: "uppercase", children: title }),
3024
+ children
3025
+ ]
3026
+ }
3027
+ );
3028
+ });
2785
3029
  const ConfirmBulkActionDialog = ({
2786
3030
  onToggleDialog,
2787
3031
  isOpen = false,
@@ -2789,7 +3033,7 @@ const ConfirmBulkActionDialog = ({
2789
3033
  endAction
2790
3034
  }) => {
2791
3035
  const { formatMessage } = useIntl();
2792
- return /* @__PURE__ */ jsx(Dialog.Root, { onOpenChange: onToggleDialog, open: isOpen, children: /* @__PURE__ */ jsxs(Dialog.Content, { children: [
3036
+ return /* @__PURE__ */ jsx(Dialog.Root, { open: isOpen, children: /* @__PURE__ */ jsxs(Dialog.Content, { children: [
2793
3037
  /* @__PURE__ */ jsx(Dialog.Header, { children: formatMessage({
2794
3038
  id: "app.components.ConfirmDialog.title",
2795
3039
  defaultMessage: "Confirmation"
@@ -2820,6 +3064,7 @@ const ConfirmDialogPublishAll = ({
2820
3064
  const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler(getTranslation);
2821
3065
  const { model, schema } = useDoc();
2822
3066
  const [{ query }] = useQueryParams();
3067
+ const enableDraftRelationsCount = false;
2823
3068
  const {
2824
3069
  data: countDraftRelations = 0,
2825
3070
  isLoading,
@@ -2831,7 +3076,7 @@ const ConfirmDialogPublishAll = ({
2831
3076
  locale: query?.plugins?.i18n?.locale
2832
3077
  },
2833
3078
  {
2834
- skip: selectedEntries.length === 0
3079
+ skip: !enableDraftRelationsCount
2835
3080
  }
2836
3081
  );
2837
3082
  React.useEffect(() => {
@@ -2910,7 +3155,14 @@ const formatErrorMessages = (errors, parentKey, formatMessage) => {
2910
3155
  )
2911
3156
  );
2912
3157
  } else {
2913
- messages.push(...formatErrorMessages(value, currentKey, formatMessage));
3158
+ messages.push(
3159
+ ...formatErrorMessages(
3160
+ // @ts-expect-error TODO: check why value is not compatible with FormErrors
3161
+ value,
3162
+ currentKey,
3163
+ formatMessage
3164
+ )
3165
+ );
2914
3166
  }
2915
3167
  } else {
2916
3168
  messages.push(
@@ -3009,7 +3261,7 @@ const SelectedEntriesTableContent = ({
3009
3261
  status: row.status
3010
3262
  }
3011
3263
  ) }),
3012
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
3264
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Flex, { children: /* @__PURE__ */ jsx(
3013
3265
  IconButton,
3014
3266
  {
3015
3267
  tag: Link,
@@ -3032,9 +3284,10 @@ const SelectedEntriesTableContent = ({
3032
3284
  ),
3033
3285
  target: "_blank",
3034
3286
  marginLeft: "auto",
3035
- children: /* @__PURE__ */ jsx(Pencil, {})
3287
+ variant: "ghost",
3288
+ children: /* @__PURE__ */ jsx(Pencil, { width: "1.6rem", height: "1.6rem" })
3036
3289
  }
3037
- ) })
3290
+ ) }) })
3038
3291
  ] }, row.id)) })
3039
3292
  ] });
3040
3293
  };
@@ -3071,7 +3324,13 @@ const SelectedEntriesModalContent = ({
3071
3324
  );
3072
3325
  const { rows, validationErrors } = React.useMemo(() => {
3073
3326
  if (data.length > 0 && schema) {
3074
- const validate = createYupSchema(schema.attributes, components);
3327
+ const validate = createYupSchema(
3328
+ schema.attributes,
3329
+ components,
3330
+ // Since this is the "Publish" action, the validation
3331
+ // schema must enforce the rules for published entities
3332
+ { status: "published" }
3333
+ );
3075
3334
  const validationErrors2 = {};
3076
3335
  const rows2 = data.map((entry) => {
3077
3336
  try {
@@ -3421,7 +3680,7 @@ const TableActions = ({ document }) => {
3421
3680
  DescriptionComponentRenderer,
3422
3681
  {
3423
3682
  props,
3424
- descriptions: plugins["content-manager"].apis.getDocumentActions(),
3683
+ descriptions: plugins["content-manager"].apis.getDocumentActions().filter((action) => action.name !== "PublishAction"),
3425
3684
  children: (actions2) => {
3426
3685
  const tableRowActions = actions2.filter((action) => {
3427
3686
  const positions = Array.isArray(action.position) ? action.position : [action.position];
@@ -3532,7 +3791,7 @@ const CloneAction = ({ model, documentId }) => {
3532
3791
  }),
3533
3792
  content: /* @__PURE__ */ jsx(AutoCloneFailureModalBody, { prohibitedFields }),
3534
3793
  footer: ({ onClose }) => {
3535
- return /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", children: [
3794
+ return /* @__PURE__ */ jsxs(Modal.Footer, { children: [
3536
3795
  /* @__PURE__ */ jsx(Button, { onClick: onClose, variant: "tertiary", children: formatMessage({
3537
3796
  id: "cancel",
3538
3797
  defaultMessage: "Cancel"
@@ -3573,8 +3832,7 @@ class ContentManagerPlugin {
3573
3832
  documentActions = [
3574
3833
  ...DEFAULT_ACTIONS,
3575
3834
  ...DEFAULT_TABLE_ROW_ACTIONS,
3576
- ...DEFAULT_HEADER_ACTIONS,
3577
- HistoryAction
3835
+ ...DEFAULT_HEADER_ACTIONS
3578
3836
  ];
3579
3837
  editViewSidePanels = [ActionsPanel];
3580
3838
  headerActions = [];
@@ -3663,6 +3921,52 @@ const getPrintableType = (value) => {
3663
3921
  }
3664
3922
  return nativeType;
3665
3923
  };
3924
+ const HistoryAction = ({ model, document }) => {
3925
+ const { formatMessage } = useIntl();
3926
+ const [{ query }] = useQueryParams();
3927
+ const navigate = useNavigate();
3928
+ const pluginsQueryParams = stringify({ plugins: query.plugins }, { encode: false });
3929
+ if (!window.strapi.features.isEnabled("cms-content-history")) {
3930
+ return null;
3931
+ }
3932
+ return {
3933
+ icon: /* @__PURE__ */ jsx(ClockCounterClockwise, {}),
3934
+ label: formatMessage({
3935
+ id: "content-manager.history.document-action",
3936
+ defaultMessage: "Content History"
3937
+ }),
3938
+ onClick: () => navigate({ pathname: "history", search: pluginsQueryParams }),
3939
+ disabled: (
3940
+ /**
3941
+ * The user is creating a new document.
3942
+ * It hasn't been saved yet, so there's no history to go to
3943
+ */
3944
+ !document || /**
3945
+ * The document has been created but the current dimension has never been saved.
3946
+ * For example, the user is creating a new locale in an existing document,
3947
+ * so there's no history for the document in that locale
3948
+ */
3949
+ !document.id || /**
3950
+ * History is only available for content types created by the user.
3951
+ * These have the `api::` prefix, as opposed to the ones created by Strapi or plugins,
3952
+ * which start with `admin::` or `plugin::`
3953
+ */
3954
+ !model.startsWith("api::")
3955
+ ),
3956
+ position: "header"
3957
+ };
3958
+ };
3959
+ HistoryAction.type = "history";
3960
+ const historyAdmin = {
3961
+ bootstrap(app) {
3962
+ const { addDocumentAction } = app.getPlugin("content-manager").apis;
3963
+ addDocumentAction((actions2) => {
3964
+ const indexOfDeleteAction = actions2.findIndex((action) => action.type === "delete");
3965
+ actions2.splice(indexOfDeleteAction, 0, HistoryAction);
3966
+ return actions2;
3967
+ });
3968
+ }
3969
+ };
3666
3970
  const initialState = {
3667
3971
  collectionTypeLinks: [],
3668
3972
  components: [],
@@ -3713,15 +4017,29 @@ const index = {
3713
4017
  defaultMessage: "Content Manager"
3714
4018
  },
3715
4019
  permissions: [],
3716
- Component: () => import("./layout-B1Z-9koY.mjs").then((mod) => ({ default: mod.Layout })),
3717
4020
  position: 1
3718
4021
  });
4022
+ app.router.addRoute({
4023
+ path: "content-manager/*",
4024
+ lazy: async () => {
4025
+ const { Layout } = await import("./layout-PNlIceEV.mjs");
4026
+ return {
4027
+ Component: Layout
4028
+ };
4029
+ },
4030
+ children: routes
4031
+ });
3719
4032
  app.registerPlugin(cm.config);
3720
4033
  },
4034
+ bootstrap(app) {
4035
+ if (typeof historyAdmin.bootstrap === "function") {
4036
+ historyAdmin.bootstrap(app);
4037
+ }
4038
+ },
3721
4039
  async registerTrads({ locales }) {
3722
4040
  const importedTrads = await Promise.all(
3723
4041
  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 }) => {
4042
+ 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
4043
  return {
3726
4044
  data: prefixPluginTranslations(data, PLUGIN_ID),
3727
4045
  locale
@@ -3749,7 +4067,8 @@ export {
3749
4067
  InjectionZone as I,
3750
4068
  useDocument as J,
3751
4069
  index as K,
3752
- useDocumentActions as L,
4070
+ useContentManagerContext as L,
4071
+ useDocumentActions as M,
3753
4072
  Panels as P,
3754
4073
  RelativeTime as R,
3755
4074
  SINGLE_TYPES as S,
@@ -3767,11 +4086,11 @@ export {
3767
4086
  PERMISSIONS as k,
3768
4087
  DocumentRBAC as l,
3769
4088
  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,
4089
+ CLONE_PATH as n,
4090
+ useDocLayout as o,
4091
+ useGetContentTypeConfigurationQuery as p,
4092
+ CREATOR_FIELDS as q,
4093
+ getMainField as r,
3775
4094
  setInitialData as s,
3776
4095
  getDisplayName as t,
3777
4096
  useContentTypeSchema as u,
@@ -3781,4 +4100,4 @@ export {
3781
4100
  capitalise as y,
3782
4101
  useUpdateContentTypeConfigurationMutation as z
3783
4102
  };
3784
- //# sourceMappingURL=index-6kKXK7y8.mjs.map
4103
+ //# sourceMappingURL=index-Bz1SCyIj.mjs.map