@strapi/content-manager 0.0.0-experimental.edc24aaa3bb5a90fa5fd4fee208167dd4e2e38d4 → 0.0.0-experimental.f0a0bc26f5ef0693aaea2a616bc6b816cfee54b6

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 (198) hide show
  1. package/dist/_chunks/{ComponentConfigurationPage-BAgyHiMm.mjs → ComponentConfigurationPage-DhWA-JzT.mjs} +4 -4
  2. package/dist/_chunks/{ComponentConfigurationPage-BAgyHiMm.mjs.map → ComponentConfigurationPage-DhWA-JzT.mjs.map} +1 -1
  3. package/dist/_chunks/{ComponentConfigurationPage-5ukroXAh.js → ComponentConfigurationPage-UduDBv3m.js} +4 -4
  4. package/dist/_chunks/{ComponentConfigurationPage-5ukroXAh.js.map → ComponentConfigurationPage-UduDBv3m.js.map} +1 -1
  5. package/dist/_chunks/{EditConfigurationPage-DmoXawIh.mjs → EditConfigurationPage-5tmx_7Hp.mjs} +4 -4
  6. package/dist/_chunks/{EditConfigurationPage-DmoXawIh.mjs.map → EditConfigurationPage-5tmx_7Hp.mjs.map} +1 -1
  7. package/dist/_chunks/{EditConfigurationPage-Xp7lun0f.js → EditConfigurationPage-Cp9UzUfs.js} +4 -4
  8. package/dist/_chunks/{EditConfigurationPage-Xp7lun0f.js.map → EditConfigurationPage-Cp9UzUfs.js.map} +1 -1
  9. package/dist/_chunks/{EditViewPage-BLsjc5F-.mjs → EditViewPage-BKoISUOu.mjs} +63 -12
  10. package/dist/_chunks/EditViewPage-BKoISUOu.mjs.map +1 -0
  11. package/dist/_chunks/{EditViewPage-C-ukDOB7.js → EditViewPage-C7l2Emuj.js} +62 -11
  12. package/dist/_chunks/EditViewPage-C7l2Emuj.js.map +1 -0
  13. package/dist/_chunks/{Field-Bfph5SOd.js → Field-BPSJpDfE.js} +211 -120
  14. package/dist/_chunks/Field-BPSJpDfE.js.map +1 -0
  15. package/dist/_chunks/{Field-Cs7duwWd.mjs → Field-BZxzYf1x.mjs} +210 -119
  16. package/dist/_chunks/Field-BZxzYf1x.mjs.map +1 -0
  17. package/dist/_chunks/{Form-Dg_GS5TQ.mjs → Form-8qyOU6YG.mjs} +36 -17
  18. package/dist/_chunks/Form-8qyOU6YG.mjs.map +1 -0
  19. package/dist/_chunks/{Form-CPYqIWDG.js → Form-DLkqDd2G.js} +36 -17
  20. package/dist/_chunks/Form-DLkqDd2G.js.map +1 -0
  21. package/dist/_chunks/{History-DNQkXANT.js → History-DYMicybF.js} +41 -98
  22. package/dist/_chunks/History-DYMicybF.js.map +1 -0
  23. package/dist/_chunks/{History-wrnHqf09.mjs → History-N_kRb1Yr.mjs} +43 -100
  24. package/dist/_chunks/History-N_kRb1Yr.mjs.map +1 -0
  25. package/dist/_chunks/{ListConfigurationPage-DScmJVkW.mjs → ListConfigurationPage-BM3qVxug.mjs} +18 -7
  26. package/dist/_chunks/ListConfigurationPage-BM3qVxug.mjs.map +1 -0
  27. package/dist/_chunks/{ListConfigurationPage-CUQxfpjT.js → ListConfigurationPage-rUF9iGWq.js} +17 -6
  28. package/dist/_chunks/ListConfigurationPage-rUF9iGWq.js.map +1 -0
  29. package/dist/_chunks/{ListViewPage-BsLiH2-2.js → ListViewPage-BSLzd7cZ.js} +108 -76
  30. package/dist/_chunks/ListViewPage-BSLzd7cZ.js.map +1 -0
  31. package/dist/_chunks/{ListViewPage-C4IvrMgY.mjs → ListViewPage-CWilGbZb.mjs} +106 -74
  32. package/dist/_chunks/ListViewPage-CWilGbZb.mjs.map +1 -0
  33. package/dist/_chunks/{NoContentTypePage-BZ-PnGAf.js → NoContentTypePage-CQccVhIX.js} +2 -2
  34. package/dist/_chunks/{NoContentTypePage-BZ-PnGAf.js.map → NoContentTypePage-CQccVhIX.js.map} +1 -1
  35. package/dist/_chunks/{NoContentTypePage-Djg8nPlj.mjs → NoContentTypePage-VWYlePwI.mjs} +2 -2
  36. package/dist/_chunks/{NoContentTypePage-Djg8nPlj.mjs.map → NoContentTypePage-VWYlePwI.mjs.map} +1 -1
  37. package/dist/_chunks/{NoPermissionsPage-_lUqjGW3.js → NoPermissionsPage-Af32Gg2m.js} +2 -2
  38. package/dist/_chunks/{NoPermissionsPage-_lUqjGW3.js.map → NoPermissionsPage-Af32Gg2m.js.map} +1 -1
  39. package/dist/_chunks/{NoPermissionsPage-DSP7R-hv.mjs → NoPermissionsPage-CS2tCmfr.mjs} +2 -2
  40. package/dist/_chunks/{NoPermissionsPage-DSP7R-hv.mjs.map → NoPermissionsPage-CS2tCmfr.mjs.map} +1 -1
  41. package/dist/_chunks/Preview-D4KzuJFL.js +291 -0
  42. package/dist/_chunks/Preview-D4KzuJFL.js.map +1 -0
  43. package/dist/_chunks/Preview-kPkuZbBJ.mjs +272 -0
  44. package/dist/_chunks/Preview-kPkuZbBJ.mjs.map +1 -0
  45. package/dist/_chunks/{Relations-BZr8tL0R.mjs → Relations-5k27Rh54.mjs} +73 -37
  46. package/dist/_chunks/Relations-5k27Rh54.mjs.map +1 -0
  47. package/dist/_chunks/{Relations-CtELXYIK.js → Relations-D_Ki5aVM.js} +72 -36
  48. package/dist/_chunks/Relations-D_Ki5aVM.js.map +1 -0
  49. package/dist/_chunks/{en-uOUIxfcQ.js → en-BK8Xyl5I.js} +28 -15
  50. package/dist/_chunks/{en-uOUIxfcQ.js.map → en-BK8Xyl5I.js.map} +1 -1
  51. package/dist/_chunks/{en-BrCTWlZv.mjs → en-Dtk_ot79.mjs} +28 -15
  52. package/dist/_chunks/{en-BrCTWlZv.mjs.map → en-Dtk_ot79.mjs.map} +1 -1
  53. package/dist/_chunks/{es-EUonQTon.js → es-9K52xZIr.js} +2 -2
  54. package/dist/_chunks/{ja-CcFe8diO.js.map → es-9K52xZIr.js.map} +1 -1
  55. package/dist/_chunks/{es-CeXiYflN.mjs → es-D34tqjMw.mjs} +2 -2
  56. package/dist/_chunks/{es-CeXiYflN.mjs.map → es-D34tqjMw.mjs.map} +1 -1
  57. package/dist/_chunks/{fr-CD9VFbPM.mjs → fr--pg5jUbt.mjs} +13 -3
  58. package/dist/_chunks/{fr-CD9VFbPM.mjs.map → fr--pg5jUbt.mjs.map} +1 -1
  59. package/dist/_chunks/{fr-B7kGGg3E.js → fr-B2Kyv8Z9.js} +13 -3
  60. package/dist/_chunks/{fr-B7kGGg3E.js.map → fr-B2Kyv8Z9.js.map} +1 -1
  61. package/dist/_chunks/{index-c_5DdJi-.mjs → index-BLPa8Dq-.mjs} +1201 -924
  62. package/dist/_chunks/index-BLPa8Dq-.mjs.map +1 -0
  63. package/dist/_chunks/{index-OerGjbAN.js → index-DwOsF7wF.js} +1182 -904
  64. package/dist/_chunks/index-DwOsF7wF.js.map +1 -0
  65. package/dist/_chunks/{ja-CcFe8diO.js → ja-7sfIbjxE.js} +2 -2
  66. package/dist/_chunks/{es-EUonQTon.js.map → ja-7sfIbjxE.js.map} +1 -1
  67. package/dist/_chunks/{ja-CtsUxOvk.mjs → ja-BHqhDq4V.mjs} +2 -2
  68. package/dist/_chunks/{ja-CtsUxOvk.mjs.map → ja-BHqhDq4V.mjs.map} +1 -1
  69. package/dist/_chunks/{layout-oPBiO7RY.mjs → layout-2Si0j0jO.mjs} +22 -9
  70. package/dist/_chunks/layout-2Si0j0jO.mjs.map +1 -0
  71. package/dist/_chunks/{layout-Ci7qHlFb.js → layout-CN2bFL9V.js} +21 -8
  72. package/dist/_chunks/layout-CN2bFL9V.js.map +1 -0
  73. package/dist/_chunks/{objects-gigeqt7s.js → objects-BcXOv6_9.js} +2 -4
  74. package/dist/_chunks/{objects-gigeqt7s.js.map → objects-BcXOv6_9.js.map} +1 -1
  75. package/dist/_chunks/{objects-mKMAmfec.mjs → objects-D6yBsdmx.mjs} +2 -4
  76. package/dist/_chunks/{objects-mKMAmfec.mjs.map → objects-D6yBsdmx.mjs.map} +1 -1
  77. package/dist/_chunks/{relations-COBpStiF.js → relations-B0E0XUY7.js} +6 -7
  78. package/dist/_chunks/relations-B0E0XUY7.js.map +1 -0
  79. package/dist/_chunks/{relations-BIdWFjdq.mjs → relations-CAxDjUJF.mjs} +6 -7
  80. package/dist/_chunks/relations-CAxDjUJF.mjs.map +1 -0
  81. package/dist/_chunks/{usePrev-B9w_-eYc.js → useDebounce-CtcjDB3L.js} +14 -1
  82. package/dist/_chunks/useDebounce-CtcjDB3L.js.map +1 -0
  83. package/dist/_chunks/useDebounce-DmuSJIF3.mjs +29 -0
  84. package/dist/_chunks/useDebounce-DmuSJIF3.mjs.map +1 -0
  85. package/dist/admin/index.js +2 -1
  86. package/dist/admin/index.js.map +1 -1
  87. package/dist/admin/index.mjs +5 -4
  88. package/dist/admin/src/exports.d.ts +1 -1
  89. package/dist/admin/src/history/services/historyVersion.d.ts +1 -1
  90. package/dist/admin/src/hooks/useDocument.d.ts +32 -1
  91. package/dist/admin/src/pages/EditView/EditViewPage.d.ts +9 -1
  92. package/dist/admin/src/pages/EditView/components/DocumentStatus.d.ts +2 -2
  93. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/EditorLayout.d.ts +2 -2
  94. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/WysiwygFooter.d.ts +2 -2
  95. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/WysiwygStyles.d.ts +4 -48
  96. package/dist/admin/src/pages/EditView/components/Header.d.ts +11 -11
  97. package/dist/admin/src/preview/components/PreviewContent.d.ts +2 -0
  98. package/dist/admin/src/preview/components/PreviewHeader.d.ts +2 -0
  99. package/dist/admin/src/preview/components/PreviewSidePanel.d.ts +3 -0
  100. package/dist/admin/src/preview/constants.d.ts +1 -0
  101. package/dist/admin/src/preview/index.d.ts +4 -0
  102. package/dist/admin/src/preview/pages/Preview.d.ts +11 -0
  103. package/dist/admin/src/preview/routes.d.ts +3 -0
  104. package/dist/admin/src/preview/services/preview.d.ts +3 -0
  105. package/dist/admin/src/router.d.ts +1 -1
  106. package/dist/admin/src/services/api.d.ts +1 -1
  107. package/dist/admin/src/services/components.d.ts +2 -2
  108. package/dist/admin/src/services/contentTypes.d.ts +3 -3
  109. package/dist/admin/src/services/documents.d.ts +19 -20
  110. package/dist/admin/src/services/init.d.ts +1 -1
  111. package/dist/admin/src/services/relations.d.ts +2 -2
  112. package/dist/admin/src/services/uid.d.ts +3 -3
  113. package/dist/admin/src/utils/validation.d.ts +4 -1
  114. package/dist/server/index.js +560 -235
  115. package/dist/server/index.js.map +1 -1
  116. package/dist/server/index.mjs +561 -236
  117. package/dist/server/index.mjs.map +1 -1
  118. package/dist/server/src/bootstrap.d.ts.map +1 -1
  119. package/dist/server/src/controllers/collection-types.d.ts.map +1 -1
  120. package/dist/server/src/controllers/index.d.ts.map +1 -1
  121. package/dist/server/src/controllers/relations.d.ts.map +1 -1
  122. package/dist/server/src/controllers/utils/metadata.d.ts +15 -1
  123. package/dist/server/src/controllers/utils/metadata.d.ts.map +1 -1
  124. package/dist/server/src/history/services/history.d.ts.map +1 -1
  125. package/dist/server/src/history/services/lifecycles.d.ts.map +1 -1
  126. package/dist/server/src/history/services/utils.d.ts +4 -4
  127. package/dist/server/src/history/services/utils.d.ts.map +1 -1
  128. package/dist/server/src/index.d.ts +4 -4
  129. package/dist/server/src/policies/hasPermissions.d.ts.map +1 -1
  130. package/dist/server/src/preview/constants.d.ts +2 -0
  131. package/dist/server/src/preview/constants.d.ts.map +1 -0
  132. package/dist/server/src/preview/controllers/index.d.ts +2 -0
  133. package/dist/server/src/preview/controllers/index.d.ts.map +1 -0
  134. package/dist/server/src/preview/controllers/preview.d.ts +13 -0
  135. package/dist/server/src/preview/controllers/preview.d.ts.map +1 -0
  136. package/dist/server/src/preview/controllers/validation/preview.d.ts +6 -0
  137. package/dist/server/src/preview/controllers/validation/preview.d.ts.map +1 -0
  138. package/dist/server/src/preview/index.d.ts +4 -0
  139. package/dist/server/src/preview/index.d.ts.map +1 -0
  140. package/dist/server/src/preview/routes/index.d.ts +8 -0
  141. package/dist/server/src/preview/routes/index.d.ts.map +1 -0
  142. package/dist/server/src/preview/routes/preview.d.ts +4 -0
  143. package/dist/server/src/preview/routes/preview.d.ts.map +1 -0
  144. package/dist/server/src/preview/services/index.d.ts +16 -0
  145. package/dist/server/src/preview/services/index.d.ts.map +1 -0
  146. package/dist/server/src/preview/services/preview-config.d.ts +32 -0
  147. package/dist/server/src/preview/services/preview-config.d.ts.map +1 -0
  148. package/dist/server/src/preview/services/preview.d.ts +12 -0
  149. package/dist/server/src/preview/services/preview.d.ts.map +1 -0
  150. package/dist/server/src/preview/utils.d.ts +19 -0
  151. package/dist/server/src/preview/utils.d.ts.map +1 -0
  152. package/dist/server/src/register.d.ts.map +1 -1
  153. package/dist/server/src/routes/index.d.ts.map +1 -1
  154. package/dist/server/src/services/document-manager.d.ts.map +1 -1
  155. package/dist/server/src/services/document-metadata.d.ts +8 -8
  156. package/dist/server/src/services/document-metadata.d.ts.map +1 -1
  157. package/dist/server/src/services/index.d.ts +4 -4
  158. package/dist/server/src/services/index.d.ts.map +1 -1
  159. package/dist/server/src/services/permission-checker.d.ts.map +1 -1
  160. package/dist/server/src/services/utils/configuration/index.d.ts +2 -2
  161. package/dist/server/src/services/utils/configuration/layouts.d.ts +2 -2
  162. package/dist/server/src/utils/index.d.ts +2 -0
  163. package/dist/server/src/utils/index.d.ts.map +1 -1
  164. package/dist/shared/contracts/collection-types.d.ts +3 -1
  165. package/dist/shared/contracts/collection-types.d.ts.map +1 -1
  166. package/dist/shared/contracts/index.d.ts +1 -0
  167. package/dist/shared/contracts/index.d.ts.map +1 -1
  168. package/dist/shared/contracts/preview.d.ts +27 -0
  169. package/dist/shared/contracts/preview.d.ts.map +1 -0
  170. package/dist/shared/index.js +4 -0
  171. package/dist/shared/index.js.map +1 -1
  172. package/dist/shared/index.mjs +4 -0
  173. package/dist/shared/index.mjs.map +1 -1
  174. package/package.json +14 -14
  175. package/dist/_chunks/EditViewPage-BLsjc5F-.mjs.map +0 -1
  176. package/dist/_chunks/EditViewPage-C-ukDOB7.js.map +0 -1
  177. package/dist/_chunks/Field-Bfph5SOd.js.map +0 -1
  178. package/dist/_chunks/Field-Cs7duwWd.mjs.map +0 -1
  179. package/dist/_chunks/Form-CPYqIWDG.js.map +0 -1
  180. package/dist/_chunks/Form-Dg_GS5TQ.mjs.map +0 -1
  181. package/dist/_chunks/History-DNQkXANT.js.map +0 -1
  182. package/dist/_chunks/History-wrnHqf09.mjs.map +0 -1
  183. package/dist/_chunks/ListConfigurationPage-CUQxfpjT.js.map +0 -1
  184. package/dist/_chunks/ListConfigurationPage-DScmJVkW.mjs.map +0 -1
  185. package/dist/_chunks/ListViewPage-BsLiH2-2.js.map +0 -1
  186. package/dist/_chunks/ListViewPage-C4IvrMgY.mjs.map +0 -1
  187. package/dist/_chunks/Relations-BZr8tL0R.mjs.map +0 -1
  188. package/dist/_chunks/Relations-CtELXYIK.js.map +0 -1
  189. package/dist/_chunks/index-OerGjbAN.js.map +0 -1
  190. package/dist/_chunks/index-c_5DdJi-.mjs.map +0 -1
  191. package/dist/_chunks/layout-Ci7qHlFb.js.map +0 -1
  192. package/dist/_chunks/layout-oPBiO7RY.mjs.map +0 -1
  193. package/dist/_chunks/relations-BIdWFjdq.mjs.map +0 -1
  194. package/dist/_chunks/relations-COBpStiF.js.map +0 -1
  195. package/dist/_chunks/usePrev-B9w_-eYc.js.map +0 -1
  196. package/dist/_chunks/usePrev-DH6iah0A.mjs +0 -16
  197. package/dist/_chunks/usePrev-DH6iah0A.mjs.map +0 -1
  198. package/strapi-server.js +0 -3
@@ -1,17 +1,18 @@
1
- import { CrossCircle, More, WarningCircle, ListPlus, Pencil, Trash, Check, CheckCircle, ArrowsCounterClockwise, ChevronRight, Duplicate, ClockCounterClockwise, Feather } from "@strapi/icons";
1
+ import { More, Cross, WarningCircle, ListPlus, Pencil, Trash, Check, CrossCircle, CheckCircle, ArrowsCounterClockwise, ChevronRight, Duplicate, ClockCounterClockwise, Feather } from "@strapi/icons";
2
2
  import { jsx, Fragment, jsxs } from "react/jsx-runtime";
3
- import { useStrapiApp, createContext, useAuth, useRBAC, Page, adminApi, translatedErrors, useNotification, useAPIErrorHandler, getYupValidationErrors, useQueryParams, useTracking, useForm, BackButton, DescriptionComponentRenderer, useTable, Table } from "@strapi/admin/strapi-admin";
3
+ import { useStrapiApp, createContext, useQueryParams, useAuth, useRBAC, Page, adminApi, translatedErrors, useNotification, useAPIErrorHandler, getYupValidationErrors, useForm, useTracking, useGuidedTour, BackButton, DescriptionComponentRenderer, useTable, Table } from "@strapi/admin/strapi-admin";
4
4
  import * as React from "react";
5
5
  import { lazy } from "react";
6
- import { Button, Menu, VisuallyHidden, Flex, Box, Typography, Dialog, Modal, Radio, Status, SingleSelect, SingleSelectOption, Loader, IconButton, Tooltip, LinkButton } from "@strapi/design-system";
6
+ import { Menu, Button, VisuallyHidden, Flex, Dialog, Modal, Typography, Radio, Status, Box, SingleSelect, SingleSelectOption, IconButton, Loader, Tooltip, LinkButton } from "@strapi/design-system";
7
+ import mapValues from "lodash/fp/mapValues";
7
8
  import { useIntl } from "react-intl";
8
- import { useParams, Navigate, useNavigate, useMatch, useLocation, Link, NavLink } from "react-router-dom";
9
+ import { useParams, useNavigate, Navigate, useMatch, useLocation, Link, NavLink } from "react-router-dom";
9
10
  import { styled } from "styled-components";
10
11
  import * as yup from "yup";
11
12
  import { ValidationError } from "yup";
13
+ import { stringify } from "qs";
12
14
  import pipe from "lodash/fp/pipe";
13
15
  import { intervalToDuration, isPast } from "date-fns";
14
- import { stringify } from "qs";
15
16
  import { createSlice, combineReducers } from "@reduxjs/toolkit";
16
17
  const __variableDynamicImportRuntimeHelper = (glob, path) => {
17
18
  const v = glob[path];
@@ -100,6 +101,7 @@ const DocumentRBAC = ({ children, permissions }) => {
100
101
  if (!slug) {
101
102
  throw new Error("Cannot find the slug param in the URL");
102
103
  }
104
+ const [{ rawQuery }] = useQueryParams();
103
105
  const userPermissions = useAuth("DocumentRBAC", (state) => state.permissions);
104
106
  const contentTypePermissions = React.useMemo(() => {
105
107
  const contentTypePermissions2 = userPermissions.filter(
@@ -110,7 +112,14 @@ const DocumentRBAC = ({ children, permissions }) => {
110
112
  return { ...acc, [action]: [permission] };
111
113
  }, {});
112
114
  }, [slug, userPermissions]);
113
- 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
+ );
114
123
  const canCreateFields = !isLoading && allowedActions.canCreate ? extractAndDedupeFields(contentTypePermissions.create) : [];
115
124
  const canReadFields = !isLoading && allowedActions.canRead ? extractAndDedupeFields(contentTypePermissions.read) : [];
116
125
  const canUpdateFields = !isLoading && allowedActions.canUpdate ? extractAndDedupeFields(contentTypePermissions.update) : [];
@@ -158,7 +167,8 @@ const contentManagerApi = adminApi.enhanceEndpoints({
158
167
  "Document",
159
168
  "InitialData",
160
169
  "HistoryVersion",
161
- "Relations"
170
+ "Relations",
171
+ "UidAvailability"
162
172
  ]
163
173
  });
164
174
  const documentApi = contentManagerApi.injectEndpoints({
@@ -188,7 +198,10 @@ const documentApi = contentManagerApi.injectEndpoints({
188
198
  params
189
199
  }
190
200
  }),
191
- 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
+ ]
192
205
  }),
193
206
  /**
194
207
  * Creates a new collection-type document. This should ONLY be used for collection-types.
@@ -205,7 +218,8 @@ const documentApi = contentManagerApi.injectEndpoints({
205
218
  }),
206
219
  invalidatesTags: (result, _error, { model }) => [
207
220
  { type: "Document", id: `${model}_LIST` },
208
- "Relations"
221
+ "Relations",
222
+ { type: "UidAvailability", id: model }
209
223
  ]
210
224
  }),
211
225
  deleteDocument: builder.mutation({
@@ -246,7 +260,8 @@ const documentApi = contentManagerApi.injectEndpoints({
246
260
  id: collectionType !== SINGLE_TYPES ? `${model}_${documentId}` : model
247
261
  },
248
262
  { type: "Document", id: `${model}_LIST` },
249
- "Relations"
263
+ "Relations",
264
+ { type: "UidAvailability", id: model }
250
265
  ];
251
266
  }
252
267
  }),
@@ -259,11 +274,12 @@ const documentApi = contentManagerApi.injectEndpoints({
259
274
  url: `/content-manager/collection-types/${model}`,
260
275
  method: "GET",
261
276
  config: {
262
- params
277
+ params: stringify(params, { encode: true })
263
278
  }
264
279
  }),
265
280
  providesTags: (result, _error, arg) => {
266
281
  return [
282
+ { type: "Document", id: `ALL_LIST` },
267
283
  { type: "Document", id: `${arg.model}_LIST` },
268
284
  ...result?.results.map(({ documentId }) => ({
269
285
  type: "Document",
@@ -302,6 +318,11 @@ const documentApi = contentManagerApi.injectEndpoints({
302
318
  {
303
319
  type: "Document",
304
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`
305
326
  }
306
327
  ];
307
328
  }
@@ -365,7 +386,8 @@ const documentApi = contentManagerApi.injectEndpoints({
365
386
  type: "Document",
366
387
  id: collectionType !== SINGLE_TYPES ? `${model}_${documentId}` : model
367
388
  },
368
- "Relations"
389
+ "Relations",
390
+ { type: "UidAvailability", id: model }
369
391
  ];
370
392
  },
371
393
  async onQueryStarted({ data, ...patch }, { dispatch, queryFulfilled }) {
@@ -440,28 +462,44 @@ const buildValidParams = (query) => {
440
462
  {}
441
463
  )
442
464
  };
443
- if ("_q" in validQueryParams) {
444
- validQueryParams._q = encodeURIComponent(validQueryParams._q);
445
- }
446
465
  return validQueryParams;
447
466
  };
448
467
  const isBaseQueryError = (error) => {
449
468
  return error.name !== void 0;
450
469
  };
451
- const createYupSchema = (attributes = {}, components = {}) => {
470
+ const arrayValidator = (attribute, options) => ({
471
+ message: translatedErrors.required,
472
+ test(value) {
473
+ if (options.status === "draft") {
474
+ return true;
475
+ }
476
+ if (!attribute.required) {
477
+ return true;
478
+ }
479
+ if (!value) {
480
+ return false;
481
+ }
482
+ if (Array.isArray(value) && value.length === 0) {
483
+ return false;
484
+ }
485
+ return true;
486
+ }
487
+ });
488
+ const createYupSchema = (attributes = {}, components = {}, options = { status: null }) => {
452
489
  const createModelSchema = (attributes2) => yup.object().shape(
453
490
  Object.entries(attributes2).reduce((acc, [name, attribute]) => {
454
491
  if (DOCUMENT_META_FIELDS.includes(name)) {
455
492
  return acc;
456
493
  }
457
494
  const validations = [
495
+ addNullableValidation,
458
496
  addRequiredValidation,
459
497
  addMinLengthValidation,
460
498
  addMaxLengthValidation,
461
499
  addMinValidation,
462
500
  addMaxValidation,
463
501
  addRegexValidation
464
- ].map((fn) => fn(attribute));
502
+ ].map((fn) => fn(attribute, options));
465
503
  const transformSchema = pipe(...validations);
466
504
  switch (attribute.type) {
467
505
  case "component": {
@@ -471,12 +509,12 @@ const createYupSchema = (attributes = {}, components = {}) => {
471
509
  ...acc,
472
510
  [name]: transformSchema(
473
511
  yup.array().of(createModelSchema(attributes3).nullable(false))
474
- )
512
+ ).test(arrayValidator(attribute, options))
475
513
  };
476
514
  } else {
477
515
  return {
478
516
  ...acc,
479
- [name]: transformSchema(createModelSchema(attributes3))
517
+ [name]: transformSchema(createModelSchema(attributes3).nullable())
480
518
  };
481
519
  }
482
520
  }
@@ -498,7 +536,7 @@ const createYupSchema = (attributes = {}, components = {}) => {
498
536
  }
499
537
  )
500
538
  )
501
- )
539
+ ).test(arrayValidator(attribute, options))
502
540
  };
503
541
  case "relation":
504
542
  return {
@@ -510,7 +548,7 @@ const createYupSchema = (attributes = {}, components = {}) => {
510
548
  } else if (Array.isArray(value)) {
511
549
  return yup.array().of(
512
550
  yup.object().shape({
513
- id: yup.string().required()
551
+ id: yup.number().required()
514
552
  })
515
553
  );
516
554
  } else if (typeof value === "object") {
@@ -562,6 +600,14 @@ const createAttributeSchema = (attribute) => {
562
600
  if (!value || typeof value === "string" && value.length === 0) {
563
601
  return true;
564
602
  }
603
+ if (typeof value === "object") {
604
+ try {
605
+ JSON.stringify(value);
606
+ return true;
607
+ } catch (err) {
608
+ return false;
609
+ }
610
+ }
565
611
  try {
566
612
  JSON.parse(value);
567
613
  return true;
@@ -580,13 +626,7 @@ const createAttributeSchema = (attribute) => {
580
626
  return yup.mixed();
581
627
  }
582
628
  };
583
- const addRequiredValidation = (attribute) => (schema) => {
584
- if ((attribute.type === "component" && attribute.repeatable || attribute.type === "dynamiczone") && attribute.required && "min" in schema) {
585
- return schema.min(1, translatedErrors.required);
586
- }
587
- if (attribute.required && attribute.type !== "relation") {
588
- return schema.required(translatedErrors.required);
589
- }
629
+ const nullableSchema = (schema) => {
590
630
  return schema?.nullable ? schema.nullable() : (
591
631
  // In some cases '.nullable' will not be available on the schema.
592
632
  // e.g. when the schema has been built using yup.lazy (e.g. for relations).
@@ -594,7 +634,22 @@ const addRequiredValidation = (attribute) => (schema) => {
594
634
  schema
595
635
  );
596
636
  };
597
- const addMinLengthValidation = (attribute) => (schema) => {
637
+ const addNullableValidation = () => (schema) => {
638
+ return nullableSchema(schema);
639
+ };
640
+ const addRequiredValidation = (attribute, options) => (schema) => {
641
+ if (options.status === "draft" || !attribute.required) {
642
+ return schema;
643
+ }
644
+ if (attribute.required && "required" in schema) {
645
+ return schema.required(translatedErrors.required);
646
+ }
647
+ return schema;
648
+ };
649
+ const addMinLengthValidation = (attribute, options) => (schema) => {
650
+ if (options.status === "draft") {
651
+ return schema;
652
+ }
598
653
  if ("minLength" in attribute && attribute.minLength && Number.isInteger(attribute.minLength) && "min" in schema) {
599
654
  return schema.min(attribute.minLength, {
600
655
  ...translatedErrors.minLength,
@@ -616,32 +671,13 @@ const addMaxLengthValidation = (attribute) => (schema) => {
616
671
  }
617
672
  return schema;
618
673
  };
619
- const addMinValidation = (attribute) => (schema) => {
620
- if ("min" in attribute) {
674
+ const addMinValidation = (attribute, options) => (schema) => {
675
+ if (options.status === "draft") {
676
+ return schema;
677
+ }
678
+ if ("min" in attribute && "min" in schema) {
621
679
  const min = toInteger(attribute.min);
622
- if (attribute.type === "component" && attribute.repeatable || attribute.type === "dynamiczone") {
623
- if (!attribute.required && "test" in schema && min) {
624
- return schema.test(
625
- "custom-min",
626
- {
627
- ...translatedErrors.min,
628
- values: {
629
- min: attribute.min
630
- }
631
- },
632
- (value) => {
633
- if (!value) {
634
- return true;
635
- }
636
- if (Array.isArray(value) && value.length === 0) {
637
- return true;
638
- }
639
- return value.length >= min;
640
- }
641
- );
642
- }
643
- }
644
- if ("min" in schema && min) {
680
+ if (min) {
645
681
  return schema.min(min, {
646
682
  ...translatedErrors.min,
647
683
  values: {
@@ -759,19 +795,115 @@ const extractContentTypeComponents = (attributes = {}, allComponents = {}) => {
759
795
  }, {});
760
796
  return componentsByKey;
761
797
  };
762
- const useDocument = (args, opts) => {
798
+ const HOOKS = {
799
+ /**
800
+ * Hook that allows to mutate the displayed headers of the list view table
801
+ * @constant
802
+ * @type {string}
803
+ */
804
+ INJECT_COLUMN_IN_TABLE: "Admin/CM/pages/ListView/inject-column-in-table",
805
+ /**
806
+ * Hook that allows to mutate the CM's collection types links pre-set filters
807
+ * @constant
808
+ * @type {string}
809
+ */
810
+ MUTATE_COLLECTION_TYPES_LINKS: "Admin/CM/pages/App/mutate-collection-types-links",
811
+ /**
812
+ * Hook that allows to mutate the CM's edit view layout
813
+ * @constant
814
+ * @type {string}
815
+ */
816
+ MUTATE_EDIT_VIEW_LAYOUT: "Admin/CM/pages/EditView/mutate-edit-view-layout",
817
+ /**
818
+ * Hook that allows to mutate the CM's single types links pre-set filters
819
+ * @constant
820
+ * @type {string}
821
+ */
822
+ MUTATE_SINGLE_TYPES_LINKS: "Admin/CM/pages/App/mutate-single-types-links"
823
+ };
824
+ const contentTypesApi = contentManagerApi.injectEndpoints({
825
+ endpoints: (builder) => ({
826
+ getContentTypeConfiguration: builder.query({
827
+ query: (uid) => ({
828
+ url: `/content-manager/content-types/${uid}/configuration`,
829
+ method: "GET"
830
+ }),
831
+ transformResponse: (response) => response.data,
832
+ providesTags: (_result, _error, uid) => [
833
+ { type: "ContentTypesConfiguration", id: uid },
834
+ { type: "ContentTypeSettings", id: "LIST" }
835
+ ]
836
+ }),
837
+ getAllContentTypeSettings: builder.query({
838
+ query: () => "/content-manager/content-types-settings",
839
+ transformResponse: (response) => response.data,
840
+ providesTags: [{ type: "ContentTypeSettings", id: "LIST" }]
841
+ }),
842
+ updateContentTypeConfiguration: builder.mutation({
843
+ query: ({ uid, ...body }) => ({
844
+ url: `/content-manager/content-types/${uid}/configuration`,
845
+ method: "PUT",
846
+ data: body
847
+ }),
848
+ transformResponse: (response) => response.data,
849
+ invalidatesTags: (_result, _error, { uid }) => [
850
+ { type: "ContentTypesConfiguration", id: uid },
851
+ { type: "ContentTypeSettings", id: "LIST" },
852
+ // Is this necessary?
853
+ { type: "InitialData" }
854
+ ]
855
+ })
856
+ })
857
+ });
858
+ const {
859
+ useGetContentTypeConfigurationQuery,
860
+ useGetAllContentTypeSettingsQuery,
861
+ useUpdateContentTypeConfigurationMutation
862
+ } = contentTypesApi;
863
+ const checkIfAttributeIsDisplayable = (attribute) => {
864
+ const { type } = attribute;
865
+ if (type === "relation") {
866
+ return !attribute.relation.toLowerCase().includes("morph");
867
+ }
868
+ return !["json", "dynamiczone", "richtext", "password", "blocks"].includes(type) && !!type;
869
+ };
870
+ const getMainField = (attribute, mainFieldName, { schemas, components }) => {
871
+ if (!mainFieldName) {
872
+ return void 0;
873
+ }
874
+ const mainFieldType = attribute.type === "component" ? components[attribute.component].attributes[mainFieldName].type : (
875
+ // @ts-expect-error – `targetModel` does exist on the attribute for a relation.
876
+ schemas.find((schema) => schema.uid === attribute.targetModel)?.attributes[mainFieldName].type
877
+ );
878
+ return {
879
+ name: mainFieldName,
880
+ type: mainFieldType ?? "string"
881
+ };
882
+ };
883
+ const DEFAULT_SETTINGS = {
884
+ bulkable: false,
885
+ filterable: false,
886
+ searchable: false,
887
+ pagination: false,
888
+ defaultSortBy: "",
889
+ defaultSortOrder: "asc",
890
+ mainField: "id",
891
+ pageSize: 10
892
+ };
893
+ const useDocumentLayout = (model) => {
894
+ const { schema, components } = useDocument({ model, collectionType: "" }, { skip: true });
895
+ const [{ query }] = useQueryParams();
896
+ const runHookWaterfall = useStrapiApp("useDocumentLayout", (state) => state.runHookWaterfall);
763
897
  const { toggleNotification } = useNotification();
764
898
  const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
899
+ const { isLoading: isLoadingSchemas, schemas } = useContentTypeSchema();
765
900
  const {
766
- currentData: data,
767
- isLoading: isLoadingDocument,
768
- isFetching: isFetchingDocument,
769
- error
770
- } = useGetDocumentQuery(args, {
771
- ...opts,
772
- skip: !args.documentId && args.collectionType !== SINGLE_TYPES || opts?.skip
773
- });
774
- const { components, schema, isLoading: isLoadingSchema } = useContentTypeSchema(args.model);
901
+ data,
902
+ isLoading: isLoadingConfigs,
903
+ error,
904
+ isFetching: isFetchingConfigs
905
+ } = useGetContentTypeConfigurationQuery(model);
906
+ const isLoading = isLoadingSchemas || isFetchingConfigs || isLoadingConfigs;
775
907
  React.useEffect(() => {
776
908
  if (error) {
777
909
  toggleNotification({
@@ -779,362 +911,438 @@ const useDocument = (args, opts) => {
779
911
  message: formatAPIError(error)
780
912
  });
781
913
  }
782
- }, [toggleNotification, error, formatAPIError, args.collectionType]);
783
- const validationSchema = React.useMemo(() => {
784
- if (!schema) {
785
- return null;
786
- }
787
- return createYupSchema(schema.attributes, components);
788
- }, [schema, components]);
789
- const validate = React.useCallback(
790
- (document) => {
791
- if (!validationSchema) {
792
- throw new Error(
793
- "There is no validation schema generated, this is likely due to the schema not being loaded yet."
794
- );
795
- }
796
- try {
797
- validationSchema.validateSync(document, { abortEarly: false, strict: true });
798
- return null;
799
- } catch (error2) {
800
- if (error2 instanceof ValidationError) {
801
- return getYupValidationErrors(error2);
802
- }
803
- throw error2;
804
- }
914
+ }, [error, formatAPIError, toggleNotification]);
915
+ const editLayout = React.useMemo(
916
+ () => data && !isLoading ? formatEditLayout(data, { schemas, schema, components }) : {
917
+ layout: [],
918
+ components: {},
919
+ metadatas: {},
920
+ options: {},
921
+ settings: DEFAULT_SETTINGS
805
922
  },
806
- [validationSchema]
923
+ [data, isLoading, schemas, schema, components]
924
+ );
925
+ const listLayout = React.useMemo(() => {
926
+ return data && !isLoading ? formatListLayout(data, { schemas, schema, components }) : {
927
+ layout: [],
928
+ metadatas: {},
929
+ options: {},
930
+ settings: DEFAULT_SETTINGS
931
+ };
932
+ }, [data, isLoading, schemas, schema, components]);
933
+ const { layout: edit } = React.useMemo(
934
+ () => runHookWaterfall(HOOKS.MUTATE_EDIT_VIEW_LAYOUT, {
935
+ layout: editLayout,
936
+ query
937
+ }),
938
+ [editLayout, query, runHookWaterfall]
807
939
  );
808
- const isLoading = isLoadingDocument || isFetchingDocument || isLoadingSchema;
809
940
  return {
810
- components,
811
- document: data?.data,
812
- meta: data?.meta,
941
+ error,
813
942
  isLoading,
814
- schema,
815
- validate
816
- };
817
- };
818
- const useDoc = () => {
819
- const { id, slug, collectionType, origin } = useParams();
820
- const [{ query }] = useQueryParams();
821
- const params = React.useMemo(() => buildValidParams(query), [query]);
822
- if (!collectionType) {
823
- throw new Error("Could not find collectionType in url params");
824
- }
825
- if (!slug) {
826
- throw new Error("Could not find model in url params");
827
- }
828
- return {
829
- collectionType,
830
- model: slug,
831
- id: origin || id === "create" ? void 0 : id,
832
- ...useDocument(
833
- { documentId: origin || id, model: slug, collectionType, params },
834
- {
835
- skip: id === "create" || !origin && !id && collectionType !== SINGLE_TYPES
836
- }
837
- )
943
+ edit,
944
+ list: listLayout
838
945
  };
839
946
  };
840
- const prefixPluginTranslations = (trad, pluginId) => {
841
- if (!pluginId) {
842
- throw new TypeError("pluginId can't be empty");
843
- }
844
- return Object.keys(trad).reduce((acc, current) => {
845
- acc[`${pluginId}.${current}`] = trad[current];
846
- return acc;
847
- }, {});
848
- };
849
- const getTranslation = (id) => `content-manager.${id}`;
850
- const DEFAULT_UNEXPECTED_ERROR_MSG = {
851
- id: "notification.error",
852
- defaultMessage: "An error occurred, please try again"
947
+ const useDocLayout = () => {
948
+ const { model } = useDoc();
949
+ return useDocumentLayout(model);
853
950
  };
854
- const useDocumentActions = () => {
855
- const { toggleNotification } = useNotification();
856
- const { formatMessage } = useIntl();
857
- const { trackUsage } = useTracking();
858
- const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
859
- const [deleteDocument] = useDeleteDocumentMutation();
860
- const _delete = React.useCallback(
861
- async ({ collectionType, model, documentId, params }, trackerProperty) => {
862
- try {
863
- trackUsage("willDeleteEntry", trackerProperty);
864
- const res = await deleteDocument({
865
- collectionType,
866
- model,
867
- documentId,
868
- params
869
- });
870
- if ("error" in res) {
871
- toggleNotification({
872
- type: "danger",
873
- message: formatAPIError(res.error)
874
- });
875
- return { error: res.error };
876
- }
877
- toggleNotification({
878
- type: "success",
879
- message: formatMessage({
880
- id: getTranslation("success.record.delete"),
881
- defaultMessage: "Deleted document"
882
- })
883
- });
884
- trackUsage("didDeleteEntry", trackerProperty);
885
- return res.data;
886
- } catch (err) {
887
- toggleNotification({
888
- type: "danger",
889
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
890
- });
891
- trackUsage("didNotDeleteEntry", { error: err, ...trackerProperty });
892
- throw err;
951
+ const formatEditLayout = (data, {
952
+ schemas,
953
+ schema,
954
+ components
955
+ }) => {
956
+ let currentPanelIndex = 0;
957
+ const panelledEditAttributes = convertEditLayoutToFieldLayouts(
958
+ data.contentType.layouts.edit,
959
+ schema?.attributes,
960
+ data.contentType.metadatas,
961
+ { configurations: data.components, schemas: components },
962
+ schemas
963
+ ).reduce((panels, row) => {
964
+ if (row.some((field) => field.type === "dynamiczone")) {
965
+ panels.push([row]);
966
+ currentPanelIndex += 2;
967
+ } else {
968
+ if (!panels[currentPanelIndex]) {
969
+ panels.push([row]);
970
+ } else {
971
+ panels[currentPanelIndex].push(row);
893
972
  }
894
- },
895
- [trackUsage, deleteDocument, toggleNotification, formatMessage, formatAPIError]
896
- );
897
- const [deleteManyDocuments] = useDeleteManyDocumentsMutation();
898
- const deleteMany = React.useCallback(
899
- async ({ model, documentIds, params }) => {
900
- try {
901
- trackUsage("willBulkDeleteEntries");
902
- const res = await deleteManyDocuments({
903
- model,
904
- documentIds,
905
- params
906
- });
907
- if ("error" in res) {
908
- toggleNotification({
909
- type: "danger",
910
- message: formatAPIError(res.error)
911
- });
912
- return { error: res.error };
973
+ }
974
+ return panels;
975
+ }, []);
976
+ const componentEditAttributes = Object.entries(data.components).reduce(
977
+ (acc, [uid, configuration]) => {
978
+ acc[uid] = {
979
+ layout: convertEditLayoutToFieldLayouts(
980
+ configuration.layouts.edit,
981
+ components[uid].attributes,
982
+ configuration.metadatas,
983
+ { configurations: data.components, schemas: components }
984
+ ),
985
+ settings: {
986
+ ...configuration.settings,
987
+ icon: components[uid].info.icon,
988
+ displayName: components[uid].info.displayName
913
989
  }
914
- toggleNotification({
915
- type: "success",
916
- title: formatMessage({
917
- id: getTranslation("success.records.delete"),
918
- defaultMessage: "Successfully deleted."
919
- }),
920
- message: ""
921
- });
922
- trackUsage("didBulkDeleteEntries");
923
- return res.data;
924
- } catch (err) {
925
- toggleNotification({
926
- type: "danger",
927
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
928
- });
929
- trackUsage("didNotBulkDeleteEntries");
930
- throw err;
931
- }
990
+ };
991
+ return acc;
932
992
  },
933
- [trackUsage, deleteManyDocuments, toggleNotification, formatMessage, formatAPIError]
993
+ {}
934
994
  );
935
- const [discardDocument] = useDiscardDocumentMutation();
936
- const discard = React.useCallback(
937
- async ({ collectionType, model, documentId, params }) => {
938
- try {
939
- const res = await discardDocument({
940
- collectionType,
941
- model,
942
- documentId,
943
- params
944
- });
945
- if ("error" in res) {
946
- toggleNotification({
947
- type: "danger",
948
- message: formatAPIError(res.error)
949
- });
950
- return { error: res.error };
951
- }
952
- toggleNotification({
953
- type: "success",
954
- message: formatMessage({
955
- id: "content-manager.success.record.discard",
956
- defaultMessage: "Changes discarded"
957
- })
958
- });
959
- return res.data;
960
- } catch (err) {
961
- toggleNotification({
962
- type: "danger",
963
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
964
- });
965
- throw err;
966
- }
995
+ const editMetadatas = Object.entries(data.contentType.metadatas).reduce(
996
+ (acc, [attribute, metadata]) => {
997
+ return {
998
+ ...acc,
999
+ [attribute]: metadata.edit
1000
+ };
967
1001
  },
968
- [discardDocument, formatAPIError, formatMessage, toggleNotification]
1002
+ {}
969
1003
  );
970
- const [publishDocument] = usePublishDocumentMutation();
971
- const publish = React.useCallback(
972
- async ({ collectionType, model, documentId, params }, data) => {
973
- try {
974
- trackUsage("willPublishEntry");
975
- const res = await publishDocument({
976
- collectionType,
977
- model,
978
- documentId,
979
- data,
980
- params
981
- });
982
- if ("error" in res) {
983
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
984
- return { error: res.error };
985
- }
986
- trackUsage("didPublishEntry");
987
- toggleNotification({
988
- type: "success",
989
- message: formatMessage({
990
- id: getTranslation("success.record.publish"),
991
- defaultMessage: "Published document"
992
- })
993
- });
994
- return res.data;
995
- } catch (err) {
996
- toggleNotification({
997
- type: "danger",
998
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
999
- });
1000
- throw err;
1001
- }
1004
+ return {
1005
+ layout: panelledEditAttributes,
1006
+ components: componentEditAttributes,
1007
+ metadatas: editMetadatas,
1008
+ settings: {
1009
+ ...data.contentType.settings,
1010
+ displayName: schema?.info.displayName
1002
1011
  },
1003
- [trackUsage, publishDocument, toggleNotification, formatMessage, formatAPIError]
1004
- );
1005
- const [publishManyDocuments] = usePublishManyDocumentsMutation();
1006
- const publishMany = React.useCallback(
1007
- async ({ model, documentIds, params }) => {
1008
- try {
1009
- const res = await publishManyDocuments({
1010
- model,
1011
- documentIds,
1012
- params
1013
- });
1014
- if ("error" in res) {
1015
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1016
- return { error: res.error };
1017
- }
1018
- toggleNotification({
1019
- type: "success",
1020
- message: formatMessage({
1021
- id: getTranslation("success.record.publish"),
1022
- defaultMessage: "Published document"
1023
- })
1024
- });
1025
- return res.data;
1026
- } catch (err) {
1027
- toggleNotification({
1028
- type: "danger",
1029
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1030
- });
1031
- throw err;
1012
+ options: {
1013
+ ...schema?.options,
1014
+ ...schema?.pluginOptions,
1015
+ ...data.contentType.options
1016
+ }
1017
+ };
1018
+ };
1019
+ const convertEditLayoutToFieldLayouts = (rows, attributes = {}, metadatas, components, schemas = []) => {
1020
+ return rows.map(
1021
+ (row) => row.map((field) => {
1022
+ const attribute = attributes[field.name];
1023
+ if (!attribute) {
1024
+ return null;
1032
1025
  }
1033
- },
1034
- [
1035
- // trackUsage,
1036
- publishManyDocuments,
1037
- toggleNotification,
1038
- formatMessage,
1039
- formatAPIError
1040
- ]
1026
+ const { edit: metadata } = metadatas[field.name];
1027
+ const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
1028
+ return {
1029
+ attribute,
1030
+ disabled: !metadata.editable,
1031
+ hint: metadata.description,
1032
+ label: metadata.label ?? "",
1033
+ name: field.name,
1034
+ // @ts-expect-error – mainField does exist on the metadata for a relation.
1035
+ mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
1036
+ schemas,
1037
+ components: components?.schemas ?? {}
1038
+ }),
1039
+ placeholder: metadata.placeholder ?? "",
1040
+ required: attribute.required ?? false,
1041
+ size: field.size,
1042
+ unique: "unique" in attribute ? attribute.unique : false,
1043
+ visible: metadata.visible ?? true,
1044
+ type: attribute.type
1045
+ };
1046
+ }).filter((field) => field !== null)
1041
1047
  );
1042
- const [updateDocument] = useUpdateDocumentMutation();
1043
- const update = React.useCallback(
1044
- async ({ collectionType, model, documentId, params }, data, trackerProperty) => {
1048
+ };
1049
+ const formatListLayout = (data, {
1050
+ schemas,
1051
+ schema,
1052
+ components
1053
+ }) => {
1054
+ const listMetadatas = Object.entries(data.contentType.metadatas).reduce(
1055
+ (acc, [attribute, metadata]) => {
1056
+ return {
1057
+ ...acc,
1058
+ [attribute]: metadata.list
1059
+ };
1060
+ },
1061
+ {}
1062
+ );
1063
+ const listAttributes = convertListLayoutToFieldLayouts(
1064
+ data.contentType.layouts.list,
1065
+ schema?.attributes,
1066
+ listMetadatas,
1067
+ { configurations: data.components, schemas: components },
1068
+ schemas
1069
+ );
1070
+ return {
1071
+ layout: listAttributes,
1072
+ settings: { ...data.contentType.settings, displayName: schema?.info.displayName },
1073
+ metadatas: listMetadatas,
1074
+ options: {
1075
+ ...schema?.options,
1076
+ ...schema?.pluginOptions,
1077
+ ...data.contentType.options
1078
+ }
1079
+ };
1080
+ };
1081
+ const convertListLayoutToFieldLayouts = (columns, attributes = {}, metadatas, components, schemas = []) => {
1082
+ return columns.map((name) => {
1083
+ const attribute = attributes[name];
1084
+ if (!attribute) {
1085
+ return null;
1086
+ }
1087
+ const metadata = metadatas[name];
1088
+ const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
1089
+ return {
1090
+ attribute,
1091
+ label: metadata.label ?? "",
1092
+ mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
1093
+ schemas,
1094
+ components: components?.schemas ?? {}
1095
+ }),
1096
+ name,
1097
+ searchable: metadata.searchable ?? true,
1098
+ sortable: metadata.sortable ?? true
1099
+ };
1100
+ }).filter((field) => field !== null);
1101
+ };
1102
+ const useDocument = (args, opts) => {
1103
+ const { toggleNotification } = useNotification();
1104
+ const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
1105
+ const {
1106
+ currentData: data,
1107
+ isLoading: isLoadingDocument,
1108
+ isFetching: isFetchingDocument,
1109
+ error
1110
+ } = useGetDocumentQuery(args, {
1111
+ ...opts,
1112
+ skip: !args.documentId && args.collectionType !== SINGLE_TYPES || opts?.skip
1113
+ });
1114
+ const {
1115
+ components,
1116
+ schema,
1117
+ schemas,
1118
+ isLoading: isLoadingSchema
1119
+ } = useContentTypeSchema(args.model);
1120
+ React.useEffect(() => {
1121
+ if (error) {
1122
+ toggleNotification({
1123
+ type: "danger",
1124
+ message: formatAPIError(error)
1125
+ });
1126
+ }
1127
+ }, [toggleNotification, error, formatAPIError, args.collectionType]);
1128
+ const validationSchema = React.useMemo(() => {
1129
+ if (!schema) {
1130
+ return null;
1131
+ }
1132
+ return createYupSchema(schema.attributes, components);
1133
+ }, [schema, components]);
1134
+ const validate = React.useCallback(
1135
+ (document) => {
1136
+ if (!validationSchema) {
1137
+ throw new Error(
1138
+ "There is no validation schema generated, this is likely due to the schema not being loaded yet."
1139
+ );
1140
+ }
1045
1141
  try {
1046
- trackUsage("willEditEntry", trackerProperty);
1047
- const res = await updateDocument({
1142
+ validationSchema.validateSync(document, { abortEarly: false, strict: true });
1143
+ return null;
1144
+ } catch (error2) {
1145
+ if (error2 instanceof ValidationError) {
1146
+ return getYupValidationErrors(error2);
1147
+ }
1148
+ throw error2;
1149
+ }
1150
+ },
1151
+ [validationSchema]
1152
+ );
1153
+ const isLoading = isLoadingDocument || isFetchingDocument || isLoadingSchema;
1154
+ const hasError = !!error;
1155
+ return {
1156
+ components,
1157
+ document: data?.data,
1158
+ meta: data?.meta,
1159
+ isLoading,
1160
+ hasError,
1161
+ schema,
1162
+ schemas,
1163
+ validate
1164
+ };
1165
+ };
1166
+ const useDoc = () => {
1167
+ const { id, slug, collectionType, origin } = useParams();
1168
+ const [{ query }] = useQueryParams();
1169
+ const params = React.useMemo(() => buildValidParams(query), [query]);
1170
+ if (!collectionType) {
1171
+ throw new Error("Could not find collectionType in url params");
1172
+ }
1173
+ if (!slug) {
1174
+ throw new Error("Could not find model in url params");
1175
+ }
1176
+ const document = useDocument(
1177
+ { documentId: origin || id, model: slug, collectionType, params },
1178
+ {
1179
+ skip: id === "create" || !origin && !id && collectionType !== SINGLE_TYPES
1180
+ }
1181
+ );
1182
+ const returnId = origin || id === "create" ? void 0 : id;
1183
+ return {
1184
+ collectionType,
1185
+ model: slug,
1186
+ id: returnId,
1187
+ ...document
1188
+ };
1189
+ };
1190
+ const useContentManagerContext = () => {
1191
+ const {
1192
+ collectionType,
1193
+ model,
1194
+ id,
1195
+ components,
1196
+ isLoading: isLoadingDoc,
1197
+ schema,
1198
+ schemas
1199
+ } = useDoc();
1200
+ const layout = useDocumentLayout(model);
1201
+ const form = useForm("useContentManagerContext", (state) => state);
1202
+ const isSingleType = collectionType === SINGLE_TYPES;
1203
+ const slug = model;
1204
+ const isCreatingEntry = id === "create";
1205
+ useContentTypeSchema();
1206
+ const isLoading = isLoadingDoc || layout.isLoading;
1207
+ const error = layout.error;
1208
+ return {
1209
+ error,
1210
+ isLoading,
1211
+ // Base metadata
1212
+ model,
1213
+ collectionType,
1214
+ id,
1215
+ slug,
1216
+ isCreatingEntry,
1217
+ isSingleType,
1218
+ hasDraftAndPublish: schema?.options?.draftAndPublish ?? false,
1219
+ // All schema infos
1220
+ components,
1221
+ contentType: schema,
1222
+ contentTypes: schemas,
1223
+ // Form state
1224
+ form,
1225
+ // layout infos
1226
+ layout
1227
+ };
1228
+ };
1229
+ const prefixPluginTranslations = (trad, pluginId) => {
1230
+ return Object.keys(trad).reduce((acc, current) => {
1231
+ acc[`${pluginId}.${current}`] = trad[current];
1232
+ return acc;
1233
+ }, {});
1234
+ };
1235
+ const getTranslation = (id) => `content-manager.${id}`;
1236
+ const DEFAULT_UNEXPECTED_ERROR_MSG = {
1237
+ id: "notification.error",
1238
+ defaultMessage: "An error occurred, please try again"
1239
+ };
1240
+ const useDocumentActions = () => {
1241
+ const { toggleNotification } = useNotification();
1242
+ const { formatMessage } = useIntl();
1243
+ const { trackUsage } = useTracking();
1244
+ const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
1245
+ const navigate = useNavigate();
1246
+ const setCurrentStep = useGuidedTour("useDocumentActions", (state) => state.setCurrentStep);
1247
+ const [deleteDocument] = useDeleteDocumentMutation();
1248
+ const _delete = React.useCallback(
1249
+ async ({ collectionType, model, documentId, params }, trackerProperty) => {
1250
+ try {
1251
+ trackUsage("willDeleteEntry", trackerProperty);
1252
+ const res = await deleteDocument({
1048
1253
  collectionType,
1049
1254
  model,
1050
1255
  documentId,
1051
- data,
1052
1256
  params
1053
1257
  });
1054
1258
  if ("error" in res) {
1055
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1056
- trackUsage("didNotEditEntry", { error: res.error, ...trackerProperty });
1259
+ toggleNotification({
1260
+ type: "danger",
1261
+ message: formatAPIError(res.error)
1262
+ });
1057
1263
  return { error: res.error };
1058
1264
  }
1059
- trackUsage("didEditEntry", trackerProperty);
1060
1265
  toggleNotification({
1061
1266
  type: "success",
1062
1267
  message: formatMessage({
1063
- id: getTranslation("success.record.save"),
1064
- defaultMessage: "Saved document"
1268
+ id: getTranslation("success.record.delete"),
1269
+ defaultMessage: "Deleted document"
1065
1270
  })
1066
1271
  });
1272
+ trackUsage("didDeleteEntry", trackerProperty);
1067
1273
  return res.data;
1068
1274
  } catch (err) {
1069
- trackUsage("didNotEditEntry", { error: err, ...trackerProperty });
1070
1275
  toggleNotification({
1071
1276
  type: "danger",
1072
1277
  message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1073
1278
  });
1279
+ trackUsage("didNotDeleteEntry", { error: err, ...trackerProperty });
1074
1280
  throw err;
1075
1281
  }
1076
1282
  },
1077
- [trackUsage, updateDocument, toggleNotification, formatMessage, formatAPIError]
1283
+ [trackUsage, deleteDocument, toggleNotification, formatMessage, formatAPIError]
1078
1284
  );
1079
- const [unpublishDocument] = useUnpublishDocumentMutation();
1080
- const unpublish = React.useCallback(
1081
- async ({ collectionType, model, documentId, params }, discardDraft = false) => {
1285
+ const [deleteManyDocuments] = useDeleteManyDocumentsMutation();
1286
+ const deleteMany = React.useCallback(
1287
+ async ({ model, documentIds, params }) => {
1082
1288
  try {
1083
- trackUsage("willUnpublishEntry");
1084
- const res = await unpublishDocument({
1085
- collectionType,
1289
+ trackUsage("willBulkDeleteEntries");
1290
+ const res = await deleteManyDocuments({
1086
1291
  model,
1087
- documentId,
1088
- params,
1089
- data: {
1090
- discardDraft
1091
- }
1292
+ documentIds,
1293
+ params
1092
1294
  });
1093
1295
  if ("error" in res) {
1094
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1296
+ toggleNotification({
1297
+ type: "danger",
1298
+ message: formatAPIError(res.error)
1299
+ });
1095
1300
  return { error: res.error };
1096
1301
  }
1097
- trackUsage("didUnpublishEntry");
1098
1302
  toggleNotification({
1099
1303
  type: "success",
1100
- message: formatMessage({
1101
- id: getTranslation("success.record.unpublish"),
1102
- defaultMessage: "Unpublished document"
1103
- })
1304
+ title: formatMessage({
1305
+ id: getTranslation("success.records.delete"),
1306
+ defaultMessage: "Successfully deleted."
1307
+ }),
1308
+ message: ""
1104
1309
  });
1310
+ trackUsage("didBulkDeleteEntries");
1105
1311
  return res.data;
1106
1312
  } catch (err) {
1107
1313
  toggleNotification({
1108
1314
  type: "danger",
1109
1315
  message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1110
1316
  });
1317
+ trackUsage("didNotBulkDeleteEntries");
1111
1318
  throw err;
1112
1319
  }
1113
1320
  },
1114
- [trackUsage, unpublishDocument, toggleNotification, formatMessage, formatAPIError]
1321
+ [trackUsage, deleteManyDocuments, toggleNotification, formatMessage, formatAPIError]
1115
1322
  );
1116
- const [unpublishManyDocuments] = useUnpublishManyDocumentsMutation();
1117
- const unpublishMany = React.useCallback(
1118
- async ({ model, documentIds, params }) => {
1323
+ const [discardDocument] = useDiscardDocumentMutation();
1324
+ const discard = React.useCallback(
1325
+ async ({ collectionType, model, documentId, params }) => {
1119
1326
  try {
1120
- trackUsage("willBulkUnpublishEntries");
1121
- const res = await unpublishManyDocuments({
1327
+ const res = await discardDocument({
1328
+ collectionType,
1122
1329
  model,
1123
- documentIds,
1330
+ documentId,
1124
1331
  params
1125
1332
  });
1126
1333
  if ("error" in res) {
1127
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1334
+ toggleNotification({
1335
+ type: "danger",
1336
+ message: formatAPIError(res.error)
1337
+ });
1128
1338
  return { error: res.error };
1129
1339
  }
1130
- trackUsage("didBulkUnpublishEntries");
1131
1340
  toggleNotification({
1132
1341
  type: "success",
1133
- title: formatMessage({
1134
- id: getTranslation("success.records.unpublish"),
1135
- defaultMessage: "Successfully unpublished."
1136
- }),
1137
- message: ""
1342
+ message: formatMessage({
1343
+ id: "content-manager.success.record.discard",
1344
+ defaultMessage: "Changes discarded"
1345
+ })
1138
1346
  });
1139
1347
  return res.data;
1140
1348
  } catch (err) {
@@ -1142,14 +1350,194 @@ const useDocumentActions = () => {
1142
1350
  type: "danger",
1143
1351
  message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1144
1352
  });
1145
- trackUsage("didNotBulkUnpublishEntries");
1146
1353
  throw err;
1147
1354
  }
1148
1355
  },
1149
- [trackUsage, unpublishManyDocuments, toggleNotification, formatMessage, formatAPIError]
1356
+ [discardDocument, formatAPIError, formatMessage, toggleNotification]
1150
1357
  );
1151
- const [createDocument] = useCreateDocumentMutation();
1152
- const create = React.useCallback(
1358
+ const [publishDocument] = usePublishDocumentMutation();
1359
+ const publish = React.useCallback(
1360
+ async ({ collectionType, model, documentId, params }, data) => {
1361
+ try {
1362
+ trackUsage("willPublishEntry");
1363
+ const res = await publishDocument({
1364
+ collectionType,
1365
+ model,
1366
+ documentId,
1367
+ data,
1368
+ params
1369
+ });
1370
+ if ("error" in res) {
1371
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1372
+ return { error: res.error };
1373
+ }
1374
+ trackUsage("didPublishEntry");
1375
+ toggleNotification({
1376
+ type: "success",
1377
+ message: formatMessage({
1378
+ id: getTranslation("success.record.publish"),
1379
+ defaultMessage: "Published document"
1380
+ })
1381
+ });
1382
+ return res.data;
1383
+ } catch (err) {
1384
+ toggleNotification({
1385
+ type: "danger",
1386
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1387
+ });
1388
+ throw err;
1389
+ }
1390
+ },
1391
+ [trackUsage, publishDocument, toggleNotification, formatMessage, formatAPIError]
1392
+ );
1393
+ const [publishManyDocuments] = usePublishManyDocumentsMutation();
1394
+ const publishMany = React.useCallback(
1395
+ async ({ model, documentIds, params }) => {
1396
+ try {
1397
+ const res = await publishManyDocuments({
1398
+ model,
1399
+ documentIds,
1400
+ params
1401
+ });
1402
+ if ("error" in res) {
1403
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1404
+ return { error: res.error };
1405
+ }
1406
+ toggleNotification({
1407
+ type: "success",
1408
+ message: formatMessage({
1409
+ id: getTranslation("success.record.publish"),
1410
+ defaultMessage: "Published document"
1411
+ })
1412
+ });
1413
+ return res.data;
1414
+ } catch (err) {
1415
+ toggleNotification({
1416
+ type: "danger",
1417
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1418
+ });
1419
+ throw err;
1420
+ }
1421
+ },
1422
+ [
1423
+ // trackUsage,
1424
+ publishManyDocuments,
1425
+ toggleNotification,
1426
+ formatMessage,
1427
+ formatAPIError
1428
+ ]
1429
+ );
1430
+ const [updateDocument] = useUpdateDocumentMutation();
1431
+ const update = React.useCallback(
1432
+ async ({ collectionType, model, documentId, params }, data, trackerProperty) => {
1433
+ try {
1434
+ trackUsage("willEditEntry", trackerProperty);
1435
+ const res = await updateDocument({
1436
+ collectionType,
1437
+ model,
1438
+ documentId,
1439
+ data,
1440
+ params
1441
+ });
1442
+ if ("error" in res) {
1443
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1444
+ trackUsage("didNotEditEntry", { error: res.error, ...trackerProperty });
1445
+ return { error: res.error };
1446
+ }
1447
+ trackUsage("didEditEntry", trackerProperty);
1448
+ toggleNotification({
1449
+ type: "success",
1450
+ message: formatMessage({
1451
+ id: getTranslation("success.record.save"),
1452
+ defaultMessage: "Saved document"
1453
+ })
1454
+ });
1455
+ return res.data;
1456
+ } catch (err) {
1457
+ trackUsage("didNotEditEntry", { error: err, ...trackerProperty });
1458
+ toggleNotification({
1459
+ type: "danger",
1460
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1461
+ });
1462
+ throw err;
1463
+ }
1464
+ },
1465
+ [trackUsage, updateDocument, toggleNotification, formatMessage, formatAPIError]
1466
+ );
1467
+ const [unpublishDocument] = useUnpublishDocumentMutation();
1468
+ const unpublish = React.useCallback(
1469
+ async ({ collectionType, model, documentId, params }, discardDraft = false) => {
1470
+ try {
1471
+ trackUsage("willUnpublishEntry");
1472
+ const res = await unpublishDocument({
1473
+ collectionType,
1474
+ model,
1475
+ documentId,
1476
+ params,
1477
+ data: {
1478
+ discardDraft
1479
+ }
1480
+ });
1481
+ if ("error" in res) {
1482
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1483
+ return { error: res.error };
1484
+ }
1485
+ trackUsage("didUnpublishEntry");
1486
+ toggleNotification({
1487
+ type: "success",
1488
+ message: formatMessage({
1489
+ id: getTranslation("success.record.unpublish"),
1490
+ defaultMessage: "Unpublished document"
1491
+ })
1492
+ });
1493
+ return res.data;
1494
+ } catch (err) {
1495
+ toggleNotification({
1496
+ type: "danger",
1497
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1498
+ });
1499
+ throw err;
1500
+ }
1501
+ },
1502
+ [trackUsage, unpublishDocument, toggleNotification, formatMessage, formatAPIError]
1503
+ );
1504
+ const [unpublishManyDocuments] = useUnpublishManyDocumentsMutation();
1505
+ const unpublishMany = React.useCallback(
1506
+ async ({ model, documentIds, params }) => {
1507
+ try {
1508
+ trackUsage("willBulkUnpublishEntries");
1509
+ const res = await unpublishManyDocuments({
1510
+ model,
1511
+ documentIds,
1512
+ params
1513
+ });
1514
+ if ("error" in res) {
1515
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1516
+ return { error: res.error };
1517
+ }
1518
+ trackUsage("didBulkUnpublishEntries");
1519
+ toggleNotification({
1520
+ type: "success",
1521
+ title: formatMessage({
1522
+ id: getTranslation("success.records.unpublish"),
1523
+ defaultMessage: "Successfully unpublished."
1524
+ }),
1525
+ message: ""
1526
+ });
1527
+ return res.data;
1528
+ } catch (err) {
1529
+ toggleNotification({
1530
+ type: "danger",
1531
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1532
+ });
1533
+ trackUsage("didNotBulkUnpublishEntries");
1534
+ throw err;
1535
+ }
1536
+ },
1537
+ [trackUsage, unpublishManyDocuments, toggleNotification, formatMessage, formatAPIError]
1538
+ );
1539
+ const [createDocument] = useCreateDocumentMutation();
1540
+ const create = React.useCallback(
1153
1541
  async ({ model, params }, data, trackerProperty) => {
1154
1542
  try {
1155
1543
  const res = await createDocument({
@@ -1170,6 +1558,7 @@ const useDocumentActions = () => {
1170
1558
  defaultMessage: "Saved document"
1171
1559
  })
1172
1560
  });
1561
+ setCurrentStep("contentManager.success");
1173
1562
  return res.data;
1174
1563
  } catch (err) {
1175
1564
  toggleNotification({
@@ -1209,7 +1598,7 @@ const useDocumentActions = () => {
1209
1598
  throw err;
1210
1599
  }
1211
1600
  },
1212
- [autoCloneDocument, formatAPIError, formatMessage, toggleNotification]
1601
+ [autoCloneDocument, formatMessage, toggleNotification]
1213
1602
  );
1214
1603
  const [cloneDocument] = useCloneDocumentMutation();
1215
1604
  const clone = React.useCallback(
@@ -1235,6 +1624,7 @@ const useDocumentActions = () => {
1235
1624
  defaultMessage: "Cloned document"
1236
1625
  })
1237
1626
  });
1627
+ navigate(`../../${res.data.data.documentId}`, { relative: "path" });
1238
1628
  return res.data;
1239
1629
  } catch (err) {
1240
1630
  toggleNotification({
@@ -1245,7 +1635,7 @@ const useDocumentActions = () => {
1245
1635
  throw err;
1246
1636
  }
1247
1637
  },
1248
- [cloneDocument, trackUsage, toggleNotification, formatMessage, formatAPIError]
1638
+ [cloneDocument, trackUsage, toggleNotification, formatMessage, formatAPIError, navigate]
1249
1639
  );
1250
1640
  const [getDoc] = useLazyGetDocumentQuery();
1251
1641
  const getDocument = React.useCallback(
@@ -1270,10 +1660,10 @@ const useDocumentActions = () => {
1270
1660
  update
1271
1661
  };
1272
1662
  };
1273
- const ProtectedHistoryPage = lazy(
1274
- () => import("./History-wrnHqf09.mjs").then((mod) => ({ default: mod.ProtectedHistoryPage }))
1663
+ const ProtectedHistoryPage = React.lazy(
1664
+ () => import("./History-N_kRb1Yr.mjs").then((mod) => ({ default: mod.ProtectedHistoryPage }))
1275
1665
  );
1276
- const routes$1 = [
1666
+ const routes$2 = [
1277
1667
  {
1278
1668
  path: ":collectionType/:slug/:id/history",
1279
1669
  Component: ProtectedHistoryPage
@@ -1283,32 +1673,45 @@ const routes$1 = [
1283
1673
  Component: ProtectedHistoryPage
1284
1674
  }
1285
1675
  ];
1676
+ const ProtectedPreviewPage = React.lazy(
1677
+ () => import("./Preview-kPkuZbBJ.mjs").then((mod) => ({ default: mod.ProtectedPreviewPage }))
1678
+ );
1679
+ const routes$1 = [
1680
+ {
1681
+ path: ":collectionType/:slug/:id/preview",
1682
+ Component: ProtectedPreviewPage
1683
+ },
1684
+ {
1685
+ path: ":collectionType/:slug/preview",
1686
+ Component: ProtectedPreviewPage
1687
+ }
1688
+ ];
1286
1689
  const ProtectedEditViewPage = lazy(
1287
- () => import("./EditViewPage-BLsjc5F-.mjs").then((mod) => ({ default: mod.ProtectedEditViewPage }))
1690
+ () => import("./EditViewPage-BKoISUOu.mjs").then((mod) => ({ default: mod.ProtectedEditViewPage }))
1288
1691
  );
1289
1692
  const ProtectedListViewPage = lazy(
1290
- () => import("./ListViewPage-C4IvrMgY.mjs").then((mod) => ({ default: mod.ProtectedListViewPage }))
1693
+ () => import("./ListViewPage-CWilGbZb.mjs").then((mod) => ({ default: mod.ProtectedListViewPage }))
1291
1694
  );
1292
1695
  const ProtectedListConfiguration = lazy(
1293
- () => import("./ListConfigurationPage-DScmJVkW.mjs").then((mod) => ({
1696
+ () => import("./ListConfigurationPage-BM3qVxug.mjs").then((mod) => ({
1294
1697
  default: mod.ProtectedListConfiguration
1295
1698
  }))
1296
1699
  );
1297
1700
  const ProtectedEditConfigurationPage = lazy(
1298
- () => import("./EditConfigurationPage-DmoXawIh.mjs").then((mod) => ({
1701
+ () => import("./EditConfigurationPage-5tmx_7Hp.mjs").then((mod) => ({
1299
1702
  default: mod.ProtectedEditConfigurationPage
1300
1703
  }))
1301
1704
  );
1302
1705
  const ProtectedComponentConfigurationPage = lazy(
1303
- () => import("./ComponentConfigurationPage-BAgyHiMm.mjs").then((mod) => ({
1706
+ () => import("./ComponentConfigurationPage-DhWA-JzT.mjs").then((mod) => ({
1304
1707
  default: mod.ProtectedComponentConfigurationPage
1305
1708
  }))
1306
1709
  );
1307
1710
  const NoPermissions = lazy(
1308
- () => import("./NoPermissionsPage-DSP7R-hv.mjs").then((mod) => ({ default: mod.NoPermissions }))
1711
+ () => import("./NoPermissionsPage-CS2tCmfr.mjs").then((mod) => ({ default: mod.NoPermissions }))
1309
1712
  );
1310
1713
  const NoContentType = lazy(
1311
- () => import("./NoContentTypePage-Djg8nPlj.mjs").then((mod) => ({ default: mod.NoContentType }))
1714
+ () => import("./NoContentTypePage-VWYlePwI.mjs").then((mod) => ({ default: mod.NoContentType }))
1312
1715
  );
1313
1716
  const CollectionTypePages = () => {
1314
1717
  const { collectionType } = useParams();
@@ -1320,7 +1723,7 @@ const CollectionTypePages = () => {
1320
1723
  const CLONE_RELATIVE_PATH = ":collectionType/:slug/clone/:origin";
1321
1724
  const CLONE_PATH = `/content-manager/${CLONE_RELATIVE_PATH}`;
1322
1725
  const LIST_RELATIVE_PATH = ":collectionType/:slug";
1323
- const LIST_PATH = `/content-manager/${LIST_RELATIVE_PATH}`;
1726
+ const LIST_PATH = `/content-manager/collection-types/:slug`;
1324
1727
  const routes = [
1325
1728
  {
1326
1729
  path: LIST_RELATIVE_PATH,
@@ -1354,6 +1757,7 @@ const routes = [
1354
1757
  path: "no-content-types",
1355
1758
  Component: NoContentType
1356
1759
  },
1760
+ ...routes$2,
1357
1761
  ...routes$1
1358
1762
  ];
1359
1763
  const DocumentActions = ({ actions: actions2 }) => {
@@ -1422,12 +1826,14 @@ const DocumentActionButton = (action) => {
1422
1826
  /* @__PURE__ */ jsx(
1423
1827
  Button,
1424
1828
  {
1425
- flex: 1,
1829
+ flex: "auto",
1426
1830
  startIcon: action.icon,
1427
1831
  disabled: action.disabled,
1428
1832
  onClick: handleClick(action),
1429
1833
  justifyContent: "center",
1430
1834
  variant: action.variant || "default",
1835
+ paddingTop: "7px",
1836
+ paddingBottom: "7px",
1431
1837
  children: action.label
1432
1838
  }
1433
1839
  ),
@@ -1450,6 +1856,11 @@ const DocumentActionButton = (action) => {
1450
1856
  ) : null
1451
1857
  ] });
1452
1858
  };
1859
+ const MenuItem = styled(Menu.Item)`
1860
+ &:hover {
1861
+ background: ${({ theme, isVariantDanger, isDisabled }) => isVariantDanger && !isDisabled ? theme.colors.danger100 : "neutral"};
1862
+ }
1863
+ `;
1453
1864
  const DocumentActionsMenu = ({
1454
1865
  actions: actions2,
1455
1866
  children,
@@ -1492,9 +1903,9 @@ const DocumentActionsMenu = ({
1492
1903
  disabled: isDisabled,
1493
1904
  size: "S",
1494
1905
  endIcon: null,
1495
- paddingTop: "7px",
1496
- paddingLeft: "9px",
1497
- paddingRight: "9px",
1906
+ paddingTop: "4px",
1907
+ paddingLeft: "7px",
1908
+ paddingRight: "7px",
1498
1909
  variant,
1499
1910
  children: [
1500
1911
  /* @__PURE__ */ jsx(More, { "aria-hidden": true, focusable: false }),
@@ -1505,36 +1916,35 @@ const DocumentActionsMenu = ({
1505
1916
  ]
1506
1917
  }
1507
1918
  ),
1508
- /* @__PURE__ */ jsxs(Menu.Content, { top: "4px", maxHeight: void 0, popoverPlacement: "bottom-end", children: [
1919
+ /* @__PURE__ */ jsxs(Menu.Content, { maxHeight: void 0, popoverPlacement: "bottom-end", children: [
1509
1920
  actions2.map((action) => {
1510
1921
  return /* @__PURE__ */ jsx(
1511
- Menu.Item,
1922
+ MenuItem,
1512
1923
  {
1513
1924
  disabled: action.disabled,
1514
1925
  onSelect: handleClick(action),
1515
1926
  display: "block",
1516
- children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", gap: 4, children: [
1517
- /* @__PURE__ */ jsxs(Flex, { color: convertActionVariantToColor(action.variant), gap: 2, tag: "span", children: [
1518
- /* @__PURE__ */ jsx(Box, { tag: "span", color: convertActionVariantToIconColor(action.variant), children: action.icon }),
1519
- action.label
1520
- ] }),
1521
- action.id.startsWith("HistoryAction") && /* @__PURE__ */ jsx(
1522
- Flex,
1523
- {
1524
- alignItems: "center",
1525
- background: "alternative100",
1526
- borderStyle: "solid",
1527
- borderColor: "alternative200",
1528
- borderWidth: "1px",
1529
- height: 5,
1530
- paddingLeft: 2,
1531
- paddingRight: 2,
1532
- hasRadius: true,
1533
- color: "alternative600",
1534
- children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", fontWeight: "bold", lineHeight: 1, children: formatMessage({ id: "global.new", defaultMessage: "New" }) })
1535
- }
1536
- )
1537
- ] })
1927
+ isVariantDanger: action.variant === "danger",
1928
+ isDisabled: action.disabled,
1929
+ children: /* @__PURE__ */ jsx(Flex, { justifyContent: "space-between", gap: 4, children: /* @__PURE__ */ jsxs(
1930
+ Flex,
1931
+ {
1932
+ color: !action.disabled ? convertActionVariantToColor(action.variant) : "inherit",
1933
+ gap: 2,
1934
+ tag: "span",
1935
+ children: [
1936
+ /* @__PURE__ */ jsx(
1937
+ Flex,
1938
+ {
1939
+ tag: "span",
1940
+ color: !action.disabled ? convertActionVariantToIconColor(action.variant) : "inherit",
1941
+ children: action.icon
1942
+ }
1943
+ ),
1944
+ action.label
1945
+ ]
1946
+ }
1947
+ ) })
1538
1948
  },
1539
1949
  action.id
1540
1950
  );
@@ -1614,11 +2024,11 @@ const DocumentActionConfirmDialog = ({
1614
2024
  /* @__PURE__ */ jsx(Dialog.Header, { children: title }),
1615
2025
  /* @__PURE__ */ jsx(Dialog.Body, { children: content }),
1616
2026
  /* @__PURE__ */ jsxs(Dialog.Footer, { children: [
1617
- /* @__PURE__ */ jsx(Dialog.Cancel, { children: /* @__PURE__ */ jsx(Button, { variant: "tertiary", children: formatMessage({
2027
+ /* @__PURE__ */ jsx(Dialog.Cancel, { children: /* @__PURE__ */ jsx(Button, { variant: "tertiary", fullWidth: true, children: formatMessage({
1618
2028
  id: "app.components.Button.cancel",
1619
2029
  defaultMessage: "Cancel"
1620
2030
  }) }) }),
1621
- /* @__PURE__ */ jsx(Button, { onClick: handleConfirm, variant, children: formatMessage({
2031
+ /* @__PURE__ */ jsx(Button, { onClick: handleConfirm, variant, fullWidth: true, children: formatMessage({
1622
2032
  id: "app.components.Button.confirm",
1623
2033
  defaultMessage: "Confirm"
1624
2034
  }) })
@@ -1645,6 +2055,18 @@ const DocumentActionModal = ({
1645
2055
  typeof Footer === "function" ? /* @__PURE__ */ jsx(Footer, { onClose: handleClose }) : Footer
1646
2056
  ] }) });
1647
2057
  };
2058
+ const transformData = (data) => {
2059
+ if (Array.isArray(data)) {
2060
+ return data.map(transformData);
2061
+ }
2062
+ if (typeof data === "object" && data !== null) {
2063
+ if ("apiData" in data) {
2064
+ return data.apiData;
2065
+ }
2066
+ return mapValues(transformData)(data);
2067
+ }
2068
+ return data;
2069
+ };
1648
2070
  const PublishAction$1 = ({
1649
2071
  activeTab,
1650
2072
  documentId,
@@ -1657,12 +2079,11 @@ const PublishAction$1 = ({
1657
2079
  const navigate = useNavigate();
1658
2080
  const { toggleNotification } = useNotification();
1659
2081
  const { _unstableFormatValidationErrors: formatValidationErrors } = useAPIErrorHandler();
2082
+ const isListView = useMatch(LIST_PATH) !== null;
1660
2083
  const isCloning = useMatch(CLONE_PATH) !== null;
2084
+ const { id } = useParams();
1661
2085
  const { formatMessage } = useIntl();
1662
- const { canPublish, canCreate, canUpdate } = useDocumentRBAC(
1663
- "PublishAction",
1664
- ({ canPublish: canPublish2, canCreate: canCreate2, canUpdate: canUpdate2 }) => ({ canPublish: canPublish2, canCreate: canCreate2, canUpdate: canUpdate2 })
1665
- );
2086
+ const canPublish = useDocumentRBAC("PublishAction", ({ canPublish: canPublish2 }) => canPublish2);
1666
2087
  const { publish } = useDocumentActions();
1667
2088
  const [
1668
2089
  countDraftRelations,
@@ -1714,24 +2135,25 @@ const PublishAction$1 = ({
1714
2135
  }
1715
2136
  }, [documentId, modified, formValues, setLocalCountOfDraftRelations]);
1716
2137
  React.useEffect(() => {
1717
- if (documentId) {
1718
- const fetchDraftRelationsCount = async () => {
1719
- const { data, error } = await countDraftRelations({
1720
- collectionType,
1721
- model,
1722
- documentId,
1723
- params
1724
- });
1725
- if (error) {
1726
- throw error;
1727
- }
1728
- if (data) {
1729
- setServerCountOfDraftRelations(data.data);
1730
- }
1731
- };
1732
- fetchDraftRelationsCount();
2138
+ if (!document || !document.documentId || isListView) {
2139
+ return;
1733
2140
  }
1734
- }, [documentId, countDraftRelations, collectionType, model, params]);
2141
+ const fetchDraftRelationsCount = async () => {
2142
+ const { data, error } = await countDraftRelations({
2143
+ collectionType,
2144
+ model,
2145
+ documentId,
2146
+ params
2147
+ });
2148
+ if (error) {
2149
+ throw error;
2150
+ }
2151
+ if (data) {
2152
+ setServerCountOfDraftRelations(data.data);
2153
+ }
2154
+ };
2155
+ fetchDraftRelationsCount();
2156
+ }, [isListView, document, documentId, countDraftRelations, collectionType, model, params]);
1735
2157
  const isDocumentPublished = (document?.[PUBLISHED_AT_ATTRIBUTE_NAME] || meta?.availableStatus.some((doc) => doc[PUBLISHED_AT_ATTRIBUTE_NAME] !== null)) && document?.status !== "modified";
1736
2158
  if (!schema?.options?.draftAndPublish) {
1737
2159
  return null;
@@ -1739,7 +2161,9 @@ const PublishAction$1 = ({
1739
2161
  const performPublish = async () => {
1740
2162
  setSubmitting(true);
1741
2163
  try {
1742
- const { errors } = await validate();
2164
+ const { errors } = await validate(true, {
2165
+ status: "published"
2166
+ });
1743
2167
  if (errors) {
1744
2168
  toggleNotification({
1745
2169
  type: "danger",
@@ -1757,13 +2181,15 @@ const PublishAction$1 = ({
1757
2181
  documentId,
1758
2182
  params
1759
2183
  },
1760
- formValues
2184
+ transformData(formValues)
1761
2185
  );
1762
2186
  if ("data" in res && collectionType !== SINGLE_TYPES) {
1763
- navigate({
1764
- pathname: `../${collectionType}/${model}/${res.data.documentId}`,
1765
- search: rawQuery
1766
- });
2187
+ if (id === "create") {
2188
+ navigate({
2189
+ pathname: `../${collectionType}/${model}/${res.data.documentId}`,
2190
+ search: rawQuery
2191
+ });
2192
+ }
1767
2193
  } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1768
2194
  setErrors(formatValidationErrors(res.error));
1769
2195
  }
@@ -1772,7 +2198,8 @@ const PublishAction$1 = ({
1772
2198
  }
1773
2199
  };
1774
2200
  const totalDraftRelations = localCountOfDraftRelations + serverCountOfDraftRelations;
1775
- const hasDraftRelations = totalDraftRelations > 0;
2201
+ const enableDraftRelationsCount = false;
2202
+ const hasDraftRelations = enableDraftRelationsCount;
1776
2203
  return {
1777
2204
  /**
1778
2205
  * Disabled when:
@@ -1782,18 +2209,13 @@ const PublishAction$1 = ({
1782
2209
  * - the document is already published & not modified
1783
2210
  * - the document is being created & not modified
1784
2211
  * - the user doesn't have the permission to publish
1785
- * - the user doesn't have the permission to create a new document
1786
- * - the user doesn't have the permission to update the document
1787
2212
  */
1788
- disabled: isCloning || isSubmitting || isLoadingDraftRelations || activeTab === "published" || !modified && isDocumentPublished || !modified && !document?.documentId || !canPublish || Boolean(!document?.documentId && !canCreate || document?.documentId && !canUpdate),
2213
+ disabled: isCloning || isSubmitting || isLoadingDraftRelations || activeTab === "published" || !modified && isDocumentPublished || !modified && !document?.documentId || !canPublish,
1789
2214
  label: formatMessage({
1790
2215
  id: "app.utils.publish",
1791
2216
  defaultMessage: "Publish"
1792
2217
  }),
1793
2218
  onClick: async () => {
1794
- if (hasDraftRelations) {
1795
- return;
1796
- }
1797
2219
  await performPublish();
1798
2220
  },
1799
2221
  dialog: hasDraftRelations ? {
@@ -1832,10 +2254,6 @@ const UpdateAction = ({
1832
2254
  const cloneMatch = useMatch(CLONE_PATH);
1833
2255
  const isCloning = cloneMatch !== null;
1834
2256
  const { formatMessage } = useIntl();
1835
- const { canCreate, canUpdate } = useDocumentRBAC("UpdateAction", ({ canCreate: canCreate2, canUpdate: canUpdate2 }) => ({
1836
- canCreate: canCreate2,
1837
- canUpdate: canUpdate2
1838
- }));
1839
2257
  const { create, update, clone } = useDocumentActions();
1840
2258
  const [{ query, rawQuery }] = useQueryParams();
1841
2259
  const params = React.useMemo(() => buildValidParams(query), [query]);
@@ -1852,18 +2270,18 @@ const UpdateAction = ({
1852
2270
  * - the form is submitting
1853
2271
  * - the document is not modified & we're not cloning (you can save a clone entity straight away)
1854
2272
  * - the active tab is the published tab
1855
- * - the user doesn't have the permission to create a new document
1856
- * - the user doesn't have the permission to update the document
1857
2273
  */
1858
- disabled: isSubmitting || !modified && !isCloning || activeTab === "published" || Boolean(!documentId && !canCreate || documentId && !canUpdate),
2274
+ disabled: isSubmitting || !modified && !isCloning || activeTab === "published",
1859
2275
  label: formatMessage({
1860
- id: "content-manager.containers.Edit.save",
2276
+ id: "global.save",
1861
2277
  defaultMessage: "Save"
1862
2278
  }),
1863
2279
  onClick: async () => {
1864
2280
  setSubmitting(true);
1865
2281
  try {
1866
- const { errors } = await validate();
2282
+ const { errors } = await validate(true, {
2283
+ status: "draft"
2284
+ });
1867
2285
  if (errors) {
1868
2286
  toggleNotification({
1869
2287
  type: "danger",
@@ -1881,7 +2299,7 @@ const UpdateAction = ({
1881
2299
  documentId: cloneMatch.params.origin,
1882
2300
  params
1883
2301
  },
1884
- document
2302
+ transformData(document)
1885
2303
  );
1886
2304
  if ("data" in res) {
1887
2305
  navigate(
@@ -1902,7 +2320,7 @@ const UpdateAction = ({
1902
2320
  documentId,
1903
2321
  params
1904
2322
  },
1905
- document
2323
+ transformData(document)
1906
2324
  );
1907
2325
  if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1908
2326
  setErrors(formatValidationErrors(res.error));
@@ -1915,7 +2333,7 @@ const UpdateAction = ({
1915
2333
  model,
1916
2334
  params
1917
2335
  },
1918
- document
2336
+ transformData(document)
1919
2337
  );
1920
2338
  if ("data" in res && collectionType !== SINGLE_TYPES) {
1921
2339
  navigate(
@@ -1968,7 +2386,7 @@ const UnpublishAction$1 = ({
1968
2386
  id: "app.utils.unpublish",
1969
2387
  defaultMessage: "Unpublish"
1970
2388
  }),
1971
- icon: /* @__PURE__ */ jsx(StyledCrossCircle, {}),
2389
+ icon: /* @__PURE__ */ jsx(Cross, {}),
1972
2390
  onClick: async () => {
1973
2391
  if (!documentId && collectionType !== SINGLE_TYPES || isDocumentModified) {
1974
2392
  if (!documentId) {
@@ -2080,7 +2498,7 @@ const DiscardAction = ({
2080
2498
  id: "content-manager.actions.discard.label",
2081
2499
  defaultMessage: "Discard changes"
2082
2500
  }),
2083
- icon: /* @__PURE__ */ jsx(StyledCrossCircle, {}),
2501
+ icon: /* @__PURE__ */ jsx(Cross, {}),
2084
2502
  position: ["panel", "table-row"],
2085
2503
  variant: "danger",
2086
2504
  dialog: {
@@ -2108,11 +2526,6 @@ const DiscardAction = ({
2108
2526
  };
2109
2527
  };
2110
2528
  DiscardAction.type = "discard";
2111
- const StyledCrossCircle = styled(CrossCircle)`
2112
- path {
2113
- fill: currentColor;
2114
- }
2115
- `;
2116
2529
  const DEFAULT_ACTIONS = [PublishAction$1, UpdateAction, UnpublishAction$1, DiscardAction];
2117
2530
  const intervals = ["years", "months", "days", "hours", "minutes", "seconds"];
2118
2531
  const RelativeTime = React.forwardRef(
@@ -2125,7 +2538,7 @@ const RelativeTime = React.forwardRef(
2125
2538
  });
2126
2539
  const unit = intervals.find((intervalUnit) => {
2127
2540
  return interval[intervalUnit] > 0 && Object.keys(interval).includes(intervalUnit);
2128
- });
2541
+ }) ?? "seconds";
2129
2542
  const relativeTime = isPast(timestamp) ? -interval[unit] : interval[unit];
2130
2543
  const customInterval = customIntervals.find(
2131
2544
  (custom) => interval[custom.unit] < custom.threshold
@@ -2159,19 +2572,29 @@ const getDisplayName = ({
2159
2572
  return email ?? "";
2160
2573
  };
2161
2574
  const capitalise = (str) => str.charAt(0).toUpperCase() + str.slice(1);
2162
- const DocumentStatus = ({ status = "draft", ...restProps }) => {
2163
- const statusVariant = status === "draft" ? "primary" : status === "published" ? "success" : "alternative";
2164
- return /* @__PURE__ */ jsx(Status, { ...restProps, showBullet: false, size: "S", variant: statusVariant, children: /* @__PURE__ */ jsx(Typography, { tag: "span", variant: "omega", fontWeight: "bold", children: capitalise(status) }) });
2575
+ const DocumentStatus = ({ status = "draft", size = "S", ...restProps }) => {
2576
+ const statusVariant = status === "draft" ? "secondary" : status === "published" ? "success" : "alternative";
2577
+ const { formatMessage } = useIntl();
2578
+ return /* @__PURE__ */ jsx(Status, { ...restProps, size, variant: statusVariant, children: /* @__PURE__ */ jsx(Typography, { tag: "span", variant: "omega", fontWeight: "bold", children: formatMessage({
2579
+ id: `content-manager.containers.List.${status}`,
2580
+ defaultMessage: capitalise(status)
2581
+ }) }) });
2165
2582
  };
2166
2583
  const Header = ({ isCreating, status, title: documentTitle = "Untitled" }) => {
2167
2584
  const { formatMessage } = useIntl();
2168
2585
  const isCloning = useMatch(CLONE_PATH) !== null;
2586
+ const params = useParams();
2169
2587
  const title = isCreating ? formatMessage({
2170
2588
  id: "content-manager.containers.edit.title.new",
2171
2589
  defaultMessage: "Create an entry"
2172
2590
  }) : documentTitle;
2173
2591
  return /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", paddingTop: 6, paddingBottom: 4, gap: 2, children: [
2174
- /* @__PURE__ */ jsx(BackButton, {}),
2592
+ /* @__PURE__ */ jsx(
2593
+ BackButton,
2594
+ {
2595
+ fallback: params.collectionType === SINGLE_TYPES ? void 0 : `../${COLLECTION_TYPES}/${params.slug}`
2596
+ }
2597
+ ),
2175
2598
  /* @__PURE__ */ jsxs(Flex, { width: "100%", justifyContent: "space-between", gap: "80px", alignItems: "flex-start", children: [
2176
2599
  /* @__PURE__ */ jsx(Typography, { variant: "alpha", tag: "h1", children: title }),
2177
2600
  /* @__PURE__ */ jsx(HeaderToolbar, {})
@@ -2259,12 +2682,12 @@ const Information = ({ activeTab }) => {
2259
2682
  isDisplayed: !!publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME],
2260
2683
  label: formatMessage({
2261
2684
  id: "content-manager.containers.edit.information.last-published.label",
2262
- defaultMessage: "Last published"
2685
+ defaultMessage: "Published"
2263
2686
  }),
2264
2687
  value: formatMessage(
2265
2688
  {
2266
2689
  id: "content-manager.containers.edit.information.last-published.value",
2267
- defaultMessage: `Published {time}{isAnonymous, select, true {} other { by {author}}}`
2690
+ defaultMessage: `{time}{isAnonymous, select, true {} other { by {author}}}`
2268
2691
  },
2269
2692
  {
2270
2693
  time: /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME]) }),
@@ -2277,12 +2700,12 @@ const Information = ({ activeTab }) => {
2277
2700
  isDisplayed: !!createAndUpdateDocument?.[UPDATED_AT_ATTRIBUTE_NAME],
2278
2701
  label: formatMessage({
2279
2702
  id: "content-manager.containers.edit.information.last-draft.label",
2280
- defaultMessage: "Last draft"
2703
+ defaultMessage: "Updated"
2281
2704
  }),
2282
2705
  value: formatMessage(
2283
2706
  {
2284
2707
  id: "content-manager.containers.edit.information.last-draft.value",
2285
- defaultMessage: `Modified {time}{isAnonymous, select, true {} other { by {author}}}`
2708
+ defaultMessage: `{time}{isAnonymous, select, true {} other { by {author}}}`
2286
2709
  },
2287
2710
  {
2288
2711
  time: /* @__PURE__ */ jsx(
@@ -2300,12 +2723,12 @@ const Information = ({ activeTab }) => {
2300
2723
  isDisplayed: !!createAndUpdateDocument?.[CREATED_AT_ATTRIBUTE_NAME],
2301
2724
  label: formatMessage({
2302
2725
  id: "content-manager.containers.edit.information.document.label",
2303
- defaultMessage: "Document"
2726
+ defaultMessage: "Created"
2304
2727
  }),
2305
2728
  value: formatMessage(
2306
2729
  {
2307
2730
  id: "content-manager.containers.edit.information.document.value",
2308
- defaultMessage: `Created {time}{isAnonymous, select, true {} other { by {author}}}`
2731
+ defaultMessage: `{time}{isAnonymous, select, true {} other { by {author}}}`
2309
2732
  },
2310
2733
  {
2311
2734
  time: /* @__PURE__ */ jsx(
@@ -2343,28 +2766,80 @@ const Information = ({ activeTab }) => {
2343
2766
  );
2344
2767
  };
2345
2768
  const HeaderActions = ({ actions: actions2 }) => {
2346
- return /* @__PURE__ */ jsx(Flex, { children: actions2.map((action) => {
2347
- if ("options" in action) {
2769
+ const [dialogId, setDialogId] = React.useState(null);
2770
+ const handleClick = (action) => async (e) => {
2771
+ if (!("options" in action)) {
2772
+ const { onClick = () => false, dialog, id } = action;
2773
+ const muteDialog = await onClick(e);
2774
+ if (dialog && !muteDialog) {
2775
+ e.preventDefault();
2776
+ setDialogId(id);
2777
+ }
2778
+ }
2779
+ };
2780
+ const handleClose = () => {
2781
+ setDialogId(null);
2782
+ };
2783
+ return /* @__PURE__ */ jsx(Flex, { gap: 1, children: actions2.map((action) => {
2784
+ if (action.options) {
2348
2785
  return /* @__PURE__ */ jsx(
2349
2786
  SingleSelect,
2350
2787
  {
2351
2788
  size: "S",
2352
- disabled: action.disabled,
2353
- "aria-label": action.label,
2354
2789
  onChange: action.onSelect,
2355
- value: action.value,
2790
+ "aria-label": action.label,
2791
+ ...action,
2356
2792
  children: action.options.map(({ label, ...option }) => /* @__PURE__ */ jsx(SingleSelectOption, { ...option, children: label }, option.value))
2357
2793
  },
2358
2794
  action.id
2359
2795
  );
2360
2796
  } else {
2361
- return null;
2362
- }
2363
- }) });
2364
- };
2365
- const ConfigureTheViewAction = ({ collectionType, model }) => {
2366
- const navigate = useNavigate();
2367
- const { formatMessage } = useIntl();
2797
+ if (action.type === "icon") {
2798
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
2799
+ /* @__PURE__ */ jsx(
2800
+ IconButton,
2801
+ {
2802
+ disabled: action.disabled,
2803
+ label: action.label,
2804
+ size: "S",
2805
+ onClick: handleClick(action),
2806
+ children: action.icon
2807
+ }
2808
+ ),
2809
+ action.dialog ? /* @__PURE__ */ jsx(
2810
+ HeaderActionDialog,
2811
+ {
2812
+ ...action.dialog,
2813
+ isOpen: dialogId === action.id,
2814
+ onClose: handleClose
2815
+ }
2816
+ ) : null
2817
+ ] }, action.id);
2818
+ }
2819
+ }
2820
+ }) });
2821
+ };
2822
+ const HeaderActionDialog = ({
2823
+ onClose,
2824
+ onCancel,
2825
+ title,
2826
+ content: Content,
2827
+ isOpen
2828
+ }) => {
2829
+ const handleClose = async () => {
2830
+ if (onCancel) {
2831
+ await onCancel();
2832
+ }
2833
+ onClose();
2834
+ };
2835
+ return /* @__PURE__ */ jsx(Dialog.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxs(Dialog.Content, { children: [
2836
+ /* @__PURE__ */ jsx(Dialog.Header, { children: title }),
2837
+ typeof Content === "function" ? /* @__PURE__ */ jsx(Content, { onClose: handleClose }) : Content
2838
+ ] }) });
2839
+ };
2840
+ const ConfigureTheViewAction = ({ collectionType, model }) => {
2841
+ const navigate = useNavigate();
2842
+ const { formatMessage } = useIntl();
2368
2843
  return {
2369
2844
  label: formatMessage({
2370
2845
  id: "app.links.configure-view",
@@ -2402,12 +2877,16 @@ const DeleteAction$1 = ({ documentId, model, collectionType, document }) => {
2402
2877
  const { delete: deleteAction } = useDocumentActions();
2403
2878
  const { toggleNotification } = useNotification();
2404
2879
  const setSubmitting = useForm("DeleteAction", (state) => state.setSubmitting);
2880
+ const isLocalized = document?.locale != null;
2405
2881
  return {
2406
2882
  disabled: !canDelete || !document,
2407
- label: formatMessage({
2408
- id: "content-manager.actions.delete.label",
2409
- defaultMessage: "Delete document"
2410
- }),
2883
+ label: formatMessage(
2884
+ {
2885
+ id: "content-manager.actions.delete.label",
2886
+ defaultMessage: "Delete entry{isLocalized, select, true { (all locales)} other {}}"
2887
+ },
2888
+ { isLocalized }
2889
+ ),
2411
2890
  icon: /* @__PURE__ */ jsx(Trash, {}),
2412
2891
  dialog: {
2413
2892
  type: "dialog",
@@ -2444,422 +2923,120 @@ const DeleteAction$1 = ({ documentId, model, collectionType, document }) => {
2444
2923
  documentId,
2445
2924
  model,
2446
2925
  collectionType,
2447
- params: {
2448
- locale: "*"
2449
- }
2450
- });
2451
- if (!("error" in res)) {
2452
- navigate({ pathname: `../${collectionType}/${model}` }, { replace: true });
2453
- }
2454
- } finally {
2455
- if (!listViewPathMatch) {
2456
- setSubmitting(false);
2457
- }
2458
- }
2459
- }
2460
- },
2461
- variant: "danger",
2462
- position: ["header", "table-row"]
2463
- };
2464
- };
2465
- DeleteAction$1.type = "delete";
2466
- const DEFAULT_HEADER_ACTIONS = [EditTheModelAction, ConfigureTheViewAction, DeleteAction$1];
2467
- const Panels = () => {
2468
- const isCloning = useMatch(CLONE_PATH) !== null;
2469
- const [
2470
- {
2471
- query: { status }
2472
- }
2473
- ] = useQueryParams({
2474
- status: "draft"
2475
- });
2476
- const { model, id, document, meta, collectionType } = useDoc();
2477
- const plugins = useStrapiApp("Panels", (state) => state.plugins);
2478
- const props = {
2479
- activeTab: status,
2480
- model,
2481
- documentId: id,
2482
- document: isCloning ? void 0 : document,
2483
- meta: isCloning ? void 0 : meta,
2484
- collectionType
2485
- };
2486
- return /* @__PURE__ */ jsx(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: /* @__PURE__ */ jsx(
2487
- DescriptionComponentRenderer,
2488
- {
2489
- props,
2490
- descriptions: plugins["content-manager"].apis.getEditViewSidePanels(),
2491
- children: (panels) => panels.map(({ content, id: id2, ...description }) => /* @__PURE__ */ jsx(Panel, { ...description, children: content }, id2))
2492
- }
2493
- ) });
2494
- };
2495
- const ActionsPanel = () => {
2496
- const { formatMessage } = useIntl();
2497
- return {
2498
- title: formatMessage({
2499
- id: "content-manager.containers.edit.panels.default.title",
2500
- defaultMessage: "Document"
2501
- }),
2502
- content: /* @__PURE__ */ jsx(ActionsPanelContent, {})
2503
- };
2504
- };
2505
- ActionsPanel.type = "actions";
2506
- const ActionsPanelContent = () => {
2507
- const isCloning = useMatch(CLONE_PATH) !== null;
2508
- const [
2509
- {
2510
- query: { status = "draft" }
2511
- }
2512
- ] = useQueryParams();
2513
- const { model, id, document, meta, collectionType } = useDoc();
2514
- const plugins = useStrapiApp("ActionsPanel", (state) => state.plugins);
2515
- const props = {
2516
- activeTab: status,
2517
- model,
2518
- documentId: id,
2519
- document: isCloning ? void 0 : document,
2520
- meta: isCloning ? void 0 : meta,
2521
- collectionType
2522
- };
2523
- return /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, width: "100%", children: [
2524
- /* @__PURE__ */ jsx(
2525
- DescriptionComponentRenderer,
2526
- {
2527
- props,
2528
- descriptions: plugins["content-manager"].apis.getDocumentActions(),
2529
- children: (actions2) => /* @__PURE__ */ jsx(DocumentActions, { actions: actions2 })
2530
- }
2531
- ),
2532
- /* @__PURE__ */ jsx(InjectionZone, { area: "editView.right-links", slug: model })
2533
- ] });
2534
- };
2535
- const Panel = React.forwardRef(({ children, title }, ref) => {
2536
- return /* @__PURE__ */ jsxs(
2537
- Flex,
2538
- {
2539
- ref,
2540
- tag: "aside",
2541
- "aria-labelledby": "additional-information",
2542
- background: "neutral0",
2543
- borderColor: "neutral150",
2544
- hasRadius: true,
2545
- paddingBottom: 4,
2546
- paddingLeft: 4,
2547
- paddingRight: 4,
2548
- paddingTop: 4,
2549
- shadow: "tableShadow",
2550
- gap: 3,
2551
- direction: "column",
2552
- justifyContent: "stretch",
2553
- alignItems: "flex-start",
2554
- children: [
2555
- /* @__PURE__ */ jsx(Typography, { tag: "h2", variant: "sigma", textTransform: "uppercase", children: title }),
2556
- children
2557
- ]
2558
- }
2559
- );
2560
- });
2561
- const HOOKS = {
2562
- /**
2563
- * Hook that allows to mutate the displayed headers of the list view table
2564
- * @constant
2565
- * @type {string}
2566
- */
2567
- INJECT_COLUMN_IN_TABLE: "Admin/CM/pages/ListView/inject-column-in-table",
2568
- /**
2569
- * Hook that allows to mutate the CM's collection types links pre-set filters
2570
- * @constant
2571
- * @type {string}
2572
- */
2573
- MUTATE_COLLECTION_TYPES_LINKS: "Admin/CM/pages/App/mutate-collection-types-links",
2574
- /**
2575
- * Hook that allows to mutate the CM's edit view layout
2576
- * @constant
2577
- * @type {string}
2578
- */
2579
- MUTATE_EDIT_VIEW_LAYOUT: "Admin/CM/pages/EditView/mutate-edit-view-layout",
2580
- /**
2581
- * Hook that allows to mutate the CM's single types links pre-set filters
2582
- * @constant
2583
- * @type {string}
2584
- */
2585
- MUTATE_SINGLE_TYPES_LINKS: "Admin/CM/pages/App/mutate-single-types-links"
2586
- };
2587
- const contentTypesApi = contentManagerApi.injectEndpoints({
2588
- endpoints: (builder) => ({
2589
- getContentTypeConfiguration: builder.query({
2590
- query: (uid) => ({
2591
- url: `/content-manager/content-types/${uid}/configuration`,
2592
- method: "GET"
2593
- }),
2594
- transformResponse: (response) => response.data,
2595
- providesTags: (_result, _error, uid) => [
2596
- { type: "ContentTypesConfiguration", id: uid },
2597
- { type: "ContentTypeSettings", id: "LIST" }
2598
- ]
2599
- }),
2600
- getAllContentTypeSettings: builder.query({
2601
- query: () => "/content-manager/content-types-settings",
2602
- transformResponse: (response) => response.data,
2603
- providesTags: [{ type: "ContentTypeSettings", id: "LIST" }]
2604
- }),
2605
- updateContentTypeConfiguration: builder.mutation({
2606
- query: ({ uid, ...body }) => ({
2607
- url: `/content-manager/content-types/${uid}/configuration`,
2608
- method: "PUT",
2609
- data: body
2610
- }),
2611
- transformResponse: (response) => response.data,
2612
- invalidatesTags: (_result, _error, { uid }) => [
2613
- { type: "ContentTypesConfiguration", id: uid },
2614
- { type: "ContentTypeSettings", id: "LIST" },
2615
- // Is this necessary?
2616
- { type: "InitialData" }
2617
- ]
2618
- })
2619
- })
2620
- });
2621
- const {
2622
- useGetContentTypeConfigurationQuery,
2623
- useGetAllContentTypeSettingsQuery,
2624
- useUpdateContentTypeConfigurationMutation
2625
- } = contentTypesApi;
2626
- const checkIfAttributeIsDisplayable = (attribute) => {
2627
- const { type } = attribute;
2628
- if (type === "relation") {
2629
- return !attribute.relation.toLowerCase().includes("morph");
2630
- }
2631
- return !["json", "dynamiczone", "richtext", "password", "blocks"].includes(type) && !!type;
2632
- };
2633
- const getMainField = (attribute, mainFieldName, { schemas, components }) => {
2634
- if (!mainFieldName) {
2635
- return void 0;
2636
- }
2637
- const mainFieldType = attribute.type === "component" ? components[attribute.component].attributes[mainFieldName].type : (
2638
- // @ts-expect-error – `targetModel` does exist on the attribute for a relation.
2639
- schemas.find((schema) => schema.uid === attribute.targetModel)?.attributes[mainFieldName].type
2640
- );
2641
- return {
2642
- name: mainFieldName,
2643
- type: mainFieldType ?? "string"
2644
- };
2645
- };
2646
- const DEFAULT_SETTINGS = {
2647
- bulkable: false,
2648
- filterable: false,
2649
- searchable: false,
2650
- pagination: false,
2651
- defaultSortBy: "",
2652
- defaultSortOrder: "asc",
2653
- mainField: "id",
2654
- pageSize: 10
2655
- };
2656
- const useDocumentLayout = (model) => {
2657
- const { schema, components } = useDocument({ model, collectionType: "" }, { skip: true });
2658
- const [{ query }] = useQueryParams();
2659
- const runHookWaterfall = useStrapiApp("useDocumentLayout", (state) => state.runHookWaterfall);
2660
- const { toggleNotification } = useNotification();
2661
- const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
2662
- const { isLoading: isLoadingSchemas, schemas } = useContentTypeSchema();
2663
- const {
2664
- data,
2665
- isLoading: isLoadingConfigs,
2666
- error,
2667
- isFetching: isFetchingConfigs
2668
- } = useGetContentTypeConfigurationQuery(model);
2669
- const isLoading = isLoadingSchemas || isFetchingConfigs || isLoadingConfigs;
2670
- React.useEffect(() => {
2671
- if (error) {
2672
- toggleNotification({
2673
- type: "danger",
2674
- message: formatAPIError(error)
2675
- });
2676
- }
2677
- }, [error, formatAPIError, toggleNotification]);
2678
- const editLayout = React.useMemo(
2679
- () => data && !isLoading ? formatEditLayout(data, { schemas, schema, components }) : {
2680
- layout: [],
2681
- components: {},
2682
- metadatas: {},
2683
- options: {},
2684
- settings: DEFAULT_SETTINGS
2685
- },
2686
- [data, isLoading, schemas, schema, components]
2687
- );
2688
- const listLayout = React.useMemo(() => {
2689
- return data && !isLoading ? formatListLayout(data, { schemas, schema, components }) : {
2690
- layout: [],
2691
- metadatas: {},
2692
- options: {},
2693
- settings: DEFAULT_SETTINGS
2694
- };
2695
- }, [data, isLoading, schemas, schema, components]);
2696
- const { layout: edit } = React.useMemo(
2697
- () => runHookWaterfall(HOOKS.MUTATE_EDIT_VIEW_LAYOUT, {
2698
- layout: editLayout,
2699
- query
2700
- }),
2701
- [editLayout, query, runHookWaterfall]
2702
- );
2703
- return {
2704
- error,
2705
- isLoading,
2706
- edit,
2707
- list: listLayout
2708
- };
2709
- };
2710
- const useDocLayout = () => {
2711
- const { model } = useDoc();
2712
- return useDocumentLayout(model);
2713
- };
2714
- const formatEditLayout = (data, {
2715
- schemas,
2716
- schema,
2717
- components
2718
- }) => {
2719
- let currentPanelIndex = 0;
2720
- const panelledEditAttributes = convertEditLayoutToFieldLayouts(
2721
- data.contentType.layouts.edit,
2722
- schema?.attributes,
2723
- data.contentType.metadatas,
2724
- { configurations: data.components, schemas: components },
2725
- schemas
2726
- ).reduce((panels, row) => {
2727
- if (row.some((field) => field.type === "dynamiczone")) {
2728
- panels.push([row]);
2729
- currentPanelIndex += 2;
2730
- } else {
2731
- if (!panels[currentPanelIndex]) {
2732
- panels.push([]);
2733
- }
2734
- panels[currentPanelIndex].push(row);
2735
- }
2736
- return panels;
2737
- }, []);
2738
- const componentEditAttributes = Object.entries(data.components).reduce(
2739
- (acc, [uid, configuration]) => {
2740
- acc[uid] = {
2741
- layout: convertEditLayoutToFieldLayouts(
2742
- configuration.layouts.edit,
2743
- components[uid].attributes,
2744
- configuration.metadatas
2745
- ),
2746
- settings: {
2747
- ...configuration.settings,
2748
- icon: components[uid].info.icon,
2749
- displayName: components[uid].info.displayName
2926
+ params: {
2927
+ locale: "*"
2928
+ }
2929
+ });
2930
+ if (!("error" in res)) {
2931
+ navigate({ pathname: `../${collectionType}/${model}` }, { replace: true });
2932
+ }
2933
+ } finally {
2934
+ if (!listViewPathMatch) {
2935
+ setSubmitting(false);
2936
+ }
2750
2937
  }
2751
- };
2752
- return acc;
2753
- },
2754
- {}
2755
- );
2756
- const editMetadatas = Object.entries(data.contentType.metadatas).reduce(
2757
- (acc, [attribute, metadata]) => {
2758
- return {
2759
- ...acc,
2760
- [attribute]: metadata.edit
2761
- };
2762
- },
2763
- {}
2764
- );
2765
- return {
2766
- layout: panelledEditAttributes,
2767
- components: componentEditAttributes,
2768
- metadatas: editMetadatas,
2769
- settings: {
2770
- ...data.contentType.settings,
2771
- displayName: schema?.info.displayName
2938
+ }
2772
2939
  },
2773
- options: {
2774
- ...schema?.options,
2775
- ...schema?.pluginOptions,
2776
- ...data.contentType.options
2777
- }
2940
+ variant: "danger",
2941
+ position: ["header", "table-row"]
2778
2942
  };
2779
2943
  };
2780
- const convertEditLayoutToFieldLayouts = (rows, attributes = {}, metadatas, components, schemas = []) => {
2781
- return rows.map(
2782
- (row) => row.map((field) => {
2783
- const attribute = attributes[field.name];
2784
- if (!attribute) {
2785
- return null;
2786
- }
2787
- const { edit: metadata } = metadatas[field.name];
2788
- const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
2789
- return {
2790
- attribute,
2791
- disabled: !metadata.editable,
2792
- hint: metadata.description,
2793
- label: metadata.label ?? "",
2794
- name: field.name,
2795
- // @ts-expect-error – mainField does exist on the metadata for a relation.
2796
- mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
2797
- schemas,
2798
- components: components?.schemas ?? {}
2799
- }),
2800
- placeholder: metadata.placeholder ?? "",
2801
- required: attribute.required ?? false,
2802
- size: field.size,
2803
- unique: "unique" in attribute ? attribute.unique : false,
2804
- visible: metadata.visible ?? true,
2805
- type: attribute.type
2806
- };
2807
- }).filter((field) => field !== null)
2808
- );
2944
+ DeleteAction$1.type = "delete";
2945
+ const DEFAULT_HEADER_ACTIONS = [EditTheModelAction, ConfigureTheViewAction, DeleteAction$1];
2946
+ const Panels = () => {
2947
+ const isCloning = useMatch(CLONE_PATH) !== null;
2948
+ const [
2949
+ {
2950
+ query: { status }
2951
+ }
2952
+ ] = useQueryParams({
2953
+ status: "draft"
2954
+ });
2955
+ const { model, id, document, meta, collectionType } = useDoc();
2956
+ const plugins = useStrapiApp("Panels", (state) => state.plugins);
2957
+ const props = {
2958
+ activeTab: status,
2959
+ model,
2960
+ documentId: id,
2961
+ document: isCloning ? void 0 : document,
2962
+ meta: isCloning ? void 0 : meta,
2963
+ collectionType
2964
+ };
2965
+ return /* @__PURE__ */ jsx(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: /* @__PURE__ */ jsx(
2966
+ DescriptionComponentRenderer,
2967
+ {
2968
+ props,
2969
+ descriptions: plugins["content-manager"].apis.getEditViewSidePanels(),
2970
+ children: (panels) => panels.map(({ content, id: id2, ...description }) => /* @__PURE__ */ jsx(Panel, { ...description, children: content }, id2))
2971
+ }
2972
+ ) });
2809
2973
  };
2810
- const formatListLayout = (data, {
2811
- schemas,
2812
- schema,
2813
- components
2814
- }) => {
2815
- const listMetadatas = Object.entries(data.contentType.metadatas).reduce(
2816
- (acc, [attribute, metadata]) => {
2817
- return {
2818
- ...acc,
2819
- [attribute]: metadata.list
2820
- };
2821
- },
2822
- {}
2823
- );
2824
- const listAttributes = convertListLayoutToFieldLayouts(
2825
- data.contentType.layouts.list,
2826
- schema?.attributes,
2827
- listMetadatas,
2828
- { configurations: data.components, schemas: components },
2829
- schemas
2830
- );
2974
+ const ActionsPanel = () => {
2975
+ const { formatMessage } = useIntl();
2831
2976
  return {
2832
- layout: listAttributes,
2833
- settings: { ...data.contentType.settings, displayName: schema?.info.displayName },
2834
- metadatas: listMetadatas,
2835
- options: {
2836
- ...schema?.options,
2837
- ...schema?.pluginOptions,
2838
- ...data.contentType.options
2839
- }
2977
+ title: formatMessage({
2978
+ id: "content-manager.containers.edit.panels.default.title",
2979
+ defaultMessage: "Entry"
2980
+ }),
2981
+ content: /* @__PURE__ */ jsx(ActionsPanelContent, {})
2840
2982
  };
2841
2983
  };
2842
- const convertListLayoutToFieldLayouts = (columns, attributes = {}, metadatas, components, schemas = []) => {
2843
- return columns.map((name) => {
2844
- const attribute = attributes[name];
2845
- if (!attribute) {
2846
- return null;
2984
+ ActionsPanel.type = "actions";
2985
+ const ActionsPanelContent = () => {
2986
+ const isCloning = useMatch(CLONE_PATH) !== null;
2987
+ const [
2988
+ {
2989
+ query: { status = "draft" }
2847
2990
  }
2848
- const metadata = metadatas[name];
2849
- const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
2850
- return {
2851
- attribute,
2852
- label: metadata.label ?? "",
2853
- mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
2854
- schemas,
2855
- components: components?.schemas ?? {}
2856
- }),
2857
- name,
2858
- searchable: metadata.searchable ?? true,
2859
- sortable: metadata.sortable ?? true
2860
- };
2861
- }).filter((field) => field !== null);
2991
+ ] = useQueryParams();
2992
+ const { model, id, document, meta, collectionType } = useDoc();
2993
+ const plugins = useStrapiApp("ActionsPanel", (state) => state.plugins);
2994
+ const props = {
2995
+ activeTab: status,
2996
+ model,
2997
+ documentId: id,
2998
+ document: isCloning ? void 0 : document,
2999
+ meta: isCloning ? void 0 : meta,
3000
+ collectionType
3001
+ };
3002
+ return /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, width: "100%", children: [
3003
+ /* @__PURE__ */ jsx(
3004
+ DescriptionComponentRenderer,
3005
+ {
3006
+ props,
3007
+ descriptions: plugins["content-manager"].apis.getDocumentActions(),
3008
+ children: (actions2) => /* @__PURE__ */ jsx(DocumentActions, { actions: actions2 })
3009
+ }
3010
+ ),
3011
+ /* @__PURE__ */ jsx(InjectionZone, { area: "editView.right-links", slug: model })
3012
+ ] });
2862
3013
  };
3014
+ const Panel = React.forwardRef(({ children, title }, ref) => {
3015
+ return /* @__PURE__ */ jsxs(
3016
+ Flex,
3017
+ {
3018
+ ref,
3019
+ tag: "aside",
3020
+ "aria-labelledby": "additional-information",
3021
+ background: "neutral0",
3022
+ borderColor: "neutral150",
3023
+ hasRadius: true,
3024
+ paddingBottom: 4,
3025
+ paddingLeft: 4,
3026
+ paddingRight: 4,
3027
+ paddingTop: 4,
3028
+ shadow: "tableShadow",
3029
+ gap: 3,
3030
+ direction: "column",
3031
+ justifyContent: "stretch",
3032
+ alignItems: "flex-start",
3033
+ children: [
3034
+ /* @__PURE__ */ jsx(Typography, { tag: "h2", variant: "sigma", textTransform: "uppercase", textColor: "neutral600", children: title }),
3035
+ children
3036
+ ]
3037
+ }
3038
+ );
3039
+ });
2863
3040
  const ConfirmBulkActionDialog = ({
2864
3041
  onToggleDialog,
2865
3042
  isOpen = false,
@@ -2898,6 +3075,7 @@ const ConfirmDialogPublishAll = ({
2898
3075
  const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler(getTranslation);
2899
3076
  const { model, schema } = useDoc();
2900
3077
  const [{ query }] = useQueryParams();
3078
+ const enableDraftRelationsCount = false;
2901
3079
  const {
2902
3080
  data: countDraftRelations = 0,
2903
3081
  isLoading,
@@ -2909,7 +3087,7 @@ const ConfirmDialogPublishAll = ({
2909
3087
  locale: query?.plugins?.i18n?.locale
2910
3088
  },
2911
3089
  {
2912
- skip: selectedEntries.length === 0
3090
+ skip: !enableDraftRelationsCount
2913
3091
  }
2914
3092
  );
2915
3093
  React.useEffect(() => {
@@ -3094,7 +3272,7 @@ const SelectedEntriesTableContent = ({
3094
3272
  status: row.status
3095
3273
  }
3096
3274
  ) }),
3097
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
3275
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Flex, { children: /* @__PURE__ */ jsx(
3098
3276
  IconButton,
3099
3277
  {
3100
3278
  tag: Link,
@@ -3103,23 +3281,16 @@ const SelectedEntriesTableContent = ({
3103
3281
  search: row.locale && `?plugins[i18n][locale]=${row.locale}`
3104
3282
  },
3105
3283
  state: { from: pathname },
3106
- label: formatMessage(
3107
- { id: "app.component.HelperPluginTable.edit", defaultMessage: "Edit {target}" },
3108
- {
3109
- target: formatMessage(
3110
- {
3111
- id: "content-manager.components.ListViewHelperPluginTable.row-line",
3112
- defaultMessage: "item line {number}"
3113
- },
3114
- { number: index2 + 1 }
3115
- )
3116
- }
3117
- ),
3284
+ label: formatMessage({
3285
+ id: "content-manager.bulk-publish.edit",
3286
+ defaultMessage: "Edit"
3287
+ }),
3118
3288
  target: "_blank",
3119
3289
  marginLeft: "auto",
3120
- children: /* @__PURE__ */ jsx(Pencil, {})
3290
+ variant: "ghost",
3291
+ children: /* @__PURE__ */ jsx(Pencil, { width: "1.6rem", height: "1.6rem" })
3121
3292
  }
3122
- ) })
3293
+ ) }) })
3123
3294
  ] }, row.id)) })
3124
3295
  ] });
3125
3296
  };
@@ -3156,7 +3327,13 @@ const SelectedEntriesModalContent = ({
3156
3327
  );
3157
3328
  const { rows, validationErrors } = React.useMemo(() => {
3158
3329
  if (data.length > 0 && schema) {
3159
- const validate = createYupSchema(schema.attributes, components);
3330
+ const validate = createYupSchema(
3331
+ schema.attributes,
3332
+ components,
3333
+ // Since this is the "Publish" action, the validation
3334
+ // schema must enforce the rules for published entities
3335
+ { status: "published" }
3336
+ );
3160
3337
  const validationErrors2 = {};
3161
3338
  const rows2 = data.map((entry) => {
3162
3339
  try {
@@ -3506,7 +3683,7 @@ const TableActions = ({ document }) => {
3506
3683
  DescriptionComponentRenderer,
3507
3684
  {
3508
3685
  props,
3509
- descriptions: plugins["content-manager"].apis.getDocumentActions(),
3686
+ descriptions: plugins["content-manager"].apis.getDocumentActions().filter((action) => action.name !== "PublishAction"),
3510
3687
  children: (actions2) => {
3511
3688
  const tableRowActions = actions2.filter((action) => {
3512
3689
  const positions = Array.isArray(action.position) ? action.position : [action.position];
@@ -3617,7 +3794,7 @@ const CloneAction = ({ model, documentId }) => {
3617
3794
  }),
3618
3795
  content: /* @__PURE__ */ jsx(AutoCloneFailureModalBody, { prohibitedFields }),
3619
3796
  footer: ({ onClose }) => {
3620
- return /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", children: [
3797
+ return /* @__PURE__ */ jsxs(Modal.Footer, { children: [
3621
3798
  /* @__PURE__ */ jsx(Button, { onClick: onClose, variant: "tertiary", children: formatMessage({
3622
3799
  id: "cancel",
3623
3800
  defaultMessage: "Cancel"
@@ -3751,17 +3928,27 @@ const HistoryAction = ({ model, document }) => {
3751
3928
  const { formatMessage } = useIntl();
3752
3929
  const [{ query }] = useQueryParams();
3753
3930
  const navigate = useNavigate();
3931
+ const { trackUsage } = useTracking();
3932
+ const { pathname } = useLocation();
3754
3933
  const pluginsQueryParams = stringify({ plugins: query.plugins }, { encode: false });
3755
3934
  if (!window.strapi.features.isEnabled("cms-content-history")) {
3756
3935
  return null;
3757
3936
  }
3937
+ const handleOnClick = () => {
3938
+ const destination = { pathname: "history", search: pluginsQueryParams };
3939
+ trackUsage("willNavigate", {
3940
+ from: pathname,
3941
+ to: `${pathname}/${destination.pathname}`
3942
+ });
3943
+ navigate(destination);
3944
+ };
3758
3945
  return {
3759
3946
  icon: /* @__PURE__ */ jsx(ClockCounterClockwise, {}),
3760
3947
  label: formatMessage({
3761
3948
  id: "content-manager.history.document-action",
3762
3949
  defaultMessage: "Content History"
3763
3950
  }),
3764
- onClick: () => navigate({ pathname: "history", search: pluginsQueryParams }),
3951
+ onClick: handleOnClick,
3765
3952
  disabled: (
3766
3953
  /**
3767
3954
  * The user is creating a new document.
@@ -3829,6 +4016,90 @@ const { setInitialData } = actions;
3829
4016
  const reducer = combineReducers({
3830
4017
  app: reducer$1
3831
4018
  });
4019
+ const previewApi = contentManagerApi.injectEndpoints({
4020
+ endpoints: (builder) => ({
4021
+ getPreviewUrl: builder.query({
4022
+ query({ query, params }) {
4023
+ return {
4024
+ url: `/content-manager/preview/url/${params.contentType}`,
4025
+ method: "GET",
4026
+ config: {
4027
+ params: query
4028
+ }
4029
+ };
4030
+ }
4031
+ })
4032
+ })
4033
+ });
4034
+ const { useGetPreviewUrlQuery } = previewApi;
4035
+ const ConditionalTooltip = ({ isShown, label, children }) => {
4036
+ if (isShown) {
4037
+ return /* @__PURE__ */ jsx(Tooltip, { label, children });
4038
+ }
4039
+ return children;
4040
+ };
4041
+ const PreviewSidePanel = ({ model, documentId, document }) => {
4042
+ const { formatMessage } = useIntl();
4043
+ const { trackUsage } = useTracking();
4044
+ const { pathname } = useLocation();
4045
+ const [{ query }] = useQueryParams();
4046
+ const isModified = useForm("PreviewSidePanel", (state) => state.modified);
4047
+ const { data, error } = useGetPreviewUrlQuery({
4048
+ params: {
4049
+ contentType: model
4050
+ },
4051
+ query: {
4052
+ documentId,
4053
+ locale: document?.locale,
4054
+ status: document?.status
4055
+ }
4056
+ });
4057
+ if (!data?.data?.url || error) {
4058
+ return null;
4059
+ }
4060
+ const trackNavigation = () => {
4061
+ const destinationPathname = pathname.replace(/\/$/, "") + "/preview";
4062
+ trackUsage("willNavigate", { from: pathname, to: destinationPathname });
4063
+ };
4064
+ return {
4065
+ title: formatMessage({ id: "content-manager.preview.panel.title", defaultMessage: "Preview" }),
4066
+ content: /* @__PURE__ */ jsx(Flex, { gap: 2, width: "100%", children: /* @__PURE__ */ jsx(
4067
+ ConditionalTooltip,
4068
+ {
4069
+ label: formatMessage({
4070
+ id: "content-manager.preview.panel.button-disabled-tooltip",
4071
+ defaultMessage: "Please save to open the preview"
4072
+ }),
4073
+ isShown: isModified,
4074
+ children: /* @__PURE__ */ jsx(
4075
+ Button,
4076
+ {
4077
+ variant: "tertiary",
4078
+ tag: Link,
4079
+ to: { pathname: "preview", search: stringify(query, { encode: false }) },
4080
+ onClick: trackNavigation,
4081
+ flex: "auto",
4082
+ disabled: isModified,
4083
+ children: formatMessage({
4084
+ id: "content-manager.preview.panel.button",
4085
+ defaultMessage: "Open preview"
4086
+ })
4087
+ }
4088
+ )
4089
+ }
4090
+ ) })
4091
+ };
4092
+ };
4093
+ const FEATURE_ID = "preview";
4094
+ const previewAdmin = {
4095
+ bootstrap(app) {
4096
+ if (!window.strapi.future.isEnabled(FEATURE_ID)) {
4097
+ return;
4098
+ }
4099
+ const contentManagerPluginApis = app.getPlugin("content-manager").apis;
4100
+ contentManagerPluginApis.addEditViewSidePanel([PreviewSidePanel]);
4101
+ }
4102
+ };
3832
4103
  const index = {
3833
4104
  register(app) {
3834
4105
  const cm = new ContentManagerPlugin();
@@ -3848,7 +4119,7 @@ const index = {
3848
4119
  app.router.addRoute({
3849
4120
  path: "content-manager/*",
3850
4121
  lazy: async () => {
3851
- const { Layout } = await import("./layout-oPBiO7RY.mjs");
4122
+ const { Layout } = await import("./layout-2Si0j0jO.mjs");
3852
4123
  return {
3853
4124
  Component: Layout
3854
4125
  };
@@ -3861,11 +4132,14 @@ const index = {
3861
4132
  if (typeof historyAdmin.bootstrap === "function") {
3862
4133
  historyAdmin.bootstrap(app);
3863
4134
  }
4135
+ if (typeof previewAdmin.bootstrap === "function") {
4136
+ previewAdmin.bootstrap(app);
4137
+ }
3864
4138
  },
3865
4139
  async registerTrads({ locales }) {
3866
4140
  const importedTrads = await Promise.all(
3867
4141
  locales.map((locale) => {
3868
- return __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/ar.json": () => import("./ar-CCEVvqGG.mjs"), "./translations/ca.json": () => import("./ca-5U32ON2v.mjs"), "./translations/cs.json": () => import("./cs-CM2aBUar.mjs"), "./translations/de.json": () => import("./de-C72KDNOl.mjs"), "./translations/en.json": () => import("./en-BrCTWlZv.mjs"), "./translations/es.json": () => import("./es-CeXiYflN.mjs"), "./translations/eu.json": () => import("./eu-CdALomew.mjs"), "./translations/fr.json": () => import("./fr-CD9VFbPM.mjs"), "./translations/gu.json": () => import("./gu-CNpaMDpH.mjs"), "./translations/hi.json": () => import("./hi-Dwvd04m3.mjs"), "./translations/hu.json": () => import("./hu-CeYvaaO0.mjs"), "./translations/id.json": () => import("./id-BtwA9WJT.mjs"), "./translations/it.json": () => import("./it-BrVPqaf1.mjs"), "./translations/ja.json": () => import("./ja-CtsUxOvk.mjs"), "./translations/ko.json": () => import("./ko-HVQRlfUI.mjs"), "./translations/ml.json": () => import("./ml-BihZwQit.mjs"), "./translations/ms.json": () => import("./ms-m_WjyWx7.mjs"), "./translations/nl.json": () => import("./nl-D4R9gHx5.mjs"), "./translations/pl.json": () => import("./pl-sbx9mSt_.mjs"), "./translations/pt-BR.json": () => import("./pt-BR-C71iDxnh.mjs"), "./translations/pt.json": () => import("./pt-BsaFvS8-.mjs"), "./translations/ru.json": () => import("./ru-BE6A4Exp.mjs"), "./translations/sa.json": () => import("./sa-Dag0k-Z8.mjs"), "./translations/sk.json": () => import("./sk-BFg-R8qJ.mjs"), "./translations/sv.json": () => import("./sv-CUnfWGsh.mjs"), "./translations/th.json": () => import("./th-BqbI8lIT.mjs"), "./translations/tr.json": () => import("./tr-CgeK3wJM.mjs"), "./translations/uk.json": () => import("./uk-CR-zDhAY.mjs"), "./translations/vi.json": () => import("./vi-DUXIk_fw.mjs"), "./translations/zh-Hans.json": () => import("./zh-Hans-BPQcRIyH.mjs"), "./translations/zh.json": () => import("./zh-BWZspA60.mjs") }), `./translations/${locale}.json`).then(({ default: data }) => {
4142
+ 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-Dtk_ot79.mjs"), "./translations/es.json": () => import("./es-D34tqjMw.mjs"), "./translations/eu.json": () => import("./eu-CdALomew.mjs"), "./translations/fr.json": () => import("./fr--pg5jUbt.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-BHqhDq4V.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 }) => {
3869
4143
  return {
3870
4144
  data: prefixPluginTranslations(data, PLUGIN_ID),
3871
4145
  locale
@@ -3886,13 +4160,16 @@ export {
3886
4160
  BulkActionsRenderer as B,
3887
4161
  COLLECTION_TYPES as C,
3888
4162
  DocumentStatus as D,
3889
- DEFAULT_SETTINGS as E,
3890
- convertEditLayoutToFieldLayouts as F,
3891
- useDocument as G,
4163
+ extractContentTypeComponents as E,
4164
+ DEFAULT_SETTINGS as F,
4165
+ convertEditLayoutToFieldLayouts as G,
3892
4166
  HOOKS as H,
3893
4167
  InjectionZone as I,
3894
- index as J,
3895
- useDocumentActions as K,
4168
+ useDocument as J,
4169
+ useGetPreviewUrlQuery as K,
4170
+ index as L,
4171
+ useContentManagerContext as M,
4172
+ useDocumentActions as N,
3896
4173
  Panels as P,
3897
4174
  RelativeTime as R,
3898
4175
  SINGLE_TYPES as S,
@@ -3910,18 +4187,18 @@ export {
3910
4187
  PERMISSIONS as k,
3911
4188
  DocumentRBAC as l,
3912
4189
  DOCUMENT_META_FIELDS as m,
3913
- useDocLayout as n,
3914
- useGetContentTypeConfigurationQuery as o,
3915
- CREATOR_FIELDS as p,
3916
- getMainField as q,
3917
- getDisplayName as r,
4190
+ CLONE_PATH as n,
4191
+ useDocLayout as o,
4192
+ useGetContentTypeConfigurationQuery as p,
4193
+ CREATOR_FIELDS as q,
4194
+ getMainField as r,
3918
4195
  setInitialData as s,
3919
- checkIfAttributeIsDisplayable as t,
4196
+ getDisplayName as t,
3920
4197
  useContentTypeSchema as u,
3921
- useGetAllDocumentsQuery as v,
3922
- convertListLayoutToFieldLayouts as w,
3923
- capitalise as x,
3924
- useUpdateContentTypeConfigurationMutation as y,
3925
- extractContentTypeComponents as z
4198
+ checkIfAttributeIsDisplayable as v,
4199
+ useGetAllDocumentsQuery as w,
4200
+ convertListLayoutToFieldLayouts as x,
4201
+ capitalise as y,
4202
+ useUpdateContentTypeConfigurationMutation as z
3926
4203
  };
3927
- //# sourceMappingURL=index-c_5DdJi-.mjs.map
4204
+ //# sourceMappingURL=index-BLPa8Dq-.mjs.map