@strapi/content-manager 0.0.0-experimental.779667bd163026468f566293decf331a0246fff9 → 0.0.0-experimental.78b47df46708173ab4833373f694257729db4b9e

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 (125) hide show
  1. package/dist/_chunks/{ComponentConfigurationPage-B1bIXVuX.mjs → ComponentConfigurationPage-7-qB29e7.mjs} +3 -3
  2. package/dist/_chunks/{ComponentConfigurationPage-B1bIXVuX.mjs.map → ComponentConfigurationPage-7-qB29e7.mjs.map} +1 -1
  3. package/dist/_chunks/{ComponentConfigurationPage-Bqgx7Mes.js → ComponentConfigurationPage-DP7AC0UU.js} +3 -3
  4. package/dist/_chunks/{ComponentConfigurationPage-Bqgx7Mes.js.map → ComponentConfigurationPage-DP7AC0UU.js.map} +1 -1
  5. package/dist/_chunks/{EditConfigurationPage-ZO0vOO8q.mjs → EditConfigurationPage-CI4XoymK.mjs} +3 -3
  6. package/dist/_chunks/{EditConfigurationPage-ZO0vOO8q.mjs.map → EditConfigurationPage-CI4XoymK.mjs.map} +1 -1
  7. package/dist/_chunks/{EditConfigurationPage-BFEwvdMW.js → EditConfigurationPage-DITVliEI.js} +3 -3
  8. package/dist/_chunks/{EditConfigurationPage-BFEwvdMW.js.map → EditConfigurationPage-DITVliEI.js.map} +1 -1
  9. package/dist/_chunks/{EditViewPage-DA95Ha6J.js → EditViewPage-CUS2EAhB.js} +24 -9
  10. package/dist/_chunks/EditViewPage-CUS2EAhB.js.map +1 -0
  11. package/dist/_chunks/{EditViewPage-DlLEyUL6.mjs → EditViewPage-Dzpno8xI.mjs} +24 -9
  12. package/dist/_chunks/EditViewPage-Dzpno8xI.mjs.map +1 -0
  13. package/dist/_chunks/{Field-Dq7bDnuh.mjs → Field-B_jG_EV9.mjs} +139 -99
  14. package/dist/_chunks/Field-B_jG_EV9.mjs.map +1 -0
  15. package/dist/_chunks/{Field-CnK8dO8N.js → Field-CtUU1Fg8.js} +141 -101
  16. package/dist/_chunks/Field-CtUU1Fg8.js.map +1 -0
  17. package/dist/_chunks/{Form-B_JE0dbz.mjs → Form-BXHao2mZ.mjs} +35 -16
  18. package/dist/_chunks/Form-BXHao2mZ.mjs.map +1 -0
  19. package/dist/_chunks/{Form-BpiR4piS.js → Form-DTqO0ymI.js} +35 -16
  20. package/dist/_chunks/Form-DTqO0ymI.js.map +1 -0
  21. package/dist/_chunks/{History-CBNGU7a-.mjs → History-2Ah2CQ4T.mjs} +21 -11
  22. package/dist/_chunks/History-2Ah2CQ4T.mjs.map +1 -0
  23. package/dist/_chunks/{History-DdIstl8b.js → History-C_uSGzO5.js} +21 -11
  24. package/dist/_chunks/History-C_uSGzO5.js.map +1 -0
  25. package/dist/_chunks/{ListConfigurationPage-5dr4qpue.mjs → ListConfigurationPage-BjSJlaoC.mjs} +14 -4
  26. package/dist/_chunks/ListConfigurationPage-BjSJlaoC.mjs.map +1 -0
  27. package/dist/_chunks/{ListConfigurationPage-DkKRparB.js → ListConfigurationPage-nyuP7OSy.js} +14 -4
  28. package/dist/_chunks/ListConfigurationPage-nyuP7OSy.js.map +1 -0
  29. package/dist/_chunks/{ListViewPage-DecLrYV6.mjs → ListViewPage-B75x3nz2.mjs} +47 -38
  30. package/dist/_chunks/ListViewPage-B75x3nz2.mjs.map +1 -0
  31. package/dist/_chunks/{ListViewPage-wE0lXqoD.js → ListViewPage-DHgHD8Xg.js} +49 -40
  32. package/dist/_chunks/ListViewPage-DHgHD8Xg.js.map +1 -0
  33. package/dist/_chunks/{NoContentTypePage-DEKR6tf9.js → NoContentTypePage-CDUKdZ7d.js} +2 -2
  34. package/dist/_chunks/{NoContentTypePage-DEKR6tf9.js.map → NoContentTypePage-CDUKdZ7d.js.map} +1 -1
  35. package/dist/_chunks/{NoContentTypePage-CiIcfYsd.mjs → NoContentTypePage-DUacQSyF.mjs} +2 -2
  36. package/dist/_chunks/{NoContentTypePage-CiIcfYsd.mjs.map → NoContentTypePage-DUacQSyF.mjs.map} +1 -1
  37. package/dist/_chunks/{NoPermissionsPage-CM5UD8ee.mjs → NoPermissionsPage-SFllMekk.mjs} +2 -2
  38. package/dist/_chunks/{NoPermissionsPage-CM5UD8ee.mjs.map → NoPermissionsPage-SFllMekk.mjs.map} +1 -1
  39. package/dist/_chunks/{NoPermissionsPage-DmNfF2Bb.js → NoPermissionsPage-zwIZydDI.js} +2 -2
  40. package/dist/_chunks/{NoPermissionsPage-DmNfF2Bb.js.map → NoPermissionsPage-zwIZydDI.js.map} +1 -1
  41. package/dist/_chunks/{Relations-Dqz0C1fz.mjs → Relations-D2NRW8fC.mjs} +14 -10
  42. package/dist/_chunks/Relations-D2NRW8fC.mjs.map +1 -0
  43. package/dist/_chunks/{Relations-L0xYRoSQ.js → Relations-NFLaRNPr.js} +14 -10
  44. package/dist/_chunks/Relations-NFLaRNPr.js.map +1 -0
  45. package/dist/_chunks/{en-uOUIxfcQ.js → en-BlhnxQfj.js} +7 -6
  46. package/dist/_chunks/{en-uOUIxfcQ.js.map → en-BlhnxQfj.js.map} +1 -1
  47. package/dist/_chunks/{en-BrCTWlZv.mjs → en-C8YBvRrK.mjs} +7 -6
  48. package/dist/_chunks/{en-BrCTWlZv.mjs.map → en-C8YBvRrK.mjs.map} +1 -1
  49. package/dist/_chunks/{index-BSn97i8U.mjs → index-C9HxCo5R.mjs} +1947 -1777
  50. package/dist/_chunks/index-C9HxCo5R.mjs.map +1 -0
  51. package/dist/_chunks/{index-DyvUPg1a.js → index-ovJRE1FM.js} +1927 -1757
  52. package/dist/_chunks/index-ovJRE1FM.js.map +1 -0
  53. package/dist/_chunks/{layout-DPaHUusj.mjs → layout-DaUjDiWQ.mjs} +22 -9
  54. package/dist/_chunks/layout-DaUjDiWQ.mjs.map +1 -0
  55. package/dist/_chunks/{layout-TPqF2oJ5.js → layout-UNWstw_s.js} +21 -8
  56. package/dist/_chunks/layout-UNWstw_s.js.map +1 -0
  57. package/dist/_chunks/{relations-Ck7-ecDT.mjs → relations-D8iFAeRu.mjs} +2 -2
  58. package/dist/_chunks/{relations-Ck7-ecDT.mjs.map → relations-D8iFAeRu.mjs.map} +1 -1
  59. package/dist/_chunks/{relations-BWYS9gkn.js → relations-NN3coOG5.js} +2 -2
  60. package/dist/_chunks/{relations-BWYS9gkn.js.map → relations-NN3coOG5.js.map} +1 -1
  61. package/dist/_chunks/{usePrev-B9w_-eYc.js → useDebounce-CtcjDB3L.js} +14 -1
  62. package/dist/_chunks/useDebounce-CtcjDB3L.js.map +1 -0
  63. package/dist/_chunks/useDebounce-DmuSJIF3.mjs +29 -0
  64. package/dist/_chunks/useDebounce-DmuSJIF3.mjs.map +1 -0
  65. package/dist/admin/index.js +2 -1
  66. package/dist/admin/index.js.map +1 -1
  67. package/dist/admin/index.mjs +5 -4
  68. package/dist/admin/src/exports.d.ts +1 -1
  69. package/dist/admin/src/history/services/historyVersion.d.ts +1 -1
  70. package/dist/admin/src/hooks/useDocument.d.ts +32 -1
  71. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/EditorLayout.d.ts +2 -2
  72. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/WysiwygFooter.d.ts +2 -2
  73. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/WysiwygStyles.d.ts +4 -48
  74. package/dist/admin/src/pages/EditView/components/Header.d.ts +11 -11
  75. package/dist/admin/src/services/api.d.ts +1 -1
  76. package/dist/admin/src/services/components.d.ts +2 -2
  77. package/dist/admin/src/services/contentTypes.d.ts +3 -3
  78. package/dist/admin/src/services/documents.d.ts +19 -17
  79. package/dist/admin/src/services/init.d.ts +1 -1
  80. package/dist/admin/src/services/relations.d.ts +2 -2
  81. package/dist/admin/src/services/uid.d.ts +3 -3
  82. package/dist/admin/src/utils/validation.d.ts +4 -1
  83. package/dist/server/index.js +184 -108
  84. package/dist/server/index.js.map +1 -1
  85. package/dist/server/index.mjs +185 -109
  86. package/dist/server/index.mjs.map +1 -1
  87. package/dist/server/src/controllers/collection-types.d.ts.map +1 -1
  88. package/dist/server/src/controllers/relations.d.ts.map +1 -1
  89. package/dist/server/src/controllers/uid.d.ts.map +1 -1
  90. package/dist/server/src/controllers/validation/dimensions.d.ts +4 -2
  91. package/dist/server/src/controllers/validation/dimensions.d.ts.map +1 -1
  92. package/dist/server/src/history/services/history.d.ts.map +1 -1
  93. package/dist/server/src/history/services/lifecycles.d.ts.map +1 -1
  94. package/dist/server/src/history/services/utils.d.ts +2 -1
  95. package/dist/server/src/history/services/utils.d.ts.map +1 -1
  96. package/dist/server/src/policies/hasPermissions.d.ts.map +1 -1
  97. package/dist/server/src/services/document-manager.d.ts.map +1 -1
  98. package/dist/server/src/services/document-metadata.d.ts.map +1 -1
  99. package/dist/server/src/services/permission-checker.d.ts.map +1 -1
  100. package/dist/server/src/services/utils/populate.d.ts.map +1 -1
  101. package/dist/shared/contracts/collection-types.d.ts +3 -1
  102. package/dist/shared/contracts/collection-types.d.ts.map +1 -1
  103. package/package.json +11 -11
  104. package/dist/_chunks/EditViewPage-DA95Ha6J.js.map +0 -1
  105. package/dist/_chunks/EditViewPage-DlLEyUL6.mjs.map +0 -1
  106. package/dist/_chunks/Field-CnK8dO8N.js.map +0 -1
  107. package/dist/_chunks/Field-Dq7bDnuh.mjs.map +0 -1
  108. package/dist/_chunks/Form-B_JE0dbz.mjs.map +0 -1
  109. package/dist/_chunks/Form-BpiR4piS.js.map +0 -1
  110. package/dist/_chunks/History-CBNGU7a-.mjs.map +0 -1
  111. package/dist/_chunks/History-DdIstl8b.js.map +0 -1
  112. package/dist/_chunks/ListConfigurationPage-5dr4qpue.mjs.map +0 -1
  113. package/dist/_chunks/ListConfigurationPage-DkKRparB.js.map +0 -1
  114. package/dist/_chunks/ListViewPage-DecLrYV6.mjs.map +0 -1
  115. package/dist/_chunks/ListViewPage-wE0lXqoD.js.map +0 -1
  116. package/dist/_chunks/Relations-Dqz0C1fz.mjs.map +0 -1
  117. package/dist/_chunks/Relations-L0xYRoSQ.js.map +0 -1
  118. package/dist/_chunks/index-BSn97i8U.mjs.map +0 -1
  119. package/dist/_chunks/index-DyvUPg1a.js.map +0 -1
  120. package/dist/_chunks/layout-DPaHUusj.mjs.map +0 -1
  121. package/dist/_chunks/layout-TPqF2oJ5.js.map +0 -1
  122. package/dist/_chunks/usePrev-B9w_-eYc.js.map +0 -1
  123. package/dist/_chunks/usePrev-DH6iah0A.mjs +0 -16
  124. package/dist/_chunks/usePrev-DH6iah0A.mjs.map +0 -1
  125. package/strapi-server.js +0 -3
@@ -1,16 +1,16 @@
1
- import { CrossCircle, More, WarningCircle, ListPlus, Pencil, Trash, Check, CheckCircle, ArrowsCounterClockwise, ChevronRight, Duplicate, ClockCounterClockwise, Feather } from "@strapi/icons";
1
+ import { More, Cross, WarningCircle, ListPlus, Pencil, Trash, Check, CrossCircle, CheckCircle, ArrowsCounterClockwise, ChevronRight, Duplicate, ClockCounterClockwise, Feather } from "@strapi/icons";
2
2
  import { jsx, Fragment, jsxs } from "react/jsx-runtime";
3
- import { useStrapiApp, createContext, useAuth, useRBAC, Page, adminApi, translatedErrors, useNotification, useAPIErrorHandler, getYupValidationErrors, useQueryParams, useTracking, useForm, BackButton, DescriptionComponentRenderer, useTable, Table } from "@strapi/admin/strapi-admin";
3
+ import { useStrapiApp, createContext, useAuth, useRBAC, Page, adminApi, translatedErrors, useNotification, useAPIErrorHandler, useQueryParams, getYupValidationErrors, useForm, useTracking, useGuidedTour, BackButton, DescriptionComponentRenderer, useTable, Table } from "@strapi/admin/strapi-admin";
4
4
  import * as React from "react";
5
5
  import { lazy } from "react";
6
- import { Button, Menu, VisuallyHidden, Flex, Box, Typography, Dialog, Modal, Radio, Status, SingleSelect, SingleSelectOption, Loader, IconButton, Tooltip, LinkButton } from "@strapi/design-system";
6
+ import { Button, Menu, VisuallyHidden, Flex, Typography, Dialog, Modal, Radio, Status, Box, SingleSelect, SingleSelectOption, IconButton, Loader, Tooltip, LinkButton } from "@strapi/design-system";
7
7
  import { useIntl } from "react-intl";
8
- import { useParams, Navigate, useNavigate, useMatch, useLocation, Link, NavLink } from "react-router-dom";
9
- import { styled } from "styled-components";
8
+ import { useParams, useNavigate, Navigate, useMatch, useLocation, Link, NavLink } from "react-router-dom";
10
9
  import * as yup from "yup";
11
10
  import { ValidationError } from "yup";
12
11
  import pipe from "lodash/fp/pipe";
13
12
  import { intervalToDuration, isPast } from "date-fns";
13
+ import { styled } from "styled-components";
14
14
  import { stringify } from "qs";
15
15
  import { createSlice, combineReducers } from "@reduxjs/toolkit";
16
16
  const __variableDynamicImportRuntimeHelper = (glob, path) => {
@@ -158,7 +158,8 @@ const contentManagerApi = adminApi.enhanceEndpoints({
158
158
  "Document",
159
159
  "InitialData",
160
160
  "HistoryVersion",
161
- "Relations"
161
+ "Relations",
162
+ "UidAvailability"
162
163
  ]
163
164
  });
164
165
  const documentApi = contentManagerApi.injectEndpoints({
@@ -172,7 +173,12 @@ const documentApi = contentManagerApi.injectEndpoints({
172
173
  params: query
173
174
  }
174
175
  }),
175
- invalidatesTags: (_result, _error, { model }) => [{ type: "Document", id: `${model}_LIST` }]
176
+ invalidatesTags: (_result, error, { model }) => {
177
+ if (error) {
178
+ return [];
179
+ }
180
+ return [{ type: "Document", id: `${model}_LIST` }];
181
+ }
176
182
  }),
177
183
  cloneDocument: builder.mutation({
178
184
  query: ({ model, sourceId, data, params }) => ({
@@ -183,7 +189,10 @@ const documentApi = contentManagerApi.injectEndpoints({
183
189
  params
184
190
  }
185
191
  }),
186
- invalidatesTags: (_result, _error, { model }) => [{ type: "Document", id: `${model}_LIST` }]
192
+ invalidatesTags: (_result, _error, { model }) => [
193
+ { type: "Document", id: `${model}_LIST` },
194
+ { type: "UidAvailability", id: model }
195
+ ]
187
196
  }),
188
197
  /**
189
198
  * Creates a new collection-type document. This should ONLY be used for collection-types.
@@ -200,7 +209,8 @@ const documentApi = contentManagerApi.injectEndpoints({
200
209
  }),
201
210
  invalidatesTags: (result, _error, { model }) => [
202
211
  { type: "Document", id: `${model}_LIST` },
203
- "Relations"
212
+ "Relations",
213
+ { type: "UidAvailability", id: model }
204
214
  ]
205
215
  }),
206
216
  deleteDocument: builder.mutation({
@@ -241,7 +251,8 @@ const documentApi = contentManagerApi.injectEndpoints({
241
251
  id: collectionType !== SINGLE_TYPES ? `${model}_${documentId}` : model
242
252
  },
243
253
  { type: "Document", id: `${model}_LIST` },
244
- "Relations"
254
+ "Relations",
255
+ { type: "UidAvailability", id: model }
245
256
  ];
246
257
  }
247
258
  }),
@@ -259,6 +270,7 @@ const documentApi = contentManagerApi.injectEndpoints({
259
270
  }),
260
271
  providesTags: (result, _error, arg) => {
261
272
  return [
273
+ { type: "Document", id: `ALL_LIST` },
262
274
  { type: "Document", id: `${arg.model}_LIST` },
263
275
  ...result?.results.map(({ documentId }) => ({
264
276
  type: "Document",
@@ -297,6 +309,11 @@ const documentApi = contentManagerApi.injectEndpoints({
297
309
  {
298
310
  type: "Document",
299
311
  id: collectionType !== SINGLE_TYPES ? `${model}_${result && "documentId" in result ? result.documentId : documentId}` : model
312
+ },
313
+ // Make it easy to invalidate all individual documents queries for a model
314
+ {
315
+ type: "Document",
316
+ id: `${model}_ALL_ITEMS`
300
317
  }
301
318
  ];
302
319
  }
@@ -360,8 +377,21 @@ const documentApi = contentManagerApi.injectEndpoints({
360
377
  type: "Document",
361
378
  id: collectionType !== SINGLE_TYPES ? `${model}_${documentId}` : model
362
379
  },
363
- "Relations"
380
+ "Relations",
381
+ { type: "UidAvailability", id: model }
364
382
  ];
383
+ },
384
+ async onQueryStarted({ data, ...patch }, { dispatch, queryFulfilled }) {
385
+ const patchResult = dispatch(
386
+ documentApi.util.updateQueryData("getDocument", patch, (draft) => {
387
+ Object.assign(draft.data, data);
388
+ })
389
+ );
390
+ try {
391
+ await queryFulfilled;
392
+ } catch {
393
+ patchResult.undo();
394
+ }
365
395
  }
366
396
  }),
367
397
  unpublishDocument: builder.mutation({
@@ -431,7 +461,7 @@ const buildValidParams = (query) => {
431
461
  const isBaseQueryError = (error) => {
432
462
  return error.name !== void 0;
433
463
  };
434
- const createYupSchema = (attributes = {}, components = {}) => {
464
+ const createYupSchema = (attributes = {}, components = {}, options = { status: null }) => {
435
465
  const createModelSchema = (attributes2) => yup.object().shape(
436
466
  Object.entries(attributes2).reduce((acc, [name, attribute]) => {
437
467
  if (DOCUMENT_META_FIELDS.includes(name)) {
@@ -444,7 +474,7 @@ const createYupSchema = (attributes = {}, components = {}) => {
444
474
  addMinValidation,
445
475
  addMaxValidation,
446
476
  addRegexValidation
447
- ].map((fn) => fn(attribute));
477
+ ].map((fn) => fn(attribute, options));
448
478
  const transformSchema = pipe(...validations);
449
479
  switch (attribute.type) {
450
480
  case "component": {
@@ -493,7 +523,7 @@ const createYupSchema = (attributes = {}, components = {}) => {
493
523
  } else if (Array.isArray(value)) {
494
524
  return yup.array().of(
495
525
  yup.object().shape({
496
- id: yup.string().required()
526
+ id: yup.number().required()
497
527
  })
498
528
  );
499
529
  } else if (typeof value === "object") {
@@ -545,6 +575,14 @@ const createAttributeSchema = (attribute) => {
545
575
  if (!value || typeof value === "string" && value.length === 0) {
546
576
  return true;
547
577
  }
578
+ if (typeof value === "object") {
579
+ try {
580
+ JSON.stringify(value);
581
+ return true;
582
+ } catch (err) {
583
+ return false;
584
+ }
585
+ }
548
586
  try {
549
587
  JSON.parse(value);
550
588
  return true;
@@ -563,13 +601,7 @@ const createAttributeSchema = (attribute) => {
563
601
  return yup.mixed();
564
602
  }
565
603
  };
566
- const addRequiredValidation = (attribute) => (schema) => {
567
- if ((attribute.type === "component" && attribute.repeatable || attribute.type === "dynamiczone") && attribute.required && "min" in schema) {
568
- return schema.min(1, translatedErrors.required);
569
- }
570
- if (attribute.required && attribute.type !== "relation") {
571
- return schema.required(translatedErrors.required);
572
- }
604
+ const nullableSchema = (schema) => {
573
605
  return schema?.nullable ? schema.nullable() : (
574
606
  // In some cases '.nullable' will not be available on the schema.
575
607
  // e.g. when the schema has been built using yup.lazy (e.g. for relations).
@@ -577,7 +609,22 @@ const addRequiredValidation = (attribute) => (schema) => {
577
609
  schema
578
610
  );
579
611
  };
580
- const addMinLengthValidation = (attribute) => (schema) => {
612
+ const addRequiredValidation = (attribute, options) => (schema) => {
613
+ if (options.status === "draft") {
614
+ return nullableSchema(schema);
615
+ }
616
+ if ((attribute.type === "component" && attribute.repeatable || attribute.type === "dynamiczone") && attribute.required && "min" in schema) {
617
+ return schema.min(1, translatedErrors.required);
618
+ }
619
+ if (attribute.required && attribute.type !== "relation") {
620
+ return schema.required(translatedErrors.required);
621
+ }
622
+ return nullableSchema(schema);
623
+ };
624
+ const addMinLengthValidation = (attribute, options) => (schema) => {
625
+ if (options.status === "draft") {
626
+ return schema;
627
+ }
581
628
  if ("minLength" in attribute && attribute.minLength && Number.isInteger(attribute.minLength) && "min" in schema) {
582
629
  return schema.min(attribute.minLength, {
583
630
  ...translatedErrors.minLength,
@@ -599,11 +646,11 @@ const addMaxLengthValidation = (attribute) => (schema) => {
599
646
  }
600
647
  return schema;
601
648
  };
602
- const addMinValidation = (attribute) => (schema) => {
649
+ const addMinValidation = (attribute, options) => (schema) => {
603
650
  if ("min" in attribute) {
604
651
  const min = toInteger(attribute.min);
605
652
  if (attribute.type === "component" && attribute.repeatable || attribute.type === "dynamiczone") {
606
- if (!attribute.required && "test" in schema && min) {
653
+ if (options.status !== "draft" && !attribute.required && "test" in schema && min) {
607
654
  return schema.test(
608
655
  "custom-min",
609
656
  {
@@ -742,19 +789,115 @@ const extractContentTypeComponents = (attributes = {}, allComponents = {}) => {
742
789
  }, {});
743
790
  return componentsByKey;
744
791
  };
745
- const useDocument = (args, opts) => {
792
+ const HOOKS = {
793
+ /**
794
+ * Hook that allows to mutate the displayed headers of the list view table
795
+ * @constant
796
+ * @type {string}
797
+ */
798
+ INJECT_COLUMN_IN_TABLE: "Admin/CM/pages/ListView/inject-column-in-table",
799
+ /**
800
+ * Hook that allows to mutate the CM's collection types links pre-set filters
801
+ * @constant
802
+ * @type {string}
803
+ */
804
+ MUTATE_COLLECTION_TYPES_LINKS: "Admin/CM/pages/App/mutate-collection-types-links",
805
+ /**
806
+ * Hook that allows to mutate the CM's edit view layout
807
+ * @constant
808
+ * @type {string}
809
+ */
810
+ MUTATE_EDIT_VIEW_LAYOUT: "Admin/CM/pages/EditView/mutate-edit-view-layout",
811
+ /**
812
+ * Hook that allows to mutate the CM's single types links pre-set filters
813
+ * @constant
814
+ * @type {string}
815
+ */
816
+ MUTATE_SINGLE_TYPES_LINKS: "Admin/CM/pages/App/mutate-single-types-links"
817
+ };
818
+ const contentTypesApi = contentManagerApi.injectEndpoints({
819
+ endpoints: (builder) => ({
820
+ getContentTypeConfiguration: builder.query({
821
+ query: (uid) => ({
822
+ url: `/content-manager/content-types/${uid}/configuration`,
823
+ method: "GET"
824
+ }),
825
+ transformResponse: (response) => response.data,
826
+ providesTags: (_result, _error, uid) => [
827
+ { type: "ContentTypesConfiguration", id: uid },
828
+ { type: "ContentTypeSettings", id: "LIST" }
829
+ ]
830
+ }),
831
+ getAllContentTypeSettings: builder.query({
832
+ query: () => "/content-manager/content-types-settings",
833
+ transformResponse: (response) => response.data,
834
+ providesTags: [{ type: "ContentTypeSettings", id: "LIST" }]
835
+ }),
836
+ updateContentTypeConfiguration: builder.mutation({
837
+ query: ({ uid, ...body }) => ({
838
+ url: `/content-manager/content-types/${uid}/configuration`,
839
+ method: "PUT",
840
+ data: body
841
+ }),
842
+ transformResponse: (response) => response.data,
843
+ invalidatesTags: (_result, _error, { uid }) => [
844
+ { type: "ContentTypesConfiguration", id: uid },
845
+ { type: "ContentTypeSettings", id: "LIST" },
846
+ // Is this necessary?
847
+ { type: "InitialData" }
848
+ ]
849
+ })
850
+ })
851
+ });
852
+ const {
853
+ useGetContentTypeConfigurationQuery,
854
+ useGetAllContentTypeSettingsQuery,
855
+ useUpdateContentTypeConfigurationMutation
856
+ } = contentTypesApi;
857
+ const checkIfAttributeIsDisplayable = (attribute) => {
858
+ const { type } = attribute;
859
+ if (type === "relation") {
860
+ return !attribute.relation.toLowerCase().includes("morph");
861
+ }
862
+ return !["json", "dynamiczone", "richtext", "password", "blocks"].includes(type) && !!type;
863
+ };
864
+ const getMainField = (attribute, mainFieldName, { schemas, components }) => {
865
+ if (!mainFieldName) {
866
+ return void 0;
867
+ }
868
+ const mainFieldType = attribute.type === "component" ? components[attribute.component].attributes[mainFieldName].type : (
869
+ // @ts-expect-error – `targetModel` does exist on the attribute for a relation.
870
+ schemas.find((schema) => schema.uid === attribute.targetModel)?.attributes[mainFieldName].type
871
+ );
872
+ return {
873
+ name: mainFieldName,
874
+ type: mainFieldType ?? "string"
875
+ };
876
+ };
877
+ const DEFAULT_SETTINGS = {
878
+ bulkable: false,
879
+ filterable: false,
880
+ searchable: false,
881
+ pagination: false,
882
+ defaultSortBy: "",
883
+ defaultSortOrder: "asc",
884
+ mainField: "id",
885
+ pageSize: 10
886
+ };
887
+ const useDocumentLayout = (model) => {
888
+ const { schema, components } = useDocument({ model, collectionType: "" }, { skip: true });
889
+ const [{ query }] = useQueryParams();
890
+ const runHookWaterfall = useStrapiApp("useDocumentLayout", (state) => state.runHookWaterfall);
746
891
  const { toggleNotification } = useNotification();
747
892
  const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
893
+ const { isLoading: isLoadingSchemas, schemas } = useContentTypeSchema();
748
894
  const {
749
- currentData: data,
750
- isLoading: isLoadingDocument,
751
- isFetching: isFetchingDocument,
752
- error
753
- } = useGetDocumentQuery(args, {
754
- ...opts,
755
- skip: !args.documentId && args.collectionType !== SINGLE_TYPES || opts?.skip
756
- });
757
- const { components, schema, isLoading: isLoadingSchema } = useContentTypeSchema(args.model);
895
+ data,
896
+ isLoading: isLoadingConfigs,
897
+ error,
898
+ isFetching: isFetchingConfigs
899
+ } = useGetContentTypeConfigurationQuery(model);
900
+ const isLoading = isLoadingSchemas || isFetchingConfigs || isLoadingConfigs;
758
901
  React.useEffect(() => {
759
902
  if (error) {
760
903
  toggleNotification({
@@ -762,382 +905,638 @@ const useDocument = (args, opts) => {
762
905
  message: formatAPIError(error)
763
906
  });
764
907
  }
765
- }, [toggleNotification, error, formatAPIError, args.collectionType]);
766
- const validationSchema = React.useMemo(() => {
767
- if (!schema) {
768
- return null;
769
- }
770
- return createYupSchema(schema.attributes, components);
771
- }, [schema, components]);
772
- const validate = React.useCallback(
773
- (document) => {
774
- if (!validationSchema) {
775
- throw new Error(
776
- "There is no validation schema generated, this is likely due to the schema not being loaded yet."
777
- );
778
- }
779
- try {
780
- validationSchema.validateSync(document, { abortEarly: false, strict: true });
781
- return null;
782
- } catch (error2) {
783
- if (error2 instanceof ValidationError) {
784
- return getYupValidationErrors(error2);
785
- }
786
- throw error2;
787
- }
908
+ }, [error, formatAPIError, toggleNotification]);
909
+ const editLayout = React.useMemo(
910
+ () => data && !isLoading ? formatEditLayout(data, { schemas, schema, components }) : {
911
+ layout: [],
912
+ components: {},
913
+ metadatas: {},
914
+ options: {},
915
+ settings: DEFAULT_SETTINGS
788
916
  },
789
- [validationSchema]
917
+ [data, isLoading, schemas, schema, components]
918
+ );
919
+ const listLayout = React.useMemo(() => {
920
+ return data && !isLoading ? formatListLayout(data, { schemas, schema, components }) : {
921
+ layout: [],
922
+ metadatas: {},
923
+ options: {},
924
+ settings: DEFAULT_SETTINGS
925
+ };
926
+ }, [data, isLoading, schemas, schema, components]);
927
+ const { layout: edit } = React.useMemo(
928
+ () => runHookWaterfall(HOOKS.MUTATE_EDIT_VIEW_LAYOUT, {
929
+ layout: editLayout,
930
+ query
931
+ }),
932
+ [editLayout, query, runHookWaterfall]
790
933
  );
791
- const isLoading = isLoadingDocument || isFetchingDocument || isLoadingSchema;
792
934
  return {
793
- components,
794
- document: data?.data,
795
- meta: data?.meta,
935
+ error,
796
936
  isLoading,
797
- schema,
798
- validate
799
- };
800
- };
801
- const useDoc = () => {
802
- const { id, slug, collectionType, origin } = useParams();
803
- const [{ query }] = useQueryParams();
804
- const params = React.useMemo(() => buildValidParams(query), [query]);
805
- if (!collectionType) {
806
- throw new Error("Could not find collectionType in url params");
807
- }
808
- if (!slug) {
809
- throw new Error("Could not find model in url params");
810
- }
811
- return {
812
- collectionType,
813
- model: slug,
814
- id: origin || id === "create" ? void 0 : id,
815
- ...useDocument(
816
- { documentId: origin || id, model: slug, collectionType, params },
817
- {
818
- skip: id === "create" || !origin && !id && collectionType !== SINGLE_TYPES
819
- }
820
- )
937
+ edit,
938
+ list: listLayout
821
939
  };
822
940
  };
823
- const prefixPluginTranslations = (trad, pluginId) => {
824
- if (!pluginId) {
825
- throw new TypeError("pluginId can't be empty");
826
- }
827
- return Object.keys(trad).reduce((acc, current) => {
828
- acc[`${pluginId}.${current}`] = trad[current];
829
- return acc;
830
- }, {});
831
- };
832
- const getTranslation = (id) => `content-manager.${id}`;
833
- const DEFAULT_UNEXPECTED_ERROR_MSG = {
834
- id: "notification.error",
835
- defaultMessage: "An error occurred, please try again"
941
+ const useDocLayout = () => {
942
+ const { model } = useDoc();
943
+ return useDocumentLayout(model);
836
944
  };
837
- const useDocumentActions = () => {
838
- const { toggleNotification } = useNotification();
839
- const { formatMessage } = useIntl();
840
- const { trackUsage } = useTracking();
841
- const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
842
- const [deleteDocument] = useDeleteDocumentMutation();
843
- const _delete = React.useCallback(
844
- async ({ collectionType, model, documentId, params }, trackerProperty) => {
845
- try {
846
- trackUsage("willDeleteEntry", trackerProperty);
847
- const res = await deleteDocument({
848
- collectionType,
849
- model,
850
- documentId,
851
- params
852
- });
853
- if ("error" in res) {
854
- toggleNotification({
855
- type: "danger",
856
- message: formatAPIError(res.error)
857
- });
858
- return { error: res.error };
859
- }
860
- toggleNotification({
861
- type: "success",
862
- message: formatMessage({
863
- id: getTranslation("success.record.delete"),
864
- defaultMessage: "Deleted document"
865
- })
866
- });
867
- trackUsage("didDeleteEntry", trackerProperty);
868
- return res.data;
869
- } catch (err) {
870
- toggleNotification({
871
- type: "danger",
872
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
873
- });
874
- trackUsage("didNotDeleteEntry", { error: err, ...trackerProperty });
875
- throw err;
945
+ const formatEditLayout = (data, {
946
+ schemas,
947
+ schema,
948
+ components
949
+ }) => {
950
+ let currentPanelIndex = 0;
951
+ const panelledEditAttributes = convertEditLayoutToFieldLayouts(
952
+ data.contentType.layouts.edit,
953
+ schema?.attributes,
954
+ data.contentType.metadatas,
955
+ { configurations: data.components, schemas: components },
956
+ schemas
957
+ ).reduce((panels, row) => {
958
+ if (row.some((field) => field.type === "dynamiczone")) {
959
+ panels.push([row]);
960
+ currentPanelIndex += 2;
961
+ } else {
962
+ if (!panels[currentPanelIndex]) {
963
+ panels.push([]);
876
964
  }
877
- },
878
- [trackUsage, deleteDocument, toggleNotification, formatMessage, formatAPIError]
879
- );
880
- const [deleteManyDocuments] = useDeleteManyDocumentsMutation();
881
- const deleteMany = React.useCallback(
882
- async ({ model, documentIds, params }) => {
883
- try {
884
- trackUsage("willBulkDeleteEntries");
885
- const res = await deleteManyDocuments({
886
- model,
887
- documentIds,
888
- params
889
- });
890
- if ("error" in res) {
891
- toggleNotification({
892
- type: "danger",
893
- message: formatAPIError(res.error)
894
- });
895
- return { error: res.error };
965
+ panels[currentPanelIndex].push(row);
966
+ }
967
+ return panels;
968
+ }, []);
969
+ const componentEditAttributes = Object.entries(data.components).reduce(
970
+ (acc, [uid, configuration]) => {
971
+ acc[uid] = {
972
+ layout: convertEditLayoutToFieldLayouts(
973
+ configuration.layouts.edit,
974
+ components[uid].attributes,
975
+ configuration.metadatas,
976
+ { configurations: data.components, schemas: components }
977
+ ),
978
+ settings: {
979
+ ...configuration.settings,
980
+ icon: components[uid].info.icon,
981
+ displayName: components[uid].info.displayName
896
982
  }
897
- toggleNotification({
898
- type: "success",
899
- title: formatMessage({
900
- id: getTranslation("success.records.delete"),
901
- defaultMessage: "Successfully deleted."
902
- }),
903
- message: ""
904
- });
905
- trackUsage("didBulkDeleteEntries");
906
- return res.data;
907
- } catch (err) {
908
- toggleNotification({
909
- type: "danger",
910
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
911
- });
912
- trackUsage("didNotBulkDeleteEntries");
913
- throw err;
914
- }
983
+ };
984
+ return acc;
915
985
  },
916
- [trackUsage, deleteManyDocuments, toggleNotification, formatMessage, formatAPIError]
986
+ {}
917
987
  );
918
- const [discardDocument] = useDiscardDocumentMutation();
919
- const discard = React.useCallback(
920
- async ({ collectionType, model, documentId, params }) => {
921
- try {
922
- const res = await discardDocument({
923
- collectionType,
924
- model,
925
- documentId,
926
- params
927
- });
928
- if ("error" in res) {
929
- toggleNotification({
930
- type: "danger",
931
- message: formatAPIError(res.error)
932
- });
933
- return { error: res.error };
934
- }
935
- toggleNotification({
936
- type: "success",
937
- message: formatMessage({
938
- id: "content-manager.success.record.discard",
939
- defaultMessage: "Changes discarded"
940
- })
941
- });
942
- return res.data;
943
- } catch (err) {
944
- toggleNotification({
945
- type: "danger",
946
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
947
- });
948
- throw err;
949
- }
988
+ const editMetadatas = Object.entries(data.contentType.metadatas).reduce(
989
+ (acc, [attribute, metadata]) => {
990
+ return {
991
+ ...acc,
992
+ [attribute]: metadata.edit
993
+ };
950
994
  },
951
- [discardDocument, formatAPIError, formatMessage, toggleNotification]
995
+ {}
952
996
  );
953
- const [publishDocument] = usePublishDocumentMutation();
954
- const publish = React.useCallback(
955
- async ({ collectionType, model, documentId, params }, data) => {
956
- try {
957
- trackUsage("willPublishEntry");
958
- const res = await publishDocument({
959
- collectionType,
960
- model,
961
- documentId,
962
- data,
963
- params
964
- });
965
- if ("error" in res) {
966
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
967
- return { error: res.error };
968
- }
969
- trackUsage("didPublishEntry");
970
- toggleNotification({
971
- type: "success",
972
- message: formatMessage({
973
- id: getTranslation("success.record.publish"),
974
- defaultMessage: "Published document"
975
- })
976
- });
977
- return res.data;
978
- } catch (err) {
979
- toggleNotification({
980
- type: "danger",
981
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
982
- });
983
- throw err;
984
- }
997
+ return {
998
+ layout: panelledEditAttributes,
999
+ components: componentEditAttributes,
1000
+ metadatas: editMetadatas,
1001
+ settings: {
1002
+ ...data.contentType.settings,
1003
+ displayName: schema?.info.displayName
985
1004
  },
986
- [trackUsage, publishDocument, toggleNotification, formatMessage, formatAPIError]
987
- );
988
- const [publishManyDocuments] = usePublishManyDocumentsMutation();
989
- const publishMany = React.useCallback(
990
- async ({ model, documentIds, params }) => {
991
- try {
992
- const res = await publishManyDocuments({
993
- model,
994
- documentIds,
995
- params
996
- });
997
- if ("error" in res) {
998
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
999
- return { error: res.error };
1000
- }
1001
- toggleNotification({
1002
- type: "success",
1003
- message: formatMessage({
1004
- id: getTranslation("success.record.publish"),
1005
- defaultMessage: "Published document"
1006
- })
1007
- });
1008
- return res.data;
1009
- } catch (err) {
1010
- toggleNotification({
1011
- type: "danger",
1012
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1013
- });
1014
- throw err;
1005
+ options: {
1006
+ ...schema?.options,
1007
+ ...schema?.pluginOptions,
1008
+ ...data.contentType.options
1009
+ }
1010
+ };
1011
+ };
1012
+ const convertEditLayoutToFieldLayouts = (rows, attributes = {}, metadatas, components, schemas = []) => {
1013
+ return rows.map(
1014
+ (row) => row.map((field) => {
1015
+ const attribute = attributes[field.name];
1016
+ if (!attribute) {
1017
+ return null;
1015
1018
  }
1016
- },
1017
- [
1018
- // trackUsage,
1019
- publishManyDocuments,
1020
- toggleNotification,
1021
- formatMessage,
1022
- formatAPIError
1023
- ]
1019
+ const { edit: metadata } = metadatas[field.name];
1020
+ const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
1021
+ return {
1022
+ attribute,
1023
+ disabled: !metadata.editable,
1024
+ hint: metadata.description,
1025
+ label: metadata.label ?? "",
1026
+ name: field.name,
1027
+ // @ts-expect-error – mainField does exist on the metadata for a relation.
1028
+ mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
1029
+ schemas,
1030
+ components: components?.schemas ?? {}
1031
+ }),
1032
+ placeholder: metadata.placeholder ?? "",
1033
+ required: attribute.required ?? false,
1034
+ size: field.size,
1035
+ unique: "unique" in attribute ? attribute.unique : false,
1036
+ visible: metadata.visible ?? true,
1037
+ type: attribute.type
1038
+ };
1039
+ }).filter((field) => field !== null)
1024
1040
  );
1025
- const [updateDocument] = useUpdateDocumentMutation();
1026
- const update = React.useCallback(
1027
- async ({ collectionType, model, documentId, params }, data, trackerProperty) => {
1028
- try {
1029
- trackUsage("willEditEntry", trackerProperty);
1030
- const res = await updateDocument({
1031
- collectionType,
1032
- model,
1033
- documentId,
1034
- data,
1035
- params
1036
- });
1037
- if ("error" in res) {
1038
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1039
- trackUsage("didNotEditEntry", { error: res.error, ...trackerProperty });
1040
- return { error: res.error };
1041
+ };
1042
+ const formatListLayout = (data, {
1043
+ schemas,
1044
+ schema,
1045
+ components
1046
+ }) => {
1047
+ const listMetadatas = Object.entries(data.contentType.metadatas).reduce(
1048
+ (acc, [attribute, metadata]) => {
1049
+ return {
1050
+ ...acc,
1051
+ [attribute]: metadata.list
1052
+ };
1053
+ },
1054
+ {}
1055
+ );
1056
+ const listAttributes = convertListLayoutToFieldLayouts(
1057
+ data.contentType.layouts.list,
1058
+ schema?.attributes,
1059
+ listMetadatas,
1060
+ { configurations: data.components, schemas: components },
1061
+ schemas
1062
+ );
1063
+ return {
1064
+ layout: listAttributes,
1065
+ settings: { ...data.contentType.settings, displayName: schema?.info.displayName },
1066
+ metadatas: listMetadatas,
1067
+ options: {
1068
+ ...schema?.options,
1069
+ ...schema?.pluginOptions,
1070
+ ...data.contentType.options
1071
+ }
1072
+ };
1073
+ };
1074
+ const convertListLayoutToFieldLayouts = (columns, attributes = {}, metadatas, components, schemas = []) => {
1075
+ return columns.map((name) => {
1076
+ const attribute = attributes[name];
1077
+ if (!attribute) {
1078
+ return null;
1079
+ }
1080
+ const metadata = metadatas[name];
1081
+ const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
1082
+ return {
1083
+ attribute,
1084
+ label: metadata.label ?? "",
1085
+ mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
1086
+ schemas,
1087
+ components: components?.schemas ?? {}
1088
+ }),
1089
+ name,
1090
+ searchable: metadata.searchable ?? true,
1091
+ sortable: metadata.sortable ?? true
1092
+ };
1093
+ }).filter((field) => field !== null);
1094
+ };
1095
+ const useDocument = (args, opts) => {
1096
+ const { toggleNotification } = useNotification();
1097
+ const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
1098
+ const {
1099
+ currentData: data,
1100
+ isLoading: isLoadingDocument,
1101
+ isFetching: isFetchingDocument,
1102
+ error
1103
+ } = useGetDocumentQuery(args, {
1104
+ ...opts,
1105
+ skip: !args.documentId && args.collectionType !== SINGLE_TYPES || opts?.skip
1106
+ });
1107
+ const {
1108
+ components,
1109
+ schema,
1110
+ schemas,
1111
+ isLoading: isLoadingSchema
1112
+ } = useContentTypeSchema(args.model);
1113
+ React.useEffect(() => {
1114
+ if (error) {
1115
+ toggleNotification({
1116
+ type: "danger",
1117
+ message: formatAPIError(error)
1118
+ });
1119
+ }
1120
+ }, [toggleNotification, error, formatAPIError, args.collectionType]);
1121
+ const validationSchema = React.useMemo(() => {
1122
+ if (!schema) {
1123
+ return null;
1124
+ }
1125
+ return createYupSchema(schema.attributes, components);
1126
+ }, [schema, components]);
1127
+ const validate = React.useCallback(
1128
+ (document) => {
1129
+ if (!validationSchema) {
1130
+ throw new Error(
1131
+ "There is no validation schema generated, this is likely due to the schema not being loaded yet."
1132
+ );
1133
+ }
1134
+ try {
1135
+ validationSchema.validateSync(document, { abortEarly: false, strict: true });
1136
+ return null;
1137
+ } catch (error2) {
1138
+ if (error2 instanceof ValidationError) {
1139
+ return getYupValidationErrors(error2);
1041
1140
  }
1042
- trackUsage("didEditEntry", trackerProperty);
1043
- toggleNotification({
1044
- type: "success",
1045
- message: formatMessage({
1046
- id: getTranslation("success.record.save"),
1047
- defaultMessage: "Saved document"
1048
- })
1049
- });
1050
- return res.data;
1051
- } catch (err) {
1052
- trackUsage("didNotEditEntry", { error: err, ...trackerProperty });
1053
- toggleNotification({
1054
- type: "danger",
1055
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1056
- });
1057
- throw err;
1141
+ throw error2;
1058
1142
  }
1059
1143
  },
1060
- [trackUsage, updateDocument, toggleNotification, formatMessage, formatAPIError]
1144
+ [validationSchema]
1061
1145
  );
1062
- const [unpublishDocument] = useUnpublishDocumentMutation();
1063
- const unpublish = React.useCallback(
1064
- async ({ collectionType, model, documentId, params }, discardDraft = false) => {
1146
+ const isLoading = isLoadingDocument || isFetchingDocument || isLoadingSchema;
1147
+ const hasError = !!error;
1148
+ return {
1149
+ components,
1150
+ document: data?.data,
1151
+ meta: data?.meta,
1152
+ isLoading,
1153
+ hasError,
1154
+ schema,
1155
+ schemas,
1156
+ validate
1157
+ };
1158
+ };
1159
+ const useDoc = () => {
1160
+ const { id, slug, collectionType, origin } = useParams();
1161
+ const [{ query }] = useQueryParams();
1162
+ const params = React.useMemo(() => buildValidParams(query), [query]);
1163
+ if (!collectionType) {
1164
+ throw new Error("Could not find collectionType in url params");
1165
+ }
1166
+ if (!slug) {
1167
+ throw new Error("Could not find model in url params");
1168
+ }
1169
+ return {
1170
+ collectionType,
1171
+ model: slug,
1172
+ id: origin || id === "create" ? void 0 : id,
1173
+ ...useDocument(
1174
+ { documentId: origin || id, model: slug, collectionType, params },
1175
+ {
1176
+ skip: id === "create" || !origin && !id && collectionType !== SINGLE_TYPES
1177
+ }
1178
+ )
1179
+ };
1180
+ };
1181
+ const useContentManagerContext = () => {
1182
+ const {
1183
+ collectionType,
1184
+ model,
1185
+ id,
1186
+ components,
1187
+ isLoading: isLoadingDoc,
1188
+ schema,
1189
+ schemas
1190
+ } = useDoc();
1191
+ const layout = useDocumentLayout(model);
1192
+ const form = useForm("useContentManagerContext", (state) => state);
1193
+ const isSingleType = collectionType === SINGLE_TYPES;
1194
+ const slug = model;
1195
+ const isCreatingEntry = id === "create";
1196
+ useContentTypeSchema();
1197
+ const isLoading = isLoadingDoc || layout.isLoading;
1198
+ const error = layout.error;
1199
+ return {
1200
+ error,
1201
+ isLoading,
1202
+ // Base metadata
1203
+ model,
1204
+ collectionType,
1205
+ id,
1206
+ slug,
1207
+ isCreatingEntry,
1208
+ isSingleType,
1209
+ hasDraftAndPublish: schema?.options?.draftAndPublish ?? false,
1210
+ // All schema infos
1211
+ components,
1212
+ contentType: schema,
1213
+ contentTypes: schemas,
1214
+ // Form state
1215
+ form,
1216
+ // layout infos
1217
+ layout
1218
+ };
1219
+ };
1220
+ const prefixPluginTranslations = (trad, pluginId) => {
1221
+ if (!pluginId) {
1222
+ throw new TypeError("pluginId can't be empty");
1223
+ }
1224
+ return Object.keys(trad).reduce((acc, current) => {
1225
+ acc[`${pluginId}.${current}`] = trad[current];
1226
+ return acc;
1227
+ }, {});
1228
+ };
1229
+ const getTranslation = (id) => `content-manager.${id}`;
1230
+ const DEFAULT_UNEXPECTED_ERROR_MSG = {
1231
+ id: "notification.error",
1232
+ defaultMessage: "An error occurred, please try again"
1233
+ };
1234
+ const useDocumentActions = () => {
1235
+ const { toggleNotification } = useNotification();
1236
+ const { formatMessage } = useIntl();
1237
+ const { trackUsage } = useTracking();
1238
+ const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
1239
+ const navigate = useNavigate();
1240
+ const setCurrentStep = useGuidedTour("useDocumentActions", (state) => state.setCurrentStep);
1241
+ const [deleteDocument] = useDeleteDocumentMutation();
1242
+ const _delete = React.useCallback(
1243
+ async ({ collectionType, model, documentId, params }, trackerProperty) => {
1065
1244
  try {
1066
- trackUsage("willUnpublishEntry");
1067
- const res = await unpublishDocument({
1245
+ trackUsage("willDeleteEntry", trackerProperty);
1246
+ const res = await deleteDocument({
1068
1247
  collectionType,
1069
1248
  model,
1070
1249
  documentId,
1071
- params,
1072
- data: {
1073
- discardDraft
1074
- }
1250
+ params
1075
1251
  });
1076
1252
  if ("error" in res) {
1077
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1253
+ toggleNotification({
1254
+ type: "danger",
1255
+ message: formatAPIError(res.error)
1256
+ });
1078
1257
  return { error: res.error };
1079
1258
  }
1080
- trackUsage("didUnpublishEntry");
1081
1259
  toggleNotification({
1082
1260
  type: "success",
1083
1261
  message: formatMessage({
1084
- id: getTranslation("success.record.unpublish"),
1085
- defaultMessage: "Unpublished document"
1262
+ id: getTranslation("success.record.delete"),
1263
+ defaultMessage: "Deleted document"
1086
1264
  })
1087
1265
  });
1266
+ trackUsage("didDeleteEntry", trackerProperty);
1088
1267
  return res.data;
1089
1268
  } catch (err) {
1090
1269
  toggleNotification({
1091
1270
  type: "danger",
1092
1271
  message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1093
1272
  });
1273
+ trackUsage("didNotDeleteEntry", { error: err, ...trackerProperty });
1094
1274
  throw err;
1095
1275
  }
1096
1276
  },
1097
- [trackUsage, unpublishDocument, toggleNotification, formatMessage, formatAPIError]
1277
+ [trackUsage, deleteDocument, toggleNotification, formatMessage, formatAPIError]
1098
1278
  );
1099
- const [unpublishManyDocuments] = useUnpublishManyDocumentsMutation();
1100
- const unpublishMany = React.useCallback(
1279
+ const [deleteManyDocuments] = useDeleteManyDocumentsMutation();
1280
+ const deleteMany = React.useCallback(
1101
1281
  async ({ model, documentIds, params }) => {
1102
1282
  try {
1103
- trackUsage("willBulkUnpublishEntries");
1104
- const res = await unpublishManyDocuments({
1283
+ trackUsage("willBulkDeleteEntries");
1284
+ const res = await deleteManyDocuments({
1105
1285
  model,
1106
1286
  documentIds,
1107
1287
  params
1108
1288
  });
1109
1289
  if ("error" in res) {
1110
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1290
+ toggleNotification({
1291
+ type: "danger",
1292
+ message: formatAPIError(res.error)
1293
+ });
1111
1294
  return { error: res.error };
1112
1295
  }
1113
- trackUsage("didBulkUnpublishEntries");
1114
1296
  toggleNotification({
1115
1297
  type: "success",
1116
1298
  title: formatMessage({
1117
- id: getTranslation("success.records.unpublish"),
1118
- defaultMessage: "Successfully unpublished."
1299
+ id: getTranslation("success.records.delete"),
1300
+ defaultMessage: "Successfully deleted."
1119
1301
  }),
1120
1302
  message: ""
1121
1303
  });
1304
+ trackUsage("didBulkDeleteEntries");
1122
1305
  return res.data;
1123
1306
  } catch (err) {
1124
1307
  toggleNotification({
1125
1308
  type: "danger",
1126
1309
  message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1127
1310
  });
1128
- trackUsage("didNotBulkUnpublishEntries");
1311
+ trackUsage("didNotBulkDeleteEntries");
1129
1312
  throw err;
1130
1313
  }
1131
1314
  },
1132
- [trackUsage, unpublishManyDocuments, toggleNotification, formatMessage, formatAPIError]
1315
+ [trackUsage, deleteManyDocuments, toggleNotification, formatMessage, formatAPIError]
1133
1316
  );
1134
- const [createDocument] = useCreateDocumentMutation();
1135
- const create = React.useCallback(
1136
- async ({ model, params }, data, trackerProperty) => {
1317
+ const [discardDocument] = useDiscardDocumentMutation();
1318
+ const discard = React.useCallback(
1319
+ async ({ collectionType, model, documentId, params }) => {
1137
1320
  try {
1138
- const res = await createDocument({
1321
+ const res = await discardDocument({
1322
+ collectionType,
1139
1323
  model,
1140
- data,
1324
+ documentId,
1325
+ params
1326
+ });
1327
+ if ("error" in res) {
1328
+ toggleNotification({
1329
+ type: "danger",
1330
+ message: formatAPIError(res.error)
1331
+ });
1332
+ return { error: res.error };
1333
+ }
1334
+ toggleNotification({
1335
+ type: "success",
1336
+ message: formatMessage({
1337
+ id: "content-manager.success.record.discard",
1338
+ defaultMessage: "Changes discarded"
1339
+ })
1340
+ });
1341
+ return res.data;
1342
+ } catch (err) {
1343
+ toggleNotification({
1344
+ type: "danger",
1345
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1346
+ });
1347
+ throw err;
1348
+ }
1349
+ },
1350
+ [discardDocument, formatAPIError, formatMessage, toggleNotification]
1351
+ );
1352
+ const [publishDocument] = usePublishDocumentMutation();
1353
+ const publish = React.useCallback(
1354
+ async ({ collectionType, model, documentId, params }, data) => {
1355
+ try {
1356
+ trackUsage("willPublishEntry");
1357
+ const res = await publishDocument({
1358
+ collectionType,
1359
+ model,
1360
+ documentId,
1361
+ data,
1362
+ params
1363
+ });
1364
+ if ("error" in res) {
1365
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1366
+ return { error: res.error };
1367
+ }
1368
+ trackUsage("didPublishEntry");
1369
+ toggleNotification({
1370
+ type: "success",
1371
+ message: formatMessage({
1372
+ id: getTranslation("success.record.publish"),
1373
+ defaultMessage: "Published document"
1374
+ })
1375
+ });
1376
+ return res.data;
1377
+ } catch (err) {
1378
+ toggleNotification({
1379
+ type: "danger",
1380
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1381
+ });
1382
+ throw err;
1383
+ }
1384
+ },
1385
+ [trackUsage, publishDocument, toggleNotification, formatMessage, formatAPIError]
1386
+ );
1387
+ const [publishManyDocuments] = usePublishManyDocumentsMutation();
1388
+ const publishMany = React.useCallback(
1389
+ async ({ model, documentIds, params }) => {
1390
+ try {
1391
+ const res = await publishManyDocuments({
1392
+ model,
1393
+ documentIds,
1394
+ params
1395
+ });
1396
+ if ("error" in res) {
1397
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1398
+ return { error: res.error };
1399
+ }
1400
+ toggleNotification({
1401
+ type: "success",
1402
+ message: formatMessage({
1403
+ id: getTranslation("success.record.publish"),
1404
+ defaultMessage: "Published document"
1405
+ })
1406
+ });
1407
+ return res.data;
1408
+ } catch (err) {
1409
+ toggleNotification({
1410
+ type: "danger",
1411
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1412
+ });
1413
+ throw err;
1414
+ }
1415
+ },
1416
+ [
1417
+ // trackUsage,
1418
+ publishManyDocuments,
1419
+ toggleNotification,
1420
+ formatMessage,
1421
+ formatAPIError
1422
+ ]
1423
+ );
1424
+ const [updateDocument] = useUpdateDocumentMutation();
1425
+ const update = React.useCallback(
1426
+ async ({ collectionType, model, documentId, params }, data, trackerProperty) => {
1427
+ try {
1428
+ trackUsage("willEditEntry", trackerProperty);
1429
+ const res = await updateDocument({
1430
+ collectionType,
1431
+ model,
1432
+ documentId,
1433
+ data,
1434
+ params
1435
+ });
1436
+ if ("error" in res) {
1437
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1438
+ trackUsage("didNotEditEntry", { error: res.error, ...trackerProperty });
1439
+ return { error: res.error };
1440
+ }
1441
+ trackUsage("didEditEntry", trackerProperty);
1442
+ toggleNotification({
1443
+ type: "success",
1444
+ message: formatMessage({
1445
+ id: getTranslation("success.record.save"),
1446
+ defaultMessage: "Saved document"
1447
+ })
1448
+ });
1449
+ return res.data;
1450
+ } catch (err) {
1451
+ trackUsage("didNotEditEntry", { error: err, ...trackerProperty });
1452
+ toggleNotification({
1453
+ type: "danger",
1454
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1455
+ });
1456
+ throw err;
1457
+ }
1458
+ },
1459
+ [trackUsage, updateDocument, toggleNotification, formatMessage, formatAPIError]
1460
+ );
1461
+ const [unpublishDocument] = useUnpublishDocumentMutation();
1462
+ const unpublish = React.useCallback(
1463
+ async ({ collectionType, model, documentId, params }, discardDraft = false) => {
1464
+ try {
1465
+ trackUsage("willUnpublishEntry");
1466
+ const res = await unpublishDocument({
1467
+ collectionType,
1468
+ model,
1469
+ documentId,
1470
+ params,
1471
+ data: {
1472
+ discardDraft
1473
+ }
1474
+ });
1475
+ if ("error" in res) {
1476
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1477
+ return { error: res.error };
1478
+ }
1479
+ trackUsage("didUnpublishEntry");
1480
+ toggleNotification({
1481
+ type: "success",
1482
+ message: formatMessage({
1483
+ id: getTranslation("success.record.unpublish"),
1484
+ defaultMessage: "Unpublished document"
1485
+ })
1486
+ });
1487
+ return res.data;
1488
+ } catch (err) {
1489
+ toggleNotification({
1490
+ type: "danger",
1491
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1492
+ });
1493
+ throw err;
1494
+ }
1495
+ },
1496
+ [trackUsage, unpublishDocument, toggleNotification, formatMessage, formatAPIError]
1497
+ );
1498
+ const [unpublishManyDocuments] = useUnpublishManyDocumentsMutation();
1499
+ const unpublishMany = React.useCallback(
1500
+ async ({ model, documentIds, params }) => {
1501
+ try {
1502
+ trackUsage("willBulkUnpublishEntries");
1503
+ const res = await unpublishManyDocuments({
1504
+ model,
1505
+ documentIds,
1506
+ params
1507
+ });
1508
+ if ("error" in res) {
1509
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1510
+ return { error: res.error };
1511
+ }
1512
+ trackUsage("didBulkUnpublishEntries");
1513
+ toggleNotification({
1514
+ type: "success",
1515
+ title: formatMessage({
1516
+ id: getTranslation("success.records.unpublish"),
1517
+ defaultMessage: "Successfully unpublished."
1518
+ }),
1519
+ message: ""
1520
+ });
1521
+ return res.data;
1522
+ } catch (err) {
1523
+ toggleNotification({
1524
+ type: "danger",
1525
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1526
+ });
1527
+ trackUsage("didNotBulkUnpublishEntries");
1528
+ throw err;
1529
+ }
1530
+ },
1531
+ [trackUsage, unpublishManyDocuments, toggleNotification, formatMessage, formatAPIError]
1532
+ );
1533
+ const [createDocument] = useCreateDocumentMutation();
1534
+ const create = React.useCallback(
1535
+ async ({ model, params }, data, trackerProperty) => {
1536
+ try {
1537
+ const res = await createDocument({
1538
+ model,
1539
+ data,
1141
1540
  params
1142
1541
  });
1143
1542
  if ("error" in res) {
@@ -1153,6 +1552,7 @@ const useDocumentActions = () => {
1153
1552
  defaultMessage: "Saved document"
1154
1553
  })
1155
1554
  });
1555
+ setCurrentStep("contentManager.success");
1156
1556
  return res.data;
1157
1557
  } catch (err) {
1158
1558
  toggleNotification({
@@ -1174,7 +1574,6 @@ const useDocumentActions = () => {
1174
1574
  sourceId
1175
1575
  });
1176
1576
  if ("error" in res) {
1177
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1178
1577
  return { error: res.error };
1179
1578
  }
1180
1579
  toggleNotification({
@@ -1193,7 +1592,7 @@ const useDocumentActions = () => {
1193
1592
  throw err;
1194
1593
  }
1195
1594
  },
1196
- [autoCloneDocument, formatAPIError, formatMessage, toggleNotification]
1595
+ [autoCloneDocument, formatMessage, toggleNotification]
1197
1596
  );
1198
1597
  const [cloneDocument] = useCloneDocumentMutation();
1199
1598
  const clone = React.useCallback(
@@ -1219,6 +1618,7 @@ const useDocumentActions = () => {
1219
1618
  defaultMessage: "Cloned document"
1220
1619
  })
1221
1620
  });
1621
+ navigate(`../../${res.data.data.documentId}`, { relative: "path" });
1222
1622
  return res.data;
1223
1623
  } catch (err) {
1224
1624
  toggleNotification({
@@ -1229,7 +1629,7 @@ const useDocumentActions = () => {
1229
1629
  throw err;
1230
1630
  }
1231
1631
  },
1232
- [cloneDocument, trackUsage, toggleNotification, formatMessage, formatAPIError]
1632
+ [cloneDocument, trackUsage, toggleNotification, formatMessage, formatAPIError, navigate]
1233
1633
  );
1234
1634
  const [getDoc] = useLazyGetDocumentQuery();
1235
1635
  const getDocument = React.useCallback(
@@ -1255,7 +1655,7 @@ const useDocumentActions = () => {
1255
1655
  };
1256
1656
  };
1257
1657
  const ProtectedHistoryPage = lazy(
1258
- () => import("./History-CBNGU7a-.mjs").then((mod) => ({ default: mod.ProtectedHistoryPage }))
1658
+ () => import("./History-2Ah2CQ4T.mjs").then((mod) => ({ default: mod.ProtectedHistoryPage }))
1259
1659
  );
1260
1660
  const routes$1 = [
1261
1661
  {
@@ -1268,31 +1668,31 @@ const routes$1 = [
1268
1668
  }
1269
1669
  ];
1270
1670
  const ProtectedEditViewPage = lazy(
1271
- () => import("./EditViewPage-DlLEyUL6.mjs").then((mod) => ({ default: mod.ProtectedEditViewPage }))
1671
+ () => import("./EditViewPage-Dzpno8xI.mjs").then((mod) => ({ default: mod.ProtectedEditViewPage }))
1272
1672
  );
1273
1673
  const ProtectedListViewPage = lazy(
1274
- () => import("./ListViewPage-DecLrYV6.mjs").then((mod) => ({ default: mod.ProtectedListViewPage }))
1674
+ () => import("./ListViewPage-B75x3nz2.mjs").then((mod) => ({ default: mod.ProtectedListViewPage }))
1275
1675
  );
1276
1676
  const ProtectedListConfiguration = lazy(
1277
- () => import("./ListConfigurationPage-5dr4qpue.mjs").then((mod) => ({
1677
+ () => import("./ListConfigurationPage-BjSJlaoC.mjs").then((mod) => ({
1278
1678
  default: mod.ProtectedListConfiguration
1279
1679
  }))
1280
1680
  );
1281
1681
  const ProtectedEditConfigurationPage = lazy(
1282
- () => import("./EditConfigurationPage-ZO0vOO8q.mjs").then((mod) => ({
1682
+ () => import("./EditConfigurationPage-CI4XoymK.mjs").then((mod) => ({
1283
1683
  default: mod.ProtectedEditConfigurationPage
1284
1684
  }))
1285
1685
  );
1286
1686
  const ProtectedComponentConfigurationPage = lazy(
1287
- () => import("./ComponentConfigurationPage-B1bIXVuX.mjs").then((mod) => ({
1687
+ () => import("./ComponentConfigurationPage-7-qB29e7.mjs").then((mod) => ({
1288
1688
  default: mod.ProtectedComponentConfigurationPage
1289
1689
  }))
1290
1690
  );
1291
1691
  const NoPermissions = lazy(
1292
- () => import("./NoPermissionsPage-CM5UD8ee.mjs").then((mod) => ({ default: mod.NoPermissions }))
1692
+ () => import("./NoPermissionsPage-SFllMekk.mjs").then((mod) => ({ default: mod.NoPermissions }))
1293
1693
  );
1294
1694
  const NoContentType = lazy(
1295
- () => import("./NoContentTypePage-CiIcfYsd.mjs").then((mod) => ({ default: mod.NoContentType }))
1695
+ () => import("./NoContentTypePage-DUacQSyF.mjs").then((mod) => ({ default: mod.NoContentType }))
1296
1696
  );
1297
1697
  const CollectionTypePages = () => {
1298
1698
  const { collectionType } = useParams();
@@ -1323,1073 +1723,758 @@ const routes = [
1323
1723
  Component: ProtectedListConfiguration
1324
1724
  },
1325
1725
  {
1326
- path: "components/:slug/configurations/edit",
1327
- Component: ProtectedComponentConfigurationPage
1328
- },
1329
- {
1330
- path: ":collectionType/:slug/configurations/edit",
1331
- Component: ProtectedEditConfigurationPage
1332
- },
1333
- {
1334
- path: "403",
1335
- Component: NoPermissions
1336
- },
1337
- {
1338
- path: "no-content-types",
1339
- Component: NoContentType
1340
- },
1341
- ...routes$1
1342
- ];
1343
- const DocumentActions = ({ actions: actions2 }) => {
1344
- const { formatMessage } = useIntl();
1345
- const [primaryAction, secondaryAction, ...restActions] = actions2.filter((action) => {
1346
- if (action.position === void 0) {
1347
- return true;
1348
- }
1349
- const positions = Array.isArray(action.position) ? action.position : [action.position];
1350
- return positions.includes("panel");
1351
- });
1352
- if (!primaryAction) {
1353
- return null;
1354
- }
1355
- return /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, alignItems: "stretch", width: "100%", children: [
1356
- /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
1357
- /* @__PURE__ */ jsx(DocumentActionButton, { ...primaryAction, variant: primaryAction.variant || "default" }),
1358
- restActions.length > 0 ? /* @__PURE__ */ jsx(
1359
- DocumentActionsMenu,
1360
- {
1361
- actions: restActions,
1362
- label: formatMessage({
1363
- id: "content-manager.containers.edit.panels.default.more-actions",
1364
- defaultMessage: "More document actions"
1365
- })
1366
- }
1367
- ) : null
1368
- ] }),
1369
- secondaryAction ? /* @__PURE__ */ jsx(
1370
- DocumentActionButton,
1371
- {
1372
- ...secondaryAction,
1373
- variant: secondaryAction.variant || "secondary"
1374
- }
1375
- ) : null
1376
- ] });
1377
- };
1378
- const DocumentActionButton = (action) => {
1379
- const [dialogId, setDialogId] = React.useState(null);
1380
- const { toggleNotification } = useNotification();
1381
- const handleClick = (action2) => async (e) => {
1382
- const { onClick = () => false, dialog, id } = action2;
1383
- const muteDialog = await onClick(e);
1384
- if (dialog && !muteDialog) {
1385
- switch (dialog.type) {
1386
- case "notification":
1387
- toggleNotification({
1388
- title: dialog.title,
1389
- message: dialog.content,
1390
- type: dialog.status,
1391
- timeout: dialog.timeout,
1392
- onClose: dialog.onClose
1393
- });
1394
- break;
1395
- case "dialog":
1396
- case "modal":
1397
- e.preventDefault();
1398
- setDialogId(id);
1399
- }
1400
- }
1401
- };
1402
- const handleClose = () => {
1403
- setDialogId(null);
1404
- };
1405
- return /* @__PURE__ */ jsxs(Fragment, { children: [
1406
- /* @__PURE__ */ jsx(
1407
- Button,
1408
- {
1409
- flex: 1,
1410
- startIcon: action.icon,
1411
- disabled: action.disabled,
1412
- onClick: handleClick(action),
1413
- justifyContent: "center",
1414
- variant: action.variant || "default",
1415
- children: action.label
1416
- }
1417
- ),
1418
- action.dialog?.type === "dialog" ? /* @__PURE__ */ jsx(
1419
- DocumentActionConfirmDialog,
1420
- {
1421
- ...action.dialog,
1422
- variant: action.dialog?.variant ?? action.variant,
1423
- isOpen: dialogId === action.id,
1424
- onClose: handleClose
1425
- }
1426
- ) : null,
1427
- action.dialog?.type === "modal" ? /* @__PURE__ */ jsx(
1428
- DocumentActionModal,
1429
- {
1430
- ...action.dialog,
1431
- onModalClose: handleClose,
1432
- isOpen: dialogId === action.id
1433
- }
1434
- ) : null
1435
- ] });
1436
- };
1437
- const DocumentActionsMenu = ({
1438
- actions: actions2,
1439
- children,
1440
- label,
1441
- variant = "tertiary"
1442
- }) => {
1443
- const [isOpen, setIsOpen] = React.useState(false);
1444
- const [dialogId, setDialogId] = React.useState(null);
1445
- const { formatMessage } = useIntl();
1446
- const { toggleNotification } = useNotification();
1447
- const isDisabled = actions2.every((action) => action.disabled) || actions2.length === 0;
1448
- const handleClick = (action) => async (e) => {
1449
- const { onClick = () => false, dialog, id } = action;
1450
- const muteDialog = await onClick(e);
1451
- if (dialog && !muteDialog) {
1452
- switch (dialog.type) {
1453
- case "notification":
1454
- toggleNotification({
1455
- title: dialog.title,
1456
- message: dialog.content,
1457
- type: dialog.status,
1458
- timeout: dialog.timeout,
1459
- onClose: dialog.onClose
1460
- });
1461
- break;
1462
- case "dialog":
1463
- case "modal":
1464
- setDialogId(id);
1465
- }
1466
- }
1467
- };
1468
- const handleClose = () => {
1469
- setDialogId(null);
1470
- setIsOpen(false);
1471
- };
1472
- return /* @__PURE__ */ jsxs(Menu.Root, { open: isOpen, onOpenChange: setIsOpen, children: [
1473
- /* @__PURE__ */ jsxs(
1474
- Menu.Trigger,
1475
- {
1476
- disabled: isDisabled,
1477
- size: "S",
1478
- endIcon: null,
1479
- paddingTop: "7px",
1480
- paddingLeft: "9px",
1481
- paddingRight: "9px",
1482
- variant,
1483
- children: [
1484
- /* @__PURE__ */ jsx(More, { "aria-hidden": true, focusable: false }),
1485
- /* @__PURE__ */ jsx(VisuallyHidden, { tag: "span", children: label || formatMessage({
1486
- id: "content-manager.containers.edit.panels.default.more-actions",
1487
- defaultMessage: "More document actions"
1488
- }) })
1489
- ]
1490
- }
1491
- ),
1492
- /* @__PURE__ */ jsxs(Menu.Content, { top: "4px", maxHeight: void 0, popoverPlacement: "bottom-end", children: [
1493
- actions2.map((action) => {
1494
- return /* @__PURE__ */ jsx(
1495
- Menu.Item,
1496
- {
1497
- disabled: action.disabled,
1498
- onSelect: handleClick(action),
1499
- display: "block",
1500
- children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", gap: 4, children: [
1501
- /* @__PURE__ */ jsxs(Flex, { color: convertActionVariantToColor(action.variant), gap: 2, tag: "span", children: [
1502
- /* @__PURE__ */ jsx(Box, { tag: "span", color: convertActionVariantToIconColor(action.variant), children: action.icon }),
1503
- action.label
1504
- ] }),
1505
- action.id.startsWith("HistoryAction") && /* @__PURE__ */ jsx(
1506
- Flex,
1507
- {
1508
- alignItems: "center",
1509
- background: "alternative100",
1510
- borderStyle: "solid",
1511
- borderColor: "alternative200",
1512
- borderWidth: "1px",
1513
- height: 5,
1514
- paddingLeft: 2,
1515
- paddingRight: 2,
1516
- hasRadius: true,
1517
- color: "alternative600",
1518
- children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", fontWeight: "bold", lineHeight: 1, children: formatMessage({ id: "global.new", defaultMessage: "New" }) })
1519
- }
1520
- )
1521
- ] })
1522
- },
1523
- action.id
1524
- );
1525
- }),
1526
- children
1527
- ] }),
1528
- actions2.map((action) => {
1529
- return /* @__PURE__ */ jsxs(React.Fragment, { children: [
1530
- action.dialog?.type === "dialog" ? /* @__PURE__ */ jsx(
1531
- DocumentActionConfirmDialog,
1532
- {
1533
- ...action.dialog,
1534
- variant: action.variant,
1535
- isOpen: dialogId === action.id,
1536
- onClose: handleClose
1537
- }
1538
- ) : null,
1539
- action.dialog?.type === "modal" ? /* @__PURE__ */ jsx(
1540
- DocumentActionModal,
1541
- {
1542
- ...action.dialog,
1543
- onModalClose: handleClose,
1544
- isOpen: dialogId === action.id
1545
- }
1546
- ) : null
1547
- ] }, action.id);
1548
- })
1549
- ] });
1550
- };
1551
- const convertActionVariantToColor = (variant = "secondary") => {
1552
- switch (variant) {
1553
- case "danger":
1554
- return "danger600";
1555
- case "secondary":
1556
- return void 0;
1557
- case "success":
1558
- return "success600";
1559
- default:
1560
- return "primary600";
1561
- }
1562
- };
1563
- const convertActionVariantToIconColor = (variant = "secondary") => {
1564
- switch (variant) {
1565
- case "danger":
1566
- return "danger600";
1567
- case "secondary":
1568
- return "neutral500";
1569
- case "success":
1570
- return "success600";
1571
- default:
1572
- return "primary600";
1573
- }
1574
- };
1575
- const DocumentActionConfirmDialog = ({
1576
- onClose,
1577
- onCancel,
1578
- onConfirm,
1579
- title,
1580
- content,
1581
- isOpen,
1582
- variant = "secondary"
1583
- }) => {
1584
- const { formatMessage } = useIntl();
1585
- const handleClose = async () => {
1586
- if (onCancel) {
1587
- await onCancel();
1588
- }
1589
- onClose();
1590
- };
1591
- const handleConfirm = async () => {
1592
- if (onConfirm) {
1593
- await onConfirm();
1594
- }
1595
- onClose();
1596
- };
1597
- return /* @__PURE__ */ jsx(Dialog.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxs(Dialog.Content, { children: [
1598
- /* @__PURE__ */ jsx(Dialog.Header, { children: title }),
1599
- /* @__PURE__ */ jsx(Dialog.Body, { children: content }),
1600
- /* @__PURE__ */ jsxs(Dialog.Footer, { children: [
1601
- /* @__PURE__ */ jsx(Dialog.Cancel, { children: /* @__PURE__ */ jsx(Button, { variant: "tertiary", children: formatMessage({
1602
- id: "app.components.Button.cancel",
1603
- defaultMessage: "Cancel"
1604
- }) }) }),
1605
- /* @__PURE__ */ jsx(Button, { onClick: handleConfirm, variant, children: formatMessage({
1606
- id: "app.components.Button.confirm",
1607
- defaultMessage: "Confirm"
1608
- }) })
1609
- ] })
1610
- ] }) });
1611
- };
1612
- const DocumentActionModal = ({
1613
- isOpen,
1614
- title,
1615
- onClose,
1616
- footer: Footer,
1617
- content: Content,
1618
- onModalClose
1619
- }) => {
1620
- const handleClose = () => {
1621
- if (onClose) {
1622
- onClose();
1623
- }
1624
- onModalClose();
1625
- };
1626
- return /* @__PURE__ */ jsx(Modal.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxs(Modal.Content, { children: [
1627
- /* @__PURE__ */ jsx(Modal.Header, { children: /* @__PURE__ */ jsx(Modal.Title, { children: title }) }),
1628
- /* @__PURE__ */ jsx(Modal.Body, { children: typeof Content === "function" ? /* @__PURE__ */ jsx(Content, { onClose: handleClose }) : Content }),
1629
- /* @__PURE__ */ jsx(Modal.Footer, { children: typeof Footer === "function" ? /* @__PURE__ */ jsx(Footer, { onClose: handleClose }) : Footer })
1630
- ] }) });
1631
- };
1632
- const PublishAction$1 = ({
1633
- activeTab,
1634
- documentId,
1635
- model,
1636
- collectionType,
1637
- meta,
1638
- document
1639
- }) => {
1640
- const { schema } = useDoc();
1641
- const navigate = useNavigate();
1642
- const { toggleNotification } = useNotification();
1643
- const { _unstableFormatValidationErrors: formatValidationErrors } = useAPIErrorHandler();
1644
- const isCloning = useMatch(CLONE_PATH) !== null;
1645
- const { formatMessage } = useIntl();
1646
- const { canPublish, canCreate, canUpdate } = useDocumentRBAC(
1647
- "PublishAction",
1648
- ({ canPublish: canPublish2, canCreate: canCreate2, canUpdate: canUpdate2 }) => ({ canPublish: canPublish2, canCreate: canCreate2, canUpdate: canUpdate2 })
1649
- );
1650
- const { publish } = useDocumentActions();
1651
- const [
1652
- countDraftRelations,
1653
- { isLoading: isLoadingDraftRelations, isError: isErrorDraftRelations }
1654
- ] = useLazyGetDraftRelationCountQuery();
1655
- const [localCountOfDraftRelations, setLocalCountOfDraftRelations] = React.useState(0);
1656
- const [serverCountOfDraftRelations, setServerCountOfDraftRelations] = React.useState(0);
1657
- const [{ query, rawQuery }] = useQueryParams();
1658
- const params = React.useMemo(() => buildValidParams(query), [query]);
1659
- const modified = useForm("PublishAction", ({ modified: modified2 }) => modified2);
1660
- const setSubmitting = useForm("PublishAction", ({ setSubmitting: setSubmitting2 }) => setSubmitting2);
1661
- const isSubmitting = useForm("PublishAction", ({ isSubmitting: isSubmitting2 }) => isSubmitting2);
1662
- const validate = useForm("PublishAction", (state) => state.validate);
1663
- const setErrors = useForm("PublishAction", (state) => state.setErrors);
1664
- const formValues = useForm("PublishAction", ({ values }) => values);
1665
- React.useEffect(() => {
1666
- if (isErrorDraftRelations) {
1667
- toggleNotification({
1668
- type: "danger",
1669
- message: formatMessage({
1670
- id: getTranslation("error.records.fetch-draft-relatons"),
1671
- defaultMessage: "An error occurred while fetching draft relations on this document."
1672
- })
1673
- });
1674
- }
1675
- }, [isErrorDraftRelations, toggleNotification, formatMessage]);
1676
- React.useEffect(() => {
1677
- const localDraftRelations = /* @__PURE__ */ new Set();
1678
- const extractDraftRelations = (data) => {
1679
- const relations = data.connect || [];
1680
- relations.forEach((relation) => {
1681
- if (relation.status === "draft") {
1682
- localDraftRelations.add(relation.id);
1683
- }
1684
- });
1685
- };
1686
- const traverseAndExtract = (data) => {
1687
- Object.entries(data).forEach(([key, value]) => {
1688
- if (key === "connect" && Array.isArray(value)) {
1689
- extractDraftRelations({ connect: value });
1690
- } else if (typeof value === "object" && value !== null) {
1691
- traverseAndExtract(value);
1692
- }
1693
- });
1694
- };
1695
- if (!documentId || modified) {
1696
- traverseAndExtract(formValues);
1697
- setLocalCountOfDraftRelations(localDraftRelations.size);
1698
- }
1699
- }, [documentId, modified, formValues, setLocalCountOfDraftRelations]);
1700
- React.useEffect(() => {
1701
- if (documentId) {
1702
- const fetchDraftRelationsCount = async () => {
1703
- const { data, error } = await countDraftRelations({
1704
- collectionType,
1705
- model,
1706
- documentId,
1707
- params
1708
- });
1709
- if (error) {
1710
- throw error;
1711
- }
1712
- if (data) {
1713
- setServerCountOfDraftRelations(data.data);
1714
- }
1715
- };
1716
- fetchDraftRelationsCount();
1726
+ path: "components/:slug/configurations/edit",
1727
+ Component: ProtectedComponentConfigurationPage
1728
+ },
1729
+ {
1730
+ path: ":collectionType/:slug/configurations/edit",
1731
+ Component: ProtectedEditConfigurationPage
1732
+ },
1733
+ {
1734
+ path: "403",
1735
+ Component: NoPermissions
1736
+ },
1737
+ {
1738
+ path: "no-content-types",
1739
+ Component: NoContentType
1740
+ },
1741
+ ...routes$1
1742
+ ];
1743
+ const DocumentActions = ({ actions: actions2 }) => {
1744
+ const { formatMessage } = useIntl();
1745
+ const [primaryAction, secondaryAction, ...restActions] = actions2.filter((action) => {
1746
+ if (action.position === void 0) {
1747
+ return true;
1717
1748
  }
1718
- }, [documentId, countDraftRelations, collectionType, model, params]);
1719
- const isDocumentPublished = (document?.[PUBLISHED_AT_ATTRIBUTE_NAME] || meta?.availableStatus.some((doc) => doc[PUBLISHED_AT_ATTRIBUTE_NAME] !== null)) && document?.status !== "modified";
1720
- if (!schema?.options?.draftAndPublish) {
1749
+ const positions = Array.isArray(action.position) ? action.position : [action.position];
1750
+ return positions.includes("panel");
1751
+ });
1752
+ if (!primaryAction) {
1721
1753
  return null;
1722
1754
  }
1723
- const performPublish = async () => {
1724
- setSubmitting(true);
1725
- try {
1726
- const { errors } = await validate();
1727
- if (errors) {
1728
- toggleNotification({
1729
- type: "danger",
1730
- message: formatMessage({
1731
- id: "content-manager.validation.error",
1732
- defaultMessage: "There are validation errors in your document. Please fix them before saving."
1733
- })
1734
- });
1735
- return;
1736
- }
1737
- const res = await publish(
1738
- {
1739
- collectionType,
1740
- model,
1741
- documentId,
1742
- params
1743
- },
1744
- formValues
1745
- );
1746
- if ("data" in res && collectionType !== SINGLE_TYPES) {
1747
- navigate({
1748
- pathname: `../${collectionType}/${model}/${res.data.documentId}`,
1749
- search: rawQuery
1750
- });
1751
- } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1752
- setErrors(formatValidationErrors(res.error));
1753
- }
1754
- } finally {
1755
- setSubmitting(false);
1756
- }
1757
- };
1758
- const totalDraftRelations = localCountOfDraftRelations + serverCountOfDraftRelations;
1759
- const hasDraftRelations = totalDraftRelations > 0;
1760
- return {
1761
- /**
1762
- * Disabled when:
1763
- * - currently if you're cloning a document we don't support publish & clone at the same time.
1764
- * - the form is submitting
1765
- * - the active tab is the published tab
1766
- * - the document is already published & not modified
1767
- * - the document is being created & not modified
1768
- * - the user doesn't have the permission to publish
1769
- * - the user doesn't have the permission to create a new document
1770
- * - the user doesn't have the permission to update the document
1771
- */
1772
- disabled: isCloning || isSubmitting || isLoadingDraftRelations || activeTab === "published" || !modified && isDocumentPublished || !modified && !document?.documentId || !canPublish || Boolean(!document?.documentId && !canCreate || document?.documentId && !canUpdate),
1773
- label: formatMessage({
1774
- id: "app.utils.publish",
1775
- defaultMessage: "Publish"
1776
- }),
1777
- onClick: async () => {
1778
- if (hasDraftRelations) {
1779
- return;
1780
- }
1781
- await performPublish();
1782
- },
1783
- dialog: hasDraftRelations ? {
1784
- type: "dialog",
1785
- variant: "danger",
1786
- footer: null,
1787
- title: formatMessage({
1788
- id: getTranslation(`popUpwarning.warning.bulk-has-draft-relations.title`),
1789
- defaultMessage: "Confirmation"
1790
- }),
1791
- content: formatMessage(
1792
- {
1793
- id: getTranslation(`popUpwarning.warning.bulk-has-draft-relations.message`),
1794
- defaultMessage: "This entry is related to {count, plural, one {# draft entry} other {# draft entries}}. Publishing it could leave broken links in your app."
1795
- },
1755
+ return /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, alignItems: "stretch", width: "100%", children: [
1756
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
1757
+ /* @__PURE__ */ jsx(DocumentActionButton, { ...primaryAction, variant: primaryAction.variant || "default" }),
1758
+ restActions.length > 0 ? /* @__PURE__ */ jsx(
1759
+ DocumentActionsMenu,
1796
1760
  {
1797
- count: totalDraftRelations
1761
+ actions: restActions,
1762
+ label: formatMessage({
1763
+ id: "content-manager.containers.edit.panels.default.more-actions",
1764
+ defaultMessage: "More document actions"
1765
+ })
1798
1766
  }
1799
- ),
1800
- onConfirm: async () => {
1801
- await performPublish();
1767
+ ) : null
1768
+ ] }),
1769
+ secondaryAction ? /* @__PURE__ */ jsx(
1770
+ DocumentActionButton,
1771
+ {
1772
+ ...secondaryAction,
1773
+ variant: secondaryAction.variant || "secondary"
1802
1774
  }
1803
- } : void 0
1804
- };
1775
+ ) : null
1776
+ ] });
1805
1777
  };
1806
- PublishAction$1.type = "publish";
1807
- const UpdateAction = ({
1808
- activeTab,
1809
- documentId,
1810
- model,
1811
- collectionType
1812
- }) => {
1813
- const navigate = useNavigate();
1778
+ const DocumentActionButton = (action) => {
1779
+ const [dialogId, setDialogId] = React.useState(null);
1814
1780
  const { toggleNotification } = useNotification();
1815
- const { _unstableFormatValidationErrors: formatValidationErrors } = useAPIErrorHandler();
1816
- const cloneMatch = useMatch(CLONE_PATH);
1817
- const isCloning = cloneMatch !== null;
1818
- const { formatMessage } = useIntl();
1819
- const { canCreate, canUpdate } = useDocumentRBAC("UpdateAction", ({ canCreate: canCreate2, canUpdate: canUpdate2 }) => ({
1820
- canCreate: canCreate2,
1821
- canUpdate: canUpdate2
1822
- }));
1823
- const { create, update, clone } = useDocumentActions();
1824
- const [{ query, rawQuery }] = useQueryParams();
1825
- const params = React.useMemo(() => buildValidParams(query), [query]);
1826
- const isSubmitting = useForm("UpdateAction", ({ isSubmitting: isSubmitting2 }) => isSubmitting2);
1827
- const modified = useForm("UpdateAction", ({ modified: modified2 }) => modified2);
1828
- const setSubmitting = useForm("UpdateAction", ({ setSubmitting: setSubmitting2 }) => setSubmitting2);
1829
- const document = useForm("UpdateAction", ({ values }) => values);
1830
- const validate = useForm("UpdateAction", (state) => state.validate);
1831
- const setErrors = useForm("UpdateAction", (state) => state.setErrors);
1832
- const resetForm = useForm("PublishAction", ({ resetForm: resetForm2 }) => resetForm2);
1833
- return {
1834
- /**
1835
- * Disabled when:
1836
- * - the form is submitting
1837
- * - the document is not modified & we're not cloning (you can save a clone entity straight away)
1838
- * - the active tab is the published tab
1839
- * - the user doesn't have the permission to create a new document
1840
- * - the user doesn't have the permission to update the document
1841
- */
1842
- disabled: isSubmitting || !modified && !isCloning || activeTab === "published" || Boolean(!documentId && !canCreate || documentId && !canUpdate),
1843
- label: formatMessage({
1844
- id: "content-manager.containers.Edit.save",
1845
- defaultMessage: "Save"
1846
- }),
1847
- onClick: async () => {
1848
- setSubmitting(true);
1849
- try {
1850
- const { errors } = await validate();
1851
- if (errors) {
1781
+ const handleClick = (action2) => async (e) => {
1782
+ const { onClick = () => false, dialog, id } = action2;
1783
+ const muteDialog = await onClick(e);
1784
+ if (dialog && !muteDialog) {
1785
+ switch (dialog.type) {
1786
+ case "notification":
1852
1787
  toggleNotification({
1853
- type: "danger",
1854
- message: formatMessage({
1855
- id: "content-manager.validation.error",
1856
- defaultMessage: "There are validation errors in your document. Please fix them before saving."
1857
- })
1788
+ title: dialog.title,
1789
+ message: dialog.content,
1790
+ type: dialog.status,
1791
+ timeout: dialog.timeout,
1792
+ onClose: dialog.onClose
1858
1793
  });
1859
- return;
1860
- }
1861
- if (isCloning) {
1862
- const res = await clone(
1863
- {
1864
- model,
1865
- documentId: cloneMatch.params.origin,
1866
- params
1867
- },
1868
- document
1869
- );
1870
- if ("data" in res) {
1871
- navigate({
1872
- pathname: `../${collectionType}/${model}/${res.data.documentId}`,
1873
- search: rawQuery
1874
- });
1875
- } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1876
- setErrors(formatValidationErrors(res.error));
1877
- }
1878
- } else if (documentId || collectionType === SINGLE_TYPES) {
1879
- const res = await update(
1880
- {
1881
- collectionType,
1882
- model,
1883
- documentId,
1884
- params
1885
- },
1886
- document
1887
- );
1888
- if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1889
- setErrors(formatValidationErrors(res.error));
1890
- } else {
1891
- resetForm();
1892
- }
1893
- } else {
1894
- const res = await create(
1895
- {
1896
- model,
1897
- params
1898
- },
1899
- document
1900
- );
1901
- if ("data" in res && collectionType !== SINGLE_TYPES) {
1902
- navigate(
1903
- {
1904
- pathname: `../${collectionType}/${model}/${res.data.documentId}`,
1905
- search: rawQuery
1906
- },
1907
- { replace: true }
1908
- );
1909
- } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1910
- setErrors(formatValidationErrors(res.error));
1911
- }
1912
- }
1913
- } finally {
1914
- setSubmitting(false);
1794
+ break;
1795
+ case "dialog":
1796
+ case "modal":
1797
+ e.preventDefault();
1798
+ setDialogId(id);
1799
+ }
1800
+ }
1801
+ };
1802
+ const handleClose = () => {
1803
+ setDialogId(null);
1804
+ };
1805
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1806
+ /* @__PURE__ */ jsx(
1807
+ Button,
1808
+ {
1809
+ flex: "auto",
1810
+ startIcon: action.icon,
1811
+ disabled: action.disabled,
1812
+ onClick: handleClick(action),
1813
+ justifyContent: "center",
1814
+ variant: action.variant || "default",
1815
+ paddingTop: "7px",
1816
+ paddingBottom: "7px",
1817
+ children: action.label
1818
+ }
1819
+ ),
1820
+ action.dialog?.type === "dialog" ? /* @__PURE__ */ jsx(
1821
+ DocumentActionConfirmDialog,
1822
+ {
1823
+ ...action.dialog,
1824
+ variant: action.dialog?.variant ?? action.variant,
1825
+ isOpen: dialogId === action.id,
1826
+ onClose: handleClose
1827
+ }
1828
+ ) : null,
1829
+ action.dialog?.type === "modal" ? /* @__PURE__ */ jsx(
1830
+ DocumentActionModal,
1831
+ {
1832
+ ...action.dialog,
1833
+ onModalClose: handleClose,
1834
+ isOpen: dialogId === action.id
1915
1835
  }
1916
- }
1917
- };
1918
- };
1919
- UpdateAction.type = "update";
1920
- const UNPUBLISH_DRAFT_OPTIONS = {
1921
- KEEP: "keep",
1922
- DISCARD: "discard"
1836
+ ) : null
1837
+ ] });
1923
1838
  };
1924
- const UnpublishAction$1 = ({
1925
- activeTab,
1926
- documentId,
1927
- model,
1928
- collectionType,
1929
- document
1839
+ const DocumentActionsMenu = ({
1840
+ actions: actions2,
1841
+ children,
1842
+ label,
1843
+ variant = "tertiary"
1930
1844
  }) => {
1845
+ const [isOpen, setIsOpen] = React.useState(false);
1846
+ const [dialogId, setDialogId] = React.useState(null);
1931
1847
  const { formatMessage } = useIntl();
1932
- const { schema } = useDoc();
1933
- const canPublish = useDocumentRBAC("UnpublishAction", ({ canPublish: canPublish2 }) => canPublish2);
1934
- const { unpublish } = useDocumentActions();
1935
- const [{ query }] = useQueryParams();
1936
- const params = React.useMemo(() => buildValidParams(query), [query]);
1937
1848
  const { toggleNotification } = useNotification();
1938
- const [shouldKeepDraft, setShouldKeepDraft] = React.useState(true);
1939
- const isDocumentModified = document?.status === "modified";
1940
- const handleChange = (value) => {
1941
- setShouldKeepDraft(value === UNPUBLISH_DRAFT_OPTIONS.KEEP);
1942
- };
1943
- if (!schema?.options?.draftAndPublish) {
1944
- return null;
1945
- }
1946
- return {
1947
- disabled: !canPublish || activeTab === "published" || document?.status !== "published" && document?.status !== "modified",
1948
- label: formatMessage({
1949
- id: "app.utils.unpublish",
1950
- defaultMessage: "Unpublish"
1951
- }),
1952
- icon: /* @__PURE__ */ jsx(StyledCrossCircle, {}),
1953
- onClick: async () => {
1954
- if (!documentId && collectionType !== SINGLE_TYPES || isDocumentModified) {
1955
- if (!documentId) {
1956
- console.error(
1957
- "You're trying to unpublish a document without an id, this is likely a bug with Strapi. Please open an issue."
1958
- );
1849
+ const isDisabled = actions2.every((action) => action.disabled) || actions2.length === 0;
1850
+ const handleClick = (action) => async (e) => {
1851
+ const { onClick = () => false, dialog, id } = action;
1852
+ const muteDialog = await onClick(e);
1853
+ if (dialog && !muteDialog) {
1854
+ switch (dialog.type) {
1855
+ case "notification":
1959
1856
  toggleNotification({
1960
- message: formatMessage({
1961
- id: "content-manager.actions.unpublish.error",
1962
- defaultMessage: "An error occurred while trying to unpublish the document."
1963
- }),
1964
- type: "danger"
1857
+ title: dialog.title,
1858
+ message: dialog.content,
1859
+ type: dialog.status,
1860
+ timeout: dialog.timeout,
1861
+ onClose: dialog.onClose
1965
1862
  });
1966
- }
1967
- return;
1863
+ break;
1864
+ case "dialog":
1865
+ case "modal":
1866
+ setDialogId(id);
1968
1867
  }
1969
- await unpublish({
1970
- collectionType,
1971
- model,
1972
- documentId,
1973
- params
1974
- });
1975
- },
1976
- dialog: isDocumentModified ? {
1977
- type: "dialog",
1978
- title: formatMessage({
1979
- id: "app.components.ConfirmDialog.title",
1980
- defaultMessage: "Confirmation"
1981
- }),
1982
- content: /* @__PURE__ */ jsxs(Flex, { alignItems: "flex-start", direction: "column", gap: 6, children: [
1983
- /* @__PURE__ */ jsxs(Flex, { width: "100%", direction: "column", gap: 2, children: [
1984
- /* @__PURE__ */ jsx(WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
1985
- /* @__PURE__ */ jsx(Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
1986
- id: "content-manager.actions.unpublish.dialog.body",
1987
- defaultMessage: "Are you sure?"
1868
+ }
1869
+ };
1870
+ const handleClose = () => {
1871
+ setDialogId(null);
1872
+ setIsOpen(false);
1873
+ };
1874
+ return /* @__PURE__ */ jsxs(Menu.Root, { open: isOpen, onOpenChange: setIsOpen, children: [
1875
+ /* @__PURE__ */ jsxs(
1876
+ Menu.Trigger,
1877
+ {
1878
+ disabled: isDisabled,
1879
+ size: "S",
1880
+ endIcon: null,
1881
+ paddingTop: "4px",
1882
+ paddingLeft: "7px",
1883
+ paddingRight: "7px",
1884
+ variant,
1885
+ children: [
1886
+ /* @__PURE__ */ jsx(More, { "aria-hidden": true, focusable: false }),
1887
+ /* @__PURE__ */ jsx(VisuallyHidden, { tag: "span", children: label || formatMessage({
1888
+ id: "content-manager.containers.edit.panels.default.more-actions",
1889
+ defaultMessage: "More document actions"
1988
1890
  }) })
1989
- ] }),
1990
- /* @__PURE__ */ jsxs(
1991
- Radio.Group,
1992
- {
1993
- defaultValue: UNPUBLISH_DRAFT_OPTIONS.KEEP,
1994
- name: "discard-options",
1995
- "aria-label": formatMessage({
1996
- id: "content-manager.actions.unpublish.dialog.radio-label",
1997
- defaultMessage: "Choose an option to unpublish the document."
1998
- }),
1999
- onValueChange: handleChange,
2000
- children: [
2001
- /* @__PURE__ */ jsx(Radio.Item, { checked: shouldKeepDraft, value: UNPUBLISH_DRAFT_OPTIONS.KEEP, children: formatMessage({
2002
- id: "content-manager.actions.unpublish.dialog.option.keep-draft",
2003
- defaultMessage: "Keep draft"
2004
- }) }),
2005
- /* @__PURE__ */ jsx(Radio.Item, { checked: !shouldKeepDraft, value: UNPUBLISH_DRAFT_OPTIONS.DISCARD, children: formatMessage({
2006
- id: "content-manager.actions.unpublish.dialog.option.replace-draft",
2007
- defaultMessage: "Replace draft"
2008
- }) })
2009
- ]
2010
- }
2011
- )
2012
- ] }),
2013
- onConfirm: async () => {
2014
- if (!documentId && collectionType !== SINGLE_TYPES) {
2015
- console.error(
2016
- "You're trying to unpublish a document without an id, this is likely a bug with Strapi. Please open an issue."
2017
- );
2018
- toggleNotification({
2019
- message: formatMessage({
2020
- id: "content-manager.actions.unpublish.error",
2021
- defaultMessage: "An error occurred while trying to unpublish the document."
2022
- }),
2023
- type: "danger"
2024
- });
2025
- }
2026
- await unpublish(
1891
+ ]
1892
+ }
1893
+ ),
1894
+ /* @__PURE__ */ jsxs(Menu.Content, { maxHeight: void 0, popoverPlacement: "bottom-end", children: [
1895
+ actions2.map((action) => {
1896
+ return /* @__PURE__ */ jsx(
1897
+ Menu.Item,
2027
1898
  {
2028
- collectionType,
2029
- model,
2030
- documentId,
2031
- params
1899
+ disabled: action.disabled,
1900
+ onSelect: handleClick(action),
1901
+ display: "block",
1902
+ children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", gap: 4, children: [
1903
+ /* @__PURE__ */ jsxs(
1904
+ Flex,
1905
+ {
1906
+ color: !action.disabled ? convertActionVariantToColor(action.variant) : "inherit",
1907
+ gap: 2,
1908
+ tag: "span",
1909
+ children: [
1910
+ /* @__PURE__ */ jsx(
1911
+ Flex,
1912
+ {
1913
+ tag: "span",
1914
+ color: !action.disabled ? convertActionVariantToIconColor(action.variant) : "inherit",
1915
+ children: action.icon
1916
+ }
1917
+ ),
1918
+ action.label
1919
+ ]
1920
+ }
1921
+ ),
1922
+ action.id.startsWith("HistoryAction") && /* @__PURE__ */ jsx(
1923
+ Flex,
1924
+ {
1925
+ alignItems: "center",
1926
+ background: "alternative100",
1927
+ borderStyle: "solid",
1928
+ borderColor: "alternative200",
1929
+ borderWidth: "1px",
1930
+ height: 5,
1931
+ paddingLeft: 2,
1932
+ paddingRight: 2,
1933
+ hasRadius: true,
1934
+ color: "alternative600",
1935
+ children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", fontWeight: "bold", lineHeight: 1, children: formatMessage({ id: "global.new", defaultMessage: "New" }) })
1936
+ }
1937
+ )
1938
+ ] })
2032
1939
  },
2033
- !shouldKeepDraft
1940
+ action.id
2034
1941
  );
2035
- }
2036
- } : void 0,
2037
- variant: "danger",
2038
- position: ["panel", "table-row"]
1942
+ }),
1943
+ children
1944
+ ] }),
1945
+ actions2.map((action) => {
1946
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
1947
+ action.dialog?.type === "dialog" ? /* @__PURE__ */ jsx(
1948
+ DocumentActionConfirmDialog,
1949
+ {
1950
+ ...action.dialog,
1951
+ variant: action.variant,
1952
+ isOpen: dialogId === action.id,
1953
+ onClose: handleClose
1954
+ }
1955
+ ) : null,
1956
+ action.dialog?.type === "modal" ? /* @__PURE__ */ jsx(
1957
+ DocumentActionModal,
1958
+ {
1959
+ ...action.dialog,
1960
+ onModalClose: handleClose,
1961
+ isOpen: dialogId === action.id
1962
+ }
1963
+ ) : null
1964
+ ] }, action.id);
1965
+ })
1966
+ ] });
1967
+ };
1968
+ const convertActionVariantToColor = (variant = "secondary") => {
1969
+ switch (variant) {
1970
+ case "danger":
1971
+ return "danger600";
1972
+ case "secondary":
1973
+ return void 0;
1974
+ case "success":
1975
+ return "success600";
1976
+ default:
1977
+ return "primary600";
1978
+ }
1979
+ };
1980
+ const convertActionVariantToIconColor = (variant = "secondary") => {
1981
+ switch (variant) {
1982
+ case "danger":
1983
+ return "danger600";
1984
+ case "secondary":
1985
+ return "neutral500";
1986
+ case "success":
1987
+ return "success600";
1988
+ default:
1989
+ return "primary600";
1990
+ }
1991
+ };
1992
+ const DocumentActionConfirmDialog = ({
1993
+ onClose,
1994
+ onCancel,
1995
+ onConfirm,
1996
+ title,
1997
+ content,
1998
+ isOpen,
1999
+ variant = "secondary"
2000
+ }) => {
2001
+ const { formatMessage } = useIntl();
2002
+ const handleClose = async () => {
2003
+ if (onCancel) {
2004
+ await onCancel();
2005
+ }
2006
+ onClose();
2039
2007
  };
2008
+ const handleConfirm = async () => {
2009
+ if (onConfirm) {
2010
+ await onConfirm();
2011
+ }
2012
+ onClose();
2013
+ };
2014
+ return /* @__PURE__ */ jsx(Dialog.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxs(Dialog.Content, { children: [
2015
+ /* @__PURE__ */ jsx(Dialog.Header, { children: title }),
2016
+ /* @__PURE__ */ jsx(Dialog.Body, { children: content }),
2017
+ /* @__PURE__ */ jsxs(Dialog.Footer, { children: [
2018
+ /* @__PURE__ */ jsx(Dialog.Cancel, { children: /* @__PURE__ */ jsx(Button, { variant: "tertiary", fullWidth: true, children: formatMessage({
2019
+ id: "app.components.Button.cancel",
2020
+ defaultMessage: "Cancel"
2021
+ }) }) }),
2022
+ /* @__PURE__ */ jsx(Button, { onClick: handleConfirm, variant, fullWidth: true, children: formatMessage({
2023
+ id: "app.components.Button.confirm",
2024
+ defaultMessage: "Confirm"
2025
+ }) })
2026
+ ] })
2027
+ ] }) });
2040
2028
  };
2041
- UnpublishAction$1.type = "unpublish";
2042
- const DiscardAction = ({
2029
+ const DocumentActionModal = ({
2030
+ isOpen,
2031
+ title,
2032
+ onClose,
2033
+ footer: Footer,
2034
+ content: Content,
2035
+ onModalClose
2036
+ }) => {
2037
+ const handleClose = () => {
2038
+ if (onClose) {
2039
+ onClose();
2040
+ }
2041
+ onModalClose();
2042
+ };
2043
+ return /* @__PURE__ */ jsx(Modal.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxs(Modal.Content, { children: [
2044
+ /* @__PURE__ */ jsx(Modal.Header, { children: /* @__PURE__ */ jsx(Modal.Title, { children: title }) }),
2045
+ typeof Content === "function" ? /* @__PURE__ */ jsx(Content, { onClose: handleClose }) : /* @__PURE__ */ jsx(Modal.Body, { children: Content }),
2046
+ typeof Footer === "function" ? /* @__PURE__ */ jsx(Footer, { onClose: handleClose }) : Footer
2047
+ ] }) });
2048
+ };
2049
+ const PublishAction$1 = ({
2043
2050
  activeTab,
2044
2051
  documentId,
2045
2052
  model,
2046
2053
  collectionType,
2054
+ meta,
2047
2055
  document
2048
2056
  }) => {
2049
- const { formatMessage } = useIntl();
2050
2057
  const { schema } = useDoc();
2051
- const canUpdate = useDocumentRBAC("DiscardAction", ({ canUpdate: canUpdate2 }) => canUpdate2);
2052
- const { discard } = useDocumentActions();
2053
- const [{ query }] = useQueryParams();
2054
- const params = React.useMemo(() => buildValidParams(query), [query]);
2055
- if (!schema?.options?.draftAndPublish) {
2056
- return null;
2057
- }
2058
- return {
2059
- disabled: !canUpdate || activeTab === "published" || document?.status !== "modified",
2060
- label: formatMessage({
2061
- id: "content-manager.actions.discard.label",
2062
- defaultMessage: "Discard changes"
2063
- }),
2064
- icon: /* @__PURE__ */ jsx(StyledCrossCircle, {}),
2065
- position: ["panel", "table-row"],
2066
- variant: "danger",
2067
- dialog: {
2068
- type: "dialog",
2069
- title: formatMessage({
2070
- id: "app.components.ConfirmDialog.title",
2071
- defaultMessage: "Confirmation"
2072
- }),
2073
- content: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, children: [
2074
- /* @__PURE__ */ jsx(WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
2075
- /* @__PURE__ */ jsx(Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
2076
- id: "content-manager.actions.discard.dialog.body",
2077
- defaultMessage: "Are you sure?"
2078
- }) })
2079
- ] }),
2080
- onConfirm: async () => {
2081
- await discard({
2082
- collectionType,
2083
- model,
2084
- documentId,
2085
- params
2086
- });
2087
- }
2088
- }
2089
- };
2090
- };
2091
- DiscardAction.type = "discard";
2092
- const StyledCrossCircle = styled(CrossCircle)`
2093
- path {
2094
- fill: currentColor;
2095
- }
2096
- `;
2097
- const DEFAULT_ACTIONS = [PublishAction$1, UpdateAction, UnpublishAction$1, DiscardAction];
2098
- const intervals = ["years", "months", "days", "hours", "minutes", "seconds"];
2099
- const RelativeTime = React.forwardRef(
2100
- ({ timestamp, customIntervals = [], ...restProps }, forwardedRef) => {
2101
- const { formatRelativeTime, formatDate, formatTime } = useIntl();
2102
- const interval = intervalToDuration({
2103
- start: timestamp,
2104
- end: Date.now()
2105
- // see https://github.com/date-fns/date-fns/issues/2891 – No idea why it's all partial it returns it every time.
2106
- });
2107
- const unit = intervals.find((intervalUnit) => {
2108
- return interval[intervalUnit] > 0 && Object.keys(interval).includes(intervalUnit);
2109
- });
2110
- const relativeTime = isPast(timestamp) ? -interval[unit] : interval[unit];
2111
- const customInterval = customIntervals.find(
2112
- (custom) => interval[custom.unit] < custom.threshold
2113
- );
2114
- const displayText = customInterval ? customInterval.text : formatRelativeTime(relativeTime, unit, { numeric: "auto" });
2115
- return /* @__PURE__ */ jsx(
2116
- "time",
2117
- {
2118
- ref: forwardedRef,
2119
- dateTime: timestamp.toISOString(),
2120
- role: "time",
2121
- title: `${formatDate(timestamp)} ${formatTime(timestamp)}`,
2122
- ...restProps,
2123
- children: displayText
2124
- }
2125
- );
2126
- }
2127
- );
2128
- const getDisplayName = ({
2129
- firstname,
2130
- lastname,
2131
- username,
2132
- email
2133
- } = {}) => {
2134
- if (username) {
2135
- return username;
2136
- }
2137
- if (firstname) {
2138
- return `${firstname} ${lastname ?? ""}`.trim();
2139
- }
2140
- return email ?? "";
2141
- };
2142
- const capitalise = (str) => str.charAt(0).toUpperCase() + str.slice(1);
2143
- const DocumentStatus = ({ status = "draft", ...restProps }) => {
2144
- const statusVariant = status === "draft" ? "primary" : status === "published" ? "success" : "alternative";
2145
- return /* @__PURE__ */ jsx(Status, { ...restProps, showBullet: false, size: "S", variant: statusVariant, children: /* @__PURE__ */ jsx(Typography, { tag: "span", variant: "omega", fontWeight: "bold", children: capitalise(status) }) });
2146
- };
2147
- const Header = ({ isCreating, status, title: documentTitle = "Untitled" }) => {
2148
- const { formatMessage } = useIntl();
2058
+ const navigate = useNavigate();
2059
+ const { toggleNotification } = useNotification();
2060
+ const { _unstableFormatValidationErrors: formatValidationErrors } = useAPIErrorHandler();
2061
+ const isListView = useMatch(LIST_PATH) !== null;
2149
2062
  const isCloning = useMatch(CLONE_PATH) !== null;
2150
- const title = isCreating ? formatMessage({
2151
- id: "content-manager.containers.edit.title.new",
2152
- defaultMessage: "Create an entry"
2153
- }) : documentTitle;
2154
- return /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", paddingTop: 6, paddingBottom: 4, gap: 2, children: [
2155
- /* @__PURE__ */ jsx(BackButton, {}),
2156
- /* @__PURE__ */ jsxs(Flex, { width: "100%", justifyContent: "space-between", gap: "80px", alignItems: "flex-start", children: [
2157
- /* @__PURE__ */ jsx(Typography, { variant: "alpha", tag: "h1", children: title }),
2158
- /* @__PURE__ */ jsx(HeaderToolbar, {})
2159
- ] }),
2160
- status ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(DocumentStatus, { status: isCloning ? "draft" : status }) }) : null
2161
- ] });
2162
- };
2163
- const HeaderToolbar = () => {
2164
2063
  const { formatMessage } = useIntl();
2165
- const isCloning = useMatch(CLONE_PATH) !== null;
2064
+ const canPublish = useDocumentRBAC("PublishAction", ({ canPublish: canPublish2 }) => canPublish2);
2065
+ const { publish } = useDocumentActions();
2166
2066
  const [
2167
- {
2168
- query: { status = "draft" }
2067
+ countDraftRelations,
2068
+ { isLoading: isLoadingDraftRelations, isError: isErrorDraftRelations }
2069
+ ] = useLazyGetDraftRelationCountQuery();
2070
+ const [localCountOfDraftRelations, setLocalCountOfDraftRelations] = React.useState(0);
2071
+ const [serverCountOfDraftRelations, setServerCountOfDraftRelations] = React.useState(0);
2072
+ const [{ query, rawQuery }] = useQueryParams();
2073
+ const params = React.useMemo(() => buildValidParams(query), [query]);
2074
+ const modified = useForm("PublishAction", ({ modified: modified2 }) => modified2);
2075
+ const setSubmitting = useForm("PublishAction", ({ setSubmitting: setSubmitting2 }) => setSubmitting2);
2076
+ const isSubmitting = useForm("PublishAction", ({ isSubmitting: isSubmitting2 }) => isSubmitting2);
2077
+ const validate = useForm("PublishAction", (state) => state.validate);
2078
+ const setErrors = useForm("PublishAction", (state) => state.setErrors);
2079
+ const formValues = useForm("PublishAction", ({ values }) => values);
2080
+ React.useEffect(() => {
2081
+ if (isErrorDraftRelations) {
2082
+ toggleNotification({
2083
+ type: "danger",
2084
+ message: formatMessage({
2085
+ id: getTranslation("error.records.fetch-draft-relatons"),
2086
+ defaultMessage: "An error occurred while fetching draft relations on this document."
2087
+ })
2088
+ });
2169
2089
  }
2170
- ] = useQueryParams();
2171
- const { model, id, document, meta, collectionType } = useDoc();
2172
- const plugins = useStrapiApp("HeaderToolbar", (state) => state.plugins);
2173
- return /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
2174
- /* @__PURE__ */ jsx(
2175
- DescriptionComponentRenderer,
2176
- {
2177
- props: {
2178
- activeTab: status,
2179
- model,
2180
- documentId: id,
2181
- document: isCloning ? void 0 : document,
2182
- meta: isCloning ? void 0 : meta,
2183
- collectionType
2184
- },
2185
- descriptions: plugins["content-manager"].apis.getHeaderActions(),
2186
- children: (actions2) => {
2187
- if (actions2.length > 0) {
2188
- return /* @__PURE__ */ jsx(HeaderActions, { actions: actions2 });
2189
- } else {
2190
- return null;
2191
- }
2090
+ }, [isErrorDraftRelations, toggleNotification, formatMessage]);
2091
+ React.useEffect(() => {
2092
+ const localDraftRelations = /* @__PURE__ */ new Set();
2093
+ const extractDraftRelations = (data) => {
2094
+ const relations = data.connect || [];
2095
+ relations.forEach((relation) => {
2096
+ if (relation.status === "draft") {
2097
+ localDraftRelations.add(relation.id);
2192
2098
  }
2193
- }
2194
- ),
2195
- /* @__PURE__ */ jsx(
2196
- DescriptionComponentRenderer,
2197
- {
2198
- props: {
2199
- activeTab: status,
2200
- model,
2201
- documentId: id,
2202
- document: isCloning ? void 0 : document,
2203
- meta: isCloning ? void 0 : meta,
2204
- collectionType
2205
- },
2206
- descriptions: plugins["content-manager"].apis.getDocumentActions(),
2207
- children: (actions2) => {
2208
- const headerActions = actions2.filter((action) => {
2209
- const positions = Array.isArray(action.position) ? action.position : [action.position];
2210
- return positions.includes("header");
2211
- });
2212
- return /* @__PURE__ */ jsx(
2213
- DocumentActionsMenu,
2214
- {
2215
- actions: headerActions,
2216
- label: formatMessage({
2217
- id: "content-manager.containers.edit.header.more-actions",
2218
- defaultMessage: "More actions"
2219
- }),
2220
- children: /* @__PURE__ */ jsx(Information, { activeTab: status })
2221
- }
2222
- );
2099
+ });
2100
+ };
2101
+ const traverseAndExtract = (data) => {
2102
+ Object.entries(data).forEach(([key, value]) => {
2103
+ if (key === "connect" && Array.isArray(value)) {
2104
+ extractDraftRelations({ connect: value });
2105
+ } else if (typeof value === "object" && value !== null) {
2106
+ traverseAndExtract(value);
2223
2107
  }
2108
+ });
2109
+ };
2110
+ if (!documentId || modified) {
2111
+ traverseAndExtract(formValues);
2112
+ setLocalCountOfDraftRelations(localDraftRelations.size);
2113
+ }
2114
+ }, [documentId, modified, formValues, setLocalCountOfDraftRelations]);
2115
+ React.useEffect(() => {
2116
+ if (!document || !document.documentId || isListView) {
2117
+ return;
2118
+ }
2119
+ const fetchDraftRelationsCount = async () => {
2120
+ const { data, error } = await countDraftRelations({
2121
+ collectionType,
2122
+ model,
2123
+ documentId,
2124
+ params
2125
+ });
2126
+ if (error) {
2127
+ throw error;
2224
2128
  }
2225
- )
2226
- ] });
2227
- };
2228
- const Information = ({ activeTab }) => {
2229
- const { formatMessage } = useIntl();
2230
- const { document, meta } = useDoc();
2231
- if (!document || !document.id) {
2129
+ if (data) {
2130
+ setServerCountOfDraftRelations(data.data);
2131
+ }
2132
+ };
2133
+ fetchDraftRelationsCount();
2134
+ }, [isListView, document, documentId, countDraftRelations, collectionType, model, params]);
2135
+ const isDocumentPublished = (document?.[PUBLISHED_AT_ATTRIBUTE_NAME] || meta?.availableStatus.some((doc) => doc[PUBLISHED_AT_ATTRIBUTE_NAME] !== null)) && document?.status !== "modified";
2136
+ if (!schema?.options?.draftAndPublish) {
2232
2137
  return null;
2233
2138
  }
2234
- const createAndUpdateDocument = activeTab === "draft" ? document : meta?.availableStatus.find((status) => status.publishedAt === null);
2235
- const publishDocument = activeTab === "published" ? document : meta?.availableStatus.find((status) => status.publishedAt !== null);
2236
- const creator = createAndUpdateDocument?.[CREATED_BY_ATTRIBUTE_NAME] ? getDisplayName(createAndUpdateDocument[CREATED_BY_ATTRIBUTE_NAME]) : null;
2237
- const updator = createAndUpdateDocument?.[UPDATED_BY_ATTRIBUTE_NAME] ? getDisplayName(createAndUpdateDocument[UPDATED_BY_ATTRIBUTE_NAME]) : null;
2238
- const information = [
2239
- {
2240
- isDisplayed: !!publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME],
2241
- label: formatMessage({
2242
- id: "content-manager.containers.edit.information.last-published.label",
2243
- defaultMessage: "Last published"
2244
- }),
2245
- value: formatMessage(
2139
+ const performPublish = async () => {
2140
+ setSubmitting(true);
2141
+ try {
2142
+ const { errors } = await validate();
2143
+ if (errors) {
2144
+ toggleNotification({
2145
+ type: "danger",
2146
+ message: formatMessage({
2147
+ id: "content-manager.validation.error",
2148
+ defaultMessage: "There are validation errors in your document. Please fix them before saving."
2149
+ })
2150
+ });
2151
+ return;
2152
+ }
2153
+ const res = await publish(
2246
2154
  {
2247
- id: "content-manager.containers.edit.information.last-published.value",
2248
- defaultMessage: `Published {time}{isAnonymous, select, true {} other { by {author}}}`
2155
+ collectionType,
2156
+ model,
2157
+ documentId,
2158
+ params
2249
2159
  },
2250
- {
2251
- time: /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME]) }),
2252
- isAnonymous: !publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME],
2253
- author: publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME] ? getDisplayName(publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME]) : null
2254
- }
2255
- )
2160
+ formValues
2161
+ );
2162
+ if ("data" in res && collectionType !== SINGLE_TYPES) {
2163
+ navigate({
2164
+ pathname: `../${collectionType}/${model}/${res.data.documentId}`,
2165
+ search: rawQuery
2166
+ });
2167
+ } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
2168
+ setErrors(formatValidationErrors(res.error));
2169
+ }
2170
+ } finally {
2171
+ setSubmitting(false);
2172
+ }
2173
+ };
2174
+ const totalDraftRelations = localCountOfDraftRelations + serverCountOfDraftRelations;
2175
+ const enableDraftRelationsCount = false;
2176
+ const hasDraftRelations = enableDraftRelationsCount;
2177
+ return {
2178
+ /**
2179
+ * Disabled when:
2180
+ * - currently if you're cloning a document we don't support publish & clone at the same time.
2181
+ * - the form is submitting
2182
+ * - the active tab is the published tab
2183
+ * - the document is already published & not modified
2184
+ * - the document is being created & not modified
2185
+ * - the user doesn't have the permission to publish
2186
+ */
2187
+ disabled: isCloning || isSubmitting || isLoadingDraftRelations || activeTab === "published" || !modified && isDocumentPublished || !modified && !document?.documentId || !canPublish,
2188
+ label: formatMessage({
2189
+ id: "app.utils.publish",
2190
+ defaultMessage: "Publish"
2191
+ }),
2192
+ onClick: async () => {
2193
+ await performPublish();
2256
2194
  },
2257
- {
2258
- isDisplayed: !!createAndUpdateDocument?.[UPDATED_AT_ATTRIBUTE_NAME],
2259
- label: formatMessage({
2260
- id: "content-manager.containers.edit.information.last-draft.label",
2261
- defaultMessage: "Last draft"
2195
+ dialog: hasDraftRelations ? {
2196
+ type: "dialog",
2197
+ variant: "danger",
2198
+ footer: null,
2199
+ title: formatMessage({
2200
+ id: getTranslation(`popUpwarning.warning.bulk-has-draft-relations.title`),
2201
+ defaultMessage: "Confirmation"
2262
2202
  }),
2263
- value: formatMessage(
2203
+ content: formatMessage(
2264
2204
  {
2265
- id: "content-manager.containers.edit.information.last-draft.value",
2266
- defaultMessage: `Modified {time}{isAnonymous, select, true {} other { by {author}}}`
2205
+ id: getTranslation(`popUpwarning.warning.bulk-has-draft-relations.message`),
2206
+ defaultMessage: "This entry is related to {count, plural, one {# draft entry} other {# draft entries}}. Publishing it could leave broken links in your app."
2267
2207
  },
2268
2208
  {
2269
- time: /* @__PURE__ */ jsx(
2270
- RelativeTime,
2271
- {
2272
- timestamp: new Date(createAndUpdateDocument?.[UPDATED_AT_ATTRIBUTE_NAME])
2273
- }
2274
- ),
2275
- isAnonymous: !updator,
2276
- author: updator
2209
+ count: totalDraftRelations
2277
2210
  }
2278
- )
2279
- },
2280
- {
2281
- isDisplayed: !!createAndUpdateDocument?.[CREATED_AT_ATTRIBUTE_NAME],
2282
- label: formatMessage({
2283
- id: "content-manager.containers.edit.information.document.label",
2284
- defaultMessage: "Document"
2285
- }),
2286
- value: formatMessage(
2287
- {
2288
- id: "content-manager.containers.edit.information.document.value",
2289
- defaultMessage: `Created {time}{isAnonymous, select, true {} other { by {author}}}`
2290
- },
2291
- {
2292
- time: /* @__PURE__ */ jsx(
2293
- RelativeTime,
2294
- {
2295
- timestamp: new Date(createAndUpdateDocument?.[CREATED_AT_ATTRIBUTE_NAME])
2296
- }
2297
- ),
2298
- isAnonymous: !creator,
2299
- author: creator
2211
+ ),
2212
+ onConfirm: async () => {
2213
+ await performPublish();
2214
+ }
2215
+ } : void 0
2216
+ };
2217
+ };
2218
+ PublishAction$1.type = "publish";
2219
+ const UpdateAction = ({
2220
+ activeTab,
2221
+ documentId,
2222
+ model,
2223
+ collectionType
2224
+ }) => {
2225
+ const navigate = useNavigate();
2226
+ const { toggleNotification } = useNotification();
2227
+ const { _unstableFormatValidationErrors: formatValidationErrors } = useAPIErrorHandler();
2228
+ const cloneMatch = useMatch(CLONE_PATH);
2229
+ const isCloning = cloneMatch !== null;
2230
+ const { formatMessage } = useIntl();
2231
+ const { create, update, clone } = useDocumentActions();
2232
+ const [{ query, rawQuery }] = useQueryParams();
2233
+ const params = React.useMemo(() => buildValidParams(query), [query]);
2234
+ const isSubmitting = useForm("UpdateAction", ({ isSubmitting: isSubmitting2 }) => isSubmitting2);
2235
+ const modified = useForm("UpdateAction", ({ modified: modified2 }) => modified2);
2236
+ const setSubmitting = useForm("UpdateAction", ({ setSubmitting: setSubmitting2 }) => setSubmitting2);
2237
+ const document = useForm("UpdateAction", ({ values }) => values);
2238
+ const validate = useForm("UpdateAction", (state) => state.validate);
2239
+ const setErrors = useForm("UpdateAction", (state) => state.setErrors);
2240
+ const resetForm = useForm("PublishAction", ({ resetForm: resetForm2 }) => resetForm2);
2241
+ return {
2242
+ /**
2243
+ * Disabled when:
2244
+ * - the form is submitting
2245
+ * - the document is not modified & we're not cloning (you can save a clone entity straight away)
2246
+ * - the active tab is the published tab
2247
+ */
2248
+ disabled: isSubmitting || !modified && !isCloning || activeTab === "published",
2249
+ label: formatMessage({
2250
+ id: "content-manager.containers.Edit.save",
2251
+ defaultMessage: "Save"
2252
+ }),
2253
+ onClick: async () => {
2254
+ setSubmitting(true);
2255
+ try {
2256
+ if (activeTab !== "draft") {
2257
+ const { errors } = await validate();
2258
+ if (errors) {
2259
+ toggleNotification({
2260
+ type: "danger",
2261
+ message: formatMessage({
2262
+ id: "content-manager.validation.error",
2263
+ defaultMessage: "There are validation errors in your document. Please fix them before saving."
2264
+ })
2265
+ });
2266
+ return;
2267
+ }
2300
2268
  }
2301
- )
2302
- }
2303
- ].filter((info) => info.isDisplayed);
2304
- return /* @__PURE__ */ jsx(
2305
- Flex,
2306
- {
2307
- borderWidth: "1px 0 0 0",
2308
- borderStyle: "solid",
2309
- borderColor: "neutral150",
2310
- direction: "column",
2311
- marginTop: 2,
2312
- tag: "dl",
2313
- padding: 5,
2314
- gap: 3,
2315
- alignItems: "flex-start",
2316
- marginLeft: "-0.4rem",
2317
- marginRight: "-0.4rem",
2318
- width: "calc(100% + 8px)",
2319
- children: information.map((info) => /* @__PURE__ */ jsxs(Flex, { gap: 1, direction: "column", alignItems: "flex-start", children: [
2320
- /* @__PURE__ */ jsx(Typography, { tag: "dt", variant: "pi", fontWeight: "bold", children: info.label }),
2321
- /* @__PURE__ */ jsx(Typography, { tag: "dd", variant: "pi", textColor: "neutral600", children: info.value })
2322
- ] }, info.label))
2269
+ if (isCloning) {
2270
+ const res = await clone(
2271
+ {
2272
+ model,
2273
+ documentId: cloneMatch.params.origin,
2274
+ params
2275
+ },
2276
+ document
2277
+ );
2278
+ if ("data" in res) {
2279
+ navigate(
2280
+ {
2281
+ pathname: `../${res.data.documentId}`,
2282
+ search: rawQuery
2283
+ },
2284
+ { relative: "path" }
2285
+ );
2286
+ } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
2287
+ setErrors(formatValidationErrors(res.error));
2288
+ }
2289
+ } else if (documentId || collectionType === SINGLE_TYPES) {
2290
+ const res = await update(
2291
+ {
2292
+ collectionType,
2293
+ model,
2294
+ documentId,
2295
+ params
2296
+ },
2297
+ document
2298
+ );
2299
+ if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
2300
+ setErrors(formatValidationErrors(res.error));
2301
+ } else {
2302
+ resetForm();
2303
+ }
2304
+ } else {
2305
+ const res = await create(
2306
+ {
2307
+ model,
2308
+ params
2309
+ },
2310
+ document
2311
+ );
2312
+ if ("data" in res && collectionType !== SINGLE_TYPES) {
2313
+ navigate(
2314
+ {
2315
+ pathname: `../${res.data.documentId}`,
2316
+ search: rawQuery
2317
+ },
2318
+ { replace: true, relative: "path" }
2319
+ );
2320
+ } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
2321
+ setErrors(formatValidationErrors(res.error));
2322
+ }
2323
+ }
2324
+ } finally {
2325
+ setSubmitting(false);
2326
+ }
2323
2327
  }
2324
- );
2328
+ };
2325
2329
  };
2326
- const HeaderActions = ({ actions: actions2 }) => {
2327
- return /* @__PURE__ */ jsx(Flex, { children: actions2.map((action) => {
2328
- if ("options" in action) {
2329
- return /* @__PURE__ */ jsx(
2330
- SingleSelect,
2331
- {
2332
- size: "S",
2333
- disabled: action.disabled,
2334
- "aria-label": action.label,
2335
- onChange: action.onSelect,
2336
- value: action.value,
2337
- children: action.options.map(({ label, ...option }) => /* @__PURE__ */ jsx(SingleSelectOption, { ...option, children: label }, option.value))
2338
- },
2339
- action.id
2340
- );
2341
- } else {
2342
- return null;
2343
- }
2344
- }) });
2330
+ UpdateAction.type = "update";
2331
+ const UNPUBLISH_DRAFT_OPTIONS = {
2332
+ KEEP: "keep",
2333
+ DISCARD: "discard"
2345
2334
  };
2346
- const ConfigureTheViewAction = ({ collectionType, model }) => {
2347
- const navigate = useNavigate();
2335
+ const UnpublishAction$1 = ({
2336
+ activeTab,
2337
+ documentId,
2338
+ model,
2339
+ collectionType,
2340
+ document
2341
+ }) => {
2348
2342
  const { formatMessage } = useIntl();
2349
- return {
2350
- label: formatMessage({
2351
- id: "app.links.configure-view",
2352
- defaultMessage: "Configure the view"
2353
- }),
2354
- icon: /* @__PURE__ */ jsx(ListPlus, {}),
2355
- onClick: () => {
2356
- navigate(`../${collectionType}/${model}/configurations/edit`);
2357
- },
2358
- position: "header"
2343
+ const { schema } = useDoc();
2344
+ const canPublish = useDocumentRBAC("UnpublishAction", ({ canPublish: canPublish2 }) => canPublish2);
2345
+ const { unpublish } = useDocumentActions();
2346
+ const [{ query }] = useQueryParams();
2347
+ const params = React.useMemo(() => buildValidParams(query), [query]);
2348
+ const { toggleNotification } = useNotification();
2349
+ const [shouldKeepDraft, setShouldKeepDraft] = React.useState(true);
2350
+ const isDocumentModified = document?.status === "modified";
2351
+ const handleChange = (value) => {
2352
+ setShouldKeepDraft(value === UNPUBLISH_DRAFT_OPTIONS.KEEP);
2359
2353
  };
2360
- };
2361
- ConfigureTheViewAction.type = "configure-the-view";
2362
- const EditTheModelAction = ({ model }) => {
2363
- const navigate = useNavigate();
2364
- const { formatMessage } = useIntl();
2354
+ if (!schema?.options?.draftAndPublish) {
2355
+ return null;
2356
+ }
2365
2357
  return {
2358
+ disabled: !canPublish || activeTab === "published" || document?.status !== "published" && document?.status !== "modified",
2366
2359
  label: formatMessage({
2367
- id: "content-manager.link-to-ctb",
2368
- defaultMessage: "Edit the model"
2360
+ id: "app.utils.unpublish",
2361
+ defaultMessage: "Unpublish"
2369
2362
  }),
2370
- icon: /* @__PURE__ */ jsx(Pencil, {}),
2371
- onClick: () => {
2372
- navigate(`/plugins/content-type-builder/content-types/${model}`);
2363
+ icon: /* @__PURE__ */ jsx(Cross, {}),
2364
+ onClick: async () => {
2365
+ if (!documentId && collectionType !== SINGLE_TYPES || isDocumentModified) {
2366
+ if (!documentId) {
2367
+ console.error(
2368
+ "You're trying to unpublish a document without an id, this is likely a bug with Strapi. Please open an issue."
2369
+ );
2370
+ toggleNotification({
2371
+ message: formatMessage({
2372
+ id: "content-manager.actions.unpublish.error",
2373
+ defaultMessage: "An error occurred while trying to unpublish the document."
2374
+ }),
2375
+ type: "danger"
2376
+ });
2377
+ }
2378
+ return;
2379
+ }
2380
+ await unpublish({
2381
+ collectionType,
2382
+ model,
2383
+ documentId,
2384
+ params
2385
+ });
2373
2386
  },
2374
- position: "header"
2387
+ dialog: isDocumentModified ? {
2388
+ type: "dialog",
2389
+ title: formatMessage({
2390
+ id: "app.components.ConfirmDialog.title",
2391
+ defaultMessage: "Confirmation"
2392
+ }),
2393
+ content: /* @__PURE__ */ jsxs(Flex, { alignItems: "flex-start", direction: "column", gap: 6, children: [
2394
+ /* @__PURE__ */ jsxs(Flex, { width: "100%", direction: "column", gap: 2, children: [
2395
+ /* @__PURE__ */ jsx(WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
2396
+ /* @__PURE__ */ jsx(Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
2397
+ id: "content-manager.actions.unpublish.dialog.body",
2398
+ defaultMessage: "Are you sure?"
2399
+ }) })
2400
+ ] }),
2401
+ /* @__PURE__ */ jsxs(
2402
+ Radio.Group,
2403
+ {
2404
+ defaultValue: UNPUBLISH_DRAFT_OPTIONS.KEEP,
2405
+ name: "discard-options",
2406
+ "aria-label": formatMessage({
2407
+ id: "content-manager.actions.unpublish.dialog.radio-label",
2408
+ defaultMessage: "Choose an option to unpublish the document."
2409
+ }),
2410
+ onValueChange: handleChange,
2411
+ children: [
2412
+ /* @__PURE__ */ jsx(Radio.Item, { checked: shouldKeepDraft, value: UNPUBLISH_DRAFT_OPTIONS.KEEP, children: formatMessage({
2413
+ id: "content-manager.actions.unpublish.dialog.option.keep-draft",
2414
+ defaultMessage: "Keep draft"
2415
+ }) }),
2416
+ /* @__PURE__ */ jsx(Radio.Item, { checked: !shouldKeepDraft, value: UNPUBLISH_DRAFT_OPTIONS.DISCARD, children: formatMessage({
2417
+ id: "content-manager.actions.unpublish.dialog.option.replace-draft",
2418
+ defaultMessage: "Replace draft"
2419
+ }) })
2420
+ ]
2421
+ }
2422
+ )
2423
+ ] }),
2424
+ onConfirm: async () => {
2425
+ if (!documentId && collectionType !== SINGLE_TYPES) {
2426
+ console.error(
2427
+ "You're trying to unpublish a document without an id, this is likely a bug with Strapi. Please open an issue."
2428
+ );
2429
+ toggleNotification({
2430
+ message: formatMessage({
2431
+ id: "content-manager.actions.unpublish.error",
2432
+ defaultMessage: "An error occurred while trying to unpublish the document."
2433
+ }),
2434
+ type: "danger"
2435
+ });
2436
+ }
2437
+ await unpublish(
2438
+ {
2439
+ collectionType,
2440
+ model,
2441
+ documentId,
2442
+ params
2443
+ },
2444
+ !shouldKeepDraft
2445
+ );
2446
+ }
2447
+ } : void 0,
2448
+ variant: "danger",
2449
+ position: ["panel", "table-row"]
2375
2450
  };
2376
2451
  };
2377
- EditTheModelAction.type = "edit-the-model";
2378
- const DeleteAction$1 = ({ documentId, model, collectionType, document }) => {
2379
- const navigate = useNavigate();
2452
+ UnpublishAction$1.type = "unpublish";
2453
+ const DiscardAction = ({
2454
+ activeTab,
2455
+ documentId,
2456
+ model,
2457
+ collectionType,
2458
+ document
2459
+ }) => {
2380
2460
  const { formatMessage } = useIntl();
2381
- const listViewPathMatch = useMatch(LIST_PATH);
2382
- const canDelete = useDocumentRBAC("DeleteAction", (state) => state.canDelete);
2383
- const { delete: deleteAction } = useDocumentActions();
2384
- const { toggleNotification } = useNotification();
2385
- const setSubmitting = useForm("DeleteAction", (state) => state.setSubmitting);
2461
+ const { schema } = useDoc();
2462
+ const canUpdate = useDocumentRBAC("DiscardAction", ({ canUpdate: canUpdate2 }) => canUpdate2);
2463
+ const { discard } = useDocumentActions();
2464
+ const [{ query }] = useQueryParams();
2465
+ const params = React.useMemo(() => buildValidParams(query), [query]);
2466
+ if (!schema?.options?.draftAndPublish) {
2467
+ return null;
2468
+ }
2386
2469
  return {
2387
- disabled: !canDelete || !document,
2470
+ disabled: !canUpdate || activeTab === "published" || document?.status !== "modified",
2388
2471
  label: formatMessage({
2389
- id: "content-manager.actions.delete.label",
2390
- defaultMessage: "Delete document"
2472
+ id: "content-manager.actions.discard.label",
2473
+ defaultMessage: "Discard changes"
2391
2474
  }),
2392
- icon: /* @__PURE__ */ jsx(Trash, {}),
2475
+ icon: /* @__PURE__ */ jsx(Cross, {}),
2476
+ position: ["panel", "table-row"],
2477
+ variant: "danger",
2393
2478
  dialog: {
2394
2479
  type: "dialog",
2395
2480
  title: formatMessage({
@@ -2399,92 +2484,90 @@ const DeleteAction$1 = ({ documentId, model, collectionType, document }) => {
2399
2484
  content: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, children: [
2400
2485
  /* @__PURE__ */ jsx(WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
2401
2486
  /* @__PURE__ */ jsx(Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
2402
- id: "content-manager.actions.delete.dialog.body",
2487
+ id: "content-manager.actions.discard.dialog.body",
2403
2488
  defaultMessage: "Are you sure?"
2404
2489
  }) })
2405
2490
  ] }),
2406
2491
  onConfirm: async () => {
2407
- if (!listViewPathMatch) {
2408
- setSubmitting(true);
2409
- }
2410
- try {
2411
- if (!documentId && collectionType !== SINGLE_TYPES) {
2412
- console.error(
2413
- "You're trying to delete a document without an id, this is likely a bug with Strapi. Please open an issue."
2414
- );
2415
- toggleNotification({
2416
- message: formatMessage({
2417
- id: "content-manager.actions.delete.error",
2418
- defaultMessage: "An error occurred while trying to delete the document."
2419
- }),
2420
- type: "danger"
2421
- });
2422
- return;
2423
- }
2424
- const res = await deleteAction({
2425
- documentId,
2426
- model,
2427
- collectionType,
2428
- params: {
2429
- locale: "*"
2430
- }
2431
- });
2432
- if (!("error" in res)) {
2433
- navigate({ pathname: `../${collectionType}/${model}` }, { replace: true });
2434
- }
2435
- } finally {
2436
- if (!listViewPathMatch) {
2437
- setSubmitting(false);
2438
- }
2439
- }
2492
+ await discard({
2493
+ collectionType,
2494
+ model,
2495
+ documentId,
2496
+ params
2497
+ });
2498
+ }
2499
+ }
2500
+ };
2501
+ };
2502
+ DiscardAction.type = "discard";
2503
+ const DEFAULT_ACTIONS = [PublishAction$1, UpdateAction, UnpublishAction$1, DiscardAction];
2504
+ const intervals = ["years", "months", "days", "hours", "minutes", "seconds"];
2505
+ const RelativeTime = React.forwardRef(
2506
+ ({ timestamp, customIntervals = [], ...restProps }, forwardedRef) => {
2507
+ const { formatRelativeTime, formatDate, formatTime } = useIntl();
2508
+ const interval = intervalToDuration({
2509
+ start: timestamp,
2510
+ end: Date.now()
2511
+ // see https://github.com/date-fns/date-fns/issues/2891 – No idea why it's all partial it returns it every time.
2512
+ });
2513
+ const unit = intervals.find((intervalUnit) => {
2514
+ return interval[intervalUnit] > 0 && Object.keys(interval).includes(intervalUnit);
2515
+ });
2516
+ const relativeTime = isPast(timestamp) ? -interval[unit] : interval[unit];
2517
+ const customInterval = customIntervals.find(
2518
+ (custom) => interval[custom.unit] < custom.threshold
2519
+ );
2520
+ const displayText = customInterval ? customInterval.text : formatRelativeTime(relativeTime, unit, { numeric: "auto" });
2521
+ return /* @__PURE__ */ jsx(
2522
+ "time",
2523
+ {
2524
+ ref: forwardedRef,
2525
+ dateTime: timestamp.toISOString(),
2526
+ role: "time",
2527
+ title: `${formatDate(timestamp)} ${formatTime(timestamp)}`,
2528
+ ...restProps,
2529
+ children: displayText
2440
2530
  }
2441
- },
2442
- variant: "danger",
2443
- position: ["header", "table-row"]
2444
- };
2531
+ );
2532
+ }
2533
+ );
2534
+ const getDisplayName = ({
2535
+ firstname,
2536
+ lastname,
2537
+ username,
2538
+ email
2539
+ } = {}) => {
2540
+ if (username) {
2541
+ return username;
2542
+ }
2543
+ if (firstname) {
2544
+ return `${firstname} ${lastname ?? ""}`.trim();
2545
+ }
2546
+ return email ?? "";
2445
2547
  };
2446
- DeleteAction$1.type = "delete";
2447
- const DEFAULT_HEADER_ACTIONS = [EditTheModelAction, ConfigureTheViewAction, DeleteAction$1];
2448
- const Panels = () => {
2449
- const isCloning = useMatch(CLONE_PATH) !== null;
2450
- const [
2451
- {
2452
- query: { status }
2453
- }
2454
- ] = useQueryParams({
2455
- status: "draft"
2456
- });
2457
- const { model, id, document, meta, collectionType } = useDoc();
2458
- const plugins = useStrapiApp("Panels", (state) => state.plugins);
2459
- const props = {
2460
- activeTab: status,
2461
- model,
2462
- documentId: id,
2463
- document: isCloning ? void 0 : document,
2464
- meta: isCloning ? void 0 : meta,
2465
- collectionType
2466
- };
2467
- return /* @__PURE__ */ jsx(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: /* @__PURE__ */ jsx(
2468
- DescriptionComponentRenderer,
2469
- {
2470
- props,
2471
- descriptions: plugins["content-manager"].apis.getEditViewSidePanels(),
2472
- children: (panels) => panels.map(({ content, id: id2, ...description }) => /* @__PURE__ */ jsx(Panel, { ...description, children: content }, id2))
2473
- }
2474
- ) });
2548
+ const capitalise = (str) => str.charAt(0).toUpperCase() + str.slice(1);
2549
+ const DocumentStatus = ({ status = "draft", ...restProps }) => {
2550
+ const statusVariant = status === "draft" ? "secondary" : status === "published" ? "success" : "alternative";
2551
+ return /* @__PURE__ */ jsx(Status, { ...restProps, showBullet: false, size: "S", variant: statusVariant, children: /* @__PURE__ */ jsx(Typography, { tag: "span", variant: "omega", fontWeight: "bold", children: capitalise(status) }) });
2475
2552
  };
2476
- const ActionsPanel = () => {
2553
+ const Header = ({ isCreating, status, title: documentTitle = "Untitled" }) => {
2477
2554
  const { formatMessage } = useIntl();
2478
- return {
2479
- title: formatMessage({
2480
- id: "content-manager.containers.edit.panels.default.title",
2481
- defaultMessage: "Document"
2482
- }),
2483
- content: /* @__PURE__ */ jsx(ActionsPanelContent, {})
2484
- };
2555
+ const isCloning = useMatch(CLONE_PATH) !== null;
2556
+ const title = isCreating ? formatMessage({
2557
+ id: "content-manager.containers.edit.title.new",
2558
+ defaultMessage: "Create an entry"
2559
+ }) : documentTitle;
2560
+ return /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", paddingTop: 6, paddingBottom: 4, gap: 2, children: [
2561
+ /* @__PURE__ */ jsx(BackButton, {}),
2562
+ /* @__PURE__ */ jsxs(Flex, { width: "100%", justifyContent: "space-between", gap: "80px", alignItems: "flex-start", children: [
2563
+ /* @__PURE__ */ jsx(Typography, { variant: "alpha", tag: "h1", children: title }),
2564
+ /* @__PURE__ */ jsx(HeaderToolbar, {})
2565
+ ] }),
2566
+ status ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(DocumentStatus, { status: isCloning ? "draft" : status }) }) : null
2567
+ ] });
2485
2568
  };
2486
- ActionsPanel.type = "actions";
2487
- const ActionsPanelContent = () => {
2569
+ const HeaderToolbar = () => {
2570
+ const { formatMessage } = useIntl();
2488
2571
  const isCloning = useMatch(CLONE_PATH) !== null;
2489
2572
  const [
2490
2573
  {
@@ -2492,355 +2575,432 @@ const ActionsPanelContent = () => {
2492
2575
  }
2493
2576
  ] = useQueryParams();
2494
2577
  const { model, id, document, meta, collectionType } = useDoc();
2495
- const plugins = useStrapiApp("ActionsPanel", (state) => state.plugins);
2496
- const props = {
2497
- activeTab: status,
2498
- model,
2499
- documentId: id,
2500
- document: isCloning ? void 0 : document,
2501
- meta: isCloning ? void 0 : meta,
2502
- collectionType
2503
- };
2504
- return /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, width: "100%", children: [
2578
+ const plugins = useStrapiApp("HeaderToolbar", (state) => state.plugins);
2579
+ return /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
2505
2580
  /* @__PURE__ */ jsx(
2506
2581
  DescriptionComponentRenderer,
2507
2582
  {
2508
- props,
2509
- descriptions: plugins["content-manager"].apis.getDocumentActions(),
2510
- children: (actions2) => /* @__PURE__ */ jsx(DocumentActions, { actions: actions2 })
2583
+ props: {
2584
+ activeTab: status,
2585
+ model,
2586
+ documentId: id,
2587
+ document: isCloning ? void 0 : document,
2588
+ meta: isCloning ? void 0 : meta,
2589
+ collectionType
2590
+ },
2591
+ descriptions: plugins["content-manager"].apis.getHeaderActions(),
2592
+ children: (actions2) => {
2593
+ if (actions2.length > 0) {
2594
+ return /* @__PURE__ */ jsx(HeaderActions, { actions: actions2 });
2595
+ } else {
2596
+ return null;
2597
+ }
2598
+ }
2511
2599
  }
2512
2600
  ),
2513
- /* @__PURE__ */ jsx(InjectionZone, { area: "editView.right-links", slug: model })
2601
+ /* @__PURE__ */ jsx(
2602
+ DescriptionComponentRenderer,
2603
+ {
2604
+ props: {
2605
+ activeTab: status,
2606
+ model,
2607
+ documentId: id,
2608
+ document: isCloning ? void 0 : document,
2609
+ meta: isCloning ? void 0 : meta,
2610
+ collectionType
2611
+ },
2612
+ descriptions: plugins["content-manager"].apis.getDocumentActions(),
2613
+ children: (actions2) => {
2614
+ const headerActions = actions2.filter((action) => {
2615
+ const positions = Array.isArray(action.position) ? action.position : [action.position];
2616
+ return positions.includes("header");
2617
+ });
2618
+ return /* @__PURE__ */ jsx(
2619
+ DocumentActionsMenu,
2620
+ {
2621
+ actions: headerActions,
2622
+ label: formatMessage({
2623
+ id: "content-manager.containers.edit.header.more-actions",
2624
+ defaultMessage: "More actions"
2625
+ }),
2626
+ children: /* @__PURE__ */ jsx(Information, { activeTab: status })
2627
+ }
2628
+ );
2629
+ }
2630
+ }
2631
+ )
2514
2632
  ] });
2515
2633
  };
2516
- const Panel = React.forwardRef(({ children, title }, ref) => {
2517
- return /* @__PURE__ */ jsxs(
2518
- Flex,
2634
+ const Information = ({ activeTab }) => {
2635
+ const { formatMessage } = useIntl();
2636
+ const { document, meta } = useDoc();
2637
+ if (!document || !document.id) {
2638
+ return null;
2639
+ }
2640
+ const createAndUpdateDocument = activeTab === "draft" ? document : meta?.availableStatus.find((status) => status.publishedAt === null);
2641
+ const publishDocument = activeTab === "published" ? document : meta?.availableStatus.find((status) => status.publishedAt !== null);
2642
+ const creator = createAndUpdateDocument?.[CREATED_BY_ATTRIBUTE_NAME] ? getDisplayName(createAndUpdateDocument[CREATED_BY_ATTRIBUTE_NAME]) : null;
2643
+ const updator = createAndUpdateDocument?.[UPDATED_BY_ATTRIBUTE_NAME] ? getDisplayName(createAndUpdateDocument[UPDATED_BY_ATTRIBUTE_NAME]) : null;
2644
+ const information = [
2519
2645
  {
2520
- ref,
2521
- tag: "aside",
2522
- "aria-labelledby": "additional-information",
2523
- background: "neutral0",
2524
- borderColor: "neutral150",
2525
- hasRadius: true,
2526
- paddingBottom: 4,
2527
- paddingLeft: 4,
2528
- paddingRight: 4,
2529
- paddingTop: 4,
2530
- shadow: "tableShadow",
2531
- gap: 3,
2532
- direction: "column",
2533
- justifyContent: "stretch",
2534
- alignItems: "flex-start",
2535
- children: [
2536
- /* @__PURE__ */ jsx(Typography, { tag: "h2", variant: "sigma", textTransform: "uppercase", children: title }),
2537
- children
2538
- ]
2539
- }
2540
- );
2541
- });
2542
- const HOOKS = {
2543
- /**
2544
- * Hook that allows to mutate the displayed headers of the list view table
2545
- * @constant
2546
- * @type {string}
2547
- */
2548
- INJECT_COLUMN_IN_TABLE: "Admin/CM/pages/ListView/inject-column-in-table",
2549
- /**
2550
- * Hook that allows to mutate the CM's collection types links pre-set filters
2551
- * @constant
2552
- * @type {string}
2553
- */
2554
- MUTATE_COLLECTION_TYPES_LINKS: "Admin/CM/pages/App/mutate-collection-types-links",
2555
- /**
2556
- * Hook that allows to mutate the CM's edit view layout
2557
- * @constant
2558
- * @type {string}
2559
- */
2560
- MUTATE_EDIT_VIEW_LAYOUT: "Admin/CM/pages/EditView/mutate-edit-view-layout",
2561
- /**
2562
- * Hook that allows to mutate the CM's single types links pre-set filters
2563
- * @constant
2564
- * @type {string}
2565
- */
2566
- MUTATE_SINGLE_TYPES_LINKS: "Admin/CM/pages/App/mutate-single-types-links"
2567
- };
2568
- const contentTypesApi = contentManagerApi.injectEndpoints({
2569
- endpoints: (builder) => ({
2570
- getContentTypeConfiguration: builder.query({
2571
- query: (uid) => ({
2572
- url: `/content-manager/content-types/${uid}/configuration`,
2573
- method: "GET"
2646
+ isDisplayed: !!publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME],
2647
+ label: formatMessage({
2648
+ id: "content-manager.containers.edit.information.last-published.label",
2649
+ defaultMessage: "Last published"
2574
2650
  }),
2575
- transformResponse: (response) => response.data,
2576
- providesTags: (_result, _error, uid) => [
2577
- { type: "ContentTypesConfiguration", id: uid },
2578
- { type: "ContentTypeSettings", id: "LIST" }
2579
- ]
2580
- }),
2581
- getAllContentTypeSettings: builder.query({
2582
- query: () => "/content-manager/content-types-settings",
2583
- transformResponse: (response) => response.data,
2584
- providesTags: [{ type: "ContentTypeSettings", id: "LIST" }]
2585
- }),
2586
- updateContentTypeConfiguration: builder.mutation({
2587
- query: ({ uid, ...body }) => ({
2588
- url: `/content-manager/content-types/${uid}/configuration`,
2589
- method: "PUT",
2590
- data: body
2651
+ value: formatMessage(
2652
+ {
2653
+ id: "content-manager.containers.edit.information.last-published.value",
2654
+ defaultMessage: `Published {time}{isAnonymous, select, true {} other { by {author}}}`
2655
+ },
2656
+ {
2657
+ time: /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME]) }),
2658
+ isAnonymous: !publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME],
2659
+ author: publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME] ? getDisplayName(publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME]) : null
2660
+ }
2661
+ )
2662
+ },
2663
+ {
2664
+ isDisplayed: !!createAndUpdateDocument?.[UPDATED_AT_ATTRIBUTE_NAME],
2665
+ label: formatMessage({
2666
+ id: "content-manager.containers.edit.information.last-draft.label",
2667
+ defaultMessage: "Last draft"
2668
+ }),
2669
+ value: formatMessage(
2670
+ {
2671
+ id: "content-manager.containers.edit.information.last-draft.value",
2672
+ defaultMessage: `Modified {time}{isAnonymous, select, true {} other { by {author}}}`
2673
+ },
2674
+ {
2675
+ time: /* @__PURE__ */ jsx(
2676
+ RelativeTime,
2677
+ {
2678
+ timestamp: new Date(createAndUpdateDocument?.[UPDATED_AT_ATTRIBUTE_NAME])
2679
+ }
2680
+ ),
2681
+ isAnonymous: !updator,
2682
+ author: updator
2683
+ }
2684
+ )
2685
+ },
2686
+ {
2687
+ isDisplayed: !!createAndUpdateDocument?.[CREATED_AT_ATTRIBUTE_NAME],
2688
+ label: formatMessage({
2689
+ id: "content-manager.containers.edit.information.document.label",
2690
+ defaultMessage: "Document"
2591
2691
  }),
2592
- transformResponse: (response) => response.data,
2593
- invalidatesTags: (_result, _error, { uid }) => [
2594
- { type: "ContentTypesConfiguration", id: uid },
2595
- { type: "ContentTypeSettings", id: "LIST" },
2596
- // Is this necessary?
2597
- { type: "InitialData" }
2598
- ]
2599
- })
2600
- })
2601
- });
2602
- const {
2603
- useGetContentTypeConfigurationQuery,
2604
- useGetAllContentTypeSettingsQuery,
2605
- useUpdateContentTypeConfigurationMutation
2606
- } = contentTypesApi;
2607
- const checkIfAttributeIsDisplayable = (attribute) => {
2608
- const { type } = attribute;
2609
- if (type === "relation") {
2610
- return !attribute.relation.toLowerCase().includes("morph");
2611
- }
2612
- return !["json", "dynamiczone", "richtext", "password", "blocks"].includes(type) && !!type;
2613
- };
2614
- const getMainField = (attribute, mainFieldName, { schemas, components }) => {
2615
- if (!mainFieldName) {
2616
- return void 0;
2617
- }
2618
- const mainFieldType = attribute.type === "component" ? components[attribute.component].attributes[mainFieldName].type : (
2619
- // @ts-expect-error – `targetModel` does exist on the attribute for a relation.
2620
- schemas.find((schema) => schema.uid === attribute.targetModel)?.attributes[mainFieldName].type
2692
+ value: formatMessage(
2693
+ {
2694
+ id: "content-manager.containers.edit.information.document.value",
2695
+ defaultMessage: `Created {time}{isAnonymous, select, true {} other { by {author}}}`
2696
+ },
2697
+ {
2698
+ time: /* @__PURE__ */ jsx(
2699
+ RelativeTime,
2700
+ {
2701
+ timestamp: new Date(createAndUpdateDocument?.[CREATED_AT_ATTRIBUTE_NAME])
2702
+ }
2703
+ ),
2704
+ isAnonymous: !creator,
2705
+ author: creator
2706
+ }
2707
+ )
2708
+ }
2709
+ ].filter((info) => info.isDisplayed);
2710
+ return /* @__PURE__ */ jsx(
2711
+ Flex,
2712
+ {
2713
+ borderWidth: "1px 0 0 0",
2714
+ borderStyle: "solid",
2715
+ borderColor: "neutral150",
2716
+ direction: "column",
2717
+ marginTop: 2,
2718
+ tag: "dl",
2719
+ padding: 5,
2720
+ gap: 3,
2721
+ alignItems: "flex-start",
2722
+ marginLeft: "-0.4rem",
2723
+ marginRight: "-0.4rem",
2724
+ width: "calc(100% + 8px)",
2725
+ children: information.map((info) => /* @__PURE__ */ jsxs(Flex, { gap: 1, direction: "column", alignItems: "flex-start", children: [
2726
+ /* @__PURE__ */ jsx(Typography, { tag: "dt", variant: "pi", fontWeight: "bold", children: info.label }),
2727
+ /* @__PURE__ */ jsx(Typography, { tag: "dd", variant: "pi", textColor: "neutral600", children: info.value })
2728
+ ] }, info.label))
2729
+ }
2621
2730
  );
2622
- return {
2623
- name: mainFieldName,
2624
- type: mainFieldType ?? "string"
2625
- };
2626
- };
2627
- const DEFAULT_SETTINGS = {
2628
- bulkable: false,
2629
- filterable: false,
2630
- searchable: false,
2631
- pagination: false,
2632
- defaultSortBy: "",
2633
- defaultSortOrder: "asc",
2634
- mainField: "id",
2635
- pageSize: 10
2636
2731
  };
2637
- const useDocumentLayout = (model) => {
2638
- const { schema, components } = useDocument({ model, collectionType: "" }, { skip: true });
2639
- const [{ query }] = useQueryParams();
2640
- const runHookWaterfall = useStrapiApp("useDocumentLayout", (state) => state.runHookWaterfall);
2641
- const { toggleNotification } = useNotification();
2642
- const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
2643
- const { isLoading: isLoadingSchemas, schemas } = useContentTypeSchema();
2644
- const {
2645
- data,
2646
- isLoading: isLoadingConfigs,
2647
- error,
2648
- isFetching: isFetchingConfigs
2649
- } = useGetContentTypeConfigurationQuery(model);
2650
- const isLoading = isLoadingSchemas || isFetchingConfigs || isLoadingConfigs;
2651
- React.useEffect(() => {
2652
- if (error) {
2653
- toggleNotification({
2654
- type: "danger",
2655
- message: formatAPIError(error)
2656
- });
2732
+ const HeaderActions = ({ actions: actions2 }) => {
2733
+ const [dialogId, setDialogId] = React.useState(null);
2734
+ const handleClick = (action) => async (e) => {
2735
+ if (!("options" in action)) {
2736
+ const { onClick = () => false, dialog, id } = action;
2737
+ const muteDialog = await onClick(e);
2738
+ if (dialog && !muteDialog) {
2739
+ e.preventDefault();
2740
+ setDialogId(id);
2741
+ }
2657
2742
  }
2658
- }, [error, formatAPIError, toggleNotification]);
2659
- const editLayout = React.useMemo(
2660
- () => data && !isLoading ? formatEditLayout(data, { schemas, schema, components }) : {
2661
- layout: [],
2662
- components: {},
2663
- metadatas: {},
2664
- options: {},
2665
- settings: DEFAULT_SETTINGS
2666
- },
2667
- [data, isLoading, schemas, schema, components]
2668
- );
2669
- const listLayout = React.useMemo(() => {
2670
- return data && !isLoading ? formatListLayout(data, { schemas, schema, components }) : {
2671
- layout: [],
2672
- metadatas: {},
2673
- options: {},
2674
- settings: DEFAULT_SETTINGS
2675
- };
2676
- }, [data, isLoading, schemas, schema, components]);
2677
- const { layout: edit } = React.useMemo(
2678
- () => runHookWaterfall(HOOKS.MUTATE_EDIT_VIEW_LAYOUT, {
2679
- layout: editLayout,
2680
- query
2681
- }),
2682
- [editLayout, query, runHookWaterfall]
2683
- );
2684
- return {
2685
- error,
2686
- isLoading,
2687
- edit,
2688
- list: listLayout
2689
2743
  };
2690
- };
2691
- const useDocLayout = () => {
2692
- const { model } = useDoc();
2693
- return useDocumentLayout(model);
2694
- };
2695
- const formatEditLayout = (data, {
2696
- schemas,
2697
- schema,
2698
- components
2699
- }) => {
2700
- let currentPanelIndex = 0;
2701
- const panelledEditAttributes = convertEditLayoutToFieldLayouts(
2702
- data.contentType.layouts.edit,
2703
- schema?.attributes,
2704
- data.contentType.metadatas,
2705
- { configurations: data.components, schemas: components },
2706
- schemas
2707
- ).reduce((panels, row) => {
2708
- if (row.some((field) => field.type === "dynamiczone")) {
2709
- panels.push([row]);
2710
- currentPanelIndex += 2;
2744
+ const handleClose = () => {
2745
+ setDialogId(null);
2746
+ };
2747
+ return /* @__PURE__ */ jsx(Flex, { gap: 1, children: actions2.map((action) => {
2748
+ if (action.options) {
2749
+ return /* @__PURE__ */ jsx(
2750
+ SingleSelect,
2751
+ {
2752
+ size: "S",
2753
+ onChange: action.onSelect,
2754
+ "aria-label": action.label,
2755
+ ...action,
2756
+ children: action.options.map(({ label, ...option }) => /* @__PURE__ */ jsx(SingleSelectOption, { ...option, children: label }, option.value))
2757
+ },
2758
+ action.id
2759
+ );
2711
2760
  } else {
2712
- if (!panels[currentPanelIndex]) {
2713
- panels.push([]);
2761
+ if (action.type === "icon") {
2762
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
2763
+ /* @__PURE__ */ jsx(
2764
+ IconButton,
2765
+ {
2766
+ disabled: action.disabled,
2767
+ label: action.label,
2768
+ size: "S",
2769
+ onClick: handleClick(action),
2770
+ children: action.icon
2771
+ }
2772
+ ),
2773
+ action.dialog ? /* @__PURE__ */ jsx(
2774
+ HeaderActionDialog,
2775
+ {
2776
+ ...action.dialog,
2777
+ isOpen: dialogId === action.id,
2778
+ onClose: handleClose
2779
+ }
2780
+ ) : null
2781
+ ] }, action.id);
2714
2782
  }
2715
- panels[currentPanelIndex].push(row);
2716
2783
  }
2717
- return panels;
2718
- }, []);
2719
- const componentEditAttributes = Object.entries(data.components).reduce(
2720
- (acc, [uid, configuration]) => {
2721
- acc[uid] = {
2722
- layout: convertEditLayoutToFieldLayouts(
2723
- configuration.layouts.edit,
2724
- components[uid].attributes,
2725
- configuration.metadatas
2726
- ),
2727
- settings: {
2728
- ...configuration.settings,
2729
- icon: components[uid].info.icon,
2730
- displayName: components[uid].info.displayName
2731
- }
2732
- };
2733
- return acc;
2734
- },
2735
- {}
2736
- );
2737
- const editMetadatas = Object.entries(data.contentType.metadatas).reduce(
2738
- (acc, [attribute, metadata]) => {
2739
- return {
2740
- ...acc,
2741
- [attribute]: metadata.edit
2742
- };
2784
+ }) });
2785
+ };
2786
+ const HeaderActionDialog = ({
2787
+ onClose,
2788
+ onCancel,
2789
+ title,
2790
+ content: Content,
2791
+ isOpen
2792
+ }) => {
2793
+ const handleClose = async () => {
2794
+ if (onCancel) {
2795
+ await onCancel();
2796
+ }
2797
+ onClose();
2798
+ };
2799
+ return /* @__PURE__ */ jsx(Dialog.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxs(Dialog.Content, { children: [
2800
+ /* @__PURE__ */ jsx(Dialog.Header, { children: title }),
2801
+ typeof Content === "function" ? /* @__PURE__ */ jsx(Content, { onClose: handleClose }) : Content
2802
+ ] }) });
2803
+ };
2804
+ const ConfigureTheViewAction = ({ collectionType, model }) => {
2805
+ const navigate = useNavigate();
2806
+ const { formatMessage } = useIntl();
2807
+ return {
2808
+ label: formatMessage({
2809
+ id: "app.links.configure-view",
2810
+ defaultMessage: "Configure the view"
2811
+ }),
2812
+ icon: /* @__PURE__ */ jsx(ListPlus, {}),
2813
+ onClick: () => {
2814
+ navigate(`../${collectionType}/${model}/configurations/edit`);
2743
2815
  },
2744
- {}
2745
- );
2816
+ position: "header"
2817
+ };
2818
+ };
2819
+ ConfigureTheViewAction.type = "configure-the-view";
2820
+ const EditTheModelAction = ({ model }) => {
2821
+ const navigate = useNavigate();
2822
+ const { formatMessage } = useIntl();
2746
2823
  return {
2747
- layout: panelledEditAttributes,
2748
- components: componentEditAttributes,
2749
- metadatas: editMetadatas,
2750
- settings: {
2751
- ...data.contentType.settings,
2752
- displayName: schema?.info.displayName
2824
+ label: formatMessage({
2825
+ id: "content-manager.link-to-ctb",
2826
+ defaultMessage: "Edit the model"
2827
+ }),
2828
+ icon: /* @__PURE__ */ jsx(Pencil, {}),
2829
+ onClick: () => {
2830
+ navigate(`/plugins/content-type-builder/content-types/${model}`);
2753
2831
  },
2754
- options: {
2755
- ...schema?.options,
2756
- ...schema?.pluginOptions,
2757
- ...data.contentType.options
2758
- }
2832
+ position: "header"
2759
2833
  };
2760
2834
  };
2761
- const convertEditLayoutToFieldLayouts = (rows, attributes = {}, metadatas, components, schemas = []) => {
2762
- return rows.map(
2763
- (row) => row.map((field) => {
2764
- const attribute = attributes[field.name];
2765
- if (!attribute) {
2766
- return null;
2835
+ EditTheModelAction.type = "edit-the-model";
2836
+ const DeleteAction$1 = ({ documentId, model, collectionType, document }) => {
2837
+ const navigate = useNavigate();
2838
+ const { formatMessage } = useIntl();
2839
+ const listViewPathMatch = useMatch(LIST_PATH);
2840
+ const canDelete = useDocumentRBAC("DeleteAction", (state) => state.canDelete);
2841
+ const { delete: deleteAction } = useDocumentActions();
2842
+ const { toggleNotification } = useNotification();
2843
+ const setSubmitting = useForm("DeleteAction", (state) => state.setSubmitting);
2844
+ const isLocalized = document?.locale != null;
2845
+ return {
2846
+ disabled: !canDelete || !document,
2847
+ label: formatMessage(
2848
+ {
2849
+ id: "content-manager.actions.delete.label",
2850
+ defaultMessage: "Delete entry{isLocalized, select, true { (all locales)} other {}}"
2851
+ },
2852
+ { isLocalized }
2853
+ ),
2854
+ icon: /* @__PURE__ */ jsx(Trash, {}),
2855
+ dialog: {
2856
+ type: "dialog",
2857
+ title: formatMessage({
2858
+ id: "app.components.ConfirmDialog.title",
2859
+ defaultMessage: "Confirmation"
2860
+ }),
2861
+ content: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, children: [
2862
+ /* @__PURE__ */ jsx(WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
2863
+ /* @__PURE__ */ jsx(Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
2864
+ id: "content-manager.actions.delete.dialog.body",
2865
+ defaultMessage: "Are you sure?"
2866
+ }) })
2867
+ ] }),
2868
+ onConfirm: async () => {
2869
+ if (!listViewPathMatch) {
2870
+ setSubmitting(true);
2871
+ }
2872
+ try {
2873
+ if (!documentId && collectionType !== SINGLE_TYPES) {
2874
+ console.error(
2875
+ "You're trying to delete a document without an id, this is likely a bug with Strapi. Please open an issue."
2876
+ );
2877
+ toggleNotification({
2878
+ message: formatMessage({
2879
+ id: "content-manager.actions.delete.error",
2880
+ defaultMessage: "An error occurred while trying to delete the document."
2881
+ }),
2882
+ type: "danger"
2883
+ });
2884
+ return;
2885
+ }
2886
+ const res = await deleteAction({
2887
+ documentId,
2888
+ model,
2889
+ collectionType,
2890
+ params: {
2891
+ locale: "*"
2892
+ }
2893
+ });
2894
+ if (!("error" in res)) {
2895
+ navigate({ pathname: `../${collectionType}/${model}` }, { replace: true });
2896
+ }
2897
+ } finally {
2898
+ if (!listViewPathMatch) {
2899
+ setSubmitting(false);
2900
+ }
2901
+ }
2767
2902
  }
2768
- const { edit: metadata } = metadatas[field.name];
2769
- const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
2770
- return {
2771
- attribute,
2772
- disabled: !metadata.editable,
2773
- hint: metadata.description,
2774
- label: metadata.label ?? "",
2775
- name: field.name,
2776
- // @ts-expect-error – mainField does exist on the metadata for a relation.
2777
- mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
2778
- schemas,
2779
- components: components?.schemas ?? {}
2780
- }),
2781
- placeholder: metadata.placeholder ?? "",
2782
- required: attribute.required ?? false,
2783
- size: field.size,
2784
- unique: "unique" in attribute ? attribute.unique : false,
2785
- visible: metadata.visible ?? true,
2786
- type: attribute.type
2787
- };
2788
- }).filter((field) => field !== null)
2789
- );
2790
- };
2791
- const formatListLayout = (data, {
2792
- schemas,
2793
- schema,
2794
- components
2795
- }) => {
2796
- const listMetadatas = Object.entries(data.contentType.metadatas).reduce(
2797
- (acc, [attribute, metadata]) => {
2798
- return {
2799
- ...acc,
2800
- [attribute]: metadata.list
2801
- };
2802
2903
  },
2803
- {}
2804
- );
2805
- const listAttributes = convertListLayoutToFieldLayouts(
2806
- data.contentType.layouts.list,
2807
- schema?.attributes,
2808
- listMetadatas,
2809
- { configurations: data.components, schemas: components },
2810
- schemas
2811
- );
2812
- return {
2813
- layout: listAttributes,
2814
- settings: { ...data.contentType.settings, displayName: schema?.info.displayName },
2815
- metadatas: listMetadatas,
2816
- options: {
2817
- ...schema?.options,
2818
- ...schema?.pluginOptions,
2819
- ...data.contentType.options
2904
+ variant: "danger",
2905
+ position: ["header", "table-row"]
2906
+ };
2907
+ };
2908
+ DeleteAction$1.type = "delete";
2909
+ const DEFAULT_HEADER_ACTIONS = [EditTheModelAction, ConfigureTheViewAction, DeleteAction$1];
2910
+ const Panels = () => {
2911
+ const isCloning = useMatch(CLONE_PATH) !== null;
2912
+ const [
2913
+ {
2914
+ query: { status }
2915
+ }
2916
+ ] = useQueryParams({
2917
+ status: "draft"
2918
+ });
2919
+ const { model, id, document, meta, collectionType } = useDoc();
2920
+ const plugins = useStrapiApp("Panels", (state) => state.plugins);
2921
+ const props = {
2922
+ activeTab: status,
2923
+ model,
2924
+ documentId: id,
2925
+ document: isCloning ? void 0 : document,
2926
+ meta: isCloning ? void 0 : meta,
2927
+ collectionType
2928
+ };
2929
+ return /* @__PURE__ */ jsx(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: /* @__PURE__ */ jsx(
2930
+ DescriptionComponentRenderer,
2931
+ {
2932
+ props,
2933
+ descriptions: plugins["content-manager"].apis.getEditViewSidePanels(),
2934
+ children: (panels) => panels.map(({ content, id: id2, ...description }) => /* @__PURE__ */ jsx(Panel, { ...description, children: content }, id2))
2820
2935
  }
2936
+ ) });
2937
+ };
2938
+ const ActionsPanel = () => {
2939
+ const { formatMessage } = useIntl();
2940
+ return {
2941
+ title: formatMessage({
2942
+ id: "content-manager.containers.edit.panels.default.title",
2943
+ defaultMessage: "Entry"
2944
+ }),
2945
+ content: /* @__PURE__ */ jsx(ActionsPanelContent, {})
2821
2946
  };
2822
2947
  };
2823
- const convertListLayoutToFieldLayouts = (columns, attributes = {}, metadatas, components, schemas = []) => {
2824
- return columns.map((name) => {
2825
- const attribute = attributes[name];
2826
- if (!attribute) {
2827
- return null;
2948
+ ActionsPanel.type = "actions";
2949
+ const ActionsPanelContent = () => {
2950
+ const isCloning = useMatch(CLONE_PATH) !== null;
2951
+ const [
2952
+ {
2953
+ query: { status = "draft" }
2828
2954
  }
2829
- const metadata = metadatas[name];
2830
- const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
2831
- return {
2832
- attribute,
2833
- label: metadata.label ?? "",
2834
- mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
2835
- schemas,
2836
- components: components?.schemas ?? {}
2837
- }),
2838
- name,
2839
- searchable: metadata.searchable ?? true,
2840
- sortable: metadata.sortable ?? true
2841
- };
2842
- }).filter((field) => field !== null);
2955
+ ] = useQueryParams();
2956
+ const { model, id, document, meta, collectionType } = useDoc();
2957
+ const plugins = useStrapiApp("ActionsPanel", (state) => state.plugins);
2958
+ const props = {
2959
+ activeTab: status,
2960
+ model,
2961
+ documentId: id,
2962
+ document: isCloning ? void 0 : document,
2963
+ meta: isCloning ? void 0 : meta,
2964
+ collectionType
2965
+ };
2966
+ return /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, width: "100%", children: [
2967
+ /* @__PURE__ */ jsx(
2968
+ DescriptionComponentRenderer,
2969
+ {
2970
+ props,
2971
+ descriptions: plugins["content-manager"].apis.getDocumentActions(),
2972
+ children: (actions2) => /* @__PURE__ */ jsx(DocumentActions, { actions: actions2 })
2973
+ }
2974
+ ),
2975
+ /* @__PURE__ */ jsx(InjectionZone, { area: "editView.right-links", slug: model })
2976
+ ] });
2843
2977
  };
2978
+ const Panel = React.forwardRef(({ children, title }, ref) => {
2979
+ return /* @__PURE__ */ jsxs(
2980
+ Flex,
2981
+ {
2982
+ ref,
2983
+ tag: "aside",
2984
+ "aria-labelledby": "additional-information",
2985
+ background: "neutral0",
2986
+ borderColor: "neutral150",
2987
+ hasRadius: true,
2988
+ paddingBottom: 4,
2989
+ paddingLeft: 4,
2990
+ paddingRight: 4,
2991
+ paddingTop: 4,
2992
+ shadow: "tableShadow",
2993
+ gap: 3,
2994
+ direction: "column",
2995
+ justifyContent: "stretch",
2996
+ alignItems: "flex-start",
2997
+ children: [
2998
+ /* @__PURE__ */ jsx(Typography, { tag: "h2", variant: "sigma", textTransform: "uppercase", children: title }),
2999
+ children
3000
+ ]
3001
+ }
3002
+ );
3003
+ });
2844
3004
  const ConfirmBulkActionDialog = ({
2845
3005
  onToggleDialog,
2846
3006
  isOpen = false,
@@ -2879,6 +3039,7 @@ const ConfirmDialogPublishAll = ({
2879
3039
  const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler(getTranslation);
2880
3040
  const { model, schema } = useDoc();
2881
3041
  const [{ query }] = useQueryParams();
3042
+ const enableDraftRelationsCount = false;
2882
3043
  const {
2883
3044
  data: countDraftRelations = 0,
2884
3045
  isLoading,
@@ -2890,7 +3051,7 @@ const ConfirmDialogPublishAll = ({
2890
3051
  locale: query?.plugins?.i18n?.locale
2891
3052
  },
2892
3053
  {
2893
- skip: selectedEntries.length === 0
3054
+ skip: !enableDraftRelationsCount
2894
3055
  }
2895
3056
  );
2896
3057
  React.useEffect(() => {
@@ -3075,7 +3236,7 @@ const SelectedEntriesTableContent = ({
3075
3236
  status: row.status
3076
3237
  }
3077
3238
  ) }),
3078
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
3239
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Flex, { children: /* @__PURE__ */ jsx(
3079
3240
  IconButton,
3080
3241
  {
3081
3242
  tag: Link,
@@ -3098,9 +3259,10 @@ const SelectedEntriesTableContent = ({
3098
3259
  ),
3099
3260
  target: "_blank",
3100
3261
  marginLeft: "auto",
3101
- children: /* @__PURE__ */ jsx(Pencil, {})
3262
+ variant: "ghost",
3263
+ children: /* @__PURE__ */ jsx(Pencil, { width: "1.6rem", height: "1.6rem" })
3102
3264
  }
3103
- ) })
3265
+ ) }) })
3104
3266
  ] }, row.id)) })
3105
3267
  ] });
3106
3268
  };
@@ -3137,7 +3299,13 @@ const SelectedEntriesModalContent = ({
3137
3299
  );
3138
3300
  const { rows, validationErrors } = React.useMemo(() => {
3139
3301
  if (data.length > 0 && schema) {
3140
- const validate = createYupSchema(schema.attributes, components);
3302
+ const validate = createYupSchema(
3303
+ schema.attributes,
3304
+ components,
3305
+ // Since this is the "Publish" action, the validation
3306
+ // schema must enforce the rules for published entities
3307
+ { status: "published" }
3308
+ );
3141
3309
  const validationErrors2 = {};
3142
3310
  const rows2 = data.map((entry) => {
3143
3311
  try {
@@ -3487,7 +3655,7 @@ const TableActions = ({ document }) => {
3487
3655
  DescriptionComponentRenderer,
3488
3656
  {
3489
3657
  props,
3490
- descriptions: plugins["content-manager"].apis.getDocumentActions(),
3658
+ descriptions: plugins["content-manager"].apis.getDocumentActions().filter((action) => action.name !== "PublishAction"),
3491
3659
  children: (actions2) => {
3492
3660
  const tableRowActions = actions2.filter((action) => {
3493
3661
  const positions = Array.isArray(action.position) ? action.position : [action.position];
@@ -3598,7 +3766,7 @@ const CloneAction = ({ model, documentId }) => {
3598
3766
  }),
3599
3767
  content: /* @__PURE__ */ jsx(AutoCloneFailureModalBody, { prohibitedFields }),
3600
3768
  footer: ({ onClose }) => {
3601
- return /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", children: [
3769
+ return /* @__PURE__ */ jsxs(Modal.Footer, { children: [
3602
3770
  /* @__PURE__ */ jsx(Button, { onClick: onClose, variant: "tertiary", children: formatMessage({
3603
3771
  id: "cancel",
3604
3772
  defaultMessage: "Cancel"
@@ -3829,7 +3997,7 @@ const index = {
3829
3997
  app.router.addRoute({
3830
3998
  path: "content-manager/*",
3831
3999
  lazy: async () => {
3832
- const { Layout } = await import("./layout-DPaHUusj.mjs");
4000
+ const { Layout } = await import("./layout-DaUjDiWQ.mjs");
3833
4001
  return {
3834
4002
  Component: Layout
3835
4003
  };
@@ -3846,7 +4014,7 @@ const index = {
3846
4014
  async registerTrads({ locales }) {
3847
4015
  const importedTrads = await Promise.all(
3848
4016
  locales.map((locale) => {
3849
- return __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/ar.json": () => import("./ar-CCEVvqGG.mjs"), "./translations/ca.json": () => import("./ca-5U32ON2v.mjs"), "./translations/cs.json": () => import("./cs-CM2aBUar.mjs"), "./translations/de.json": () => import("./de-C72KDNOl.mjs"), "./translations/en.json": () => import("./en-BrCTWlZv.mjs"), "./translations/es.json": () => import("./es-CeXiYflN.mjs"), "./translations/eu.json": () => import("./eu-CdALomew.mjs"), "./translations/fr.json": () => import("./fr-CD9VFbPM.mjs"), "./translations/gu.json": () => import("./gu-CNpaMDpH.mjs"), "./translations/hi.json": () => import("./hi-Dwvd04m3.mjs"), "./translations/hu.json": () => import("./hu-CeYvaaO0.mjs"), "./translations/id.json": () => import("./id-BtwA9WJT.mjs"), "./translations/it.json": () => import("./it-BrVPqaf1.mjs"), "./translations/ja.json": () => import("./ja-CtsUxOvk.mjs"), "./translations/ko.json": () => import("./ko-HVQRlfUI.mjs"), "./translations/ml.json": () => import("./ml-BihZwQit.mjs"), "./translations/ms.json": () => import("./ms-m_WjyWx7.mjs"), "./translations/nl.json": () => import("./nl-D4R9gHx5.mjs"), "./translations/pl.json": () => import("./pl-sbx9mSt_.mjs"), "./translations/pt-BR.json": () => import("./pt-BR-C71iDxnh.mjs"), "./translations/pt.json": () => import("./pt-BsaFvS8-.mjs"), "./translations/ru.json": () => import("./ru-BE6A4Exp.mjs"), "./translations/sa.json": () => import("./sa-Dag0k-Z8.mjs"), "./translations/sk.json": () => import("./sk-BFg-R8qJ.mjs"), "./translations/sv.json": () => import("./sv-CUnfWGsh.mjs"), "./translations/th.json": () => import("./th-BqbI8lIT.mjs"), "./translations/tr.json": () => import("./tr-CgeK3wJM.mjs"), "./translations/uk.json": () => import("./uk-CR-zDhAY.mjs"), "./translations/vi.json": () => import("./vi-DUXIk_fw.mjs"), "./translations/zh-Hans.json": () => import("./zh-Hans-BPQcRIyH.mjs"), "./translations/zh.json": () => import("./zh-BWZspA60.mjs") }), `./translations/${locale}.json`).then(({ default: data }) => {
4017
+ 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-C8YBvRrK.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 }) => {
3850
4018
  return {
3851
4019
  data: prefixPluginTranslations(data, PLUGIN_ID),
3852
4020
  locale
@@ -3867,13 +4035,15 @@ export {
3867
4035
  BulkActionsRenderer as B,
3868
4036
  COLLECTION_TYPES as C,
3869
4037
  DocumentStatus as D,
3870
- DEFAULT_SETTINGS as E,
3871
- convertEditLayoutToFieldLayouts as F,
3872
- useDocument as G,
4038
+ extractContentTypeComponents as E,
4039
+ DEFAULT_SETTINGS as F,
4040
+ convertEditLayoutToFieldLayouts as G,
3873
4041
  HOOKS as H,
3874
4042
  InjectionZone as I,
3875
- index as J,
3876
- useDocumentActions as K,
4043
+ useDocument as J,
4044
+ index as K,
4045
+ useContentManagerContext as L,
4046
+ useDocumentActions as M,
3877
4047
  Panels as P,
3878
4048
  RelativeTime as R,
3879
4049
  SINGLE_TYPES as S,
@@ -3891,18 +4061,18 @@ export {
3891
4061
  PERMISSIONS as k,
3892
4062
  DocumentRBAC as l,
3893
4063
  DOCUMENT_META_FIELDS as m,
3894
- useDocLayout as n,
3895
- useGetContentTypeConfigurationQuery as o,
3896
- CREATOR_FIELDS as p,
3897
- getMainField as q,
3898
- getDisplayName as r,
4064
+ CLONE_PATH as n,
4065
+ useDocLayout as o,
4066
+ useGetContentTypeConfigurationQuery as p,
4067
+ CREATOR_FIELDS as q,
4068
+ getMainField as r,
3899
4069
  setInitialData as s,
3900
- checkIfAttributeIsDisplayable as t,
4070
+ getDisplayName as t,
3901
4071
  useContentTypeSchema as u,
3902
- useGetAllDocumentsQuery as v,
3903
- convertListLayoutToFieldLayouts as w,
3904
- capitalise as x,
3905
- useUpdateContentTypeConfigurationMutation as y,
3906
- extractContentTypeComponents as z
4072
+ checkIfAttributeIsDisplayable as v,
4073
+ useGetAllDocumentsQuery as w,
4074
+ convertListLayoutToFieldLayouts as x,
4075
+ capitalise as y,
4076
+ useUpdateContentTypeConfigurationMutation as z
3907
4077
  };
3908
- //# sourceMappingURL=index-BSn97i8U.mjs.map
4078
+ //# sourceMappingURL=index-C9HxCo5R.mjs.map