@strapi/content-manager 0.0.0-experimental.a65a85fdea97faae8679d3ffc5f9d79af61abd26 → 0.0.0-experimental.a687a6977f91492ccfc6771bf398a5236ece3c94

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 (260) hide show
  1. package/LICENSE +18 -3
  2. package/dist/_chunks/{CardDragPreview-DSVYodBX.js → CardDragPreview-C0QyJgRA.js} +10 -14
  3. package/dist/_chunks/CardDragPreview-C0QyJgRA.js.map +1 -0
  4. package/dist/_chunks/{CardDragPreview-ikSG4M46.mjs → CardDragPreview-DOxamsuj.mjs} +7 -9
  5. package/dist/_chunks/CardDragPreview-DOxamsuj.mjs.map +1 -0
  6. package/dist/_chunks/{ComponentConfigurationPage-43KmCNQE.js → ComponentConfigurationPage-BebDdCkl.js} +4 -4
  7. package/dist/_chunks/{ComponentConfigurationPage-43KmCNQE.js.map → ComponentConfigurationPage-BebDdCkl.js.map} +1 -1
  8. package/dist/_chunks/{ComponentConfigurationPage--2aLCv-G.mjs → ComponentConfigurationPage-XdGcgtZh.mjs} +4 -4
  9. package/dist/_chunks/{ComponentConfigurationPage--2aLCv-G.mjs.map → ComponentConfigurationPage-XdGcgtZh.mjs.map} +1 -1
  10. package/dist/_chunks/{ComponentIcon-BBQsYCVn.js → ComponentIcon-BXdiCGQp.js} +8 -2
  11. package/dist/_chunks/ComponentIcon-BXdiCGQp.js.map +1 -0
  12. package/dist/_chunks/{ComponentIcon-BOFnK76n.mjs → ComponentIcon-u4bIXTFY.mjs} +9 -3
  13. package/dist/_chunks/ComponentIcon-u4bIXTFY.mjs.map +1 -0
  14. package/dist/_chunks/{EditConfigurationPage-CUcGHHvQ.mjs → EditConfigurationPage-DaNf9MoK.mjs} +4 -4
  15. package/dist/_chunks/{EditConfigurationPage-CUcGHHvQ.mjs.map → EditConfigurationPage-DaNf9MoK.mjs.map} +1 -1
  16. package/dist/_chunks/{EditConfigurationPage-BfFzJ4Br.js → EditConfigurationPage-sdGi-bna.js} +4 -4
  17. package/dist/_chunks/{EditConfigurationPage-BfFzJ4Br.js.map → EditConfigurationPage-sdGi-bna.js.map} +1 -1
  18. package/dist/_chunks/{EditViewPage-CzOT5Kpj.js → EditViewPage-BRA-JSnw.js} +101 -52
  19. package/dist/_chunks/EditViewPage-BRA-JSnw.js.map +1 -0
  20. package/dist/_chunks/EditViewPage-DtbTsNeX.mjs +254 -0
  21. package/dist/_chunks/EditViewPage-DtbTsNeX.mjs.map +1 -0
  22. package/dist/_chunks/{Field-Dlh0uGnL.mjs → Field-CDmB9zqh.mjs} +1075 -812
  23. package/dist/_chunks/Field-CDmB9zqh.mjs.map +1 -0
  24. package/dist/_chunks/{Field-Caef4JjM.js → Field-DoVRA4co.js} +1120 -858
  25. package/dist/_chunks/Field-DoVRA4co.js.map +1 -0
  26. package/dist/_chunks/{Form-BzuAjtRq.js → Form-BEh514bg.js} +69 -49
  27. package/dist/_chunks/Form-BEh514bg.js.map +1 -0
  28. package/dist/_chunks/{Form-EnaQL_6L.mjs → Form-Cle2X-4n.mjs} +70 -49
  29. package/dist/_chunks/Form-Cle2X-4n.mjs.map +1 -0
  30. package/dist/_chunks/{History-C17LiyRg.js → History-BsPXl1XH.js} +182 -146
  31. package/dist/_chunks/History-BsPXl1XH.js.map +1 -0
  32. package/dist/_chunks/{History-D6sbCJvo.mjs → History-ktAPcvHJ.mjs} +182 -145
  33. package/dist/_chunks/History-ktAPcvHJ.mjs.map +1 -0
  34. package/dist/_chunks/{ListConfigurationPage-Ce4qs7qE.mjs → ListConfigurationPage-BNAS_v2s.mjs} +71 -60
  35. package/dist/_chunks/ListConfigurationPage-BNAS_v2s.mjs.map +1 -0
  36. package/dist/_chunks/{ListConfigurationPage-Dks5SX6f.js → ListConfigurationPage-CN1-7Pgi.js} +73 -63
  37. package/dist/_chunks/ListConfigurationPage-CN1-7Pgi.js.map +1 -0
  38. package/dist/_chunks/{ListViewPage-Be7S5aKL.mjs → ListViewPage-B15c_0Vt.mjs} +143 -139
  39. package/dist/_chunks/ListViewPage-B15c_0Vt.mjs.map +1 -0
  40. package/dist/_chunks/{ListViewPage-BwrZrPsh.js → ListViewPage-BUKFOJuS.js} +146 -142
  41. package/dist/_chunks/ListViewPage-BUKFOJuS.js.map +1 -0
  42. package/dist/_chunks/{NoContentTypePage-CIPmYQMm.mjs → NoContentTypePage-CAgdmhlo.mjs} +7 -7
  43. package/dist/_chunks/NoContentTypePage-CAgdmhlo.mjs.map +1 -0
  44. package/dist/_chunks/{NoContentTypePage-Cu5r1-JT.js → NoContentTypePage-Cs7Kk9EW.js} +5 -5
  45. package/dist/_chunks/NoContentTypePage-Cs7Kk9EW.js.map +1 -0
  46. package/dist/_chunks/{NoPermissionsPage-C-j6TEUF.js → NoPermissionsPage-DRPnEq9t.js} +4 -5
  47. package/dist/_chunks/NoPermissionsPage-DRPnEq9t.js.map +1 -0
  48. package/dist/_chunks/{NoPermissionsPage-DhJ7LYrr.mjs → NoPermissionsPage-JxX4Y4RJ.mjs} +5 -6
  49. package/dist/_chunks/NoPermissionsPage-JxX4Y4RJ.mjs.map +1 -0
  50. package/dist/_chunks/Preview-BKuVG1dV.js +286 -0
  51. package/dist/_chunks/Preview-BKuVG1dV.js.map +1 -0
  52. package/dist/_chunks/Preview-CQlTtbLc.mjs +267 -0
  53. package/dist/_chunks/Preview-CQlTtbLc.mjs.map +1 -0
  54. package/dist/_chunks/{Relations-CY7AtkDA.mjs → Relations-DMQ93R3L.mjs} +135 -89
  55. package/dist/_chunks/Relations-DMQ93R3L.mjs.map +1 -0
  56. package/dist/_chunks/{Relations-Czs-uZ-s.js → Relations-DTEGSaM_.js} +138 -93
  57. package/dist/_chunks/Relations-DTEGSaM_.js.map +1 -0
  58. package/dist/_chunks/{en-MBPul9Su.mjs → en-CfIXaZf9.mjs} +36 -17
  59. package/dist/_chunks/{en-MBPul9Su.mjs.map → en-CfIXaZf9.mjs.map} +1 -1
  60. package/dist/_chunks/{en-C-V1_90f.js → en-DTWPCdTS.js} +36 -17
  61. package/dist/_chunks/{en-C-V1_90f.js.map → en-DTWPCdTS.js.map} +1 -1
  62. package/dist/_chunks/{es-EUonQTon.js → es-9K52xZIr.js} +2 -2
  63. package/dist/_chunks/{ja-CcFe8diO.js.map → es-9K52xZIr.js.map} +1 -1
  64. package/dist/_chunks/{es-CeXiYflN.mjs → es-D34tqjMw.mjs} +2 -2
  65. package/dist/_chunks/{es-CeXiYflN.mjs.map → es-D34tqjMw.mjs.map} +1 -1
  66. package/dist/_chunks/{fr-CD9VFbPM.mjs → fr--pg5jUbt.mjs} +13 -3
  67. package/dist/_chunks/{fr-CD9VFbPM.mjs.map → fr--pg5jUbt.mjs.map} +1 -1
  68. package/dist/_chunks/{fr-B7kGGg3E.js → fr-B2Kyv8Z9.js} +13 -3
  69. package/dist/_chunks/{fr-B7kGGg3E.js.map → fr-B2Kyv8Z9.js.map} +1 -1
  70. package/dist/_chunks/{index-DNVx8ssZ.mjs → index-BdUq-Dtg.mjs} +1839 -831
  71. package/dist/_chunks/index-BdUq-Dtg.mjs.map +1 -0
  72. package/dist/_chunks/{index-X_2tafck.js → index-DjV7tyGu.js} +1826 -818
  73. package/dist/_chunks/index-DjV7tyGu.js.map +1 -0
  74. package/dist/_chunks/{ja-CcFe8diO.js → ja-7sfIbjxE.js} +2 -2
  75. package/dist/_chunks/{es-EUonQTon.js.map → ja-7sfIbjxE.js.map} +1 -1
  76. package/dist/_chunks/{ja-CtsUxOvk.mjs → ja-BHqhDq4V.mjs} +2 -2
  77. package/dist/_chunks/{ja-CtsUxOvk.mjs.map → ja-BHqhDq4V.mjs.map} +1 -1
  78. package/dist/_chunks/{layout-Dnh0PNp9.mjs → layout-BJ8CpEJu.mjs} +47 -29
  79. package/dist/_chunks/layout-BJ8CpEJu.mjs.map +1 -0
  80. package/dist/_chunks/{layout-dBc7wN7L.js → layout-Cx9LHtH5.js} +47 -31
  81. package/dist/_chunks/layout-Cx9LHtH5.js.map +1 -0
  82. package/dist/_chunks/{objects-gigeqt7s.js → objects-BcXOv6_9.js} +2 -4
  83. package/dist/_chunks/{objects-gigeqt7s.js.map → objects-BcXOv6_9.js.map} +1 -1
  84. package/dist/_chunks/{objects-mKMAmfec.mjs → objects-D6yBsdmx.mjs} +2 -4
  85. package/dist/_chunks/{objects-mKMAmfec.mjs.map → objects-D6yBsdmx.mjs.map} +1 -1
  86. package/dist/_chunks/{relations-4pHtBrHJ.js → relations-5bc76OJz.js} +6 -7
  87. package/dist/_chunks/relations-5bc76OJz.js.map +1 -0
  88. package/dist/_chunks/{relations-Dx7tMKJN.mjs → relations-BeezTo41.mjs} +6 -7
  89. package/dist/_chunks/relations-BeezTo41.mjs.map +1 -0
  90. package/dist/_chunks/useDebounce-CtcjDB3L.js +28 -0
  91. package/dist/_chunks/useDebounce-CtcjDB3L.js.map +1 -0
  92. package/dist/_chunks/useDebounce-DmuSJIF3.mjs +29 -0
  93. package/dist/_chunks/useDebounce-DmuSJIF3.mjs.map +1 -0
  94. package/dist/_chunks/useDragAndDrop-DdHgKsqq.mjs.map +1 -1
  95. package/dist/_chunks/useDragAndDrop-J0TUUbR6.js.map +1 -1
  96. package/dist/admin/index.js +3 -1
  97. package/dist/admin/index.js.map +1 -1
  98. package/dist/admin/index.mjs +9 -7
  99. package/dist/admin/src/components/ComponentIcon.d.ts +6 -3
  100. package/dist/admin/src/content-manager.d.ts +3 -3
  101. package/dist/admin/src/exports.d.ts +2 -1
  102. package/dist/admin/src/history/components/VersionInputRenderer.d.ts +1 -1
  103. package/dist/admin/src/history/index.d.ts +3 -0
  104. package/dist/admin/src/history/services/historyVersion.d.ts +1 -1
  105. package/dist/admin/src/hooks/useDocument.d.ts +37 -9
  106. package/dist/admin/src/hooks/useDocumentActions.d.ts +24 -3
  107. package/dist/admin/src/hooks/useDocumentLayout.d.ts +2 -2
  108. package/dist/admin/src/hooks/useDragAndDrop.d.ts +4 -4
  109. package/dist/admin/src/hooks/useKeyboardDragAndDrop.d.ts +1 -1
  110. package/dist/admin/src/index.d.ts +1 -0
  111. package/dist/admin/src/pages/EditView/EditViewPage.d.ts +9 -1
  112. package/dist/admin/src/pages/EditView/components/DocumentActions.d.ts +11 -4
  113. package/dist/admin/src/pages/EditView/components/DocumentStatus.d.ts +2 -2
  114. package/dist/admin/src/pages/EditView/components/FormInputs/BlocksInput/BlocksInput.d.ts +3 -3
  115. package/dist/admin/src/pages/EditView/components/FormInputs/BlocksInput/utils/constants.d.ts +4 -0
  116. package/dist/admin/src/pages/EditView/components/FormInputs/Component/Input.d.ts +2 -2
  117. package/dist/admin/src/pages/EditView/components/FormInputs/DynamicZone/ComponentCategory.d.ts +3 -5
  118. package/dist/admin/src/pages/EditView/components/FormInputs/DynamicZone/Field.d.ts +1 -1
  119. package/dist/admin/src/pages/EditView/components/FormInputs/Relations.d.ts +30 -18
  120. package/dist/admin/src/pages/EditView/components/FormInputs/UID.d.ts +2 -2
  121. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/EditorLayout.d.ts +3 -49
  122. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/Field.d.ts +2 -2
  123. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/WysiwygFooter.d.ts +2 -2
  124. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/WysiwygStyles.d.ts +16 -53
  125. package/dist/admin/src/pages/EditView/components/Header.d.ts +11 -11
  126. package/dist/admin/src/pages/EditView/components/InputRenderer.d.ts +2 -10
  127. package/dist/admin/src/pages/ListView/components/BulkActions/Actions.d.ts +3 -30
  128. package/dist/admin/src/pages/ListView/components/BulkActions/ConfirmBulkActionDialog.d.ts +2 -2
  129. package/dist/admin/src/pages/ListView/components/BulkActions/PublishAction.d.ts +9 -26
  130. package/dist/admin/src/preview/components/PreviewContent.d.ts +2 -0
  131. package/dist/admin/src/preview/components/PreviewHeader.d.ts +2 -0
  132. package/dist/admin/src/preview/components/PreviewSidePanel.d.ts +3 -0
  133. package/dist/admin/src/preview/constants.d.ts +1 -0
  134. package/dist/admin/src/preview/index.d.ts +4 -0
  135. package/dist/admin/src/preview/pages/Preview.d.ts +11 -0
  136. package/dist/admin/src/preview/routes.d.ts +3 -0
  137. package/dist/admin/src/preview/services/preview.d.ts +3 -0
  138. package/dist/admin/src/router.d.ts +1 -1
  139. package/dist/admin/src/services/api.d.ts +2 -3
  140. package/dist/admin/src/services/components.d.ts +2 -2
  141. package/dist/admin/src/services/contentTypes.d.ts +5 -5
  142. package/dist/admin/src/services/documents.d.ts +31 -20
  143. package/dist/admin/src/services/init.d.ts +2 -2
  144. package/dist/admin/src/services/relations.d.ts +3 -3
  145. package/dist/admin/src/services/uid.d.ts +3 -3
  146. package/dist/admin/src/utils/api.d.ts +4 -18
  147. package/dist/admin/src/utils/validation.d.ts +5 -7
  148. package/dist/server/index.js +1008 -594
  149. package/dist/server/index.js.map +1 -1
  150. package/dist/server/index.mjs +1014 -600
  151. package/dist/server/index.mjs.map +1 -1
  152. package/dist/server/src/bootstrap.d.ts.map +1 -1
  153. package/dist/server/src/controllers/collection-types.d.ts.map +1 -1
  154. package/dist/server/src/controllers/index.d.ts.map +1 -1
  155. package/dist/server/src/controllers/relations.d.ts.map +1 -1
  156. package/dist/server/src/controllers/single-types.d.ts.map +1 -1
  157. package/dist/server/src/controllers/uid.d.ts.map +1 -1
  158. package/dist/server/src/controllers/utils/metadata.d.ts +22 -0
  159. package/dist/server/src/controllers/utils/metadata.d.ts.map +1 -0
  160. package/dist/server/src/controllers/validation/dimensions.d.ts +11 -0
  161. package/dist/server/src/controllers/validation/dimensions.d.ts.map +1 -0
  162. package/dist/server/src/controllers/validation/index.d.ts +1 -1
  163. package/dist/server/src/history/services/history.d.ts +2 -4
  164. package/dist/server/src/history/services/history.d.ts.map +1 -1
  165. package/dist/server/src/history/services/index.d.ts +6 -2
  166. package/dist/server/src/history/services/index.d.ts.map +1 -1
  167. package/dist/server/src/history/services/lifecycles.d.ts +9 -0
  168. package/dist/server/src/history/services/lifecycles.d.ts.map +1 -0
  169. package/dist/server/src/history/services/utils.d.ts +41 -9
  170. package/dist/server/src/history/services/utils.d.ts.map +1 -1
  171. package/dist/server/src/history/utils.d.ts +6 -2
  172. package/dist/server/src/history/utils.d.ts.map +1 -1
  173. package/dist/server/src/index.d.ts +21 -42
  174. package/dist/server/src/index.d.ts.map +1 -1
  175. package/dist/server/src/policies/hasPermissions.d.ts.map +1 -1
  176. package/dist/server/src/preview/constants.d.ts +2 -0
  177. package/dist/server/src/preview/constants.d.ts.map +1 -0
  178. package/dist/server/src/preview/controllers/index.d.ts +2 -0
  179. package/dist/server/src/preview/controllers/index.d.ts.map +1 -0
  180. package/dist/server/src/preview/controllers/preview.d.ts +13 -0
  181. package/dist/server/src/preview/controllers/preview.d.ts.map +1 -0
  182. package/dist/server/src/preview/controllers/validation/preview.d.ts +6 -0
  183. package/dist/server/src/preview/controllers/validation/preview.d.ts.map +1 -0
  184. package/dist/server/src/preview/index.d.ts +4 -0
  185. package/dist/server/src/preview/index.d.ts.map +1 -0
  186. package/dist/server/src/preview/routes/index.d.ts +8 -0
  187. package/dist/server/src/preview/routes/index.d.ts.map +1 -0
  188. package/dist/server/src/preview/routes/preview.d.ts +4 -0
  189. package/dist/server/src/preview/routes/preview.d.ts.map +1 -0
  190. package/dist/server/src/preview/services/index.d.ts +15 -0
  191. package/dist/server/src/preview/services/index.d.ts.map +1 -0
  192. package/dist/server/src/preview/services/preview-config.d.ts +30 -0
  193. package/dist/server/src/preview/services/preview-config.d.ts.map +1 -0
  194. package/dist/server/src/preview/services/preview.d.ts +12 -0
  195. package/dist/server/src/preview/services/preview.d.ts.map +1 -0
  196. package/dist/server/src/preview/utils.d.ts +18 -0
  197. package/dist/server/src/preview/utils.d.ts.map +1 -0
  198. package/dist/server/src/routes/index.d.ts.map +1 -1
  199. package/dist/server/src/services/document-manager.d.ts +13 -12
  200. package/dist/server/src/services/document-manager.d.ts.map +1 -1
  201. package/dist/server/src/services/document-metadata.d.ts +14 -35
  202. package/dist/server/src/services/document-metadata.d.ts.map +1 -1
  203. package/dist/server/src/services/index.d.ts +21 -42
  204. package/dist/server/src/services/index.d.ts.map +1 -1
  205. package/dist/server/src/services/permission-checker.d.ts.map +1 -1
  206. package/dist/server/src/services/utils/configuration/index.d.ts +2 -2
  207. package/dist/server/src/services/utils/configuration/layouts.d.ts +2 -2
  208. package/dist/server/src/services/utils/populate.d.ts +8 -1
  209. package/dist/server/src/services/utils/populate.d.ts.map +1 -1
  210. package/dist/server/src/utils/index.d.ts +2 -0
  211. package/dist/server/src/utils/index.d.ts.map +1 -1
  212. package/dist/shared/contracts/collection-types.d.ts +17 -7
  213. package/dist/shared/contracts/collection-types.d.ts.map +1 -1
  214. package/dist/shared/contracts/index.d.ts +1 -0
  215. package/dist/shared/contracts/index.d.ts.map +1 -1
  216. package/dist/shared/contracts/preview.d.ts +27 -0
  217. package/dist/shared/contracts/preview.d.ts.map +1 -0
  218. package/dist/shared/contracts/relations.d.ts +2 -2
  219. package/dist/shared/contracts/relations.d.ts.map +1 -1
  220. package/dist/shared/index.js +4 -0
  221. package/dist/shared/index.js.map +1 -1
  222. package/dist/shared/index.mjs +4 -0
  223. package/dist/shared/index.mjs.map +1 -1
  224. package/package.json +19 -20
  225. package/dist/_chunks/CardDragPreview-DSVYodBX.js.map +0 -1
  226. package/dist/_chunks/CardDragPreview-ikSG4M46.mjs.map +0 -1
  227. package/dist/_chunks/ComponentIcon-BBQsYCVn.js.map +0 -1
  228. package/dist/_chunks/ComponentIcon-BOFnK76n.mjs.map +0 -1
  229. package/dist/_chunks/EditViewPage-Bm8lgcm6.mjs +0 -203
  230. package/dist/_chunks/EditViewPage-Bm8lgcm6.mjs.map +0 -1
  231. package/dist/_chunks/EditViewPage-CzOT5Kpj.js.map +0 -1
  232. package/dist/_chunks/Field-Caef4JjM.js.map +0 -1
  233. package/dist/_chunks/Field-Dlh0uGnL.mjs.map +0 -1
  234. package/dist/_chunks/Form-BzuAjtRq.js.map +0 -1
  235. package/dist/_chunks/Form-EnaQL_6L.mjs.map +0 -1
  236. package/dist/_chunks/History-C17LiyRg.js.map +0 -1
  237. package/dist/_chunks/History-D6sbCJvo.mjs.map +0 -1
  238. package/dist/_chunks/ListConfigurationPage-Ce4qs7qE.mjs.map +0 -1
  239. package/dist/_chunks/ListConfigurationPage-Dks5SX6f.js.map +0 -1
  240. package/dist/_chunks/ListViewPage-Be7S5aKL.mjs.map +0 -1
  241. package/dist/_chunks/ListViewPage-BwrZrPsh.js.map +0 -1
  242. package/dist/_chunks/NoContentTypePage-CIPmYQMm.mjs.map +0 -1
  243. package/dist/_chunks/NoContentTypePage-Cu5r1-JT.js.map +0 -1
  244. package/dist/_chunks/NoPermissionsPage-C-j6TEUF.js.map +0 -1
  245. package/dist/_chunks/NoPermissionsPage-DhJ7LYrr.mjs.map +0 -1
  246. package/dist/_chunks/Relations-CY7AtkDA.mjs.map +0 -1
  247. package/dist/_chunks/Relations-Czs-uZ-s.js.map +0 -1
  248. package/dist/_chunks/index-DNVx8ssZ.mjs.map +0 -1
  249. package/dist/_chunks/index-X_2tafck.js.map +0 -1
  250. package/dist/_chunks/layout-Dnh0PNp9.mjs.map +0 -1
  251. package/dist/_chunks/layout-dBc7wN7L.js.map +0 -1
  252. package/dist/_chunks/relations-4pHtBrHJ.js.map +0 -1
  253. package/dist/_chunks/relations-Dx7tMKJN.mjs.map +0 -1
  254. package/dist/_chunks/urls-CbOsUOoW.mjs +0 -7
  255. package/dist/_chunks/urls-CbOsUOoW.mjs.map +0 -1
  256. package/dist/_chunks/urls-DzZya_gm.js +0 -6
  257. package/dist/_chunks/urls-DzZya_gm.js.map +0 -1
  258. package/dist/server/src/controllers/utils/dimensions.d.ts +0 -5
  259. package/dist/server/src/controllers/utils/dimensions.d.ts.map +0 -1
  260. package/strapi-server.js +0 -3
@@ -1,17 +1,16 @@
1
- import { ClockCounterClockwise, CrossCircle, More, WarningCircle, Cog, Pencil, Trash, ChevronRight, Duplicate, Feather } from "@strapi/icons";
1
+ import { More, Cross, WarningCircle, ListPlus, Pencil, Trash, Check, CrossCircle, CheckCircle, ArrowsCounterClockwise, ChevronRight, Duplicate, ClockCounterClockwise, Feather } from "@strapi/icons";
2
2
  import { jsx, Fragment, jsxs } from "react/jsx-runtime";
3
- import { useStrapiApp, useQueryParams, createContext, useAuth, useRBAC, Page, getFetchClient, translatedErrors, useNotification, useAPIErrorHandler, useTracking, useForm, BackButton, DescriptionComponentRenderer } from "@strapi/admin/strapi-admin";
4
- import { stringify } from "qs";
5
- import { useIntl } from "react-intl";
6
- import { useNavigate, useParams, Navigate, useMatch, NavLink } from "react-router-dom";
3
+ import { useStrapiApp, createContext, useQueryParams, useAuth, useRBAC, Page, adminApi, translatedErrors, useNotification, useAPIErrorHandler, getYupValidationErrors, useForm, useTracking, useGuidedTour, BackButton, DescriptionComponentRenderer, useTable, Table } from "@strapi/admin/strapi-admin";
7
4
  import * as React from "react";
8
5
  import { lazy } from "react";
9
- import { Menu, VisuallyHidden, Flex, Typography, Dialog, DialogBody, DialogFooter, Button, ModalLayout, ModalHeader, ModalBody, Box, Radio, Status, SingleSelect, SingleSelectOption, LinkButton } from "@strapi/design-system";
10
- import styled from "styled-components";
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";
8
+ import { useIntl } from "react-intl";
9
+ import { useParams, useNavigate, Navigate, useMatch, useLocation, Link, NavLink } from "react-router-dom";
10
+ import { styled } from "styled-components";
11
11
  import * as yup from "yup";
12
12
  import { ValidationError } from "yup";
13
- import { createApi } from "@reduxjs/toolkit/query/react";
14
- import { isAxiosError } from "axios";
13
+ import { stringify } from "qs";
15
14
  import pipe from "lodash/fp/pipe";
16
15
  import { intervalToDuration, isPast } from "date-fns";
17
16
  import { createSlice, combineReducers } from "@reduxjs/toolkit";
@@ -51,42 +50,6 @@ const useInjectionZone = (area) => {
51
50
  const [page, position] = area.split(".");
52
51
  return contentManagerPlugin.getInjectedComponents(page, position);
53
52
  };
54
- const HistoryAction = ({ model, document }) => {
55
- const { formatMessage } = useIntl();
56
- const [{ query }] = useQueryParams();
57
- const navigate = useNavigate();
58
- const pluginsQueryParams = stringify({ plugins: query.plugins }, { encode: false });
59
- if (!window.strapi.features.isEnabled("cms-content-history")) {
60
- return null;
61
- }
62
- return {
63
- icon: /* @__PURE__ */ jsx(ClockCounterClockwise, {}),
64
- label: formatMessage({
65
- id: "content-manager.history.document-action",
66
- defaultMessage: "Content History"
67
- }),
68
- onClick: () => navigate({ pathname: "history", search: pluginsQueryParams }),
69
- disabled: (
70
- /**
71
- * The user is creating a new document.
72
- * It hasn't been saved yet, so there's no history to go to
73
- */
74
- !document || /**
75
- * The document has been created but the current dimension has never been saved.
76
- * For example, the user is creating a new locale in an existing document,
77
- * so there's no history for the document in that locale
78
- */
79
- !document.id || /**
80
- * History is only available for content types created by the user.
81
- * These have the `api::` prefix, as opposed to the ones created by Strapi or plugins,
82
- * which start with `admin::` or `plugin::`
83
- */
84
- !model.startsWith("api::")
85
- ),
86
- position: "header"
87
- };
88
- };
89
- HistoryAction.type = "history";
90
53
  const ID = "id";
91
54
  const CREATED_BY_ATTRIBUTE_NAME = "createdBy";
92
55
  const UPDATED_BY_ATTRIBUTE_NAME = "updatedBy";
@@ -138,6 +101,7 @@ const DocumentRBAC = ({ children, permissions }) => {
138
101
  if (!slug) {
139
102
  throw new Error("Cannot find the slug param in the URL");
140
103
  }
104
+ const [{ rawQuery }] = useQueryParams();
141
105
  const userPermissions = useAuth("DocumentRBAC", (state) => state.permissions);
142
106
  const contentTypePermissions = React.useMemo(() => {
143
107
  const contentTypePermissions2 = userPermissions.filter(
@@ -148,7 +112,14 @@ const DocumentRBAC = ({ children, permissions }) => {
148
112
  return { ...acc, [action]: [permission] };
149
113
  }, {});
150
114
  }, [slug, userPermissions]);
151
- 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
+ );
152
123
  const canCreateFields = !isLoading && allowedActions.canCreate ? extractAndDedupeFields(contentTypePermissions.create) : [];
153
124
  const canReadFields = !isLoading && allowedActions.canRead ? extractAndDedupeFields(contentTypePermissions.read) : [];
154
125
  const canUpdateFields = !isLoading && allowedActions.canUpdate ? extractAndDedupeFields(contentTypePermissions.update) : [];
@@ -157,9 +128,8 @@ const DocumentRBAC = ({ children, permissions }) => {
157
128
  const name = removeNumericalStrings(fieldName.split("."));
158
129
  const componentFieldNames = fieldsUserCanAction.filter((field) => field.split(".").length > 1);
159
130
  if (fieldType === "component") {
160
- const componentOrDynamicZoneFields = componentFieldNames.map((field) => field.split("."));
161
- return componentOrDynamicZoneFields.some((field) => {
162
- return field.includes(fieldName);
131
+ return componentFieldNames.some((field) => {
132
+ return field.includes(name.join("."));
163
133
  });
164
134
  }
165
135
  if (name.length > 1) {
@@ -189,89 +159,20 @@ const extractAndDedupeFields = (permissions = []) => permissions.flatMap((permis
189
159
  (field, index2, arr) => arr.indexOf(field) === index2 && typeof field === "string"
190
160
  );
191
161
  const removeNumericalStrings = (arr) => arr.filter((item) => isNaN(Number(item)));
192
- const buildValidParams = (query) => {
193
- if (!query)
194
- return query;
195
- const { plugins: _, ...validQueryParams } = {
196
- ...query,
197
- ...Object.values(query?.plugins ?? {}).reduce(
198
- (acc, current) => Object.assign(acc, current),
199
- {}
200
- )
201
- };
202
- if ("_q" in validQueryParams) {
203
- validQueryParams._q = encodeURIComponent(validQueryParams._q);
204
- }
205
- return validQueryParams;
206
- };
207
- const axiosBaseQuery = () => async (query, { signal }) => {
208
- try {
209
- const { get, post, del, put } = getFetchClient();
210
- if (typeof query === "string") {
211
- const result = await get(query, { signal });
212
- return { data: result.data };
213
- } else {
214
- const { url, method = "GET", data, config } = query;
215
- if (method === "POST") {
216
- const result2 = await post(url, data, { ...config, signal });
217
- return { data: result2.data };
218
- }
219
- if (method === "DELETE") {
220
- const result2 = await del(url, { ...config, signal });
221
- return { data: result2.data };
222
- }
223
- if (method === "PUT") {
224
- const result2 = await put(url, data, { ...config, signal });
225
- return { data: result2.data };
226
- }
227
- const result = await get(url, { ...config, signal });
228
- return { data: result.data };
229
- }
230
- } catch (err) {
231
- if (isAxiosError(err)) {
232
- if (typeof err.response?.data === "object" && err.response?.data !== null && "error" in err.response?.data) {
233
- return { data: void 0, error: err.response?.data.error };
234
- } else {
235
- return {
236
- data: void 0,
237
- error: {
238
- name: "UnknownError",
239
- message: "There was an unknown error response from the API",
240
- details: err.response?.data,
241
- status: err.response?.status
242
- }
243
- };
244
- }
245
- }
246
- const error = err;
247
- return {
248
- data: void 0,
249
- error: {
250
- name: error.name,
251
- message: error.message,
252
- stack: error.stack
253
- }
254
- };
255
- }
256
- };
257
- const isBaseQueryError = (error) => {
258
- return error.name !== void 0;
259
- };
260
- const contentManagerApi = createApi({
261
- reducerPath: "contentManagerApi",
262
- baseQuery: axiosBaseQuery(),
263
- tagTypes: [
162
+ const contentManagerApi = adminApi.enhanceEndpoints({
163
+ addTagTypes: [
264
164
  "ComponentConfiguration",
265
165
  "ContentTypesConfiguration",
266
166
  "ContentTypeSettings",
267
167
  "Document",
268
168
  "InitialData",
269
169
  "HistoryVersion",
270
- "Relations"
271
- ],
272
- endpoints: () => ({})
170
+ "Relations",
171
+ "UidAvailability"
172
+ ]
273
173
  });
274
174
  const documentApi = contentManagerApi.injectEndpoints({
175
+ overrideExisting: true,
275
176
  endpoints: (builder) => ({
276
177
  autoCloneDocument: builder.mutation({
277
178
  query: ({ model, sourceId, query }) => ({
@@ -281,7 +182,12 @@ const documentApi = contentManagerApi.injectEndpoints({
281
182
  params: query
282
183
  }
283
184
  }),
284
- invalidatesTags: (_result, _error, { model }) => [{ type: "Document", id: `${model}_LIST` }]
185
+ invalidatesTags: (_result, error, { model }) => {
186
+ if (error) {
187
+ return [];
188
+ }
189
+ return [{ type: "Document", id: `${model}_LIST` }];
190
+ }
285
191
  }),
286
192
  cloneDocument: builder.mutation({
287
193
  query: ({ model, sourceId, data, params }) => ({
@@ -292,7 +198,10 @@ const documentApi = contentManagerApi.injectEndpoints({
292
198
  params
293
199
  }
294
200
  }),
295
- 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
+ ]
296
205
  }),
297
206
  /**
298
207
  * Creates a new collection-type document. This should ONLY be used for collection-types.
@@ -309,7 +218,8 @@ const documentApi = contentManagerApi.injectEndpoints({
309
218
  }),
310
219
  invalidatesTags: (result, _error, { model }) => [
311
220
  { type: "Document", id: `${model}_LIST` },
312
- "Relations"
221
+ "Relations",
222
+ { type: "UidAvailability", id: model }
313
223
  ]
314
224
  }),
315
225
  deleteDocument: builder.mutation({
@@ -325,12 +235,15 @@ const documentApi = contentManagerApi.injectEndpoints({
325
235
  ]
326
236
  }),
327
237
  deleteManyDocuments: builder.mutation({
328
- query: ({ model, ...body }) => ({
238
+ query: ({ model, params, ...body }) => ({
329
239
  url: `/content-manager/collection-types/${model}/actions/bulkDelete`,
330
240
  method: "POST",
331
- data: body
241
+ data: body,
242
+ config: {
243
+ params
244
+ }
332
245
  }),
333
- invalidatesTags: (_res, _error, { model, documentIds }) => documentIds.map((id) => ({ type: "Document", id: `${model}_${id}` }))
246
+ invalidatesTags: (_res, _error, { model }) => [{ type: "Document", id: `${model}_LIST` }]
334
247
  }),
335
248
  discardDocument: builder.mutation({
336
249
  query: ({ collectionType, model, documentId, params }) => ({
@@ -347,7 +260,8 @@ const documentApi = contentManagerApi.injectEndpoints({
347
260
  id: collectionType !== SINGLE_TYPES ? `${model}_${documentId}` : model
348
261
  },
349
262
  { type: "Document", id: `${model}_LIST` },
350
- "Relations"
263
+ "Relations",
264
+ { type: "UidAvailability", id: model }
351
265
  ];
352
266
  }
353
267
  }),
@@ -360,11 +274,12 @@ const documentApi = contentManagerApi.injectEndpoints({
360
274
  url: `/content-manager/collection-types/${model}`,
361
275
  method: "GET",
362
276
  config: {
363
- params
277
+ params: stringify(params, { encode: true })
364
278
  }
365
279
  }),
366
280
  providesTags: (result, _error, arg) => {
367
281
  return [
282
+ { type: "Document", id: `ALL_LIST` },
368
283
  { type: "Document", id: `${arg.model}_LIST` },
369
284
  ...result?.results.map(({ documentId }) => ({
370
285
  type: "Document",
@@ -403,6 +318,11 @@ const documentApi = contentManagerApi.injectEndpoints({
403
318
  {
404
319
  type: "Document",
405
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`
406
326
  }
407
327
  ];
408
328
  }
@@ -441,10 +361,13 @@ const documentApi = contentManagerApi.injectEndpoints({
441
361
  }
442
362
  }),
443
363
  publishManyDocuments: builder.mutation({
444
- query: ({ model, ...body }) => ({
364
+ query: ({ model, params, ...body }) => ({
445
365
  url: `/content-manager/collection-types/${model}/actions/bulkPublish`,
446
366
  method: "POST",
447
- data: body
367
+ data: body,
368
+ config: {
369
+ params
370
+ }
448
371
  }),
449
372
  invalidatesTags: (_res, _error, { model, documentIds }) => documentIds.map((id) => ({ type: "Document", id: `${model}_${id}` }))
450
373
  }),
@@ -463,8 +386,21 @@ const documentApi = contentManagerApi.injectEndpoints({
463
386
  type: "Document",
464
387
  id: collectionType !== SINGLE_TYPES ? `${model}_${documentId}` : model
465
388
  },
466
- "Relations"
389
+ "Relations",
390
+ { type: "UidAvailability", id: model }
467
391
  ];
392
+ },
393
+ async onQueryStarted({ data, ...patch }, { dispatch, queryFulfilled }) {
394
+ const patchResult = dispatch(
395
+ documentApi.util.updateQueryData("getDocument", patch, (draft) => {
396
+ Object.assign(draft.data, data);
397
+ })
398
+ );
399
+ try {
400
+ await queryFulfilled;
401
+ } catch {
402
+ patchResult.undo();
403
+ }
468
404
  }
469
405
  }),
470
406
  unpublishDocument: builder.mutation({
@@ -486,10 +422,13 @@ const documentApi = contentManagerApi.injectEndpoints({
486
422
  }
487
423
  }),
488
424
  unpublishManyDocuments: builder.mutation({
489
- query: ({ model, ...body }) => ({
425
+ query: ({ model, params, ...body }) => ({
490
426
  url: `/content-manager/collection-types/${model}/actions/bulkUnpublish`,
491
427
  method: "POST",
492
- data: body
428
+ data: body,
429
+ config: {
430
+ params
431
+ }
493
432
  }),
494
433
  invalidatesTags: (_res, _error, { model, documentIds }) => documentIds.map((id) => ({ type: "Document", id: `${model}_${id}` }))
495
434
  })
@@ -513,20 +452,54 @@ const {
513
452
  useUnpublishDocumentMutation,
514
453
  useUnpublishManyDocumentsMutation
515
454
  } = documentApi;
516
- const createYupSchema = (attributes = {}, components = {}) => {
455
+ const buildValidParams = (query) => {
456
+ if (!query)
457
+ return query;
458
+ const { plugins: _, ...validQueryParams } = {
459
+ ...query,
460
+ ...Object.values(query?.plugins ?? {}).reduce(
461
+ (acc, current) => Object.assign(acc, current),
462
+ {}
463
+ )
464
+ };
465
+ return validQueryParams;
466
+ };
467
+ const isBaseQueryError = (error) => {
468
+ return error.name !== void 0;
469
+ };
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 }) => {
517
489
  const createModelSchema = (attributes2) => yup.object().shape(
518
490
  Object.entries(attributes2).reduce((acc, [name, attribute]) => {
519
491
  if (DOCUMENT_META_FIELDS.includes(name)) {
520
492
  return acc;
521
493
  }
522
494
  const validations = [
495
+ addNullableValidation,
523
496
  addRequiredValidation,
524
497
  addMinLengthValidation,
525
498
  addMaxLengthValidation,
526
499
  addMinValidation,
527
500
  addMaxValidation,
528
501
  addRegexValidation
529
- ].map((fn) => fn(attribute));
502
+ ].map((fn) => fn(attribute, options));
530
503
  const transformSchema = pipe(...validations);
531
504
  switch (attribute.type) {
532
505
  case "component": {
@@ -536,12 +509,12 @@ const createYupSchema = (attributes = {}, components = {}) => {
536
509
  ...acc,
537
510
  [name]: transformSchema(
538
511
  yup.array().of(createModelSchema(attributes3).nullable(false))
539
- )
512
+ ).test(arrayValidator(attribute, options))
540
513
  };
541
514
  } else {
542
515
  return {
543
516
  ...acc,
544
- [name]: transformSchema(createModelSchema(attributes3))
517
+ [name]: transformSchema(createModelSchema(attributes3).nullable())
545
518
  };
546
519
  }
547
520
  }
@@ -552,24 +525,42 @@ const createYupSchema = (attributes = {}, components = {}) => {
552
525
  yup.array().of(
553
526
  yup.lazy(
554
527
  (data) => {
555
- const { attributes: attributes3 } = components[data.__component];
556
- return yup.object().shape({
528
+ const attributes3 = components?.[data?.__component]?.attributes;
529
+ const validation = yup.object().shape({
557
530
  __component: yup.string().required().oneOf(Object.keys(components))
558
- }).nullable(false).concat(createModelSchema(attributes3));
531
+ }).nullable(false);
532
+ if (!attributes3) {
533
+ return validation;
534
+ }
535
+ return validation.concat(createModelSchema(attributes3));
559
536
  }
560
537
  )
561
538
  )
562
- )
539
+ ).test(arrayValidator(attribute, options))
563
540
  };
564
541
  case "relation":
565
542
  return {
566
543
  ...acc,
567
544
  [name]: transformSchema(
568
- yup.array().of(
569
- yup.object().shape({
570
- id: yup.string().required()
571
- })
572
- )
545
+ yup.lazy((value) => {
546
+ if (!value) {
547
+ return yup.mixed().nullable(true);
548
+ } else if (Array.isArray(value)) {
549
+ return yup.array().of(
550
+ yup.object().shape({
551
+ id: yup.number().required()
552
+ })
553
+ );
554
+ } else if (typeof value === "object") {
555
+ return yup.object();
556
+ } else {
557
+ return yup.mixed().test(
558
+ "type-error",
559
+ "Relation values must be either null, an array of objects with {id} or an object.",
560
+ () => false
561
+ );
562
+ }
563
+ })
573
564
  )
574
565
  };
575
566
  default:
@@ -609,6 +600,14 @@ const createAttributeSchema = (attribute) => {
609
600
  if (!value || typeof value === "string" && value.length === 0) {
610
601
  return true;
611
602
  }
603
+ if (typeof value === "object") {
604
+ try {
605
+ JSON.stringify(value);
606
+ return true;
607
+ } catch (err) {
608
+ return false;
609
+ }
610
+ }
612
611
  try {
613
612
  JSON.parse(value);
614
613
  return true;
@@ -627,16 +626,30 @@ const createAttributeSchema = (attribute) => {
627
626
  return yup.mixed();
628
627
  }
629
628
  };
630
- const addRequiredValidation = (attribute) => (schema) => {
631
- if (attribute.required) {
632
- return schema.required({
633
- id: translatedErrors.required.id,
634
- defaultMessage: "This field is required."
635
- });
629
+ const nullableSchema = (schema) => {
630
+ return schema?.nullable ? schema.nullable() : (
631
+ // In some cases '.nullable' will not be available on the schema.
632
+ // e.g. when the schema has been built using yup.lazy (e.g. for relations).
633
+ // In these cases we should just return the schema as it is.
634
+ schema
635
+ );
636
+ };
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);
636
646
  }
637
- return schema.nullable();
647
+ return schema;
638
648
  };
639
- const addMinLengthValidation = (attribute) => (schema) => {
649
+ const addMinLengthValidation = (attribute, options) => (schema) => {
650
+ if (options.status === "draft") {
651
+ return schema;
652
+ }
640
653
  if ("minLength" in attribute && attribute.minLength && Number.isInteger(attribute.minLength) && "min" in schema) {
641
654
  return schema.min(attribute.minLength, {
642
655
  ...translatedErrors.minLength,
@@ -658,10 +671,13 @@ const addMaxLengthValidation = (attribute) => (schema) => {
658
671
  }
659
672
  return schema;
660
673
  };
661
- const addMinValidation = (attribute) => (schema) => {
662
- 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) {
663
679
  const min = toInteger(attribute.min);
664
- if ("min" in schema && min) {
680
+ if (min) {
665
681
  return schema.min(min, {
666
682
  ...translatedErrors.min,
667
683
  values: {
@@ -706,24 +722,6 @@ const addRegexValidation = (attribute) => (schema) => {
706
722
  }
707
723
  return schema;
708
724
  };
709
- const extractValuesFromYupError = (errorType, errorParams) => {
710
- if (!errorType || !errorParams) {
711
- return {};
712
- }
713
- return {
714
- [errorType]: errorParams[errorType]
715
- };
716
- };
717
- const getInnerErrors = (error) => (error?.inner || []).reduce((acc, currentError) => {
718
- if (currentError.path) {
719
- acc[currentError.path.split("[").join(".").split("]").join("")] = {
720
- id: currentError.message,
721
- defaultMessage: currentError.message,
722
- values: extractValuesFromYupError(currentError?.type, currentError?.params)
723
- };
724
- }
725
- return acc;
726
- }, {});
727
725
  const initApi = contentManagerApi.injectEndpoints({
728
726
  endpoints: (builder) => ({
729
727
  getInitialData: builder.query({
@@ -737,27 +735,20 @@ const { useGetInitialDataQuery } = initApi;
737
735
  const useContentTypeSchema = (model) => {
738
736
  const { toggleNotification } = useNotification();
739
737
  const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
740
- const { components, contentType, contentTypes, error, isLoading, isFetching } = useGetInitialDataQuery(void 0, {
741
- selectFromResult: (res) => {
742
- const contentType2 = res.data?.contentTypes.find((ct) => ct.uid === model);
743
- const componentsByKey = res.data?.components.reduce(
744
- (acc, component) => {
745
- acc[component.uid] = component;
746
- return acc;
747
- },
748
- {}
749
- );
750
- const components2 = extractContentTypeComponents(contentType2?.attributes, componentsByKey);
751
- return {
752
- isLoading: res.isLoading,
753
- isFetching: res.isFetching,
754
- error: res.error,
755
- components: Object.keys(components2).length === 0 ? void 0 : components2,
756
- contentType: contentType2,
757
- contentTypes: res.data?.contentTypes ?? []
758
- };
759
- }
760
- });
738
+ const { data, error, isLoading, isFetching } = useGetInitialDataQuery(void 0);
739
+ const { components, contentType, contentTypes } = React.useMemo(() => {
740
+ const contentType2 = data?.contentTypes.find((ct) => ct.uid === model);
741
+ const componentsByKey = data?.components.reduce((acc, component) => {
742
+ acc[component.uid] = component;
743
+ return acc;
744
+ }, {});
745
+ const components2 = extractContentTypeComponents(contentType2?.attributes, componentsByKey);
746
+ return {
747
+ components: Object.keys(components2).length === 0 ? void 0 : components2,
748
+ contentType: contentType2,
749
+ contentTypes: data?.contentTypes ?? []
750
+ };
751
+ }, [model, data]);
761
752
  React.useEffect(() => {
762
753
  if (error) {
763
754
  toggleNotification({
@@ -804,16 +795,328 @@ const extractContentTypeComponents = (attributes = {}, allComponents = {}) => {
804
795
  }, {});
805
796
  return componentsByKey;
806
797
  };
807
- 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);
808
897
  const { toggleNotification } = useNotification();
809
898
  const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
899
+ const { isLoading: isLoadingSchemas, schemas } = useContentTypeSchema();
810
900
  const {
811
- currentData: data,
812
- isLoading: isLoadingDocument,
813
- isFetching: isFetchingDocument,
814
- error
815
- } = useGetDocumentQuery(args, opts);
816
- 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;
907
+ React.useEffect(() => {
908
+ if (error) {
909
+ toggleNotification({
910
+ type: "danger",
911
+ message: formatAPIError(error)
912
+ });
913
+ }
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
922
+ },
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]
939
+ );
940
+ return {
941
+ error,
942
+ isLoading,
943
+ edit,
944
+ list: listLayout
945
+ };
946
+ };
947
+ const useDocLayout = () => {
948
+ const { model } = useDoc();
949
+ return useDocumentLayout(model);
950
+ };
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);
972
+ }
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
989
+ }
990
+ };
991
+ return acc;
992
+ },
993
+ {}
994
+ );
995
+ const editMetadatas = Object.entries(data.contentType.metadatas).reduce(
996
+ (acc, [attribute, metadata]) => {
997
+ return {
998
+ ...acc,
999
+ [attribute]: metadata.edit
1000
+ };
1001
+ },
1002
+ {}
1003
+ );
1004
+ return {
1005
+ layout: panelledEditAttributes,
1006
+ components: componentEditAttributes,
1007
+ metadatas: editMetadatas,
1008
+ settings: {
1009
+ ...data.contentType.settings,
1010
+ displayName: schema?.info.displayName
1011
+ },
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;
1025
+ }
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)
1047
+ );
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);
817
1120
  React.useEffect(() => {
818
1121
  if (error) {
819
1122
  toggleNotification({
@@ -840,7 +1143,7 @@ const useDocument = (args, opts) => {
840
1143
  return null;
841
1144
  } catch (error2) {
842
1145
  if (error2 instanceof ValidationError) {
843
- return getInnerErrors(error2);
1146
+ return getYupValidationErrors(error2);
844
1147
  }
845
1148
  throw error2;
846
1149
  }
@@ -848,12 +1151,15 @@ const useDocument = (args, opts) => {
848
1151
  [validationSchema]
849
1152
  );
850
1153
  const isLoading = isLoadingDocument || isFetchingDocument || isLoadingSchema;
1154
+ const hasError = !!error;
851
1155
  return {
852
1156
  components,
853
1157
  document: data?.data,
854
1158
  meta: data?.meta,
855
1159
  isLoading,
1160
+ hasError,
856
1161
  schema,
1162
+ schemas,
857
1163
  validate
858
1164
  };
859
1165
  };
@@ -867,22 +1173,60 @@ const useDoc = () => {
867
1173
  if (!slug) {
868
1174
  throw new Error("Could not find model in url params");
869
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;
870
1183
  return {
871
1184
  collectionType,
872
1185
  model: slug,
873
- id: origin || id === "create" ? void 0 : id,
874
- ...useDocument(
875
- { documentId: origin || id, model: slug, collectionType, params },
876
- {
877
- skip: id === "create" || !origin && !id && collectionType !== SINGLE_TYPES
878
- }
879
- )
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
880
1227
  };
881
1228
  };
882
1229
  const prefixPluginTranslations = (trad, pluginId) => {
883
- if (!pluginId) {
884
- throw new TypeError("pluginId can't be empty");
885
- }
886
1230
  return Object.keys(trad).reduce((acc, current) => {
887
1231
  acc[`${pluginId}.${current}`] = trad[current];
888
1232
  return acc;
@@ -898,6 +1242,8 @@ const useDocumentActions = () => {
898
1242
  const { formatMessage } = useIntl();
899
1243
  const { trackUsage } = useTracking();
900
1244
  const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
1245
+ const navigate = useNavigate();
1246
+ const setCurrentStep = useGuidedTour("useDocumentActions", (state) => state.setCurrentStep);
901
1247
  const [deleteDocument] = useDeleteDocumentMutation();
902
1248
  const _delete = React.useCallback(
903
1249
  async ({ collectionType, model, documentId, params }, trackerProperty) => {
@@ -936,14 +1282,15 @@ const useDocumentActions = () => {
936
1282
  },
937
1283
  [trackUsage, deleteDocument, toggleNotification, formatMessage, formatAPIError]
938
1284
  );
939
- const [discardDocument] = useDiscardDocumentMutation();
940
- const discard = React.useCallback(
941
- async ({ collectionType, model, documentId }) => {
1285
+ const [deleteManyDocuments] = useDeleteManyDocumentsMutation();
1286
+ const deleteMany = React.useCallback(
1287
+ async ({ model, documentIds, params }) => {
942
1288
  try {
943
- const res = await discardDocument({
944
- collectionType,
1289
+ trackUsage("willBulkDeleteEntries");
1290
+ const res = await deleteManyDocuments({
945
1291
  model,
946
- documentId
1292
+ documentIds,
1293
+ params
947
1294
  });
948
1295
  if ("error" in res) {
949
1296
  toggleNotification({
@@ -954,36 +1301,74 @@ const useDocumentActions = () => {
954
1301
  }
955
1302
  toggleNotification({
956
1303
  type: "success",
957
- message: formatMessage({
958
- id: "content-manager.success.record.discard",
959
- defaultMessage: "Changes discarded"
960
- })
1304
+ title: formatMessage({
1305
+ id: getTranslation("success.records.delete"),
1306
+ defaultMessage: "Successfully deleted."
1307
+ }),
1308
+ message: ""
961
1309
  });
1310
+ trackUsage("didBulkDeleteEntries");
962
1311
  return res.data;
963
1312
  } catch (err) {
964
1313
  toggleNotification({
965
1314
  type: "danger",
966
1315
  message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
967
1316
  });
1317
+ trackUsage("didNotBulkDeleteEntries");
968
1318
  throw err;
969
1319
  }
970
1320
  },
971
- [discardDocument, formatAPIError, formatMessage, toggleNotification]
1321
+ [trackUsage, deleteManyDocuments, toggleNotification, formatMessage, formatAPIError]
972
1322
  );
973
- const [publishDocument] = usePublishDocumentMutation();
974
- const publish = React.useCallback(
975
- async ({ collectionType, model, documentId, params }, data) => {
1323
+ const [discardDocument] = useDiscardDocumentMutation();
1324
+ const discard = React.useCallback(
1325
+ async ({ collectionType, model, documentId, params }) => {
976
1326
  try {
977
- trackUsage("willPublishEntry");
978
- const res = await publishDocument({
1327
+ const res = await discardDocument({
979
1328
  collectionType,
980
1329
  model,
981
1330
  documentId,
982
- data,
983
1331
  params
984
1332
  });
985
1333
  if ("error" in res) {
986
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1334
+ toggleNotification({
1335
+ type: "danger",
1336
+ message: formatAPIError(res.error)
1337
+ });
1338
+ return { error: res.error };
1339
+ }
1340
+ toggleNotification({
1341
+ type: "success",
1342
+ message: formatMessage({
1343
+ id: "content-manager.success.record.discard",
1344
+ defaultMessage: "Changes discarded"
1345
+ })
1346
+ });
1347
+ return res.data;
1348
+ } catch (err) {
1349
+ toggleNotification({
1350
+ type: "danger",
1351
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1352
+ });
1353
+ throw err;
1354
+ }
1355
+ },
1356
+ [discardDocument, formatAPIError, formatMessage, toggleNotification]
1357
+ );
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) });
987
1372
  return { error: res.error };
988
1373
  }
989
1374
  trackUsage("didPublishEntry");
@@ -1005,6 +1390,43 @@ const useDocumentActions = () => {
1005
1390
  },
1006
1391
  [trackUsage, publishDocument, toggleNotification, formatMessage, formatAPIError]
1007
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
+ );
1008
1430
  const [updateDocument] = useUpdateDocumentMutation();
1009
1431
  const update = React.useCallback(
1010
1432
  async ({ collectionType, model, documentId, params }, data, trackerProperty) => {
@@ -1079,6 +1501,41 @@ const useDocumentActions = () => {
1079
1501
  },
1080
1502
  [trackUsage, unpublishDocument, toggleNotification, formatMessage, formatAPIError]
1081
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
+ );
1082
1539
  const [createDocument] = useCreateDocumentMutation();
1083
1540
  const create = React.useCallback(
1084
1541
  async ({ model, params }, data, trackerProperty) => {
@@ -1101,6 +1558,7 @@ const useDocumentActions = () => {
1101
1558
  defaultMessage: "Saved document"
1102
1559
  })
1103
1560
  });
1561
+ setCurrentStep("contentManager.success");
1104
1562
  return res.data;
1105
1563
  } catch (err) {
1106
1564
  toggleNotification({
@@ -1122,7 +1580,6 @@ const useDocumentActions = () => {
1122
1580
  sourceId
1123
1581
  });
1124
1582
  if ("error" in res) {
1125
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1126
1583
  return { error: res.error };
1127
1584
  }
1128
1585
  toggleNotification({
@@ -1141,7 +1598,7 @@ const useDocumentActions = () => {
1141
1598
  throw err;
1142
1599
  }
1143
1600
  },
1144
- [autoCloneDocument, formatAPIError, formatMessage, toggleNotification]
1601
+ [autoCloneDocument, formatMessage, toggleNotification]
1145
1602
  );
1146
1603
  const [cloneDocument] = useCloneDocumentMutation();
1147
1604
  const clone = React.useCallback(
@@ -1167,6 +1624,7 @@ const useDocumentActions = () => {
1167
1624
  defaultMessage: "Cloned document"
1168
1625
  })
1169
1626
  });
1627
+ navigate(`../../${res.data.data.documentId}`, { relative: "path" });
1170
1628
  return res.data;
1171
1629
  } catch (err) {
1172
1630
  toggleNotification({
@@ -1177,7 +1635,7 @@ const useDocumentActions = () => {
1177
1635
  throw err;
1178
1636
  }
1179
1637
  },
1180
- [cloneDocument, trackUsage, toggleNotification, formatMessage, formatAPIError]
1638
+ [cloneDocument, trackUsage, toggleNotification, formatMessage, formatAPIError, navigate]
1181
1639
  );
1182
1640
  const [getDoc] = useLazyGetDocumentQuery();
1183
1641
  const getDocument = React.useCallback(
@@ -1192,17 +1650,20 @@ const useDocumentActions = () => {
1192
1650
  clone,
1193
1651
  create,
1194
1652
  delete: _delete,
1653
+ deleteMany,
1195
1654
  discard,
1196
1655
  getDocument,
1197
1656
  publish,
1657
+ publishMany,
1198
1658
  unpublish,
1659
+ unpublishMany,
1199
1660
  update
1200
1661
  };
1201
1662
  };
1202
- const ProtectedHistoryPage = lazy(
1203
- () => import("./History-D6sbCJvo.mjs").then((mod) => ({ default: mod.ProtectedHistoryPage }))
1663
+ const ProtectedHistoryPage = React.lazy(
1664
+ () => import("./History-ktAPcvHJ.mjs").then((mod) => ({ default: mod.ProtectedHistoryPage }))
1204
1665
  );
1205
- const routes$1 = [
1666
+ const routes$2 = [
1206
1667
  {
1207
1668
  path: ":collectionType/:slug/:id/history",
1208
1669
  Component: ProtectedHistoryPage
@@ -1212,32 +1673,45 @@ const routes$1 = [
1212
1673
  Component: ProtectedHistoryPage
1213
1674
  }
1214
1675
  ];
1676
+ const ProtectedPreviewPage = React.lazy(
1677
+ () => import("./Preview-CQlTtbLc.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
+ ];
1215
1689
  const ProtectedEditViewPage = lazy(
1216
- () => import("./EditViewPage-Bm8lgcm6.mjs").then((mod) => ({ default: mod.ProtectedEditViewPage }))
1690
+ () => import("./EditViewPage-DtbTsNeX.mjs").then((mod) => ({ default: mod.ProtectedEditViewPage }))
1217
1691
  );
1218
1692
  const ProtectedListViewPage = lazy(
1219
- () => import("./ListViewPage-Be7S5aKL.mjs").then((mod) => ({ default: mod.ProtectedListViewPage }))
1693
+ () => import("./ListViewPage-B15c_0Vt.mjs").then((mod) => ({ default: mod.ProtectedListViewPage }))
1220
1694
  );
1221
1695
  const ProtectedListConfiguration = lazy(
1222
- () => import("./ListConfigurationPage-Ce4qs7qE.mjs").then((mod) => ({
1696
+ () => import("./ListConfigurationPage-BNAS_v2s.mjs").then((mod) => ({
1223
1697
  default: mod.ProtectedListConfiguration
1224
1698
  }))
1225
1699
  );
1226
1700
  const ProtectedEditConfigurationPage = lazy(
1227
- () => import("./EditConfigurationPage-CUcGHHvQ.mjs").then((mod) => ({
1701
+ () => import("./EditConfigurationPage-DaNf9MoK.mjs").then((mod) => ({
1228
1702
  default: mod.ProtectedEditConfigurationPage
1229
1703
  }))
1230
1704
  );
1231
1705
  const ProtectedComponentConfigurationPage = lazy(
1232
- () => import("./ComponentConfigurationPage--2aLCv-G.mjs").then((mod) => ({
1706
+ () => import("./ComponentConfigurationPage-XdGcgtZh.mjs").then((mod) => ({
1233
1707
  default: mod.ProtectedComponentConfigurationPage
1234
1708
  }))
1235
1709
  );
1236
1710
  const NoPermissions = lazy(
1237
- () => import("./NoPermissionsPage-DhJ7LYrr.mjs").then((mod) => ({ default: mod.NoPermissions }))
1711
+ () => import("./NoPermissionsPage-JxX4Y4RJ.mjs").then((mod) => ({ default: mod.NoPermissions }))
1238
1712
  );
1239
1713
  const NoContentType = lazy(
1240
- () => import("./NoContentTypePage-CIPmYQMm.mjs").then((mod) => ({ default: mod.NoContentType }))
1714
+ () => import("./NoContentTypePage-CAgdmhlo.mjs").then((mod) => ({ default: mod.NoContentType }))
1241
1715
  );
1242
1716
  const CollectionTypePages = () => {
1243
1717
  const { collectionType } = useParams();
@@ -1249,7 +1723,7 @@ const CollectionTypePages = () => {
1249
1723
  const CLONE_RELATIVE_PATH = ":collectionType/:slug/clone/:origin";
1250
1724
  const CLONE_PATH = `/content-manager/${CLONE_RELATIVE_PATH}`;
1251
1725
  const LIST_RELATIVE_PATH = ":collectionType/:slug";
1252
- const LIST_PATH = `/content-manager/${LIST_RELATIVE_PATH}`;
1726
+ const LIST_PATH = `/content-manager/collection-types/:slug`;
1253
1727
  const routes = [
1254
1728
  {
1255
1729
  path: LIST_RELATIVE_PATH,
@@ -1283,6 +1757,7 @@ const routes = [
1283
1757
  path: "no-content-types",
1284
1758
  Component: NoContentType
1285
1759
  },
1760
+ ...routes$2,
1286
1761
  ...routes$1
1287
1762
  ];
1288
1763
  const DocumentActions = ({ actions: actions2 }) => {
@@ -1351,12 +1826,14 @@ const DocumentActionButton = (action) => {
1351
1826
  /* @__PURE__ */ jsx(
1352
1827
  Button,
1353
1828
  {
1354
- flex: 1,
1829
+ flex: "auto",
1355
1830
  startIcon: action.icon,
1356
1831
  disabled: action.disabled,
1357
1832
  onClick: handleClick(action),
1358
1833
  justifyContent: "center",
1359
1834
  variant: action.variant || "default",
1835
+ paddingTop: "7px",
1836
+ paddingBottom: "7px",
1360
1837
  children: action.label
1361
1838
  }
1362
1839
  ),
@@ -1364,7 +1841,7 @@ const DocumentActionButton = (action) => {
1364
1841
  DocumentActionConfirmDialog,
1365
1842
  {
1366
1843
  ...action.dialog,
1367
- variant: action.variant,
1844
+ variant: action.dialog?.variant ?? action.variant,
1368
1845
  isOpen: dialogId === action.id,
1369
1846
  onClose: handleClose
1370
1847
  }
@@ -1379,6 +1856,11 @@ const DocumentActionButton = (action) => {
1379
1856
  ) : null
1380
1857
  ] });
1381
1858
  };
1859
+ const MenuItem = styled(Menu.Item)`
1860
+ &:hover {
1861
+ background: ${({ theme, isVariantDanger, isDisabled }) => isVariantDanger && !isDisabled ? theme.colors.danger100 : "neutral"};
1862
+ }
1863
+ `;
1382
1864
  const DocumentActionsMenu = ({
1383
1865
  actions: actions2,
1384
1866
  children,
@@ -1421,49 +1903,48 @@ const DocumentActionsMenu = ({
1421
1903
  disabled: isDisabled,
1422
1904
  size: "S",
1423
1905
  endIcon: null,
1424
- paddingTop: "7px",
1425
- paddingLeft: "9px",
1426
- paddingRight: "9px",
1906
+ paddingTop: "4px",
1907
+ paddingLeft: "7px",
1908
+ paddingRight: "7px",
1427
1909
  variant,
1428
1910
  children: [
1429
1911
  /* @__PURE__ */ jsx(More, { "aria-hidden": true, focusable: false }),
1430
- /* @__PURE__ */ jsx(VisuallyHidden, { as: "span", children: label || formatMessage({
1912
+ /* @__PURE__ */ jsx(VisuallyHidden, { tag: "span", children: label || formatMessage({
1431
1913
  id: "content-manager.containers.edit.panels.default.more-actions",
1432
1914
  defaultMessage: "More document actions"
1433
1915
  }) })
1434
1916
  ]
1435
1917
  }
1436
1918
  ),
1437
- /* @__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: [
1438
1920
  actions2.map((action) => {
1439
1921
  return /* @__PURE__ */ jsx(
1440
- Menu.Item,
1922
+ MenuItem,
1441
1923
  {
1442
1924
  disabled: action.disabled,
1443
1925
  onSelect: handleClick(action),
1444
1926
  display: "block",
1445
- children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", gap: 4, children: [
1446
- /* @__PURE__ */ jsxs(Flex, { color: convertActionVariantToColor(action.variant), gap: 2, as: "span", children: [
1447
- action.icon,
1448
- action.label
1449
- ] }),
1450
- action.id.startsWith("HistoryAction") && /* @__PURE__ */ jsx(
1451
- Flex,
1452
- {
1453
- alignItems: "center",
1454
- background: "alternative100",
1455
- borderStyle: "solid",
1456
- borderColor: "alternative200",
1457
- borderWidth: "1px",
1458
- height: 5,
1459
- paddingLeft: 2,
1460
- paddingRight: 2,
1461
- hasRadius: true,
1462
- color: "alternative600",
1463
- children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", fontWeight: "bold", lineHeight: 1, children: formatMessage({ id: "global.new", defaultMessage: "New" }) })
1464
- }
1465
- )
1466
- ] })
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
+ ) })
1467
1948
  },
1468
1949
  action.id
1469
1950
  );
@@ -1505,6 +1986,18 @@ const convertActionVariantToColor = (variant = "secondary") => {
1505
1986
  return "primary600";
1506
1987
  }
1507
1988
  };
1989
+ const convertActionVariantToIconColor = (variant = "secondary") => {
1990
+ switch (variant) {
1991
+ case "danger":
1992
+ return "danger600";
1993
+ case "secondary":
1994
+ return "neutral500";
1995
+ case "success":
1996
+ return "success600";
1997
+ default:
1998
+ return "primary600";
1999
+ }
2000
+ };
1508
2001
  const DocumentActionConfirmDialog = ({
1509
2002
  onClose,
1510
2003
  onCancel,
@@ -1527,61 +2020,54 @@ const DocumentActionConfirmDialog = ({
1527
2020
  }
1528
2021
  onClose();
1529
2022
  };
1530
- return /* @__PURE__ */ jsxs(Dialog, { isOpen, title, onClose: handleClose, children: [
1531
- /* @__PURE__ */ jsx(DialogBody, { children: content }),
1532
- /* @__PURE__ */ jsx(
1533
- DialogFooter,
1534
- {
1535
- startAction: /* @__PURE__ */ jsx(Button, { onClick: handleClose, variant: "tertiary", children: formatMessage({
1536
- id: "app.components.Button.cancel",
1537
- defaultMessage: "Cancel"
1538
- }) }),
1539
- endAction: /* @__PURE__ */ jsx(Button, { onClick: handleConfirm, variant, children: formatMessage({
1540
- id: "app.components.Button.confirm",
1541
- defaultMessage: "Confirm"
1542
- }) })
1543
- }
1544
- )
1545
- ] });
2023
+ return /* @__PURE__ */ jsx(Dialog.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxs(Dialog.Content, { children: [
2024
+ /* @__PURE__ */ jsx(Dialog.Header, { children: title }),
2025
+ /* @__PURE__ */ jsx(Dialog.Body, { children: content }),
2026
+ /* @__PURE__ */ jsxs(Dialog.Footer, { children: [
2027
+ /* @__PURE__ */ jsx(Dialog.Cancel, { children: /* @__PURE__ */ jsx(Button, { variant: "tertiary", fullWidth: true, children: formatMessage({
2028
+ id: "app.components.Button.cancel",
2029
+ defaultMessage: "Cancel"
2030
+ }) }) }),
2031
+ /* @__PURE__ */ jsx(Button, { onClick: handleConfirm, variant, fullWidth: true, children: formatMessage({
2032
+ id: "app.components.Button.confirm",
2033
+ defaultMessage: "Confirm"
2034
+ }) })
2035
+ ] })
2036
+ ] }) });
1546
2037
  };
1547
2038
  const DocumentActionModal = ({
1548
2039
  isOpen,
1549
2040
  title,
1550
2041
  onClose,
1551
2042
  footer: Footer,
1552
- content,
2043
+ content: Content,
1553
2044
  onModalClose
1554
2045
  }) => {
1555
- const id = React.useId();
1556
- if (!isOpen) {
1557
- return null;
1558
- }
1559
2046
  const handleClose = () => {
1560
2047
  if (onClose) {
1561
2048
  onClose();
1562
2049
  }
1563
2050
  onModalClose();
1564
2051
  };
1565
- return /* @__PURE__ */ jsxs(ModalLayout, { borderRadius: "4px", overflow: "hidden", onClose: handleClose, labelledBy: id, children: [
1566
- /* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", textColor: "neutral800", as: "h2", id, children: title }) }),
1567
- /* @__PURE__ */ jsx(ModalBody, { children: content }),
1568
- /* @__PURE__ */ jsx(
1569
- Box,
1570
- {
1571
- paddingTop: 4,
1572
- paddingBottom: 4,
1573
- paddingLeft: 5,
1574
- paddingRight: 5,
1575
- borderWidth: "1px 0 0 0",
1576
- borderStyle: "solid",
1577
- borderColor: "neutral150",
1578
- background: "neutral100",
1579
- children: typeof Footer === "function" ? /* @__PURE__ */ jsx(Footer, { onClose: handleClose }) : Footer
1580
- }
1581
- )
1582
- ] });
2052
+ return /* @__PURE__ */ jsx(Modal.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxs(Modal.Content, { children: [
2053
+ /* @__PURE__ */ jsx(Modal.Header, { children: /* @__PURE__ */ jsx(Modal.Title, { children: title }) }),
2054
+ typeof Content === "function" ? /* @__PURE__ */ jsx(Content, { onClose: handleClose }) : /* @__PURE__ */ jsx(Modal.Body, { children: Content }),
2055
+ typeof Footer === "function" ? /* @__PURE__ */ jsx(Footer, { onClose: handleClose }) : Footer
2056
+ ] }) });
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;
1583
2069
  };
1584
- const PublishAction = ({
2070
+ const PublishAction$1 = ({
1585
2071
  activeTab,
1586
2072
  documentId,
1587
2073
  model,
@@ -1593,13 +2079,17 @@ const PublishAction = ({
1593
2079
  const navigate = useNavigate();
1594
2080
  const { toggleNotification } = useNotification();
1595
2081
  const { _unstableFormatValidationErrors: formatValidationErrors } = useAPIErrorHandler();
2082
+ const isListView = useMatch(LIST_PATH) !== null;
1596
2083
  const isCloning = useMatch(CLONE_PATH) !== null;
1597
2084
  const { formatMessage } = useIntl();
1598
- const { canPublish, canCreate, canUpdate } = useDocumentRBAC(
1599
- "PublishAction",
1600
- ({ canPublish: canPublish2, canCreate: canCreate2, canUpdate: canUpdate2 }) => ({ canPublish: canPublish2, canCreate: canCreate2, canUpdate: canUpdate2 })
1601
- );
2085
+ const canPublish = useDocumentRBAC("PublishAction", ({ canPublish: canPublish2 }) => canPublish2);
1602
2086
  const { publish } = useDocumentActions();
2087
+ const [
2088
+ countDraftRelations,
2089
+ { isLoading: isLoadingDraftRelations, isError: isErrorDraftRelations }
2090
+ ] = useLazyGetDraftRelationCountQuery();
2091
+ const [localCountOfDraftRelations, setLocalCountOfDraftRelations] = React.useState(0);
2092
+ const [serverCountOfDraftRelations, setServerCountOfDraftRelations] = React.useState(0);
1603
2093
  const [{ query, rawQuery }] = useQueryParams();
1604
2094
  const params = React.useMemo(() => buildValidParams(query), [query]);
1605
2095
  const modified = useForm("PublishAction", ({ modified: modified2 }) => modified2);
@@ -1608,10 +2098,105 @@ const PublishAction = ({
1608
2098
  const validate = useForm("PublishAction", (state) => state.validate);
1609
2099
  const setErrors = useForm("PublishAction", (state) => state.setErrors);
1610
2100
  const formValues = useForm("PublishAction", ({ values }) => values);
2101
+ React.useEffect(() => {
2102
+ if (isErrorDraftRelations) {
2103
+ toggleNotification({
2104
+ type: "danger",
2105
+ message: formatMessage({
2106
+ id: getTranslation("error.records.fetch-draft-relatons"),
2107
+ defaultMessage: "An error occurred while fetching draft relations on this document."
2108
+ })
2109
+ });
2110
+ }
2111
+ }, [isErrorDraftRelations, toggleNotification, formatMessage]);
2112
+ React.useEffect(() => {
2113
+ const localDraftRelations = /* @__PURE__ */ new Set();
2114
+ const extractDraftRelations = (data) => {
2115
+ const relations = data.connect || [];
2116
+ relations.forEach((relation) => {
2117
+ if (relation.status === "draft") {
2118
+ localDraftRelations.add(relation.id);
2119
+ }
2120
+ });
2121
+ };
2122
+ const traverseAndExtract = (data) => {
2123
+ Object.entries(data).forEach(([key, value]) => {
2124
+ if (key === "connect" && Array.isArray(value)) {
2125
+ extractDraftRelations({ connect: value });
2126
+ } else if (typeof value === "object" && value !== null) {
2127
+ traverseAndExtract(value);
2128
+ }
2129
+ });
2130
+ };
2131
+ if (!documentId || modified) {
2132
+ traverseAndExtract(formValues);
2133
+ setLocalCountOfDraftRelations(localDraftRelations.size);
2134
+ }
2135
+ }, [documentId, modified, formValues, setLocalCountOfDraftRelations]);
2136
+ React.useEffect(() => {
2137
+ if (!document || !document.documentId || isListView) {
2138
+ return;
2139
+ }
2140
+ const fetchDraftRelationsCount = async () => {
2141
+ const { data, error } = await countDraftRelations({
2142
+ collectionType,
2143
+ model,
2144
+ documentId,
2145
+ params
2146
+ });
2147
+ if (error) {
2148
+ throw error;
2149
+ }
2150
+ if (data) {
2151
+ setServerCountOfDraftRelations(data.data);
2152
+ }
2153
+ };
2154
+ fetchDraftRelationsCount();
2155
+ }, [isListView, document, documentId, countDraftRelations, collectionType, model, params]);
1611
2156
  const isDocumentPublished = (document?.[PUBLISHED_AT_ATTRIBUTE_NAME] || meta?.availableStatus.some((doc) => doc[PUBLISHED_AT_ATTRIBUTE_NAME] !== null)) && document?.status !== "modified";
1612
2157
  if (!schema?.options?.draftAndPublish) {
1613
2158
  return null;
1614
2159
  }
2160
+ const performPublish = async () => {
2161
+ setSubmitting(true);
2162
+ try {
2163
+ const { errors } = await validate(true, {
2164
+ status: "published"
2165
+ });
2166
+ if (errors) {
2167
+ toggleNotification({
2168
+ type: "danger",
2169
+ message: formatMessage({
2170
+ id: "content-manager.validation.error",
2171
+ defaultMessage: "There are validation errors in your document. Please fix them before saving."
2172
+ })
2173
+ });
2174
+ return;
2175
+ }
2176
+ const res = await publish(
2177
+ {
2178
+ collectionType,
2179
+ model,
2180
+ documentId,
2181
+ params
2182
+ },
2183
+ transformData(formValues)
2184
+ );
2185
+ if ("data" in res && collectionType !== SINGLE_TYPES) {
2186
+ navigate({
2187
+ pathname: `../${collectionType}/${model}/${res.data.documentId}`,
2188
+ search: rawQuery
2189
+ });
2190
+ } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
2191
+ setErrors(formatValidationErrors(res.error));
2192
+ }
2193
+ } finally {
2194
+ setSubmitting(false);
2195
+ }
2196
+ };
2197
+ const totalDraftRelations = localCountOfDraftRelations + serverCountOfDraftRelations;
2198
+ const enableDraftRelationsCount = false;
2199
+ const hasDraftRelations = enableDraftRelationsCount;
1615
2200
  return {
1616
2201
  /**
1617
2202
  * Disabled when:
@@ -1621,52 +2206,39 @@ const PublishAction = ({
1621
2206
  * - the document is already published & not modified
1622
2207
  * - the document is being created & not modified
1623
2208
  * - the user doesn't have the permission to publish
1624
- * - the user doesn't have the permission to create a new document
1625
- * - the user doesn't have the permission to update the document
1626
2209
  */
1627
- disabled: isCloning || isSubmitting || activeTab === "published" || !modified && isDocumentPublished || !modified && !document?.documentId || !canPublish || Boolean(!document?.documentId && !canCreate || document?.documentId && !canUpdate),
2210
+ disabled: isCloning || isSubmitting || isLoadingDraftRelations || activeTab === "published" || !modified && isDocumentPublished || !modified && !document?.documentId || !canPublish,
1628
2211
  label: formatMessage({
1629
2212
  id: "app.utils.publish",
1630
2213
  defaultMessage: "Publish"
1631
2214
  }),
1632
2215
  onClick: async () => {
1633
- setSubmitting(true);
1634
- try {
1635
- const { errors } = await validate();
1636
- if (errors) {
1637
- toggleNotification({
1638
- type: "danger",
1639
- message: formatMessage({
1640
- id: "content-manager.validation.error",
1641
- defaultMessage: "There are validation errors in your document. Please fix them before saving."
1642
- })
1643
- });
1644
- return;
1645
- }
1646
- const res = await publish(
1647
- {
1648
- collectionType,
1649
- model,
1650
- documentId,
1651
- params
1652
- },
1653
- formValues
1654
- );
1655
- if ("data" in res && collectionType !== SINGLE_TYPES) {
1656
- navigate({
1657
- pathname: `../${collectionType}/${model}/${res.data.documentId}`,
1658
- search: rawQuery
1659
- });
1660
- } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1661
- setErrors(formatValidationErrors(res.error));
2216
+ await performPublish();
2217
+ },
2218
+ dialog: hasDraftRelations ? {
2219
+ type: "dialog",
2220
+ variant: "danger",
2221
+ footer: null,
2222
+ title: formatMessage({
2223
+ id: getTranslation(`popUpwarning.warning.bulk-has-draft-relations.title`),
2224
+ defaultMessage: "Confirmation"
2225
+ }),
2226
+ content: formatMessage(
2227
+ {
2228
+ id: getTranslation(`popUpwarning.warning.bulk-has-draft-relations.message`),
2229
+ defaultMessage: "This entry is related to {count, plural, one {# draft entry} other {# draft entries}}. Publishing it could leave broken links in your app."
2230
+ },
2231
+ {
2232
+ count: totalDraftRelations
1662
2233
  }
1663
- } finally {
1664
- setSubmitting(false);
2234
+ ),
2235
+ onConfirm: async () => {
2236
+ await performPublish();
1665
2237
  }
1666
- }
2238
+ } : void 0
1667
2239
  };
1668
2240
  };
1669
- PublishAction.type = "publish";
2241
+ PublishAction$1.type = "publish";
1670
2242
  const UpdateAction = ({
1671
2243
  activeTab,
1672
2244
  documentId,
@@ -1679,10 +2251,6 @@ const UpdateAction = ({
1679
2251
  const cloneMatch = useMatch(CLONE_PATH);
1680
2252
  const isCloning = cloneMatch !== null;
1681
2253
  const { formatMessage } = useIntl();
1682
- const { canCreate, canUpdate } = useDocumentRBAC("UpdateAction", ({ canCreate: canCreate2, canUpdate: canUpdate2 }) => ({
1683
- canCreate: canCreate2,
1684
- canUpdate: canUpdate2
1685
- }));
1686
2254
  const { create, update, clone } = useDocumentActions();
1687
2255
  const [{ query, rawQuery }] = useQueryParams();
1688
2256
  const params = React.useMemo(() => buildValidParams(query), [query]);
@@ -1699,18 +2267,18 @@ const UpdateAction = ({
1699
2267
  * - the form is submitting
1700
2268
  * - the document is not modified & we're not cloning (you can save a clone entity straight away)
1701
2269
  * - the active tab is the published tab
1702
- * - the user doesn't have the permission to create a new document
1703
- * - the user doesn't have the permission to update the document
1704
2270
  */
1705
- disabled: isSubmitting || !modified && !isCloning || activeTab === "published" || Boolean(!documentId && !canCreate || documentId && !canUpdate),
2271
+ disabled: isSubmitting || !modified && !isCloning || activeTab === "published",
1706
2272
  label: formatMessage({
1707
- id: "content-manager.containers.Edit.save",
2273
+ id: "global.save",
1708
2274
  defaultMessage: "Save"
1709
2275
  }),
1710
2276
  onClick: async () => {
1711
2277
  setSubmitting(true);
1712
2278
  try {
1713
- const { errors } = await validate();
2279
+ const { errors } = await validate(true, {
2280
+ status: "draft"
2281
+ });
1714
2282
  if (errors) {
1715
2283
  toggleNotification({
1716
2284
  type: "danger",
@@ -1728,13 +2296,16 @@ const UpdateAction = ({
1728
2296
  documentId: cloneMatch.params.origin,
1729
2297
  params
1730
2298
  },
1731
- document
2299
+ transformData(document)
1732
2300
  );
1733
2301
  if ("data" in res) {
1734
- navigate({
1735
- pathname: `../${collectionType}/${model}/${res.data.documentId}`,
1736
- search: rawQuery
1737
- });
2302
+ navigate(
2303
+ {
2304
+ pathname: `../${res.data.documentId}`,
2305
+ search: rawQuery
2306
+ },
2307
+ { relative: "path" }
2308
+ );
1738
2309
  } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1739
2310
  setErrors(formatValidationErrors(res.error));
1740
2311
  }
@@ -1746,7 +2317,7 @@ const UpdateAction = ({
1746
2317
  documentId,
1747
2318
  params
1748
2319
  },
1749
- document
2320
+ transformData(document)
1750
2321
  );
1751
2322
  if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1752
2323
  setErrors(formatValidationErrors(res.error));
@@ -1759,13 +2330,16 @@ const UpdateAction = ({
1759
2330
  model,
1760
2331
  params
1761
2332
  },
1762
- document
2333
+ transformData(document)
1763
2334
  );
1764
2335
  if ("data" in res && collectionType !== SINGLE_TYPES) {
1765
- navigate({
1766
- pathname: `../${collectionType}/${model}/${res.data.documentId}`,
1767
- search: rawQuery
1768
- });
2336
+ navigate(
2337
+ {
2338
+ pathname: `../${res.data.documentId}`,
2339
+ search: rawQuery
2340
+ },
2341
+ { replace: true, relative: "path" }
2342
+ );
1769
2343
  } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1770
2344
  setErrors(formatValidationErrors(res.error));
1771
2345
  }
@@ -1781,7 +2355,7 @@ const UNPUBLISH_DRAFT_OPTIONS = {
1781
2355
  KEEP: "keep",
1782
2356
  DISCARD: "discard"
1783
2357
  };
1784
- const UnpublishAction = ({
2358
+ const UnpublishAction$1 = ({
1785
2359
  activeTab,
1786
2360
  documentId,
1787
2361
  model,
@@ -1797,10 +2371,8 @@ const UnpublishAction = ({
1797
2371
  const { toggleNotification } = useNotification();
1798
2372
  const [shouldKeepDraft, setShouldKeepDraft] = React.useState(true);
1799
2373
  const isDocumentModified = document?.status === "modified";
1800
- const handleChange = (e) => {
1801
- if ("value" in e.target) {
1802
- setShouldKeepDraft(e.target.value === UNPUBLISH_DRAFT_OPTIONS.KEEP);
1803
- }
2374
+ const handleChange = (value) => {
2375
+ setShouldKeepDraft(value === UNPUBLISH_DRAFT_OPTIONS.KEEP);
1804
2376
  };
1805
2377
  if (!schema?.options?.draftAndPublish) {
1806
2378
  return null;
@@ -1811,7 +2383,7 @@ const UnpublishAction = ({
1811
2383
  id: "app.utils.unpublish",
1812
2384
  defaultMessage: "Unpublish"
1813
2385
  }),
1814
- icon: /* @__PURE__ */ jsx(StyledCrossCircle, {}),
2386
+ icon: /* @__PURE__ */ jsx(Cross, {}),
1815
2387
  onClick: async () => {
1816
2388
  if (!documentId && collectionType !== SINGLE_TYPES || isDocumentModified) {
1817
2389
  if (!documentId) {
@@ -1844,45 +2416,30 @@ const UnpublishAction = ({
1844
2416
  content: /* @__PURE__ */ jsxs(Flex, { alignItems: "flex-start", direction: "column", gap: 6, children: [
1845
2417
  /* @__PURE__ */ jsxs(Flex, { width: "100%", direction: "column", gap: 2, children: [
1846
2418
  /* @__PURE__ */ jsx(WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
1847
- /* @__PURE__ */ jsx(Typography, { as: "p", variant: "omega", textAlign: "center", children: formatMessage({
2419
+ /* @__PURE__ */ jsx(Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
1848
2420
  id: "content-manager.actions.unpublish.dialog.body",
1849
2421
  defaultMessage: "Are you sure?"
1850
2422
  }) })
1851
2423
  ] }),
1852
2424
  /* @__PURE__ */ jsxs(
1853
- Flex,
2425
+ Radio.Group,
1854
2426
  {
1855
- onChange: handleChange,
1856
- direction: "column",
1857
- alignItems: "flex-start",
1858
- as: "fieldset",
1859
- gap: 3,
2427
+ defaultValue: UNPUBLISH_DRAFT_OPTIONS.KEEP,
2428
+ name: "discard-options",
2429
+ "aria-label": formatMessage({
2430
+ id: "content-manager.actions.unpublish.dialog.radio-label",
2431
+ defaultMessage: "Choose an option to unpublish the document."
2432
+ }),
2433
+ onValueChange: handleChange,
1860
2434
  children: [
1861
- /* @__PURE__ */ jsx(VisuallyHidden, { as: "legend" }),
1862
- /* @__PURE__ */ jsx(
1863
- Radio,
1864
- {
1865
- checked: shouldKeepDraft,
1866
- value: UNPUBLISH_DRAFT_OPTIONS.KEEP,
1867
- name: "discard-options",
1868
- children: formatMessage({
1869
- id: "content-manager.actions.unpublish.dialog.option.keep-draft",
1870
- defaultMessage: "Keep draft"
1871
- })
1872
- }
1873
- ),
1874
- /* @__PURE__ */ jsx(
1875
- Radio,
1876
- {
1877
- checked: !shouldKeepDraft,
1878
- value: UNPUBLISH_DRAFT_OPTIONS.DISCARD,
1879
- name: "discard-options",
1880
- children: formatMessage({
1881
- id: "content-manager.actions.unpublish.dialog.option.replace-draft",
1882
- defaultMessage: "Replace draft"
1883
- })
1884
- }
1885
- )
2435
+ /* @__PURE__ */ jsx(Radio.Item, { checked: shouldKeepDraft, value: UNPUBLISH_DRAFT_OPTIONS.KEEP, children: formatMessage({
2436
+ id: "content-manager.actions.unpublish.dialog.option.keep-draft",
2437
+ defaultMessage: "Keep draft"
2438
+ }) }),
2439
+ /* @__PURE__ */ jsx(Radio.Item, { checked: !shouldKeepDraft, value: UNPUBLISH_DRAFT_OPTIONS.DISCARD, children: formatMessage({
2440
+ id: "content-manager.actions.unpublish.dialog.option.replace-draft",
2441
+ defaultMessage: "Replace draft"
2442
+ }) })
1886
2443
  ]
1887
2444
  }
1888
2445
  )
@@ -1915,7 +2472,7 @@ const UnpublishAction = ({
1915
2472
  position: ["panel", "table-row"]
1916
2473
  };
1917
2474
  };
1918
- UnpublishAction.type = "unpublish";
2475
+ UnpublishAction$1.type = "unpublish";
1919
2476
  const DiscardAction = ({
1920
2477
  activeTab,
1921
2478
  documentId,
@@ -1938,7 +2495,7 @@ const DiscardAction = ({
1938
2495
  id: "content-manager.actions.discard.label",
1939
2496
  defaultMessage: "Discard changes"
1940
2497
  }),
1941
- icon: /* @__PURE__ */ jsx(StyledCrossCircle, {}),
2498
+ icon: /* @__PURE__ */ jsx(Cross, {}),
1942
2499
  position: ["panel", "table-row"],
1943
2500
  variant: "danger",
1944
2501
  dialog: {
@@ -1949,7 +2506,7 @@ const DiscardAction = ({
1949
2506
  }),
1950
2507
  content: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, children: [
1951
2508
  /* @__PURE__ */ jsx(WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
1952
- /* @__PURE__ */ jsx(Typography, { as: "p", variant: "omega", textAlign: "center", children: formatMessage({
2509
+ /* @__PURE__ */ jsx(Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
1953
2510
  id: "content-manager.actions.discard.dialog.body",
1954
2511
  defaultMessage: "Are you sure?"
1955
2512
  }) })
@@ -1966,12 +2523,7 @@ const DiscardAction = ({
1966
2523
  };
1967
2524
  };
1968
2525
  DiscardAction.type = "discard";
1969
- const StyledCrossCircle = styled(CrossCircle)`
1970
- path {
1971
- fill: currentColor;
1972
- }
1973
- `;
1974
- const DEFAULT_ACTIONS = [PublishAction, UpdateAction, UnpublishAction, DiscardAction];
2526
+ const DEFAULT_ACTIONS = [PublishAction$1, UpdateAction, UnpublishAction$1, DiscardAction];
1975
2527
  const intervals = ["years", "months", "days", "hours", "minutes", "seconds"];
1976
2528
  const RelativeTime = React.forwardRef(
1977
2529
  ({ timestamp, customIntervals = [], ...restProps }, forwardedRef) => {
@@ -1983,7 +2535,7 @@ const RelativeTime = React.forwardRef(
1983
2535
  });
1984
2536
  const unit = intervals.find((intervalUnit) => {
1985
2537
  return interval[intervalUnit] > 0 && Object.keys(interval).includes(intervalUnit);
1986
- });
2538
+ }) ?? "seconds";
1987
2539
  const relativeTime = isPast(timestamp) ? -interval[unit] : interval[unit];
1988
2540
  const customInterval = customIntervals.find(
1989
2541
  (custom) => interval[custom.unit] < custom.threshold
@@ -2017,34 +2569,34 @@ const getDisplayName = ({
2017
2569
  return email ?? "";
2018
2570
  };
2019
2571
  const capitalise = (str) => str.charAt(0).toUpperCase() + str.slice(1);
2020
- const DocumentStatus = ({ status = "draft", ...restProps }) => {
2021
- const statusVariant = status === "draft" ? "primary" : status === "published" ? "success" : "alternative";
2022
- return /* @__PURE__ */ jsx(Status, { ...restProps, showBullet: false, size: "S", variant: statusVariant, children: /* @__PURE__ */ jsx(Typography, { as: "span", variant: "omega", fontWeight: "bold", children: capitalise(status) }) });
2572
+ const DocumentStatus = ({ status = "draft", size = "S", ...restProps }) => {
2573
+ const statusVariant = status === "draft" ? "secondary" : status === "published" ? "success" : "alternative";
2574
+ const { formatMessage } = useIntl();
2575
+ return /* @__PURE__ */ jsx(Status, { ...restProps, size, variant: statusVariant, children: /* @__PURE__ */ jsx(Typography, { tag: "span", variant: "omega", fontWeight: "bold", children: formatMessage({
2576
+ id: `content-manager.containers.List.${status}`,
2577
+ defaultMessage: capitalise(status)
2578
+ }) }) });
2023
2579
  };
2024
2580
  const Header = ({ isCreating, status, title: documentTitle = "Untitled" }) => {
2025
2581
  const { formatMessage } = useIntl();
2026
2582
  const isCloning = useMatch(CLONE_PATH) !== null;
2583
+ const params = useParams();
2027
2584
  const title = isCreating ? formatMessage({
2028
2585
  id: "content-manager.containers.edit.title.new",
2029
2586
  defaultMessage: "Create an entry"
2030
2587
  }) : documentTitle;
2031
- return /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", paddingTop: 8, paddingBottom: 4, gap: 3, children: [
2032
- /* @__PURE__ */ jsx(BackButton, {}),
2033
- /* @__PURE__ */ jsxs(
2034
- Flex,
2588
+ return /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", paddingTop: 6, paddingBottom: 4, gap: 2, children: [
2589
+ /* @__PURE__ */ jsx(
2590
+ BackButton,
2035
2591
  {
2036
- width: "100%",
2037
- justifyContent: "space-between",
2038
- paddingTop: 1,
2039
- gap: "80px",
2040
- alignItems: "flex-start",
2041
- children: [
2042
- /* @__PURE__ */ jsx(Typography, { variant: "alpha", as: "h1", children: title }),
2043
- /* @__PURE__ */ jsx(HeaderToolbar, {})
2044
- ]
2592
+ fallback: params.collectionType === SINGLE_TYPES ? void 0 : `../${COLLECTION_TYPES}/${params.slug}`
2045
2593
  }
2046
2594
  ),
2047
- status ? /* @__PURE__ */ jsx(DocumentStatus, { status: isCloning ? "draft" : status }) : null
2595
+ /* @__PURE__ */ jsxs(Flex, { width: "100%", justifyContent: "space-between", gap: "80px", alignItems: "flex-start", children: [
2596
+ /* @__PURE__ */ jsx(Typography, { variant: "alpha", tag: "h1", children: title }),
2597
+ /* @__PURE__ */ jsx(HeaderToolbar, {})
2598
+ ] }),
2599
+ status ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(DocumentStatus, { status: isCloning ? "draft" : status }) }) : null
2048
2600
  ] });
2049
2601
  };
2050
2602
  const HeaderToolbar = () => {
@@ -2127,12 +2679,12 @@ const Information = ({ activeTab }) => {
2127
2679
  isDisplayed: !!publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME],
2128
2680
  label: formatMessage({
2129
2681
  id: "content-manager.containers.edit.information.last-published.label",
2130
- defaultMessage: "Last published"
2682
+ defaultMessage: "Published"
2131
2683
  }),
2132
2684
  value: formatMessage(
2133
2685
  {
2134
2686
  id: "content-manager.containers.edit.information.last-published.value",
2135
- defaultMessage: `Published {time}{isAnonymous, select, true {} other { by {author}}}`
2687
+ defaultMessage: `{time}{isAnonymous, select, true {} other { by {author}}}`
2136
2688
  },
2137
2689
  {
2138
2690
  time: /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME]) }),
@@ -2145,12 +2697,12 @@ const Information = ({ activeTab }) => {
2145
2697
  isDisplayed: !!createAndUpdateDocument?.[UPDATED_AT_ATTRIBUTE_NAME],
2146
2698
  label: formatMessage({
2147
2699
  id: "content-manager.containers.edit.information.last-draft.label",
2148
- defaultMessage: "Last draft"
2700
+ defaultMessage: "Updated"
2149
2701
  }),
2150
2702
  value: formatMessage(
2151
2703
  {
2152
2704
  id: "content-manager.containers.edit.information.last-draft.value",
2153
- defaultMessage: `Modified {time}{isAnonymous, select, true {} other { by {author}}}`
2705
+ defaultMessage: `{time}{isAnonymous, select, true {} other { by {author}}}`
2154
2706
  },
2155
2707
  {
2156
2708
  time: /* @__PURE__ */ jsx(
@@ -2168,12 +2720,12 @@ const Information = ({ activeTab }) => {
2168
2720
  isDisplayed: !!createAndUpdateDocument?.[CREATED_AT_ATTRIBUTE_NAME],
2169
2721
  label: formatMessage({
2170
2722
  id: "content-manager.containers.edit.information.document.label",
2171
- defaultMessage: "Document"
2723
+ defaultMessage: "Created"
2172
2724
  }),
2173
2725
  value: formatMessage(
2174
2726
  {
2175
2727
  id: "content-manager.containers.edit.information.document.value",
2176
- defaultMessage: `Created {time}{isAnonymous, select, true {} other { by {author}}}`
2728
+ defaultMessage: `{time}{isAnonymous, select, true {} other { by {author}}}`
2177
2729
  },
2178
2730
  {
2179
2731
  time: /* @__PURE__ */ jsx(
@@ -2196,7 +2748,7 @@ const Information = ({ activeTab }) => {
2196
2748
  borderColor: "neutral150",
2197
2749
  direction: "column",
2198
2750
  marginTop: 2,
2199
- as: "dl",
2751
+ tag: "dl",
2200
2752
  padding: 5,
2201
2753
  gap: 3,
2202
2754
  alignItems: "flex-start",
@@ -2204,41 +2756,93 @@ const Information = ({ activeTab }) => {
2204
2756
  marginRight: "-0.4rem",
2205
2757
  width: "calc(100% + 8px)",
2206
2758
  children: information.map((info) => /* @__PURE__ */ jsxs(Flex, { gap: 1, direction: "column", alignItems: "flex-start", children: [
2207
- /* @__PURE__ */ jsx(Typography, { as: "dt", variant: "pi", fontWeight: "bold", children: info.label }),
2208
- /* @__PURE__ */ jsx(Typography, { as: "dd", variant: "pi", textColor: "neutral600", children: info.value })
2759
+ /* @__PURE__ */ jsx(Typography, { tag: "dt", variant: "pi", fontWeight: "bold", children: info.label }),
2760
+ /* @__PURE__ */ jsx(Typography, { tag: "dd", variant: "pi", textColor: "neutral600", children: info.value })
2209
2761
  ] }, info.label))
2210
2762
  }
2211
2763
  );
2212
2764
  };
2213
2765
  const HeaderActions = ({ actions: actions2 }) => {
2214
- return /* @__PURE__ */ jsx(Flex, { children: actions2.map((action) => {
2215
- if ("options" in action) {
2766
+ const [dialogId, setDialogId] = React.useState(null);
2767
+ const handleClick = (action) => async (e) => {
2768
+ if (!("options" in action)) {
2769
+ const { onClick = () => false, dialog, id } = action;
2770
+ const muteDialog = await onClick(e);
2771
+ if (dialog && !muteDialog) {
2772
+ e.preventDefault();
2773
+ setDialogId(id);
2774
+ }
2775
+ }
2776
+ };
2777
+ const handleClose = () => {
2778
+ setDialogId(null);
2779
+ };
2780
+ return /* @__PURE__ */ jsx(Flex, { gap: 1, children: actions2.map((action) => {
2781
+ if (action.options) {
2216
2782
  return /* @__PURE__ */ jsx(
2217
2783
  SingleSelect,
2218
2784
  {
2219
2785
  size: "S",
2220
- disabled: action.disabled,
2221
- "aria-label": action.label,
2222
2786
  onChange: action.onSelect,
2223
- value: action.value,
2787
+ "aria-label": action.label,
2788
+ ...action,
2224
2789
  children: action.options.map(({ label, ...option }) => /* @__PURE__ */ jsx(SingleSelectOption, { ...option, children: label }, option.value))
2225
2790
  },
2226
2791
  action.id
2227
2792
  );
2228
2793
  } else {
2229
- return null;
2794
+ if (action.type === "icon") {
2795
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
2796
+ /* @__PURE__ */ jsx(
2797
+ IconButton,
2798
+ {
2799
+ disabled: action.disabled,
2800
+ label: action.label,
2801
+ size: "S",
2802
+ onClick: handleClick(action),
2803
+ children: action.icon
2804
+ }
2805
+ ),
2806
+ action.dialog ? /* @__PURE__ */ jsx(
2807
+ HeaderActionDialog,
2808
+ {
2809
+ ...action.dialog,
2810
+ isOpen: dialogId === action.id,
2811
+ onClose: handleClose
2812
+ }
2813
+ ) : null
2814
+ ] }, action.id);
2815
+ }
2230
2816
  }
2231
2817
  }) });
2232
2818
  };
2233
- const ConfigureTheViewAction = ({ collectionType, model }) => {
2234
- const navigate = useNavigate();
2235
- const { formatMessage } = useIntl();
2236
- return {
2237
- label: formatMessage({
2238
- id: "app.links.configure-view",
2819
+ const HeaderActionDialog = ({
2820
+ onClose,
2821
+ onCancel,
2822
+ title,
2823
+ content: Content,
2824
+ isOpen
2825
+ }) => {
2826
+ const handleClose = async () => {
2827
+ if (onCancel) {
2828
+ await onCancel();
2829
+ }
2830
+ onClose();
2831
+ };
2832
+ return /* @__PURE__ */ jsx(Dialog.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxs(Dialog.Content, { children: [
2833
+ /* @__PURE__ */ jsx(Dialog.Header, { children: title }),
2834
+ typeof Content === "function" ? /* @__PURE__ */ jsx(Content, { onClose: handleClose }) : Content
2835
+ ] }) });
2836
+ };
2837
+ const ConfigureTheViewAction = ({ collectionType, model }) => {
2838
+ const navigate = useNavigate();
2839
+ const { formatMessage } = useIntl();
2840
+ return {
2841
+ label: formatMessage({
2842
+ id: "app.links.configure-view",
2239
2843
  defaultMessage: "Configure the view"
2240
2844
  }),
2241
- icon: /* @__PURE__ */ jsx(StyledCog, {}),
2845
+ icon: /* @__PURE__ */ jsx(ListPlus, {}),
2242
2846
  onClick: () => {
2243
2847
  navigate(`../${collectionType}/${model}/configurations/edit`);
2244
2848
  },
@@ -2246,11 +2850,6 @@ const ConfigureTheViewAction = ({ collectionType, model }) => {
2246
2850
  };
2247
2851
  };
2248
2852
  ConfigureTheViewAction.type = "configure-the-view";
2249
- const StyledCog = styled(Cog)`
2250
- path {
2251
- fill: currentColor;
2252
- }
2253
- `;
2254
2853
  const EditTheModelAction = ({ model }) => {
2255
2854
  const navigate = useNavigate();
2256
2855
  const { formatMessage } = useIntl();
@@ -2259,7 +2858,7 @@ const EditTheModelAction = ({ model }) => {
2259
2858
  id: "content-manager.link-to-ctb",
2260
2859
  defaultMessage: "Edit the model"
2261
2860
  }),
2262
- icon: /* @__PURE__ */ jsx(StyledPencil$1, {}),
2861
+ icon: /* @__PURE__ */ jsx(Pencil, {}),
2263
2862
  onClick: () => {
2264
2863
  navigate(`/plugins/content-type-builder/content-types/${model}`);
2265
2864
  },
@@ -2267,12 +2866,7 @@ const EditTheModelAction = ({ model }) => {
2267
2866
  };
2268
2867
  };
2269
2868
  EditTheModelAction.type = "edit-the-model";
2270
- const StyledPencil$1 = styled(Pencil)`
2271
- path {
2272
- fill: currentColor;
2273
- }
2274
- `;
2275
- const DeleteAction = ({ documentId, model, collectionType, document }) => {
2869
+ const DeleteAction$1 = ({ documentId, model, collectionType, document }) => {
2276
2870
  const navigate = useNavigate();
2277
2871
  const { formatMessage } = useIntl();
2278
2872
  const listViewPathMatch = useMatch(LIST_PATH);
@@ -2280,13 +2874,17 @@ const DeleteAction = ({ documentId, model, collectionType, document }) => {
2280
2874
  const { delete: deleteAction } = useDocumentActions();
2281
2875
  const { toggleNotification } = useNotification();
2282
2876
  const setSubmitting = useForm("DeleteAction", (state) => state.setSubmitting);
2877
+ const isLocalized = document?.locale != null;
2283
2878
  return {
2284
2879
  disabled: !canDelete || !document,
2285
- label: formatMessage({
2286
- id: "content-manager.actions.delete.label",
2287
- defaultMessage: "Delete document"
2288
- }),
2289
- icon: /* @__PURE__ */ jsx(StyledTrash, {}),
2880
+ label: formatMessage(
2881
+ {
2882
+ id: "content-manager.actions.delete.label",
2883
+ defaultMessage: "Delete entry{isLocalized, select, true { (all locales)} other {}}"
2884
+ },
2885
+ { isLocalized }
2886
+ ),
2887
+ icon: /* @__PURE__ */ jsx(Trash, {}),
2290
2888
  dialog: {
2291
2889
  type: "dialog",
2292
2890
  title: formatMessage({
@@ -2295,7 +2893,7 @@ const DeleteAction = ({ documentId, model, collectionType, document }) => {
2295
2893
  }),
2296
2894
  content: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, children: [
2297
2895
  /* @__PURE__ */ jsx(WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
2298
- /* @__PURE__ */ jsx(Typography, { as: "p", variant: "omega", textAlign: "center", children: formatMessage({
2896
+ /* @__PURE__ */ jsx(Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
2299
2897
  id: "content-manager.actions.delete.dialog.body",
2300
2898
  defaultMessage: "Are you sure?"
2301
2899
  }) })
@@ -2340,13 +2938,8 @@ const DeleteAction = ({ documentId, model, collectionType, document }) => {
2340
2938
  position: ["header", "table-row"]
2341
2939
  };
2342
2940
  };
2343
- DeleteAction.type = "delete";
2344
- const StyledTrash = styled(Trash)`
2345
- path {
2346
- fill: currentColor;
2347
- }
2348
- `;
2349
- const DEFAULT_HEADER_ACTIONS = [EditTheModelAction, ConfigureTheViewAction, DeleteAction];
2941
+ DeleteAction$1.type = "delete";
2942
+ const DEFAULT_HEADER_ACTIONS = [EditTheModelAction, ConfigureTheViewAction, DeleteAction$1];
2350
2943
  const Panels = () => {
2351
2944
  const isCloning = useMatch(CLONE_PATH) !== null;
2352
2945
  const [
@@ -2380,7 +2973,7 @@ const ActionsPanel = () => {
2380
2973
  return {
2381
2974
  title: formatMessage({
2382
2975
  id: "content-manager.containers.edit.panels.default.title",
2383
- defaultMessage: "Document"
2976
+ defaultMessage: "Entry"
2384
2977
  }),
2385
2978
  content: /* @__PURE__ */ jsx(ActionsPanelContent, {})
2386
2979
  };
@@ -2420,7 +3013,7 @@ const Panel = React.forwardRef(({ children, title }, ref) => {
2420
3013
  Flex,
2421
3014
  {
2422
3015
  ref,
2423
- as: "aside",
3016
+ tag: "aside",
2424
3017
  "aria-labelledby": "additional-information",
2425
3018
  background: "neutral0",
2426
3019
  borderColor: "neutral150",
@@ -2435,13 +3028,590 @@ const Panel = React.forwardRef(({ children, title }, ref) => {
2435
3028
  justifyContent: "stretch",
2436
3029
  alignItems: "flex-start",
2437
3030
  children: [
2438
- /* @__PURE__ */ jsx(Typography, { as: "h2", variant: "sigma", textTransform: "uppercase", children: title }),
3031
+ /* @__PURE__ */ jsx(Typography, { tag: "h2", variant: "sigma", textTransform: "uppercase", textColor: "neutral600", children: title }),
2439
3032
  children
2440
3033
  ]
2441
3034
  }
2442
3035
  );
2443
3036
  });
2444
- const DEFAULT_BULK_ACTIONS = [];
3037
+ const ConfirmBulkActionDialog = ({
3038
+ onToggleDialog,
3039
+ isOpen = false,
3040
+ dialogBody,
3041
+ endAction
3042
+ }) => {
3043
+ const { formatMessage } = useIntl();
3044
+ return /* @__PURE__ */ jsx(Dialog.Root, { open: isOpen, children: /* @__PURE__ */ jsxs(Dialog.Content, { children: [
3045
+ /* @__PURE__ */ jsx(Dialog.Header, { children: formatMessage({
3046
+ id: "app.components.ConfirmDialog.title",
3047
+ defaultMessage: "Confirmation"
3048
+ }) }),
3049
+ /* @__PURE__ */ jsx(Dialog.Body, { children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
3050
+ /* @__PURE__ */ jsx(Flex, { justifyContent: "center", children: /* @__PURE__ */ jsx(WarningCircle, { width: "24px", height: "24px", fill: "danger600" }) }),
3051
+ dialogBody
3052
+ ] }) }),
3053
+ /* @__PURE__ */ jsxs(Dialog.Footer, { children: [
3054
+ /* @__PURE__ */ jsx(Dialog.Cancel, { children: /* @__PURE__ */ jsx(Button, { fullWidth: true, onClick: onToggleDialog, variant: "tertiary", children: formatMessage({
3055
+ id: "app.components.Button.cancel",
3056
+ defaultMessage: "Cancel"
3057
+ }) }) }),
3058
+ endAction
3059
+ ] })
3060
+ ] }) });
3061
+ };
3062
+ const BoldChunk$1 = (chunks) => /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", children: chunks });
3063
+ const ConfirmDialogPublishAll = ({
3064
+ isOpen,
3065
+ onToggleDialog,
3066
+ isConfirmButtonLoading = false,
3067
+ onConfirm
3068
+ }) => {
3069
+ const { formatMessage } = useIntl();
3070
+ const selectedEntries = useTable("ConfirmDialogPublishAll", (state) => state.selectedRows);
3071
+ const { toggleNotification } = useNotification();
3072
+ const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler(getTranslation);
3073
+ const { model, schema } = useDoc();
3074
+ const [{ query }] = useQueryParams();
3075
+ const enableDraftRelationsCount = false;
3076
+ const {
3077
+ data: countDraftRelations = 0,
3078
+ isLoading,
3079
+ error
3080
+ } = useGetManyDraftRelationCountQuery(
3081
+ {
3082
+ model,
3083
+ documentIds: selectedEntries.map((entry) => entry.documentId),
3084
+ locale: query?.plugins?.i18n?.locale
3085
+ },
3086
+ {
3087
+ skip: !enableDraftRelationsCount
3088
+ }
3089
+ );
3090
+ React.useEffect(() => {
3091
+ if (error) {
3092
+ toggleNotification({ type: "danger", message: formatAPIError(error) });
3093
+ }
3094
+ }, [error, formatAPIError, toggleNotification]);
3095
+ if (error) {
3096
+ return null;
3097
+ }
3098
+ return /* @__PURE__ */ jsx(
3099
+ ConfirmBulkActionDialog,
3100
+ {
3101
+ isOpen: isOpen && !isLoading,
3102
+ onToggleDialog,
3103
+ dialogBody: /* @__PURE__ */ jsxs(Fragment, { children: [
3104
+ /* @__PURE__ */ jsxs(Typography, { id: "confirm-description", textAlign: "center", children: [
3105
+ countDraftRelations > 0 && formatMessage(
3106
+ {
3107
+ id: getTranslation(`popUpwarning.warning.bulk-has-draft-relations.message`),
3108
+ defaultMessage: "<b>{count} {count, plural, one { relation } other { relations } } out of {entities} { entities, plural, one { entry } other { entries } } {count, plural, one { is } other { are } }</b> not published yet and might lead to unexpected behavior. "
3109
+ },
3110
+ {
3111
+ b: BoldChunk$1,
3112
+ count: countDraftRelations,
3113
+ entities: selectedEntries.length
3114
+ }
3115
+ ),
3116
+ formatMessage({
3117
+ id: getTranslation("popUpWarning.bodyMessage.contentType.publish.all"),
3118
+ defaultMessage: "Are you sure you want to publish these entries?"
3119
+ })
3120
+ ] }),
3121
+ schema?.pluginOptions && "i18n" in schema.pluginOptions && schema?.pluginOptions.i18n && /* @__PURE__ */ jsx(Typography, { textColor: "danger500", textAlign: "center", children: formatMessage(
3122
+ {
3123
+ id: getTranslation("Settings.list.actions.publishAdditionalInfos"),
3124
+ defaultMessage: "This will publish the active locale versions <em>(from Internationalization)</em>"
3125
+ },
3126
+ {
3127
+ em: Emphasis
3128
+ }
3129
+ ) })
3130
+ ] }),
3131
+ endAction: /* @__PURE__ */ jsx(
3132
+ Button,
3133
+ {
3134
+ onClick: onConfirm,
3135
+ variant: "secondary",
3136
+ startIcon: /* @__PURE__ */ jsx(Check, {}),
3137
+ loading: isConfirmButtonLoading,
3138
+ children: formatMessage({
3139
+ id: "app.utils.publish",
3140
+ defaultMessage: "Publish"
3141
+ })
3142
+ }
3143
+ )
3144
+ }
3145
+ );
3146
+ };
3147
+ const TypographyMaxWidth = styled(Typography)`
3148
+ max-width: 300px;
3149
+ `;
3150
+ const formatErrorMessages = (errors, parentKey, formatMessage) => {
3151
+ const messages = [];
3152
+ Object.entries(errors).forEach(([key, value]) => {
3153
+ const currentKey = parentKey ? `${parentKey}.${key}` : key;
3154
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
3155
+ if ("id" in value && "defaultMessage" in value) {
3156
+ messages.push(
3157
+ formatMessage(
3158
+ {
3159
+ id: `${value.id}.withField`,
3160
+ defaultMessage: value.defaultMessage
3161
+ },
3162
+ { field: currentKey }
3163
+ )
3164
+ );
3165
+ } else {
3166
+ messages.push(
3167
+ ...formatErrorMessages(
3168
+ // @ts-expect-error TODO: check why value is not compatible with FormErrors
3169
+ value,
3170
+ currentKey,
3171
+ formatMessage
3172
+ )
3173
+ );
3174
+ }
3175
+ } else {
3176
+ messages.push(
3177
+ formatMessage(
3178
+ {
3179
+ id: `${value}.withField`,
3180
+ defaultMessage: value
3181
+ },
3182
+ { field: currentKey }
3183
+ )
3184
+ );
3185
+ }
3186
+ });
3187
+ return messages;
3188
+ };
3189
+ const EntryValidationText = ({ validationErrors, status }) => {
3190
+ const { formatMessage } = useIntl();
3191
+ if (validationErrors) {
3192
+ const validationErrorsMessages = formatErrorMessages(validationErrors, "", formatMessage).join(
3193
+ " "
3194
+ );
3195
+ return /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
3196
+ /* @__PURE__ */ jsx(CrossCircle, { fill: "danger600" }),
3197
+ /* @__PURE__ */ jsx(Tooltip, { description: validationErrorsMessages, children: /* @__PURE__ */ jsx(TypographyMaxWidth, { textColor: "danger600", variant: "omega", fontWeight: "semiBold", ellipsis: true, children: validationErrorsMessages }) })
3198
+ ] });
3199
+ }
3200
+ if (status === "published") {
3201
+ return /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
3202
+ /* @__PURE__ */ jsx(CheckCircle, { fill: "success600" }),
3203
+ /* @__PURE__ */ jsx(Typography, { textColor: "success600", fontWeight: "bold", children: formatMessage({
3204
+ id: "content-manager.bulk-publish.already-published",
3205
+ defaultMessage: "Already Published"
3206
+ }) })
3207
+ ] });
3208
+ }
3209
+ if (status === "modified") {
3210
+ return /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
3211
+ /* @__PURE__ */ jsx(ArrowsCounterClockwise, { fill: "alternative600" }),
3212
+ /* @__PURE__ */ jsx(Typography, { children: formatMessage({
3213
+ id: "content-manager.bulk-publish.modified",
3214
+ defaultMessage: "Ready to publish changes"
3215
+ }) })
3216
+ ] });
3217
+ }
3218
+ return /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
3219
+ /* @__PURE__ */ jsx(CheckCircle, { fill: "success600" }),
3220
+ /* @__PURE__ */ jsx(Typography, { children: formatMessage({
3221
+ id: "app.utils.ready-to-publish",
3222
+ defaultMessage: "Ready to publish"
3223
+ }) })
3224
+ ] });
3225
+ };
3226
+ const TABLE_HEADERS = [
3227
+ { name: "id", label: "id" },
3228
+ { name: "name", label: "name" },
3229
+ { name: "status", label: "status" },
3230
+ { name: "publicationStatus", label: "Publication status" }
3231
+ ];
3232
+ const SelectedEntriesTableContent = ({
3233
+ isPublishing,
3234
+ rowsToDisplay = [],
3235
+ entriesToPublish = [],
3236
+ validationErrors = {}
3237
+ }) => {
3238
+ const { pathname } = useLocation();
3239
+ const { formatMessage } = useIntl();
3240
+ const {
3241
+ list: {
3242
+ settings: { mainField }
3243
+ }
3244
+ } = useDocLayout();
3245
+ const shouldDisplayMainField = mainField != null && mainField !== "id";
3246
+ return /* @__PURE__ */ jsxs(Table.Content, { children: [
3247
+ /* @__PURE__ */ jsxs(Table.Head, { children: [
3248
+ /* @__PURE__ */ jsx(Table.HeaderCheckboxCell, {}),
3249
+ TABLE_HEADERS.filter((head) => head.name !== "name" || shouldDisplayMainField).map(
3250
+ (head) => /* @__PURE__ */ jsx(Table.HeaderCell, { ...head }, head.name)
3251
+ )
3252
+ ] }),
3253
+ /* @__PURE__ */ jsx(Table.Loading, {}),
3254
+ /* @__PURE__ */ jsx(Table.Body, { children: rowsToDisplay.map((row, index2) => /* @__PURE__ */ jsxs(Table.Row, { children: [
3255
+ /* @__PURE__ */ jsx(Table.CheckboxCell, { id: row.id }),
3256
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Typography, { children: row.id }) }),
3257
+ shouldDisplayMainField && /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Typography, { children: row[mainField] }) }),
3258
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(DocumentStatus, { status: row.status, maxWidth: "min-content" }) }),
3259
+ /* @__PURE__ */ jsx(Table.Cell, { children: isPublishing && entriesToPublish.includes(row.documentId) ? /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
3260
+ /* @__PURE__ */ jsx(Typography, { children: formatMessage({
3261
+ id: "content-manager.success.record.publishing",
3262
+ defaultMessage: "Publishing..."
3263
+ }) }),
3264
+ /* @__PURE__ */ jsx(Loader, { small: true })
3265
+ ] }) : /* @__PURE__ */ jsx(
3266
+ EntryValidationText,
3267
+ {
3268
+ validationErrors: validationErrors[row.documentId],
3269
+ status: row.status
3270
+ }
3271
+ ) }),
3272
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Flex, { children: /* @__PURE__ */ jsx(
3273
+ IconButton,
3274
+ {
3275
+ tag: Link,
3276
+ to: {
3277
+ pathname: `${pathname}/${row.documentId}`,
3278
+ search: row.locale && `?plugins[i18n][locale]=${row.locale}`
3279
+ },
3280
+ state: { from: pathname },
3281
+ label: formatMessage({
3282
+ id: "content-manager.bulk-publish.edit",
3283
+ defaultMessage: "Edit"
3284
+ }),
3285
+ target: "_blank",
3286
+ marginLeft: "auto",
3287
+ variant: "ghost",
3288
+ children: /* @__PURE__ */ jsx(Pencil, { width: "1.6rem", height: "1.6rem" })
3289
+ }
3290
+ ) }) })
3291
+ ] }, row.id)) })
3292
+ ] });
3293
+ };
3294
+ const BoldChunk = (chunks) => /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", children: chunks });
3295
+ const SelectedEntriesModalContent = ({
3296
+ listViewSelectedEntries,
3297
+ toggleModal,
3298
+ setListViewSelectedDocuments,
3299
+ model
3300
+ }) => {
3301
+ const { formatMessage } = useIntl();
3302
+ const { schema, components } = useContentTypeSchema(model);
3303
+ const documentIds = listViewSelectedEntries.map(({ documentId }) => documentId);
3304
+ const [{ query }] = useQueryParams();
3305
+ const params = React.useMemo(() => buildValidParams(query), [query]);
3306
+ const { data, isLoading, isFetching, refetch } = useGetAllDocumentsQuery(
3307
+ {
3308
+ model,
3309
+ params: {
3310
+ page: "1",
3311
+ pageSize: documentIds.length.toString(),
3312
+ sort: query.sort,
3313
+ filters: {
3314
+ documentId: {
3315
+ $in: documentIds
3316
+ }
3317
+ },
3318
+ locale: query.plugins?.i18n?.locale
3319
+ }
3320
+ },
3321
+ {
3322
+ selectFromResult: ({ data: data2, ...restRes }) => ({ data: data2?.results ?? [], ...restRes })
3323
+ }
3324
+ );
3325
+ const { rows, validationErrors } = React.useMemo(() => {
3326
+ if (data.length > 0 && schema) {
3327
+ const validate = createYupSchema(
3328
+ schema.attributes,
3329
+ components,
3330
+ // Since this is the "Publish" action, the validation
3331
+ // schema must enforce the rules for published entities
3332
+ { status: "published" }
3333
+ );
3334
+ const validationErrors2 = {};
3335
+ const rows2 = data.map((entry) => {
3336
+ try {
3337
+ validate.validateSync(entry, { abortEarly: false });
3338
+ return entry;
3339
+ } catch (e) {
3340
+ if (e instanceof ValidationError) {
3341
+ validationErrors2[entry.documentId] = getYupValidationErrors(e);
3342
+ }
3343
+ return entry;
3344
+ }
3345
+ });
3346
+ return { rows: rows2, validationErrors: validationErrors2 };
3347
+ }
3348
+ return {
3349
+ rows: [],
3350
+ validationErrors: {}
3351
+ };
3352
+ }, [components, data, schema]);
3353
+ const [publishedCount, setPublishedCount] = React.useState(0);
3354
+ const [isDialogOpen, setIsDialogOpen] = React.useState(false);
3355
+ const { publishMany: bulkPublishAction } = useDocumentActions();
3356
+ const [, { isLoading: isSubmittingForm }] = usePublishManyDocumentsMutation();
3357
+ const selectedRows = useTable("publishAction", (state) => state.selectedRows);
3358
+ const selectedEntries = rows.filter(
3359
+ (entry) => selectedRows.some((selectedEntry) => selectedEntry.documentId === entry.documentId)
3360
+ );
3361
+ const entriesToPublish = selectedEntries.filter((entry) => !validationErrors[entry.documentId]).map((entry) => entry.documentId);
3362
+ const selectedEntriesWithErrorsCount = selectedEntries.filter(
3363
+ ({ documentId }) => validationErrors[documentId]
3364
+ ).length;
3365
+ const selectedEntriesPublished = selectedEntries.filter(
3366
+ ({ status }) => status === "published"
3367
+ ).length;
3368
+ const selectedEntriesWithNoErrorsCount = selectedEntries.length - selectedEntriesWithErrorsCount - selectedEntriesPublished;
3369
+ const toggleDialog = () => setIsDialogOpen((prev) => !prev);
3370
+ const handleConfirmBulkPublish = async () => {
3371
+ toggleDialog();
3372
+ const res = await bulkPublishAction({ model, documentIds: entriesToPublish, params });
3373
+ if (!("error" in res)) {
3374
+ setPublishedCount(res.count);
3375
+ const unpublishedEntries = rows.filter((row) => {
3376
+ return !entriesToPublish.includes(row.documentId);
3377
+ });
3378
+ setListViewSelectedDocuments(unpublishedEntries);
3379
+ }
3380
+ };
3381
+ const getFormattedCountMessage = () => {
3382
+ if (publishedCount) {
3383
+ return formatMessage(
3384
+ {
3385
+ id: getTranslation("containers.list.selectedEntriesModal.publishedCount"),
3386
+ defaultMessage: "<b>{publishedCount}</b> {publishedCount, plural, =0 {entries} one {entry} other {entries}} published. <b>{withErrorsCount}</b> {withErrorsCount, plural, =0 {entries} one {entry} other {entries}} waiting for action."
3387
+ },
3388
+ {
3389
+ publishedCount,
3390
+ withErrorsCount: selectedEntriesWithErrorsCount,
3391
+ b: BoldChunk
3392
+ }
3393
+ );
3394
+ }
3395
+ return formatMessage(
3396
+ {
3397
+ id: getTranslation("containers.list.selectedEntriesModal.selectedCount"),
3398
+ defaultMessage: "<b>{alreadyPublishedCount}</b> {alreadyPublishedCount, plural, =0 {entries} one {entry} other {entries}} already published. <b>{readyToPublishCount}</b> {readyToPublishCount, plural, =0 {entries} one {entry} other {entries}} ready to publish. <b>{withErrorsCount}</b> {withErrorsCount, plural, =0 {entries} one {entry} other {entries}} waiting for action."
3399
+ },
3400
+ {
3401
+ readyToPublishCount: selectedEntriesWithNoErrorsCount,
3402
+ withErrorsCount: selectedEntriesWithErrorsCount,
3403
+ alreadyPublishedCount: selectedEntriesPublished,
3404
+ b: BoldChunk
3405
+ }
3406
+ );
3407
+ };
3408
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
3409
+ /* @__PURE__ */ jsxs(Modal.Body, { children: [
3410
+ /* @__PURE__ */ jsx(Typography, { children: getFormattedCountMessage() }),
3411
+ /* @__PURE__ */ jsx(Box, { marginTop: 5, children: /* @__PURE__ */ jsx(
3412
+ SelectedEntriesTableContent,
3413
+ {
3414
+ isPublishing: isSubmittingForm,
3415
+ rowsToDisplay: rows,
3416
+ entriesToPublish,
3417
+ validationErrors
3418
+ }
3419
+ ) })
3420
+ ] }),
3421
+ /* @__PURE__ */ jsxs(Modal.Footer, { children: [
3422
+ /* @__PURE__ */ jsx(Button, { onClick: toggleModal, variant: "tertiary", children: formatMessage({
3423
+ id: "app.components.Button.cancel",
3424
+ defaultMessage: "Cancel"
3425
+ }) }),
3426
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
3427
+ /* @__PURE__ */ jsx(Button, { onClick: refetch, variant: "tertiary", loading: isFetching, children: formatMessage({ id: "app.utils.refresh", defaultMessage: "Refresh" }) }),
3428
+ /* @__PURE__ */ jsx(
3429
+ Button,
3430
+ {
3431
+ onClick: toggleDialog,
3432
+ disabled: selectedEntries.length === 0 || selectedEntries.length === selectedEntriesWithErrorsCount || selectedEntriesPublished === selectedEntries.length || isLoading,
3433
+ loading: isSubmittingForm,
3434
+ children: formatMessage({ id: "app.utils.publish", defaultMessage: "Publish" })
3435
+ }
3436
+ )
3437
+ ] })
3438
+ ] }),
3439
+ /* @__PURE__ */ jsx(
3440
+ ConfirmDialogPublishAll,
3441
+ {
3442
+ isOpen: isDialogOpen,
3443
+ onToggleDialog: toggleDialog,
3444
+ isConfirmButtonLoading: isSubmittingForm,
3445
+ onConfirm: handleConfirmBulkPublish
3446
+ }
3447
+ )
3448
+ ] });
3449
+ };
3450
+ const PublishAction = ({ documents, model }) => {
3451
+ const { formatMessage } = useIntl();
3452
+ const hasPublishPermission = useDocumentRBAC("unpublishAction", (state) => state.canPublish);
3453
+ const showPublishButton = hasPublishPermission && documents.some(({ status }) => status !== "published");
3454
+ const setListViewSelectedDocuments = useTable("publishAction", (state) => state.selectRow);
3455
+ const refetchList = () => {
3456
+ contentManagerApi.util.invalidateTags([{ type: "Document", id: `${model}_LIST` }]);
3457
+ };
3458
+ if (!showPublishButton)
3459
+ return null;
3460
+ return {
3461
+ actionType: "publish",
3462
+ variant: "tertiary",
3463
+ label: formatMessage({ id: "app.utils.publish", defaultMessage: "Publish" }),
3464
+ dialog: {
3465
+ type: "modal",
3466
+ title: formatMessage({
3467
+ id: getTranslation("containers.ListPage.selectedEntriesModal.title"),
3468
+ defaultMessage: "Publish entries"
3469
+ }),
3470
+ content: ({ onClose }) => {
3471
+ return /* @__PURE__ */ jsx(Table.Root, { rows: documents, defaultSelectedRows: documents, headers: TABLE_HEADERS, children: /* @__PURE__ */ jsx(
3472
+ SelectedEntriesModalContent,
3473
+ {
3474
+ listViewSelectedEntries: documents,
3475
+ toggleModal: () => {
3476
+ onClose();
3477
+ refetchList();
3478
+ },
3479
+ setListViewSelectedDocuments,
3480
+ model
3481
+ }
3482
+ ) });
3483
+ },
3484
+ onClose: () => {
3485
+ refetchList();
3486
+ }
3487
+ }
3488
+ };
3489
+ };
3490
+ const BulkActionsRenderer = () => {
3491
+ const plugins = useStrapiApp("BulkActionsRenderer", (state) => state.plugins);
3492
+ const { model, collectionType } = useDoc();
3493
+ const { selectedRows } = useTable("BulkActionsRenderer", (state) => state);
3494
+ return /* @__PURE__ */ jsx(Flex, { gap: 2, children: /* @__PURE__ */ jsx(
3495
+ DescriptionComponentRenderer,
3496
+ {
3497
+ props: {
3498
+ model,
3499
+ collectionType,
3500
+ documents: selectedRows
3501
+ },
3502
+ descriptions: plugins["content-manager"].apis.getBulkActions(),
3503
+ children: (actions2) => actions2.map((action) => /* @__PURE__ */ jsx(DocumentActionButton, { ...action }, action.id))
3504
+ }
3505
+ ) });
3506
+ };
3507
+ const DeleteAction = ({ documents, model }) => {
3508
+ const { formatMessage } = useIntl();
3509
+ const { schema: contentType } = useDoc();
3510
+ const selectRow = useTable("DeleteAction", (state) => state.selectRow);
3511
+ const hasI18nEnabled = Boolean(contentType?.pluginOptions?.i18n);
3512
+ const [{ query }] = useQueryParams();
3513
+ const params = React.useMemo(() => buildValidParams(query), [query]);
3514
+ const hasDeletePermission = useDocumentRBAC("deleteAction", (state) => state.canDelete);
3515
+ const { deleteMany: bulkDeleteAction } = useDocumentActions();
3516
+ const documentIds = documents.map(({ documentId }) => documentId);
3517
+ const handleConfirmBulkDelete = async () => {
3518
+ const res = await bulkDeleteAction({
3519
+ documentIds,
3520
+ model,
3521
+ params
3522
+ });
3523
+ if (!("error" in res)) {
3524
+ selectRow([]);
3525
+ }
3526
+ };
3527
+ if (!hasDeletePermission)
3528
+ return null;
3529
+ return {
3530
+ variant: "danger-light",
3531
+ label: formatMessage({ id: "global.delete", defaultMessage: "Delete" }),
3532
+ dialog: {
3533
+ type: "dialog",
3534
+ title: formatMessage({
3535
+ id: "app.components.ConfirmDialog.title",
3536
+ defaultMessage: "Confirmation"
3537
+ }),
3538
+ content: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
3539
+ /* @__PURE__ */ jsx(Flex, { justifyContent: "center", children: /* @__PURE__ */ jsx(WarningCircle, { width: "24px", height: "24px", fill: "danger600" }) }),
3540
+ /* @__PURE__ */ jsx(Typography, { id: "confirm-description", textAlign: "center", children: formatMessage({
3541
+ id: "popUpWarning.bodyMessage.contentType.delete.all",
3542
+ defaultMessage: "Are you sure you want to delete these entries?"
3543
+ }) }),
3544
+ hasI18nEnabled && /* @__PURE__ */ jsx(Box, { textAlign: "center", padding: 3, children: /* @__PURE__ */ jsx(Typography, { textColor: "danger500", children: formatMessage(
3545
+ {
3546
+ id: getTranslation("Settings.list.actions.deleteAdditionalInfos"),
3547
+ defaultMessage: "This will delete the active locale versions <em>(from Internationalization)</em>"
3548
+ },
3549
+ {
3550
+ em: Emphasis
3551
+ }
3552
+ ) }) })
3553
+ ] }),
3554
+ onConfirm: handleConfirmBulkDelete
3555
+ }
3556
+ };
3557
+ };
3558
+ DeleteAction.type = "delete";
3559
+ const UnpublishAction = ({ documents, model }) => {
3560
+ const { formatMessage } = useIntl();
3561
+ const { schema } = useDoc();
3562
+ const selectRow = useTable("UnpublishAction", (state) => state.selectRow);
3563
+ const hasPublishPermission = useDocumentRBAC("unpublishAction", (state) => state.canPublish);
3564
+ const hasI18nEnabled = Boolean(schema?.pluginOptions?.i18n);
3565
+ const hasDraftAndPublishEnabled = Boolean(schema?.options?.draftAndPublish);
3566
+ const { unpublishMany: bulkUnpublishAction } = useDocumentActions();
3567
+ const documentIds = documents.map(({ documentId }) => documentId);
3568
+ const [{ query }] = useQueryParams();
3569
+ const params = React.useMemo(() => buildValidParams(query), [query]);
3570
+ const handleConfirmBulkUnpublish = async () => {
3571
+ const data = await bulkUnpublishAction({ documentIds, model, params });
3572
+ if (!("error" in data)) {
3573
+ selectRow([]);
3574
+ }
3575
+ };
3576
+ const showUnpublishButton = hasDraftAndPublishEnabled && hasPublishPermission && documents.some((entry) => entry.status === "published" || entry.status === "modified");
3577
+ if (!showUnpublishButton)
3578
+ return null;
3579
+ return {
3580
+ variant: "tertiary",
3581
+ label: formatMessage({ id: "app.utils.unpublish", defaultMessage: "Unpublish" }),
3582
+ dialog: {
3583
+ type: "dialog",
3584
+ title: formatMessage({
3585
+ id: "app.components.ConfirmDialog.title",
3586
+ defaultMessage: "Confirmation"
3587
+ }),
3588
+ content: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
3589
+ /* @__PURE__ */ jsx(Flex, { justifyContent: "center", children: /* @__PURE__ */ jsx(WarningCircle, { width: "24px", height: "24px", fill: "danger600" }) }),
3590
+ /* @__PURE__ */ jsx(Typography, { id: "confirm-description", textAlign: "center", children: formatMessage({
3591
+ id: "popUpWarning.bodyMessage.contentType.unpublish.all",
3592
+ defaultMessage: "Are you sure you want to unpublish these entries?"
3593
+ }) }),
3594
+ hasI18nEnabled && /* @__PURE__ */ jsx(Box, { textAlign: "center", padding: 3, children: /* @__PURE__ */ jsx(Typography, { textColor: "danger500", children: formatMessage(
3595
+ {
3596
+ id: getTranslation("Settings.list.actions.unpublishAdditionalInfos"),
3597
+ defaultMessage: "This will unpublish the active locale versions <em>(from Internationalization)</em>"
3598
+ },
3599
+ {
3600
+ em: Emphasis
3601
+ }
3602
+ ) }) })
3603
+ ] }),
3604
+ confirmButton: formatMessage({
3605
+ id: "app.utils.unpublish",
3606
+ defaultMessage: "Unpublish"
3607
+ }),
3608
+ onConfirm: handleConfirmBulkUnpublish
3609
+ }
3610
+ };
3611
+ };
3612
+ UnpublishAction.type = "unpublish";
3613
+ const Emphasis = (chunks) => /* @__PURE__ */ jsx(Typography, { fontWeight: "semiBold", textColor: "danger500", children: chunks });
3614
+ const DEFAULT_BULK_ACTIONS = [PublishAction, UnpublishAction, DeleteAction];
2445
3615
  const AutoCloneFailureModalBody = ({ prohibitedFields }) => {
2446
3616
  const { formatMessage } = useIntl();
2447
3617
  const getDefaultErrorMessage = (reason) => {
@@ -2473,7 +3643,7 @@ const AutoCloneFailureModalBody = ({ prohibitedFields }) => {
2473
3643
  hasRadius: true,
2474
3644
  padding: 6,
2475
3645
  children: [
2476
- /* @__PURE__ */ jsx(Flex, { direction: "row", as: "ol", children: fieldPath.map((pathSegment, index2) => /* @__PURE__ */ jsxs(Typography, { fontWeight: "semiBold", as: "li", children: [
3646
+ /* @__PURE__ */ jsx(Flex, { direction: "row", tag: "ol", children: fieldPath.map((pathSegment, index2) => /* @__PURE__ */ jsxs(Typography, { fontWeight: "semiBold", tag: "li", children: [
2477
3647
  pathSegment,
2478
3648
  index2 !== fieldPath.length - 1 && /* @__PURE__ */ jsx(
2479
3649
  ChevronRight,
@@ -2485,7 +3655,7 @@ const AutoCloneFailureModalBody = ({ prohibitedFields }) => {
2485
3655
  }
2486
3656
  )
2487
3657
  ] }, index2)) }),
2488
- /* @__PURE__ */ jsx(Typography, { as: "p", textColor: "neutral600", children: formatMessage({
3658
+ /* @__PURE__ */ jsx(Typography, { tag: "p", textColor: "neutral600", children: formatMessage({
2489
3659
  id: getTranslation(`containers.list.autoCloneModal.error.${reason}`),
2490
3660
  defaultMessage: getDefaultErrorMessage(reason)
2491
3661
  }) })
@@ -2510,7 +3680,7 @@ const TableActions = ({ document }) => {
2510
3680
  DescriptionComponentRenderer,
2511
3681
  {
2512
3682
  props,
2513
- descriptions: plugins["content-manager"].apis.getDocumentActions(),
3683
+ descriptions: plugins["content-manager"].apis.getDocumentActions().filter((action) => action.name !== "PublishAction"),
2514
3684
  children: (actions2) => {
2515
3685
  const tableRowActions = actions2.filter((action) => {
2516
3686
  const positions = Array.isArray(action.position) ? action.position : [action.position];
@@ -2621,7 +3791,7 @@ const CloneAction = ({ model, documentId }) => {
2621
3791
  }),
2622
3792
  content: /* @__PURE__ */ jsx(AutoCloneFailureModalBody, { prohibitedFields }),
2623
3793
  footer: ({ onClose }) => {
2624
- return /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", children: [
3794
+ return /* @__PURE__ */ jsxs(Modal.Footer, { children: [
2625
3795
  /* @__PURE__ */ jsx(Button, { onClick: onClose, variant: "tertiary", children: formatMessage({
2626
3796
  id: "cancel",
2627
3797
  defaultMessage: "Cancel"
@@ -2629,7 +3799,7 @@ const CloneAction = ({ model, documentId }) => {
2629
3799
  /* @__PURE__ */ jsx(
2630
3800
  LinkButton,
2631
3801
  {
2632
- as: NavLink,
3802
+ tag: NavLink,
2633
3803
  to: {
2634
3804
  pathname: `clone/${documentId}`
2635
3805
  },
@@ -2662,8 +3832,7 @@ class ContentManagerPlugin {
2662
3832
  documentActions = [
2663
3833
  ...DEFAULT_ACTIONS,
2664
3834
  ...DEFAULT_TABLE_ROW_ACTIONS,
2665
- ...DEFAULT_HEADER_ACTIONS,
2666
- HistoryAction
3835
+ ...DEFAULT_HEADER_ACTIONS
2667
3836
  ];
2668
3837
  editViewSidePanels = [ActionsPanel];
2669
3838
  headerActions = [];
@@ -2752,16 +3921,72 @@ const getPrintableType = (value) => {
2752
3921
  }
2753
3922
  return nativeType;
2754
3923
  };
2755
- const initialState = {
2756
- collectionTypeLinks: [],
2757
- components: [],
2758
- fieldSizes: {},
2759
- models: [],
2760
- singleTypeLinks: [],
2761
- isLoading: true
2762
- };
2763
- const appSlice = createSlice({
2764
- name: "app",
3924
+ const HistoryAction = ({ model, document }) => {
3925
+ const { formatMessage } = useIntl();
3926
+ const [{ query }] = useQueryParams();
3927
+ const navigate = useNavigate();
3928
+ const { trackUsage } = useTracking();
3929
+ const { pathname } = useLocation();
3930
+ const pluginsQueryParams = stringify({ plugins: query.plugins }, { encode: false });
3931
+ if (!window.strapi.features.isEnabled("cms-content-history")) {
3932
+ return null;
3933
+ }
3934
+ const handleOnClick = () => {
3935
+ const destination = { pathname: "history", search: pluginsQueryParams };
3936
+ trackUsage("willNavigate", {
3937
+ from: pathname,
3938
+ to: `${pathname}/${destination.pathname}`
3939
+ });
3940
+ navigate(destination);
3941
+ };
3942
+ return {
3943
+ icon: /* @__PURE__ */ jsx(ClockCounterClockwise, {}),
3944
+ label: formatMessage({
3945
+ id: "content-manager.history.document-action",
3946
+ defaultMessage: "Content History"
3947
+ }),
3948
+ onClick: handleOnClick,
3949
+ disabled: (
3950
+ /**
3951
+ * The user is creating a new document.
3952
+ * It hasn't been saved yet, so there's no history to go to
3953
+ */
3954
+ !document || /**
3955
+ * The document has been created but the current dimension has never been saved.
3956
+ * For example, the user is creating a new locale in an existing document,
3957
+ * so there's no history for the document in that locale
3958
+ */
3959
+ !document.id || /**
3960
+ * History is only available for content types created by the user.
3961
+ * These have the `api::` prefix, as opposed to the ones created by Strapi or plugins,
3962
+ * which start with `admin::` or `plugin::`
3963
+ */
3964
+ !model.startsWith("api::")
3965
+ ),
3966
+ position: "header"
3967
+ };
3968
+ };
3969
+ HistoryAction.type = "history";
3970
+ const historyAdmin = {
3971
+ bootstrap(app) {
3972
+ const { addDocumentAction } = app.getPlugin("content-manager").apis;
3973
+ addDocumentAction((actions2) => {
3974
+ const indexOfDeleteAction = actions2.findIndex((action) => action.type === "delete");
3975
+ actions2.splice(indexOfDeleteAction, 0, HistoryAction);
3976
+ return actions2;
3977
+ });
3978
+ }
3979
+ };
3980
+ const initialState = {
3981
+ collectionTypeLinks: [],
3982
+ components: [],
3983
+ fieldSizes: {},
3984
+ models: [],
3985
+ singleTypeLinks: [],
3986
+ isLoading: true
3987
+ };
3988
+ const appSlice = createSlice({
3989
+ name: "app",
2765
3990
  initialState,
2766
3991
  reducers: {
2767
3992
  setInitialData(state, action) {
@@ -2788,316 +4013,78 @@ const { setInitialData } = actions;
2788
4013
  const reducer = combineReducers({
2789
4014
  app: reducer$1
2790
4015
  });
2791
- const HOOKS = {
2792
- /**
2793
- * Hook that allows to mutate the displayed headers of the list view table
2794
- * @constant
2795
- * @type {string}
2796
- */
2797
- INJECT_COLUMN_IN_TABLE: "Admin/CM/pages/ListView/inject-column-in-table",
2798
- /**
2799
- * Hook that allows to mutate the CM's collection types links pre-set filters
2800
- * @constant
2801
- * @type {string}
2802
- */
2803
- MUTATE_COLLECTION_TYPES_LINKS: "Admin/CM/pages/App/mutate-collection-types-links",
2804
- /**
2805
- * Hook that allows to mutate the CM's edit view layout
2806
- * @constant
2807
- * @type {string}
2808
- */
2809
- MUTATE_EDIT_VIEW_LAYOUT: "Admin/CM/pages/EditView/mutate-edit-view-layout",
2810
- /**
2811
- * Hook that allows to mutate the CM's single types links pre-set filters
2812
- * @constant
2813
- * @type {string}
2814
- */
2815
- MUTATE_SINGLE_TYPES_LINKS: "Admin/CM/pages/App/mutate-single-types-links"
2816
- };
2817
- const contentTypesApi = contentManagerApi.injectEndpoints({
4016
+ const previewApi = contentManagerApi.injectEndpoints({
2818
4017
  endpoints: (builder) => ({
2819
- getContentTypeConfiguration: builder.query({
2820
- query: (uid) => ({
2821
- url: `/content-manager/content-types/${uid}/configuration`,
2822
- method: "GET"
2823
- }),
2824
- transformResponse: (response) => response.data,
2825
- providesTags: (_result, _error, uid) => [
2826
- { type: "ContentTypesConfiguration", id: uid },
2827
- { type: "ContentTypeSettings", id: "LIST" }
2828
- ]
2829
- }),
2830
- getAllContentTypeSettings: builder.query({
2831
- query: () => "/content-manager/content-types-settings",
2832
- transformResponse: (response) => response.data,
2833
- providesTags: [{ type: "ContentTypeSettings", id: "LIST" }]
2834
- }),
2835
- updateContentTypeConfiguration: builder.mutation({
2836
- query: ({ uid, ...body }) => ({
2837
- url: `/content-manager/content-types/${uid}/configuration`,
2838
- method: "PUT",
2839
- data: body
2840
- }),
2841
- transformResponse: (response) => response.data,
2842
- invalidatesTags: (_result, _error, { uid }) => [
2843
- { type: "ContentTypesConfiguration", id: uid },
2844
- { type: "ContentTypeSettings", id: "LIST" },
2845
- // Is this necessary?
2846
- { type: "InitialData" }
2847
- ]
4018
+ getPreviewUrl: builder.query({
4019
+ query({ query, params }) {
4020
+ return {
4021
+ url: `/content-manager/preview/url/${params.contentType}`,
4022
+ method: "GET",
4023
+ config: {
4024
+ params: query
4025
+ }
4026
+ };
4027
+ }
2848
4028
  })
2849
4029
  })
2850
4030
  });
2851
- const {
2852
- useGetContentTypeConfigurationQuery,
2853
- useGetAllContentTypeSettingsQuery,
2854
- useUpdateContentTypeConfigurationMutation
2855
- } = contentTypesApi;
2856
- const checkIfAttributeIsDisplayable = (attribute) => {
2857
- const { type } = attribute;
2858
- if (type === "relation") {
2859
- return !attribute.relation.toLowerCase().includes("morph");
2860
- }
2861
- return !["json", "dynamiczone", "richtext", "password", "blocks"].includes(type) && !!type;
2862
- };
2863
- const getMainField = (attribute, mainFieldName, { schemas, components }) => {
2864
- if (!mainFieldName) {
2865
- return void 0;
2866
- }
2867
- const mainFieldType = attribute.type === "component" ? components[attribute.component].attributes[mainFieldName].type : (
2868
- // @ts-expect-error – `targetModel` does exist on the attribute for a relation.
2869
- schemas.find((schema) => schema.uid === attribute.targetModel)?.attributes[mainFieldName].type
2870
- );
2871
- return {
2872
- name: mainFieldName,
2873
- type: mainFieldType ?? "string"
2874
- };
2875
- };
2876
- const DEFAULT_SETTINGS = {
2877
- bulkable: false,
2878
- filterable: false,
2879
- searchable: false,
2880
- pagination: false,
2881
- defaultSortBy: "",
2882
- defaultSortOrder: "asc",
2883
- mainField: "id",
2884
- pageSize: 10
2885
- };
2886
- const useDocumentLayout = (model) => {
2887
- const { schema, components } = useDocument({ model, collectionType: "" }, { skip: true });
4031
+ const { useGetPreviewUrlQuery } = previewApi;
4032
+ const PreviewSidePanel = ({ model, documentId, document }) => {
4033
+ const { formatMessage } = useIntl();
4034
+ const { trackUsage } = useTracking();
4035
+ const { pathname } = useLocation();
2888
4036
  const [{ query }] = useQueryParams();
2889
- const runHookWaterfall = useStrapiApp("useDocumentLayout", (state) => state.runHookWaterfall);
2890
- const { toggleNotification } = useNotification();
2891
- const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
2892
- const { isLoading: isLoadingSchemas, schemas } = useContentTypeSchema();
2893
- const {
2894
- data,
2895
- isLoading: isLoadingConfigs,
2896
- error,
2897
- isFetching: isFetchingConfigs
2898
- } = useGetContentTypeConfigurationQuery(model);
2899
- const isLoading = isLoadingSchemas || isFetchingConfigs || isLoadingConfigs;
2900
- React.useEffect(() => {
2901
- if (error) {
2902
- toggleNotification({
2903
- type: "danger",
2904
- message: formatAPIError(error)
2905
- });
2906
- }
2907
- }, [error, formatAPIError, toggleNotification]);
2908
- const editLayout = React.useMemo(
2909
- () => data && !isLoading ? formatEditLayout(data, { schemas, schema, components }) : {
2910
- layout: [],
2911
- components: {},
2912
- metadatas: {},
2913
- options: {},
2914
- settings: DEFAULT_SETTINGS
2915
- },
2916
- [data, isLoading, schemas, schema, components]
2917
- );
2918
- const listLayout = React.useMemo(() => {
2919
- return data && !isLoading ? formatListLayout(data, { schemas, schema, components }) : {
2920
- layout: [],
2921
- metadatas: {},
2922
- options: {},
2923
- settings: DEFAULT_SETTINGS
2924
- };
2925
- }, [data, isLoading, schemas, schema, components]);
2926
- const { layout: edit } = React.useMemo(
2927
- () => runHookWaterfall(HOOKS.MUTATE_EDIT_VIEW_LAYOUT, {
2928
- layout: editLayout,
2929
- query
2930
- }),
2931
- [editLayout, query, runHookWaterfall]
2932
- );
2933
- return {
2934
- error,
2935
- isLoading,
2936
- edit,
2937
- list: listLayout
2938
- };
2939
- };
2940
- const useDocLayout = () => {
2941
- const { model } = useDoc();
2942
- return useDocumentLayout(model);
2943
- };
2944
- const formatEditLayout = (data, {
2945
- schemas,
2946
- schema,
2947
- components
2948
- }) => {
2949
- let currentPanelIndex = 0;
2950
- const panelledEditAttributes = convertEditLayoutToFieldLayouts(
2951
- data.contentType.layouts.edit,
2952
- schema?.attributes,
2953
- data.contentType.metadatas,
2954
- { configurations: data.components, schemas: components },
2955
- schemas
2956
- ).reduce((panels, row) => {
2957
- if (row.some((field) => field.type === "dynamiczone")) {
2958
- panels.push([row]);
2959
- currentPanelIndex += 2;
2960
- } else {
2961
- if (!panels[currentPanelIndex]) {
2962
- panels.push([]);
2963
- }
2964
- panels[currentPanelIndex].push(row);
2965
- }
2966
- return panels;
2967
- }, []);
2968
- const componentEditAttributes = Object.entries(data.components).reduce(
2969
- (acc, [uid, configuration]) => {
2970
- acc[uid] = {
2971
- layout: convertEditLayoutToFieldLayouts(
2972
- configuration.layouts.edit,
2973
- components[uid].attributes,
2974
- configuration.metadatas
2975
- ),
2976
- settings: {
2977
- ...configuration.settings,
2978
- icon: components[uid].info.icon,
2979
- displayName: components[uid].info.displayName
2980
- }
2981
- };
2982
- return acc;
4037
+ const { data, error } = useGetPreviewUrlQuery({
4038
+ params: {
4039
+ contentType: model
2983
4040
  },
2984
- {}
2985
- );
2986
- const editMetadatas = Object.entries(data.contentType.metadatas).reduce(
2987
- (acc, [attribute, metadata]) => {
2988
- return {
2989
- ...acc,
2990
- [attribute]: metadata.edit
2991
- };
2992
- },
2993
- {}
2994
- );
2995
- return {
2996
- layout: panelledEditAttributes,
2997
- components: componentEditAttributes,
2998
- metadatas: editMetadatas,
2999
- settings: {
3000
- ...data.contentType.settings,
3001
- displayName: schema?.info.displayName
3002
- },
3003
- options: {
3004
- ...schema?.options,
3005
- ...schema?.pluginOptions,
3006
- ...data.contentType.options
4041
+ query: {
4042
+ documentId,
4043
+ locale: document?.locale,
4044
+ status: document?.status
3007
4045
  }
4046
+ });
4047
+ if (!data?.data?.url || error) {
4048
+ return null;
4049
+ }
4050
+ const trackNavigation = () => {
4051
+ const destinationPathname = pathname.replace(/\/$/, "") + "/preview";
4052
+ trackUsage("willNavigate", { from: pathname, to: destinationPathname });
3008
4053
  };
3009
- };
3010
- const convertEditLayoutToFieldLayouts = (rows, attributes = {}, metadatas, components, schemas = []) => {
3011
- return rows.map(
3012
- (row) => row.map((field) => {
3013
- const attribute = attributes[field.name];
3014
- if (!attribute) {
3015
- return null;
3016
- }
3017
- const { edit: metadata } = metadatas[field.name];
3018
- const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
3019
- return {
3020
- attribute,
3021
- disabled: !metadata.editable,
3022
- hint: metadata.description,
3023
- label: metadata.label ?? "",
3024
- name: field.name,
3025
- // @ts-expect-error – mainField does exist on the metadata for a relation.
3026
- mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
3027
- schemas,
3028
- components: components?.schemas ?? {}
3029
- }),
3030
- placeholder: metadata.placeholder ?? "",
3031
- required: attribute.required ?? false,
3032
- size: field.size,
3033
- unique: "unique" in attribute ? attribute.unique : false,
3034
- visible: metadata.visible ?? true,
3035
- type: attribute.type
3036
- };
3037
- }).filter((field) => field !== null)
3038
- );
3039
- };
3040
- const formatListLayout = (data, {
3041
- schemas,
3042
- schema,
3043
- components
3044
- }) => {
3045
- const listMetadatas = Object.entries(data.contentType.metadatas).reduce(
3046
- (acc, [attribute, metadata]) => {
3047
- return {
3048
- ...acc,
3049
- [attribute]: metadata.list
3050
- };
3051
- },
3052
- {}
3053
- );
3054
- const listAttributes = convertListLayoutToFieldLayouts(
3055
- data.contentType.layouts.list,
3056
- schema?.attributes,
3057
- listMetadatas,
3058
- { configurations: data.components, schemas: components },
3059
- schemas
3060
- );
3061
4054
  return {
3062
- layout: listAttributes,
3063
- settings: { ...data.contentType.settings, displayName: schema?.info.displayName },
3064
- metadatas: listMetadatas,
3065
- options: {
3066
- ...schema?.options,
3067
- ...schema?.pluginOptions,
3068
- ...data.contentType.options
3069
- }
4055
+ title: formatMessage({ id: "content-manager.preview.panel.title", defaultMessage: "Preview" }),
4056
+ content: /* @__PURE__ */ jsx(Flex, { gap: 2, width: "100%", children: /* @__PURE__ */ jsx(
4057
+ Button,
4058
+ {
4059
+ variant: "tertiary",
4060
+ tag: Link,
4061
+ to: { pathname: "preview", search: stringify(query, { encode: false }) },
4062
+ onClick: trackNavigation,
4063
+ flex: "auto",
4064
+ children: formatMessage({
4065
+ id: "content-manager.preview.panel.button",
4066
+ defaultMessage: "Open preview"
4067
+ })
4068
+ }
4069
+ ) })
3070
4070
  };
3071
4071
  };
3072
- const convertListLayoutToFieldLayouts = (columns, attributes = {}, metadatas, components, schemas = []) => {
3073
- return columns.map((name) => {
3074
- const attribute = attributes[name];
3075
- if (!attribute) {
3076
- return null;
4072
+ const FEATURE_ID = "preview";
4073
+ const previewAdmin = {
4074
+ bootstrap(app) {
4075
+ if (!window.strapi.future.isEnabled(FEATURE_ID)) {
4076
+ return;
3077
4077
  }
3078
- const metadata = metadatas[name];
3079
- const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
3080
- return {
3081
- attribute,
3082
- label: metadata.label ?? "",
3083
- mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
3084
- schemas,
3085
- components: components?.schemas ?? {}
3086
- }),
3087
- name,
3088
- searchable: metadata.searchable ?? true,
3089
- sortable: metadata.sortable ?? true
3090
- };
3091
- }).filter((field) => field !== null);
4078
+ const contentManagerPluginApis = app.getPlugin("content-manager").apis;
4079
+ contentManagerPluginApis.addEditViewSidePanel([PreviewSidePanel]);
4080
+ }
3092
4081
  };
3093
4082
  const index = {
3094
4083
  register(app) {
3095
4084
  const cm = new ContentManagerPlugin();
3096
4085
  app.addReducers({
3097
- [contentManagerApi.reducerPath]: contentManagerApi.reducer,
3098
4086
  [PLUGIN_ID]: reducer
3099
4087
  });
3100
- app.addMiddlewares([() => contentManagerApi.middleware]);
3101
4088
  app.addMenuLink({
3102
4089
  to: PLUGIN_ID,
3103
4090
  icon: Feather,
@@ -3106,14 +4093,32 @@ const index = {
3106
4093
  defaultMessage: "Content Manager"
3107
4094
  },
3108
4095
  permissions: [],
3109
- Component: () => import("./layout-Dnh0PNp9.mjs").then((mod) => ({ default: mod.Layout }))
4096
+ position: 1
4097
+ });
4098
+ app.router.addRoute({
4099
+ path: "content-manager/*",
4100
+ lazy: async () => {
4101
+ const { Layout } = await import("./layout-BJ8CpEJu.mjs");
4102
+ return {
4103
+ Component: Layout
4104
+ };
4105
+ },
4106
+ children: routes
3110
4107
  });
3111
4108
  app.registerPlugin(cm.config);
3112
4109
  },
4110
+ bootstrap(app) {
4111
+ if (typeof historyAdmin.bootstrap === "function") {
4112
+ historyAdmin.bootstrap(app);
4113
+ }
4114
+ if (typeof previewAdmin.bootstrap === "function") {
4115
+ previewAdmin.bootstrap(app);
4116
+ }
4117
+ },
3113
4118
  async registerTrads({ locales }) {
3114
4119
  const importedTrads = await Promise.all(
3115
4120
  locales.map((locale) => {
3116
- 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-MBPul9Su.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 }) => {
4121
+ 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-CfIXaZf9.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 }) => {
3117
4122
  return {
3118
4123
  data: prefixPluginTranslations(data, PLUGIN_ID),
3119
4124
  locale
@@ -3131,45 +4136,48 @@ const index = {
3131
4136
  };
3132
4137
  export {
3133
4138
  ATTRIBUTE_TYPES_THAT_CANNOT_BE_MAIN_FIELD as A,
3134
- extractContentTypeComponents as B,
4139
+ BulkActionsRenderer as B,
3135
4140
  COLLECTION_TYPES as C,
3136
4141
  DocumentStatus as D,
3137
- DEFAULT_SETTINGS as E,
3138
- convertEditLayoutToFieldLayouts as F,
3139
- useDocument as G,
4142
+ extractContentTypeComponents as E,
4143
+ DEFAULT_SETTINGS as F,
4144
+ convertEditLayoutToFieldLayouts as G,
3140
4145
  HOOKS as H,
3141
4146
  InjectionZone as I,
3142
- index as J,
3143
- useDocumentActions as K,
4147
+ useDocument as J,
4148
+ useGetPreviewUrlQuery as K,
4149
+ index as L,
4150
+ useContentManagerContext as M,
4151
+ useDocumentActions as N,
3144
4152
  Panels as P,
3145
4153
  RelativeTime as R,
3146
4154
  SINGLE_TYPES as S,
3147
4155
  TableActions as T,
3148
- useGetAllContentTypeSettingsQuery as a,
3149
- useDoc as b,
3150
- buildValidParams as c,
3151
- contentManagerApi as d,
3152
- useDocumentRBAC as e,
3153
- useDocumentLayout as f,
4156
+ useGetInitialDataQuery as a,
4157
+ useGetAllContentTypeSettingsQuery as b,
4158
+ useDoc as c,
4159
+ buildValidParams as d,
4160
+ contentManagerApi as e,
4161
+ useDocumentRBAC as f,
3154
4162
  getTranslation as g,
3155
- createYupSchema as h,
3156
- Header as i,
3157
- PERMISSIONS as j,
3158
- DocumentRBAC as k,
3159
- DOCUMENT_META_FIELDS as l,
3160
- useDocLayout as m,
3161
- useContentTypeSchema as n,
3162
- useGetContentTypeConfigurationQuery as o,
3163
- CREATOR_FIELDS as p,
3164
- getMainField as q,
3165
- routes as r,
4163
+ useDocumentLayout as h,
4164
+ createYupSchema as i,
4165
+ Header as j,
4166
+ PERMISSIONS as k,
4167
+ DocumentRBAC as l,
4168
+ DOCUMENT_META_FIELDS as m,
4169
+ CLONE_PATH as n,
4170
+ useDocLayout as o,
4171
+ useGetContentTypeConfigurationQuery as p,
4172
+ CREATOR_FIELDS as q,
4173
+ getMainField as r,
3166
4174
  setInitialData as s,
3167
4175
  getDisplayName as t,
3168
- useGetInitialDataQuery as u,
4176
+ useContentTypeSchema as u,
3169
4177
  checkIfAttributeIsDisplayable as v,
3170
4178
  useGetAllDocumentsQuery as w,
3171
4179
  convertListLayoutToFieldLayouts as x,
3172
4180
  capitalise as y,
3173
4181
  useUpdateContentTypeConfigurationMutation as z
3174
4182
  };
3175
- //# sourceMappingURL=index-DNVx8ssZ.mjs.map
4183
+ //# sourceMappingURL=index-BdUq-Dtg.mjs.map