@strapi/content-manager 0.0.0-experimental.145e7d7ddefd1aef71aaf3d9bb86440d013035bf → 0.0.0-experimental.1610404a03d98b65f497f9adda35815021b8fd76

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-DJEJ49QD.mjs → ComponentConfigurationPage-C5xJlht8.mjs} +4 -4
  2. package/dist/_chunks/{ComponentConfigurationPage-DJEJ49QD.mjs.map → ComponentConfigurationPage-C5xJlht8.mjs.map} +1 -1
  3. package/dist/_chunks/{ComponentConfigurationPage-D_g11bYV.js → ComponentConfigurationPage-CyzIs3Wp.js} +5 -6
  4. package/dist/_chunks/{ComponentConfigurationPage-D_g11bYV.js.map → ComponentConfigurationPage-CyzIs3Wp.js.map} +1 -1
  5. package/dist/_chunks/{ComponentIcon-BXdiCGQp.js → ComponentIcon-CRbtQEUV.js} +2 -3
  6. package/dist/_chunks/{ComponentIcon-BXdiCGQp.js.map → ComponentIcon-CRbtQEUV.js.map} +1 -1
  7. package/dist/_chunks/{EditConfigurationPage-QBZdUYyG.mjs → EditConfigurationPage-B1JTGbDa.mjs} +4 -4
  8. package/dist/_chunks/{EditConfigurationPage-QBZdUYyG.mjs.map → EditConfigurationPage-B1JTGbDa.mjs.map} +1 -1
  9. package/dist/_chunks/{EditConfigurationPage-CeL712KW.js → EditConfigurationPage-BoY4_TQp.js} +5 -6
  10. package/dist/_chunks/{EditConfigurationPage-CeL712KW.js.map → EditConfigurationPage-BoY4_TQp.js.map} +1 -1
  11. package/dist/_chunks/{EditViewPage-CvRUUpVh.mjs → EditViewPage-B8Uz26ZK.mjs} +15 -5
  12. package/dist/_chunks/EditViewPage-B8Uz26ZK.mjs.map +1 -0
  13. package/dist/_chunks/{EditViewPage-g5TwrgRY.js → EditViewPage-DxszpwJd.js} +16 -7
  14. package/dist/_chunks/EditViewPage-DxszpwJd.js.map +1 -0
  15. package/dist/_chunks/{Field-reyvfnop.mjs → Field-CquZadQb.mjs} +93 -78
  16. package/dist/_chunks/Field-CquZadQb.mjs.map +1 -0
  17. package/dist/_chunks/{Field-ncdInvxS.js → Field-DzgX4ael.js} +99 -85
  18. package/dist/_chunks/Field-DzgX4ael.js.map +1 -0
  19. package/dist/_chunks/{Form-BJ7bYiUx.js → Form-C8a2ftQg.js} +17 -12
  20. package/dist/_chunks/Form-C8a2ftQg.js.map +1 -0
  21. package/dist/_chunks/{Form-DoMGsYxH.mjs → Form-DuJm__5A.mjs} +15 -9
  22. package/dist/_chunks/Form-DuJm__5A.mjs.map +1 -0
  23. package/dist/_chunks/{History-pbhkxIrf.js → History-B1sa22d-.js} +25 -13
  24. package/dist/_chunks/History-B1sa22d-.js.map +1 -0
  25. package/dist/_chunks/{History-BseDJOrj.mjs → History-C7xPTX16.mjs} +25 -12
  26. package/dist/_chunks/History-C7xPTX16.mjs.map +1 -0
  27. package/dist/_chunks/{ListConfigurationPage-DWE_fr5B.mjs → ListConfigurationPage-C6zgYhFZ.mjs} +4 -4
  28. package/dist/_chunks/{ListConfigurationPage-DWE_fr5B.mjs.map → ListConfigurationPage-C6zgYhFZ.mjs.map} +1 -1
  29. package/dist/_chunks/{ListConfigurationPage-Bna8zfjr.js → ListConfigurationPage-CYWB9ZNc.js} +5 -6
  30. package/dist/_chunks/{ListConfigurationPage-Bna8zfjr.js.map → ListConfigurationPage-CYWB9ZNc.js.map} +1 -1
  31. package/dist/_chunks/{ListViewPage-lQ-VLV2G.mjs → ListViewPage-BkzGZ8pS.mjs} +36 -13
  32. package/dist/_chunks/ListViewPage-BkzGZ8pS.mjs.map +1 -0
  33. package/dist/_chunks/{ListViewPage-Dymsvnv6.js → ListViewPage-D7jZkQ1q.js} +41 -19
  34. package/dist/_chunks/ListViewPage-D7jZkQ1q.js.map +1 -0
  35. package/dist/_chunks/{NoContentTypePage-B4t_OsDR.js → NoContentTypePage-C-eluJ5b.js} +2 -2
  36. package/dist/_chunks/{NoContentTypePage-B4t_OsDR.js.map → NoContentTypePage-C-eluJ5b.js.map} +1 -1
  37. package/dist/_chunks/{NoContentTypePage-VCQOMwlf.mjs → NoContentTypePage-CNTp9Je-.mjs} +2 -2
  38. package/dist/_chunks/{NoContentTypePage-VCQOMwlf.mjs.map → NoContentTypePage-CNTp9Je-.mjs.map} +1 -1
  39. package/dist/_chunks/{NoPermissionsPage-BOwB6hki.js → NoPermissionsPage-5BRSh_ql.js} +2 -2
  40. package/dist/_chunks/{NoPermissionsPage-BOwB6hki.js.map → NoPermissionsPage-5BRSh_ql.js.map} +1 -1
  41. package/dist/_chunks/{NoPermissionsPage-TV830k4P.mjs → NoPermissionsPage-B1-Nl_T8.mjs} +2 -2
  42. package/dist/_chunks/{NoPermissionsPage-TV830k4P.mjs.map → NoPermissionsPage-B1-Nl_T8.mjs.map} +1 -1
  43. package/dist/_chunks/{Relations-DdlstXTu.js → Relations-CXy88hPa.js} +72 -42
  44. package/dist/_chunks/Relations-CXy88hPa.js.map +1 -0
  45. package/dist/_chunks/{Relations-D6NAlnsl.mjs → Relations-DqHS7-Cx.mjs} +72 -41
  46. package/dist/_chunks/Relations-DqHS7-Cx.mjs.map +1 -0
  47. package/dist/_chunks/{en-Cf41pH5f.js → en-Bm0D0IWz.js} +9 -9
  48. package/dist/_chunks/{en-Cf41pH5f.js.map → en-Bm0D0IWz.js.map} +1 -1
  49. package/dist/_chunks/{en-DCszE74t.mjs → en-DKV44jRb.mjs} +9 -9
  50. package/dist/_chunks/{en-DCszE74t.mjs.map → en-DKV44jRb.mjs.map} +1 -1
  51. package/dist/_chunks/{index-CQos-KS0.js → index-BwWfprNi.js} +1905 -1763
  52. package/dist/_chunks/index-BwWfprNi.js.map +1 -0
  53. package/dist/_chunks/{index-BYSWwHBJ.mjs → index-LwSbWlvf.mjs} +1906 -1764
  54. package/dist/_chunks/index-LwSbWlvf.mjs.map +1 -0
  55. package/dist/_chunks/{layout-0TY7UtKO.mjs → layout-BS7u3JUZ.mjs} +6 -5
  56. package/dist/_chunks/{layout-0TY7UtKO.mjs.map → layout-BS7u3JUZ.mjs.map} +1 -1
  57. package/dist/_chunks/{layout-B4XAqu1v.js → layout-a7hNwceU.js} +7 -7
  58. package/dist/_chunks/{layout-B4XAqu1v.js.map → layout-a7hNwceU.js.map} +1 -1
  59. package/dist/_chunks/{objects-gigeqt7s.js → objects-BcXOv6_9.js} +2 -4
  60. package/dist/_chunks/{objects-gigeqt7s.js.map → objects-BcXOv6_9.js.map} +1 -1
  61. package/dist/_chunks/{objects-mKMAmfec.mjs → objects-D6yBsdmx.mjs} +2 -4
  62. package/dist/_chunks/{objects-mKMAmfec.mjs.map → objects-D6yBsdmx.mjs.map} +1 -1
  63. package/dist/_chunks/{relations-xZ2tMj1G.js → relations-DedckVWc.js} +3 -7
  64. package/dist/_chunks/relations-DedckVWc.js.map +1 -0
  65. package/dist/_chunks/{relations-DFDWfa0s.mjs → relations-L9TlNEnv.mjs} +3 -7
  66. package/dist/_chunks/relations-L9TlNEnv.mjs.map +1 -0
  67. package/dist/_chunks/{usePrev-B9w_-eYc.js → useDebounce-CtcjDB3L.js} +14 -1
  68. package/dist/_chunks/useDebounce-CtcjDB3L.js.map +1 -0
  69. package/dist/_chunks/useDebounce-DmuSJIF3.mjs +29 -0
  70. package/dist/_chunks/useDebounce-DmuSJIF3.mjs.map +1 -0
  71. package/dist/_chunks/{useDragAndDrop-J0TUUbR6.js → useDragAndDrop-BMtgCYzL.js} +5 -9
  72. package/dist/_chunks/{useDragAndDrop-J0TUUbR6.js.map → useDragAndDrop-BMtgCYzL.js.map} +1 -1
  73. package/dist/_chunks/{useDragAndDrop-DdHgKsqq.mjs → useDragAndDrop-DJ6jqvZN.mjs} +4 -7
  74. package/dist/_chunks/{useDragAndDrop-DdHgKsqq.mjs.map → useDragAndDrop-DJ6jqvZN.mjs.map} +1 -1
  75. package/dist/admin/index.js +2 -1
  76. package/dist/admin/index.js.map +1 -1
  77. package/dist/admin/index.mjs +3 -2
  78. package/dist/admin/src/exports.d.ts +1 -1
  79. package/dist/admin/src/hooks/useDocument.d.ts +32 -1
  80. package/dist/admin/src/pages/EditView/components/Header.d.ts +11 -11
  81. package/dist/admin/src/preview/constants.d.ts +1 -0
  82. package/dist/admin/src/preview/index.d.ts +4 -0
  83. package/dist/server/index.js +104 -69
  84. package/dist/server/index.js.map +1 -1
  85. package/dist/server/index.mjs +103 -67
  86. package/dist/server/index.mjs.map +1 -1
  87. package/dist/server/src/bootstrap.d.ts.map +1 -1
  88. package/dist/server/src/controllers/collection-types.d.ts.map +1 -1
  89. package/dist/server/src/controllers/relations.d.ts.map +1 -1
  90. package/dist/server/src/controllers/utils/metadata.d.ts +15 -1
  91. package/dist/server/src/controllers/utils/metadata.d.ts.map +1 -1
  92. package/dist/server/src/history/services/history.d.ts.map +1 -1
  93. package/dist/server/src/index.d.ts +4 -4
  94. package/dist/server/src/preview/constants.d.ts +2 -0
  95. package/dist/server/src/preview/constants.d.ts.map +1 -0
  96. package/dist/server/src/preview/index.d.ts +4 -0
  97. package/dist/server/src/preview/index.d.ts.map +1 -0
  98. package/dist/server/src/services/document-metadata.d.ts +8 -8
  99. package/dist/server/src/services/document-metadata.d.ts.map +1 -1
  100. package/dist/server/src/services/index.d.ts +4 -4
  101. package/dist/server/src/services/utils/configuration/index.d.ts +2 -2
  102. package/dist/server/src/services/utils/configuration/layouts.d.ts +2 -2
  103. package/dist/server/src/utils/index.d.ts +2 -0
  104. package/dist/server/src/utils/index.d.ts.map +1 -1
  105. package/package.json +13 -13
  106. package/dist/_chunks/EditViewPage-CvRUUpVh.mjs.map +0 -1
  107. package/dist/_chunks/EditViewPage-g5TwrgRY.js.map +0 -1
  108. package/dist/_chunks/Field-ncdInvxS.js.map +0 -1
  109. package/dist/_chunks/Field-reyvfnop.mjs.map +0 -1
  110. package/dist/_chunks/Form-BJ7bYiUx.js.map +0 -1
  111. package/dist/_chunks/Form-DoMGsYxH.mjs.map +0 -1
  112. package/dist/_chunks/History-BseDJOrj.mjs.map +0 -1
  113. package/dist/_chunks/History-pbhkxIrf.js.map +0 -1
  114. package/dist/_chunks/ListViewPage-Dymsvnv6.js.map +0 -1
  115. package/dist/_chunks/ListViewPage-lQ-VLV2G.mjs.map +0 -1
  116. package/dist/_chunks/Relations-D6NAlnsl.mjs.map +0 -1
  117. package/dist/_chunks/Relations-DdlstXTu.js.map +0 -1
  118. package/dist/_chunks/index-BYSWwHBJ.mjs.map +0 -1
  119. package/dist/_chunks/index-CQos-KS0.js.map +0 -1
  120. package/dist/_chunks/relations-DFDWfa0s.mjs.map +0 -1
  121. package/dist/_chunks/relations-xZ2tMj1G.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,9 +1,10 @@
1
1
  import { More, Cross, WarningCircle, ListPlus, Pencil, Trash, Check, CrossCircle, CheckCircle, ArrowsCounterClockwise, ChevronRight, Duplicate, ClockCounterClockwise, Feather } from "@strapi/icons";
2
2
  import { jsx, Fragment, jsxs } from "react/jsx-runtime";
3
- import { useStrapiApp, createContext, useAuth, useRBAC, Page, adminApi, translatedErrors, useNotification, useAPIErrorHandler, getYupValidationErrors, useQueryParams, useTracking, useForm, BackButton, DescriptionComponentRenderer, useTable, Table } from "@strapi/admin/strapi-admin";
3
+ import { useStrapiApp, createContext, useQueryParams, useAuth, useRBAC, Page, adminApi, translatedErrors, useNotification, useAPIErrorHandler, getYupValidationErrors, useForm, useTracking, useGuidedTour, BackButton, DescriptionComponentRenderer, useTable, Table } from "@strapi/admin/strapi-admin";
4
4
  import * as React from "react";
5
5
  import { lazy } from "react";
6
- import { Button, Menu, VisuallyHidden, Flex, Typography, Dialog, Modal, Radio, Status, Box, 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
+ import mapValues from "lodash/fp/mapValues";
7
8
  import { useIntl } from "react-intl";
8
9
  import { useParams, useNavigate, Navigate, useMatch, useLocation, Link, NavLink } from "react-router-dom";
9
10
  import * as yup from "yup";
@@ -13,13 +14,20 @@ import { intervalToDuration, isPast } from "date-fns";
13
14
  import { styled } from "styled-components";
14
15
  import { stringify } from "qs";
15
16
  import { createSlice, combineReducers } from "@reduxjs/toolkit";
16
- const __variableDynamicImportRuntimeHelper = (glob, path) => {
17
+ const __variableDynamicImportRuntimeHelper = (glob, path, segs) => {
17
18
  const v = glob[path];
18
19
  if (v) {
19
20
  return typeof v === "function" ? v() : Promise.resolve(v);
20
21
  }
21
22
  return new Promise((_, reject) => {
22
- (typeof queueMicrotask === "function" ? queueMicrotask : setTimeout)(reject.bind(null, new Error("Unknown variable dynamic import: " + path)));
23
+ (typeof queueMicrotask === "function" ? queueMicrotask : setTimeout)(
24
+ reject.bind(
25
+ null,
26
+ new Error(
27
+ "Unknown variable dynamic import: " + path + (path.split("/").length !== segs ? ". Note that variables only represent file names one level deep." : "")
28
+ )
29
+ )
30
+ );
23
31
  });
24
32
  };
25
33
  const PLUGIN_ID = "content-manager";
@@ -100,6 +108,7 @@ const DocumentRBAC = ({ children, permissions }) => {
100
108
  if (!slug) {
101
109
  throw new Error("Cannot find the slug param in the URL");
102
110
  }
111
+ const [{ rawQuery }] = useQueryParams();
103
112
  const userPermissions = useAuth("DocumentRBAC", (state) => state.permissions);
104
113
  const contentTypePermissions = React.useMemo(() => {
105
114
  const contentTypePermissions2 = userPermissions.filter(
@@ -110,7 +119,14 @@ const DocumentRBAC = ({ children, permissions }) => {
110
119
  return { ...acc, [action]: [permission] };
111
120
  }, {});
112
121
  }, [slug, userPermissions]);
113
- const { isLoading, allowedActions } = useRBAC(contentTypePermissions, permissions ?? void 0);
122
+ const { isLoading, allowedActions } = useRBAC(
123
+ contentTypePermissions,
124
+ permissions ?? void 0,
125
+ // TODO: useRBAC context should be typed and built differently
126
+ // We are passing raw query as context to the hook so that it can
127
+ // rely on the locale provided from DocumentRBAC for its permission calculations.
128
+ rawQuery
129
+ );
114
130
  const canCreateFields = !isLoading && allowedActions.canCreate ? extractAndDedupeFields(contentTypePermissions.create) : [];
115
131
  const canReadFields = !isLoading && allowedActions.canRead ? extractAndDedupeFields(contentTypePermissions.read) : [];
116
132
  const canUpdateFields = !isLoading && allowedActions.canUpdate ? extractAndDedupeFields(contentTypePermissions.update) : [];
@@ -262,7 +278,7 @@ const documentApi = contentManagerApi.injectEndpoints({
262
278
  */
263
279
  getAllDocuments: builder.query({
264
280
  query: ({ model, params }) => ({
265
- url: `/content-manager/collection-types/${model}`,
281
+ url: `/content-manager/collection-types/${model}${params ? `?${params}` : ""}`,
266
282
  method: "GET",
267
283
  config: {
268
284
  params
@@ -444,8 +460,7 @@ const {
444
460
  useUnpublishManyDocumentsMutation
445
461
  } = documentApi;
446
462
  const buildValidParams = (query) => {
447
- if (!query)
448
- return query;
463
+ if (!query) return query;
449
464
  const { plugins: _, ...validQueryParams } = {
450
465
  ...query,
451
466
  ...Object.values(query?.plugins ?? {}).reduce(
@@ -461,6 +476,24 @@ const buildValidParams = (query) => {
461
476
  const isBaseQueryError = (error) => {
462
477
  return error.name !== void 0;
463
478
  };
479
+ const arrayValidator = (attribute, options) => ({
480
+ message: translatedErrors.required,
481
+ test(value) {
482
+ if (options.status === "draft") {
483
+ return true;
484
+ }
485
+ if (!attribute.required) {
486
+ return true;
487
+ }
488
+ if (!value) {
489
+ return false;
490
+ }
491
+ if (Array.isArray(value) && value.length === 0) {
492
+ return false;
493
+ }
494
+ return true;
495
+ }
496
+ });
464
497
  const createYupSchema = (attributes = {}, components = {}, options = { status: null }) => {
465
498
  const createModelSchema = (attributes2) => yup.object().shape(
466
499
  Object.entries(attributes2).reduce((acc, [name, attribute]) => {
@@ -468,6 +501,7 @@ const createYupSchema = (attributes = {}, components = {}, options = { status: n
468
501
  return acc;
469
502
  }
470
503
  const validations = [
504
+ addNullableValidation,
471
505
  addRequiredValidation,
472
506
  addMinLengthValidation,
473
507
  addMaxLengthValidation,
@@ -484,12 +518,12 @@ const createYupSchema = (attributes = {}, components = {}, options = { status: n
484
518
  ...acc,
485
519
  [name]: transformSchema(
486
520
  yup.array().of(createModelSchema(attributes3).nullable(false))
487
- )
521
+ ).test(arrayValidator(attribute, options))
488
522
  };
489
523
  } else {
490
524
  return {
491
525
  ...acc,
492
- [name]: transformSchema(createModelSchema(attributes3))
526
+ [name]: transformSchema(createModelSchema(attributes3).nullable())
493
527
  };
494
528
  }
495
529
  }
@@ -511,7 +545,7 @@ const createYupSchema = (attributes = {}, components = {}, options = { status: n
511
545
  }
512
546
  )
513
547
  )
514
- )
548
+ ).test(arrayValidator(attribute, options))
515
549
  };
516
550
  case "relation":
517
551
  return {
@@ -523,7 +557,7 @@ const createYupSchema = (attributes = {}, components = {}, options = { status: n
523
557
  } else if (Array.isArray(value)) {
524
558
  return yup.array().of(
525
559
  yup.object().shape({
526
- id: yup.string().required()
560
+ id: yup.number().required()
527
561
  })
528
562
  );
529
563
  } else if (typeof value === "object") {
@@ -609,17 +643,17 @@ const nullableSchema = (schema) => {
609
643
  schema
610
644
  );
611
645
  };
646
+ const addNullableValidation = () => (schema) => {
647
+ return nullableSchema(schema);
648
+ };
612
649
  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);
650
+ if (options.status === "draft" || !attribute.required) {
651
+ return schema;
618
652
  }
619
- if (attribute.required && attribute.type !== "relation") {
653
+ if (attribute.required && "required" in schema) {
620
654
  return schema.required(translatedErrors.required);
621
655
  }
622
- return nullableSchema(schema);
656
+ return schema;
623
657
  };
624
658
  const addMinLengthValidation = (attribute, options) => (schema) => {
625
659
  if (options.status === "draft") {
@@ -647,31 +681,12 @@ const addMaxLengthValidation = (attribute) => (schema) => {
647
681
  return schema;
648
682
  };
649
683
  const addMinValidation = (attribute, options) => (schema) => {
650
- if ("min" in attribute) {
684
+ if (options.status === "draft") {
685
+ return schema;
686
+ }
687
+ if ("min" in attribute && "min" in schema) {
651
688
  const min = toInteger(attribute.min);
652
- if (attribute.type === "component" && attribute.repeatable || attribute.type === "dynamiczone") {
653
- if (options.status !== "draft" && !attribute.required && "test" in schema && min) {
654
- return schema.test(
655
- "custom-min",
656
- {
657
- ...translatedErrors.min,
658
- values: {
659
- min: attribute.min
660
- }
661
- },
662
- (value) => {
663
- if (!value) {
664
- return true;
665
- }
666
- if (Array.isArray(value) && value.length === 0) {
667
- return true;
668
- }
669
- return value.length >= min;
670
- }
671
- );
672
- }
673
- }
674
- if ("min" in schema && min) {
689
+ if (min) {
675
690
  return schema.min(min, {
676
691
  ...translatedErrors.min,
677
692
  values: {
@@ -789,19 +804,115 @@ const extractContentTypeComponents = (attributes = {}, allComponents = {}) => {
789
804
  }, {});
790
805
  return componentsByKey;
791
806
  };
792
- const useDocument = (args, opts) => {
807
+ const HOOKS = {
808
+ /**
809
+ * Hook that allows to mutate the displayed headers of the list view table
810
+ * @constant
811
+ * @type {string}
812
+ */
813
+ INJECT_COLUMN_IN_TABLE: "Admin/CM/pages/ListView/inject-column-in-table",
814
+ /**
815
+ * Hook that allows to mutate the CM's collection types links pre-set filters
816
+ * @constant
817
+ * @type {string}
818
+ */
819
+ MUTATE_COLLECTION_TYPES_LINKS: "Admin/CM/pages/App/mutate-collection-types-links",
820
+ /**
821
+ * Hook that allows to mutate the CM's edit view layout
822
+ * @constant
823
+ * @type {string}
824
+ */
825
+ MUTATE_EDIT_VIEW_LAYOUT: "Admin/CM/pages/EditView/mutate-edit-view-layout",
826
+ /**
827
+ * Hook that allows to mutate the CM's single types links pre-set filters
828
+ * @constant
829
+ * @type {string}
830
+ */
831
+ MUTATE_SINGLE_TYPES_LINKS: "Admin/CM/pages/App/mutate-single-types-links"
832
+ };
833
+ const contentTypesApi = contentManagerApi.injectEndpoints({
834
+ endpoints: (builder) => ({
835
+ getContentTypeConfiguration: builder.query({
836
+ query: (uid) => ({
837
+ url: `/content-manager/content-types/${uid}/configuration`,
838
+ method: "GET"
839
+ }),
840
+ transformResponse: (response) => response.data,
841
+ providesTags: (_result, _error, uid) => [
842
+ { type: "ContentTypesConfiguration", id: uid },
843
+ { type: "ContentTypeSettings", id: "LIST" }
844
+ ]
845
+ }),
846
+ getAllContentTypeSettings: builder.query({
847
+ query: () => "/content-manager/content-types-settings",
848
+ transformResponse: (response) => response.data,
849
+ providesTags: [{ type: "ContentTypeSettings", id: "LIST" }]
850
+ }),
851
+ updateContentTypeConfiguration: builder.mutation({
852
+ query: ({ uid, ...body }) => ({
853
+ url: `/content-manager/content-types/${uid}/configuration`,
854
+ method: "PUT",
855
+ data: body
856
+ }),
857
+ transformResponse: (response) => response.data,
858
+ invalidatesTags: (_result, _error, { uid }) => [
859
+ { type: "ContentTypesConfiguration", id: uid },
860
+ { type: "ContentTypeSettings", id: "LIST" },
861
+ // Is this necessary?
862
+ { type: "InitialData" }
863
+ ]
864
+ })
865
+ })
866
+ });
867
+ const {
868
+ useGetContentTypeConfigurationQuery,
869
+ useGetAllContentTypeSettingsQuery,
870
+ useUpdateContentTypeConfigurationMutation
871
+ } = contentTypesApi;
872
+ const checkIfAttributeIsDisplayable = (attribute) => {
873
+ const { type } = attribute;
874
+ if (type === "relation") {
875
+ return !attribute.relation.toLowerCase().includes("morph");
876
+ }
877
+ return !["json", "dynamiczone", "richtext", "password", "blocks"].includes(type) && !!type;
878
+ };
879
+ const getMainField = (attribute, mainFieldName, { schemas, components }) => {
880
+ if (!mainFieldName) {
881
+ return void 0;
882
+ }
883
+ const mainFieldType = attribute.type === "component" ? components[attribute.component].attributes[mainFieldName].type : (
884
+ // @ts-expect-error – `targetModel` does exist on the attribute for a relation.
885
+ schemas.find((schema) => schema.uid === attribute.targetModel)?.attributes[mainFieldName].type
886
+ );
887
+ return {
888
+ name: mainFieldName,
889
+ type: mainFieldType ?? "string"
890
+ };
891
+ };
892
+ const DEFAULT_SETTINGS = {
893
+ bulkable: false,
894
+ filterable: false,
895
+ searchable: false,
896
+ pagination: false,
897
+ defaultSortBy: "",
898
+ defaultSortOrder: "asc",
899
+ mainField: "id",
900
+ pageSize: 10
901
+ };
902
+ const useDocumentLayout = (model) => {
903
+ const { schema, components } = useDocument({ model, collectionType: "" }, { skip: true });
904
+ const [{ query }] = useQueryParams();
905
+ const runHookWaterfall = useStrapiApp("useDocumentLayout", (state) => state.runHookWaterfall);
793
906
  const { toggleNotification } = useNotification();
794
907
  const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
908
+ const { isLoading: isLoadingSchemas, schemas } = useContentTypeSchema();
795
909
  const {
796
- currentData: data,
797
- isLoading: isLoadingDocument,
798
- isFetching: isFetchingDocument,
799
- error
800
- } = useGetDocumentQuery(args, {
801
- ...opts,
802
- skip: !args.documentId && args.collectionType !== SINGLE_TYPES || opts?.skip
803
- });
804
- const { components, schema, isLoading: isLoadingSchema } = useContentTypeSchema(args.model);
910
+ data,
911
+ isLoading: isLoadingConfigs,
912
+ error,
913
+ isFetching: isFetchingConfigs
914
+ } = useGetContentTypeConfigurationQuery(model);
915
+ const isLoading = isLoadingSchemas || isFetchingConfigs || isLoadingConfigs;
805
916
  React.useEffect(() => {
806
917
  if (error) {
807
918
  toggleNotification({
@@ -809,399 +920,655 @@ const useDocument = (args, opts) => {
809
920
  message: formatAPIError(error)
810
921
  });
811
922
  }
812
- }, [toggleNotification, error, formatAPIError, args.collectionType]);
813
- const validationSchema = React.useMemo(() => {
814
- if (!schema) {
815
- return null;
816
- }
817
- return createYupSchema(schema.attributes, components);
818
- }, [schema, components]);
819
- const validate = React.useCallback(
820
- (document) => {
821
- if (!validationSchema) {
822
- throw new Error(
823
- "There is no validation schema generated, this is likely due to the schema not being loaded yet."
824
- );
825
- }
826
- try {
827
- validationSchema.validateSync(document, { abortEarly: false, strict: true });
828
- return null;
829
- } catch (error2) {
830
- if (error2 instanceof ValidationError) {
831
- return getYupValidationErrors(error2);
832
- }
833
- throw error2;
834
- }
923
+ }, [error, formatAPIError, toggleNotification]);
924
+ const editLayout = React.useMemo(
925
+ () => data && !isLoading ? formatEditLayout(data, { schemas, schema, components }) : {
926
+ layout: [],
927
+ components: {},
928
+ metadatas: {},
929
+ options: {},
930
+ settings: DEFAULT_SETTINGS
835
931
  },
836
- [validationSchema]
932
+ [data, isLoading, schemas, schema, components]
933
+ );
934
+ const listLayout = React.useMemo(() => {
935
+ return data && !isLoading ? formatListLayout(data, { schemas, schema, components }) : {
936
+ layout: [],
937
+ metadatas: {},
938
+ options: {},
939
+ settings: DEFAULT_SETTINGS
940
+ };
941
+ }, [data, isLoading, schemas, schema, components]);
942
+ const { layout: edit } = React.useMemo(
943
+ () => runHookWaterfall(HOOKS.MUTATE_EDIT_VIEW_LAYOUT, {
944
+ layout: editLayout,
945
+ query
946
+ }),
947
+ [editLayout, query, runHookWaterfall]
837
948
  );
838
- const isLoading = isLoadingDocument || isFetchingDocument || isLoadingSchema;
839
949
  return {
840
- components,
841
- document: data?.data,
842
- meta: data?.meta,
950
+ error,
843
951
  isLoading,
844
- schema,
845
- validate
846
- };
847
- };
848
- const useDoc = () => {
849
- const { id, slug, collectionType, origin } = useParams();
850
- const [{ query }] = useQueryParams();
851
- const params = React.useMemo(() => buildValidParams(query), [query]);
852
- if (!collectionType) {
853
- throw new Error("Could not find collectionType in url params");
854
- }
855
- if (!slug) {
856
- throw new Error("Could not find model in url params");
857
- }
858
- return {
859
- collectionType,
860
- model: slug,
861
- id: origin || id === "create" ? void 0 : id,
862
- ...useDocument(
863
- { documentId: origin || id, model: slug, collectionType, params },
864
- {
865
- skip: id === "create" || !origin && !id && collectionType !== SINGLE_TYPES
866
- }
867
- )
952
+ edit,
953
+ list: listLayout
868
954
  };
869
955
  };
870
- const prefixPluginTranslations = (trad, pluginId) => {
871
- if (!pluginId) {
872
- throw new TypeError("pluginId can't be empty");
873
- }
874
- return Object.keys(trad).reduce((acc, current) => {
875
- acc[`${pluginId}.${current}`] = trad[current];
876
- return acc;
877
- }, {});
878
- };
879
- const getTranslation = (id) => `content-manager.${id}`;
880
- const DEFAULT_UNEXPECTED_ERROR_MSG = {
881
- id: "notification.error",
882
- defaultMessage: "An error occurred, please try again"
956
+ const useDocLayout = () => {
957
+ const { model } = useDoc();
958
+ return useDocumentLayout(model);
883
959
  };
884
- const useDocumentActions = () => {
885
- const { toggleNotification } = useNotification();
886
- const { formatMessage } = useIntl();
887
- const { trackUsage } = useTracking();
888
- const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
889
- const navigate = useNavigate();
890
- const [deleteDocument] = useDeleteDocumentMutation();
891
- const _delete = React.useCallback(
892
- async ({ collectionType, model, documentId, params }, trackerProperty) => {
893
- try {
894
- trackUsage("willDeleteEntry", trackerProperty);
895
- const res = await deleteDocument({
896
- collectionType,
897
- model,
898
- documentId,
899
- params
900
- });
901
- if ("error" in res) {
902
- toggleNotification({
903
- type: "danger",
904
- message: formatAPIError(res.error)
905
- });
906
- return { error: res.error };
907
- }
908
- toggleNotification({
909
- type: "success",
910
- message: formatMessage({
911
- id: getTranslation("success.record.delete"),
912
- defaultMessage: "Deleted document"
913
- })
914
- });
915
- trackUsage("didDeleteEntry", trackerProperty);
916
- return res.data;
917
- } catch (err) {
918
- toggleNotification({
919
- type: "danger",
920
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
921
- });
922
- trackUsage("didNotDeleteEntry", { error: err, ...trackerProperty });
923
- throw err;
960
+ const formatEditLayout = (data, {
961
+ schemas,
962
+ schema,
963
+ components
964
+ }) => {
965
+ let currentPanelIndex = 0;
966
+ const panelledEditAttributes = convertEditLayoutToFieldLayouts(
967
+ data.contentType.layouts.edit,
968
+ schema?.attributes,
969
+ data.contentType.metadatas,
970
+ { configurations: data.components, schemas: components },
971
+ schemas
972
+ ).reduce((panels, row) => {
973
+ if (row.some((field) => field.type === "dynamiczone")) {
974
+ panels.push([row]);
975
+ currentPanelIndex += 2;
976
+ } else {
977
+ if (!panels[currentPanelIndex]) {
978
+ panels.push([row]);
979
+ } else {
980
+ panels[currentPanelIndex].push(row);
924
981
  }
925
- },
926
- [trackUsage, deleteDocument, toggleNotification, formatMessage, formatAPIError]
927
- );
928
- const [deleteManyDocuments] = useDeleteManyDocumentsMutation();
929
- const deleteMany = React.useCallback(
930
- async ({ model, documentIds, params }) => {
931
- try {
932
- trackUsage("willBulkDeleteEntries");
933
- const res = await deleteManyDocuments({
934
- model,
935
- documentIds,
936
- params
937
- });
938
- if ("error" in res) {
939
- toggleNotification({
940
- type: "danger",
941
- message: formatAPIError(res.error)
942
- });
943
- return { error: res.error };
982
+ }
983
+ return panels;
984
+ }, []);
985
+ const componentEditAttributes = Object.entries(data.components).reduce(
986
+ (acc, [uid, configuration]) => {
987
+ acc[uid] = {
988
+ layout: convertEditLayoutToFieldLayouts(
989
+ configuration.layouts.edit,
990
+ components[uid].attributes,
991
+ configuration.metadatas,
992
+ { configurations: data.components, schemas: components }
993
+ ),
994
+ settings: {
995
+ ...configuration.settings,
996
+ icon: components[uid].info.icon,
997
+ displayName: components[uid].info.displayName
944
998
  }
945
- toggleNotification({
946
- type: "success",
947
- title: formatMessage({
948
- id: getTranslation("success.records.delete"),
949
- defaultMessage: "Successfully deleted."
950
- }),
951
- message: ""
952
- });
953
- trackUsage("didBulkDeleteEntries");
954
- return res.data;
955
- } catch (err) {
956
- toggleNotification({
957
- type: "danger",
958
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
959
- });
960
- trackUsage("didNotBulkDeleteEntries");
961
- throw err;
962
- }
999
+ };
1000
+ return acc;
963
1001
  },
964
- [trackUsage, deleteManyDocuments, toggleNotification, formatMessage, formatAPIError]
1002
+ {}
965
1003
  );
966
- const [discardDocument] = useDiscardDocumentMutation();
967
- const discard = React.useCallback(
968
- async ({ collectionType, model, documentId, params }) => {
969
- try {
970
- const res = await discardDocument({
971
- collectionType,
972
- model,
973
- documentId,
974
- params
975
- });
976
- if ("error" in res) {
977
- toggleNotification({
978
- type: "danger",
979
- message: formatAPIError(res.error)
980
- });
981
- return { error: res.error };
982
- }
983
- toggleNotification({
984
- type: "success",
985
- message: formatMessage({
986
- id: "content-manager.success.record.discard",
987
- defaultMessage: "Changes discarded"
988
- })
989
- });
990
- return res.data;
991
- } catch (err) {
992
- toggleNotification({
993
- type: "danger",
994
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
995
- });
996
- throw err;
997
- }
1004
+ const editMetadatas = Object.entries(data.contentType.metadatas).reduce(
1005
+ (acc, [attribute, metadata]) => {
1006
+ return {
1007
+ ...acc,
1008
+ [attribute]: metadata.edit
1009
+ };
998
1010
  },
999
- [discardDocument, formatAPIError, formatMessage, toggleNotification]
1011
+ {}
1000
1012
  );
1001
- const [publishDocument] = usePublishDocumentMutation();
1002
- const publish = React.useCallback(
1003
- async ({ collectionType, model, documentId, params }, data) => {
1004
- try {
1005
- trackUsage("willPublishEntry");
1006
- const res = await publishDocument({
1007
- collectionType,
1008
- model,
1009
- documentId,
1010
- data,
1011
- params
1012
- });
1013
- if ("error" in res) {
1014
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1015
- return { error: res.error };
1016
- }
1017
- trackUsage("didPublishEntry");
1018
- toggleNotification({
1019
- type: "success",
1020
- message: formatMessage({
1021
- id: getTranslation("success.record.publish"),
1022
- defaultMessage: "Published document"
1023
- })
1024
- });
1025
- return res.data;
1026
- } catch (err) {
1027
- toggleNotification({
1028
- type: "danger",
1029
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1030
- });
1031
- throw err;
1032
- }
1013
+ return {
1014
+ layout: panelledEditAttributes,
1015
+ components: componentEditAttributes,
1016
+ metadatas: editMetadatas,
1017
+ settings: {
1018
+ ...data.contentType.settings,
1019
+ displayName: schema?.info.displayName
1033
1020
  },
1034
- [trackUsage, publishDocument, toggleNotification, formatMessage, formatAPIError]
1035
- );
1036
- const [publishManyDocuments] = usePublishManyDocumentsMutation();
1037
- const publishMany = React.useCallback(
1038
- async ({ model, documentIds, params }) => {
1039
- try {
1040
- const res = await publishManyDocuments({
1041
- model,
1042
- documentIds,
1043
- params
1044
- });
1045
- if ("error" in res) {
1046
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1047
- return { error: res.error };
1048
- }
1049
- toggleNotification({
1050
- type: "success",
1051
- message: formatMessage({
1052
- id: getTranslation("success.record.publish"),
1053
- defaultMessage: "Published document"
1054
- })
1055
- });
1056
- return res.data;
1057
- } catch (err) {
1058
- toggleNotification({
1059
- type: "danger",
1060
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1061
- });
1062
- throw err;
1021
+ options: {
1022
+ ...schema?.options,
1023
+ ...schema?.pluginOptions,
1024
+ ...data.contentType.options
1025
+ }
1026
+ };
1027
+ };
1028
+ const convertEditLayoutToFieldLayouts = (rows, attributes = {}, metadatas, components, schemas = []) => {
1029
+ return rows.map(
1030
+ (row) => row.map((field) => {
1031
+ const attribute = attributes[field.name];
1032
+ if (!attribute) {
1033
+ return null;
1063
1034
  }
1064
- },
1065
- [
1066
- // trackUsage,
1067
- publishManyDocuments,
1068
- toggleNotification,
1069
- formatMessage,
1070
- formatAPIError
1071
- ]
1035
+ const { edit: metadata } = metadatas[field.name];
1036
+ const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
1037
+ return {
1038
+ attribute,
1039
+ disabled: !metadata.editable,
1040
+ hint: metadata.description,
1041
+ label: metadata.label ?? "",
1042
+ name: field.name,
1043
+ // @ts-expect-error – mainField does exist on the metadata for a relation.
1044
+ mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
1045
+ schemas,
1046
+ components: components?.schemas ?? {}
1047
+ }),
1048
+ placeholder: metadata.placeholder ?? "",
1049
+ required: attribute.required ?? false,
1050
+ size: field.size,
1051
+ unique: "unique" in attribute ? attribute.unique : false,
1052
+ visible: metadata.visible ?? true,
1053
+ type: attribute.type
1054
+ };
1055
+ }).filter((field) => field !== null)
1072
1056
  );
1073
- const [updateDocument] = useUpdateDocumentMutation();
1074
- const update = React.useCallback(
1075
- async ({ collectionType, model, documentId, params }, data, trackerProperty) => {
1076
- try {
1077
- trackUsage("willEditEntry", trackerProperty);
1078
- const res = await updateDocument({
1079
- collectionType,
1080
- model,
1081
- documentId,
1082
- data,
1083
- params
1084
- });
1085
- if ("error" in res) {
1086
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1087
- trackUsage("didNotEditEntry", { error: res.error, ...trackerProperty });
1088
- return { error: res.error };
1057
+ };
1058
+ const formatListLayout = (data, {
1059
+ schemas,
1060
+ schema,
1061
+ components
1062
+ }) => {
1063
+ const listMetadatas = Object.entries(data.contentType.metadatas).reduce(
1064
+ (acc, [attribute, metadata]) => {
1065
+ return {
1066
+ ...acc,
1067
+ [attribute]: metadata.list
1068
+ };
1069
+ },
1070
+ {}
1071
+ );
1072
+ const listAttributes = convertListLayoutToFieldLayouts(
1073
+ data.contentType.layouts.list,
1074
+ schema?.attributes,
1075
+ listMetadatas,
1076
+ { configurations: data.components, schemas: components },
1077
+ schemas
1078
+ );
1079
+ return {
1080
+ layout: listAttributes,
1081
+ settings: { ...data.contentType.settings, displayName: schema?.info.displayName },
1082
+ metadatas: listMetadatas,
1083
+ options: {
1084
+ ...schema?.options,
1085
+ ...schema?.pluginOptions,
1086
+ ...data.contentType.options
1087
+ }
1088
+ };
1089
+ };
1090
+ const convertListLayoutToFieldLayouts = (columns, attributes = {}, metadatas, components, schemas = []) => {
1091
+ return columns.map((name) => {
1092
+ const attribute = attributes[name];
1093
+ if (!attribute) {
1094
+ return null;
1095
+ }
1096
+ const metadata = metadatas[name];
1097
+ const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
1098
+ return {
1099
+ attribute,
1100
+ label: metadata.label ?? "",
1101
+ mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
1102
+ schemas,
1103
+ components: components?.schemas ?? {}
1104
+ }),
1105
+ name,
1106
+ searchable: metadata.searchable ?? true,
1107
+ sortable: metadata.sortable ?? true
1108
+ };
1109
+ }).filter((field) => field !== null);
1110
+ };
1111
+ const useDocument = (args, opts) => {
1112
+ const { toggleNotification } = useNotification();
1113
+ const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
1114
+ const {
1115
+ currentData: data,
1116
+ isLoading: isLoadingDocument,
1117
+ isFetching: isFetchingDocument,
1118
+ error
1119
+ } = useGetDocumentQuery(args, {
1120
+ ...opts,
1121
+ skip: !args.documentId && args.collectionType !== SINGLE_TYPES || opts?.skip
1122
+ });
1123
+ const {
1124
+ components,
1125
+ schema,
1126
+ schemas,
1127
+ isLoading: isLoadingSchema
1128
+ } = useContentTypeSchema(args.model);
1129
+ React.useEffect(() => {
1130
+ if (error) {
1131
+ toggleNotification({
1132
+ type: "danger",
1133
+ message: formatAPIError(error)
1134
+ });
1135
+ }
1136
+ }, [toggleNotification, error, formatAPIError, args.collectionType]);
1137
+ const validationSchema = React.useMemo(() => {
1138
+ if (!schema) {
1139
+ return null;
1140
+ }
1141
+ return createYupSchema(schema.attributes, components);
1142
+ }, [schema, components]);
1143
+ const validate = React.useCallback(
1144
+ (document) => {
1145
+ if (!validationSchema) {
1146
+ throw new Error(
1147
+ "There is no validation schema generated, this is likely due to the schema not being loaded yet."
1148
+ );
1149
+ }
1150
+ try {
1151
+ validationSchema.validateSync(document, { abortEarly: false, strict: true });
1152
+ return null;
1153
+ } catch (error2) {
1154
+ if (error2 instanceof ValidationError) {
1155
+ return getYupValidationErrors(error2);
1089
1156
  }
1090
- trackUsage("didEditEntry", trackerProperty);
1091
- toggleNotification({
1092
- type: "success",
1093
- message: formatMessage({
1094
- id: getTranslation("success.record.save"),
1095
- defaultMessage: "Saved document"
1096
- })
1097
- });
1098
- return res.data;
1099
- } catch (err) {
1100
- trackUsage("didNotEditEntry", { error: err, ...trackerProperty });
1101
- toggleNotification({
1102
- type: "danger",
1103
- message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1104
- });
1105
- throw err;
1157
+ throw error2;
1106
1158
  }
1107
1159
  },
1108
- [trackUsage, updateDocument, toggleNotification, formatMessage, formatAPIError]
1160
+ [validationSchema]
1109
1161
  );
1110
- const [unpublishDocument] = useUnpublishDocumentMutation();
1111
- const unpublish = React.useCallback(
1112
- async ({ collectionType, model, documentId, params }, discardDraft = false) => {
1162
+ const isLoading = isLoadingDocument || isFetchingDocument || isLoadingSchema;
1163
+ const hasError = !!error;
1164
+ return {
1165
+ components,
1166
+ document: data?.data,
1167
+ meta: data?.meta,
1168
+ isLoading,
1169
+ hasError,
1170
+ schema,
1171
+ schemas,
1172
+ validate
1173
+ };
1174
+ };
1175
+ const useDoc = () => {
1176
+ const { id, slug, collectionType, origin } = useParams();
1177
+ const [{ query }] = useQueryParams();
1178
+ const params = React.useMemo(() => buildValidParams(query), [query]);
1179
+ if (!collectionType) {
1180
+ throw new Error("Could not find collectionType in url params");
1181
+ }
1182
+ if (!slug) {
1183
+ throw new Error("Could not find model in url params");
1184
+ }
1185
+ const document = useDocument(
1186
+ { documentId: origin || id, model: slug, collectionType, params },
1187
+ {
1188
+ skip: id === "create" || !origin && !id && collectionType !== SINGLE_TYPES
1189
+ }
1190
+ );
1191
+ const returnId = origin || id === "create" ? void 0 : id;
1192
+ return {
1193
+ collectionType,
1194
+ model: slug,
1195
+ id: returnId,
1196
+ ...document
1197
+ };
1198
+ };
1199
+ const useContentManagerContext = () => {
1200
+ const {
1201
+ collectionType,
1202
+ model,
1203
+ id,
1204
+ components,
1205
+ isLoading: isLoadingDoc,
1206
+ schema,
1207
+ schemas
1208
+ } = useDoc();
1209
+ const layout = useDocumentLayout(model);
1210
+ const form = useForm("useContentManagerContext", (state) => state);
1211
+ const isSingleType = collectionType === SINGLE_TYPES;
1212
+ const slug = model;
1213
+ const isCreatingEntry = id === "create";
1214
+ useContentTypeSchema();
1215
+ const isLoading = isLoadingDoc || layout.isLoading;
1216
+ const error = layout.error;
1217
+ return {
1218
+ error,
1219
+ isLoading,
1220
+ // Base metadata
1221
+ model,
1222
+ collectionType,
1223
+ id,
1224
+ slug,
1225
+ isCreatingEntry,
1226
+ isSingleType,
1227
+ hasDraftAndPublish: schema?.options?.draftAndPublish ?? false,
1228
+ // All schema infos
1229
+ components,
1230
+ contentType: schema,
1231
+ contentTypes: schemas,
1232
+ // Form state
1233
+ form,
1234
+ // layout infos
1235
+ layout
1236
+ };
1237
+ };
1238
+ const prefixPluginTranslations = (trad, pluginId) => {
1239
+ return Object.keys(trad).reduce((acc, current) => {
1240
+ acc[`${pluginId}.${current}`] = trad[current];
1241
+ return acc;
1242
+ }, {});
1243
+ };
1244
+ const getTranslation = (id) => `content-manager.${id}`;
1245
+ const DEFAULT_UNEXPECTED_ERROR_MSG = {
1246
+ id: "notification.error",
1247
+ defaultMessage: "An error occurred, please try again"
1248
+ };
1249
+ const useDocumentActions = () => {
1250
+ const { toggleNotification } = useNotification();
1251
+ const { formatMessage } = useIntl();
1252
+ const { trackUsage } = useTracking();
1253
+ const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
1254
+ const navigate = useNavigate();
1255
+ const setCurrentStep = useGuidedTour("useDocumentActions", (state) => state.setCurrentStep);
1256
+ const [deleteDocument] = useDeleteDocumentMutation();
1257
+ const _delete = React.useCallback(
1258
+ async ({ collectionType, model, documentId, params }, trackerProperty) => {
1113
1259
  try {
1114
- trackUsage("willUnpublishEntry");
1115
- const res = await unpublishDocument({
1260
+ trackUsage("willDeleteEntry", trackerProperty);
1261
+ const res = await deleteDocument({
1116
1262
  collectionType,
1117
1263
  model,
1118
1264
  documentId,
1119
- params,
1120
- data: {
1121
- discardDraft
1122
- }
1265
+ params
1123
1266
  });
1124
1267
  if ("error" in res) {
1125
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1268
+ toggleNotification({
1269
+ type: "danger",
1270
+ message: formatAPIError(res.error)
1271
+ });
1126
1272
  return { error: res.error };
1127
1273
  }
1128
- trackUsage("didUnpublishEntry");
1129
1274
  toggleNotification({
1130
1275
  type: "success",
1131
1276
  message: formatMessage({
1132
- id: getTranslation("success.record.unpublish"),
1133
- defaultMessage: "Unpublished document"
1277
+ id: getTranslation("success.record.delete"),
1278
+ defaultMessage: "Deleted document"
1134
1279
  })
1135
1280
  });
1281
+ trackUsage("didDeleteEntry", trackerProperty);
1136
1282
  return res.data;
1137
1283
  } catch (err) {
1138
1284
  toggleNotification({
1139
1285
  type: "danger",
1140
1286
  message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1141
1287
  });
1288
+ trackUsage("didNotDeleteEntry", { error: err, ...trackerProperty });
1142
1289
  throw err;
1143
1290
  }
1144
1291
  },
1145
- [trackUsage, unpublishDocument, toggleNotification, formatMessage, formatAPIError]
1292
+ [trackUsage, deleteDocument, toggleNotification, formatMessage, formatAPIError]
1146
1293
  );
1147
- const [unpublishManyDocuments] = useUnpublishManyDocumentsMutation();
1148
- const unpublishMany = React.useCallback(
1294
+ const [deleteManyDocuments] = useDeleteManyDocumentsMutation();
1295
+ const deleteMany = React.useCallback(
1149
1296
  async ({ model, documentIds, params }) => {
1150
1297
  try {
1151
- trackUsage("willBulkUnpublishEntries");
1152
- const res = await unpublishManyDocuments({
1298
+ trackUsage("willBulkDeleteEntries");
1299
+ const res = await deleteManyDocuments({
1153
1300
  model,
1154
1301
  documentIds,
1155
1302
  params
1156
1303
  });
1157
1304
  if ("error" in res) {
1158
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1305
+ toggleNotification({
1306
+ type: "danger",
1307
+ message: formatAPIError(res.error)
1308
+ });
1159
1309
  return { error: res.error };
1160
1310
  }
1161
- trackUsage("didBulkUnpublishEntries");
1162
1311
  toggleNotification({
1163
1312
  type: "success",
1164
1313
  title: formatMessage({
1165
- id: getTranslation("success.records.unpublish"),
1166
- defaultMessage: "Successfully unpublished."
1314
+ id: getTranslation("success.records.delete"),
1315
+ defaultMessage: "Successfully deleted."
1167
1316
  }),
1168
1317
  message: ""
1169
1318
  });
1319
+ trackUsage("didBulkDeleteEntries");
1170
1320
  return res.data;
1171
1321
  } catch (err) {
1172
1322
  toggleNotification({
1173
1323
  type: "danger",
1174
1324
  message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1175
1325
  });
1176
- trackUsage("didNotBulkUnpublishEntries");
1326
+ trackUsage("didNotBulkDeleteEntries");
1177
1327
  throw err;
1178
1328
  }
1179
1329
  },
1180
- [trackUsage, unpublishManyDocuments, toggleNotification, formatMessage, formatAPIError]
1330
+ [trackUsage, deleteManyDocuments, toggleNotification, formatMessage, formatAPIError]
1181
1331
  );
1182
- const [createDocument] = useCreateDocumentMutation();
1183
- const create = React.useCallback(
1184
- async ({ model, params }, data, trackerProperty) => {
1332
+ const [discardDocument] = useDiscardDocumentMutation();
1333
+ const discard = React.useCallback(
1334
+ async ({ collectionType, model, documentId, params }) => {
1185
1335
  try {
1186
- const res = await createDocument({
1336
+ const res = await discardDocument({
1337
+ collectionType,
1187
1338
  model,
1188
- data,
1339
+ documentId,
1189
1340
  params
1190
1341
  });
1191
1342
  if ("error" in res) {
1192
- toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1193
- trackUsage("didNotCreateEntry", { error: res.error, ...trackerProperty });
1343
+ toggleNotification({
1344
+ type: "danger",
1345
+ message: formatAPIError(res.error)
1346
+ });
1194
1347
  return { error: res.error };
1195
1348
  }
1196
- trackUsage("didCreateEntry", trackerProperty);
1197
1349
  toggleNotification({
1198
1350
  type: "success",
1199
1351
  message: formatMessage({
1200
- id: getTranslation("success.record.save"),
1201
- defaultMessage: "Saved document"
1352
+ id: "content-manager.success.record.discard",
1353
+ defaultMessage: "Changes discarded"
1202
1354
  })
1203
1355
  });
1204
1356
  return res.data;
1357
+ } catch (err) {
1358
+ toggleNotification({
1359
+ type: "danger",
1360
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1361
+ });
1362
+ throw err;
1363
+ }
1364
+ },
1365
+ [discardDocument, formatAPIError, formatMessage, toggleNotification]
1366
+ );
1367
+ const [publishDocument] = usePublishDocumentMutation();
1368
+ const publish = React.useCallback(
1369
+ async ({ collectionType, model, documentId, params }, data) => {
1370
+ try {
1371
+ trackUsage("willPublishEntry");
1372
+ const res = await publishDocument({
1373
+ collectionType,
1374
+ model,
1375
+ documentId,
1376
+ data,
1377
+ params
1378
+ });
1379
+ if ("error" in res) {
1380
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1381
+ return { error: res.error };
1382
+ }
1383
+ trackUsage("didPublishEntry");
1384
+ toggleNotification({
1385
+ type: "success",
1386
+ message: formatMessage({
1387
+ id: getTranslation("success.record.publish"),
1388
+ defaultMessage: "Published document"
1389
+ })
1390
+ });
1391
+ return res.data;
1392
+ } catch (err) {
1393
+ toggleNotification({
1394
+ type: "danger",
1395
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1396
+ });
1397
+ throw err;
1398
+ }
1399
+ },
1400
+ [trackUsage, publishDocument, toggleNotification, formatMessage, formatAPIError]
1401
+ );
1402
+ const [publishManyDocuments] = usePublishManyDocumentsMutation();
1403
+ const publishMany = React.useCallback(
1404
+ async ({ model, documentIds, params }) => {
1405
+ try {
1406
+ const res = await publishManyDocuments({
1407
+ model,
1408
+ documentIds,
1409
+ params
1410
+ });
1411
+ if ("error" in res) {
1412
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1413
+ return { error: res.error };
1414
+ }
1415
+ toggleNotification({
1416
+ type: "success",
1417
+ message: formatMessage({
1418
+ id: getTranslation("success.record.publish"),
1419
+ defaultMessage: "Published document"
1420
+ })
1421
+ });
1422
+ return res.data;
1423
+ } catch (err) {
1424
+ toggleNotification({
1425
+ type: "danger",
1426
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1427
+ });
1428
+ throw err;
1429
+ }
1430
+ },
1431
+ [
1432
+ // trackUsage,
1433
+ publishManyDocuments,
1434
+ toggleNotification,
1435
+ formatMessage,
1436
+ formatAPIError
1437
+ ]
1438
+ );
1439
+ const [updateDocument] = useUpdateDocumentMutation();
1440
+ const update = React.useCallback(
1441
+ async ({ collectionType, model, documentId, params }, data, trackerProperty) => {
1442
+ try {
1443
+ trackUsage("willEditEntry", trackerProperty);
1444
+ const res = await updateDocument({
1445
+ collectionType,
1446
+ model,
1447
+ documentId,
1448
+ data,
1449
+ params
1450
+ });
1451
+ if ("error" in res) {
1452
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1453
+ trackUsage("didNotEditEntry", { error: res.error, ...trackerProperty });
1454
+ return { error: res.error };
1455
+ }
1456
+ trackUsage("didEditEntry", trackerProperty);
1457
+ toggleNotification({
1458
+ type: "success",
1459
+ message: formatMessage({
1460
+ id: getTranslation("success.record.save"),
1461
+ defaultMessage: "Saved document"
1462
+ })
1463
+ });
1464
+ return res.data;
1465
+ } catch (err) {
1466
+ trackUsage("didNotEditEntry", { error: err, ...trackerProperty });
1467
+ toggleNotification({
1468
+ type: "danger",
1469
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1470
+ });
1471
+ throw err;
1472
+ }
1473
+ },
1474
+ [trackUsage, updateDocument, toggleNotification, formatMessage, formatAPIError]
1475
+ );
1476
+ const [unpublishDocument] = useUnpublishDocumentMutation();
1477
+ const unpublish = React.useCallback(
1478
+ async ({ collectionType, model, documentId, params }, discardDraft = false) => {
1479
+ try {
1480
+ trackUsage("willUnpublishEntry");
1481
+ const res = await unpublishDocument({
1482
+ collectionType,
1483
+ model,
1484
+ documentId,
1485
+ params,
1486
+ data: {
1487
+ discardDraft
1488
+ }
1489
+ });
1490
+ if ("error" in res) {
1491
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1492
+ return { error: res.error };
1493
+ }
1494
+ trackUsage("didUnpublishEntry");
1495
+ toggleNotification({
1496
+ type: "success",
1497
+ message: formatMessage({
1498
+ id: getTranslation("success.record.unpublish"),
1499
+ defaultMessage: "Unpublished document"
1500
+ })
1501
+ });
1502
+ return res.data;
1503
+ } catch (err) {
1504
+ toggleNotification({
1505
+ type: "danger",
1506
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1507
+ });
1508
+ throw err;
1509
+ }
1510
+ },
1511
+ [trackUsage, unpublishDocument, toggleNotification, formatMessage, formatAPIError]
1512
+ );
1513
+ const [unpublishManyDocuments] = useUnpublishManyDocumentsMutation();
1514
+ const unpublishMany = React.useCallback(
1515
+ async ({ model, documentIds, params }) => {
1516
+ try {
1517
+ trackUsage("willBulkUnpublishEntries");
1518
+ const res = await unpublishManyDocuments({
1519
+ model,
1520
+ documentIds,
1521
+ params
1522
+ });
1523
+ if ("error" in res) {
1524
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1525
+ return { error: res.error };
1526
+ }
1527
+ trackUsage("didBulkUnpublishEntries");
1528
+ toggleNotification({
1529
+ type: "success",
1530
+ title: formatMessage({
1531
+ id: getTranslation("success.records.unpublish"),
1532
+ defaultMessage: "Successfully unpublished."
1533
+ }),
1534
+ message: ""
1535
+ });
1536
+ return res.data;
1537
+ } catch (err) {
1538
+ toggleNotification({
1539
+ type: "danger",
1540
+ message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
1541
+ });
1542
+ trackUsage("didNotBulkUnpublishEntries");
1543
+ throw err;
1544
+ }
1545
+ },
1546
+ [trackUsage, unpublishManyDocuments, toggleNotification, formatMessage, formatAPIError]
1547
+ );
1548
+ const [createDocument] = useCreateDocumentMutation();
1549
+ const create = React.useCallback(
1550
+ async ({ model, params }, data, trackerProperty) => {
1551
+ try {
1552
+ const res = await createDocument({
1553
+ model,
1554
+ data,
1555
+ params
1556
+ });
1557
+ if ("error" in res) {
1558
+ toggleNotification({ type: "danger", message: formatAPIError(res.error) });
1559
+ trackUsage("didNotCreateEntry", { error: res.error, ...trackerProperty });
1560
+ return { error: res.error };
1561
+ }
1562
+ trackUsage("didCreateEntry", trackerProperty);
1563
+ toggleNotification({
1564
+ type: "success",
1565
+ message: formatMessage({
1566
+ id: getTranslation("success.record.save"),
1567
+ defaultMessage: "Saved document"
1568
+ })
1569
+ });
1570
+ setCurrentStep("contentManager.success");
1571
+ return res.data;
1205
1572
  } catch (err) {
1206
1573
  toggleNotification({
1207
1574
  type: "danger",
@@ -1303,7 +1670,7 @@ const useDocumentActions = () => {
1303
1670
  };
1304
1671
  };
1305
1672
  const ProtectedHistoryPage = lazy(
1306
- () => import("./History-BseDJOrj.mjs").then((mod) => ({ default: mod.ProtectedHistoryPage }))
1673
+ () => import("./History-C7xPTX16.mjs").then((mod) => ({ default: mod.ProtectedHistoryPage }))
1307
1674
  );
1308
1675
  const routes$1 = [
1309
1676
  {
@@ -1316,31 +1683,31 @@ const routes$1 = [
1316
1683
  }
1317
1684
  ];
1318
1685
  const ProtectedEditViewPage = lazy(
1319
- () => import("./EditViewPage-CvRUUpVh.mjs").then((mod) => ({ default: mod.ProtectedEditViewPage }))
1686
+ () => import("./EditViewPage-B8Uz26ZK.mjs").then((mod) => ({ default: mod.ProtectedEditViewPage }))
1320
1687
  );
1321
1688
  const ProtectedListViewPage = lazy(
1322
- () => import("./ListViewPage-lQ-VLV2G.mjs").then((mod) => ({ default: mod.ProtectedListViewPage }))
1689
+ () => import("./ListViewPage-BkzGZ8pS.mjs").then((mod) => ({ default: mod.ProtectedListViewPage }))
1323
1690
  );
1324
1691
  const ProtectedListConfiguration = lazy(
1325
- () => import("./ListConfigurationPage-DWE_fr5B.mjs").then((mod) => ({
1692
+ () => import("./ListConfigurationPage-C6zgYhFZ.mjs").then((mod) => ({
1326
1693
  default: mod.ProtectedListConfiguration
1327
1694
  }))
1328
1695
  );
1329
1696
  const ProtectedEditConfigurationPage = lazy(
1330
- () => import("./EditConfigurationPage-QBZdUYyG.mjs").then((mod) => ({
1697
+ () => import("./EditConfigurationPage-B1JTGbDa.mjs").then((mod) => ({
1331
1698
  default: mod.ProtectedEditConfigurationPage
1332
1699
  }))
1333
1700
  );
1334
1701
  const ProtectedComponentConfigurationPage = lazy(
1335
- () => import("./ComponentConfigurationPage-DJEJ49QD.mjs").then((mod) => ({
1702
+ () => import("./ComponentConfigurationPage-C5xJlht8.mjs").then((mod) => ({
1336
1703
  default: mod.ProtectedComponentConfigurationPage
1337
1704
  }))
1338
1705
  );
1339
1706
  const NoPermissions = lazy(
1340
- () => import("./NoPermissionsPage-TV830k4P.mjs").then((mod) => ({ default: mod.NoPermissions }))
1707
+ () => import("./NoPermissionsPage-B1-Nl_T8.mjs").then((mod) => ({ default: mod.NoPermissions }))
1341
1708
  );
1342
1709
  const NoContentType = lazy(
1343
- () => import("./NoContentTypePage-VCQOMwlf.mjs").then((mod) => ({ default: mod.NoContentType }))
1710
+ () => import("./NoContentTypePage-CNTp9Je-.mjs").then((mod) => ({ default: mod.NoContentType }))
1344
1711
  );
1345
1712
  const CollectionTypePages = () => {
1346
1713
  const { collectionType } = useParams();
@@ -1405,1045 +1772,738 @@ const DocumentActions = ({ actions: actions2 }) => {
1405
1772
  /* @__PURE__ */ jsx(DocumentActionButton, { ...primaryAction, variant: primaryAction.variant || "default" }),
1406
1773
  restActions.length > 0 ? /* @__PURE__ */ jsx(
1407
1774
  DocumentActionsMenu,
1408
- {
1409
- actions: restActions,
1410
- label: formatMessage({
1411
- id: "content-manager.containers.edit.panels.default.more-actions",
1412
- defaultMessage: "More document actions"
1413
- })
1414
- }
1415
- ) : null
1416
- ] }),
1417
- secondaryAction ? /* @__PURE__ */ jsx(
1418
- DocumentActionButton,
1419
- {
1420
- ...secondaryAction,
1421
- variant: secondaryAction.variant || "secondary"
1422
- }
1423
- ) : null
1424
- ] });
1425
- };
1426
- const DocumentActionButton = (action) => {
1427
- const [dialogId, setDialogId] = React.useState(null);
1428
- const { toggleNotification } = useNotification();
1429
- const handleClick = (action2) => async (e) => {
1430
- const { onClick = () => false, dialog, id } = action2;
1431
- const muteDialog = await onClick(e);
1432
- if (dialog && !muteDialog) {
1433
- switch (dialog.type) {
1434
- case "notification":
1435
- toggleNotification({
1436
- title: dialog.title,
1437
- message: dialog.content,
1438
- type: dialog.status,
1439
- timeout: dialog.timeout,
1440
- onClose: dialog.onClose
1441
- });
1442
- break;
1443
- case "dialog":
1444
- case "modal":
1445
- e.preventDefault();
1446
- setDialogId(id);
1447
- }
1448
- }
1449
- };
1450
- const handleClose = () => {
1451
- setDialogId(null);
1452
- };
1453
- return /* @__PURE__ */ jsxs(Fragment, { children: [
1454
- /* @__PURE__ */ jsx(
1455
- Button,
1456
- {
1457
- flex: "auto",
1458
- startIcon: action.icon,
1459
- disabled: action.disabled,
1460
- onClick: handleClick(action),
1461
- justifyContent: "center",
1462
- variant: action.variant || "default",
1463
- paddingTop: "7px",
1464
- paddingBottom: "7px",
1465
- children: action.label
1466
- }
1467
- ),
1468
- action.dialog?.type === "dialog" ? /* @__PURE__ */ jsx(
1469
- DocumentActionConfirmDialog,
1470
- {
1471
- ...action.dialog,
1472
- variant: action.dialog?.variant ?? action.variant,
1473
- isOpen: dialogId === action.id,
1474
- onClose: handleClose
1475
- }
1476
- ) : null,
1477
- action.dialog?.type === "modal" ? /* @__PURE__ */ jsx(
1478
- DocumentActionModal,
1479
- {
1480
- ...action.dialog,
1481
- onModalClose: handleClose,
1482
- isOpen: dialogId === action.id
1483
- }
1484
- ) : null
1485
- ] });
1486
- };
1487
- const DocumentActionsMenu = ({
1488
- actions: actions2,
1489
- children,
1490
- label,
1491
- variant = "tertiary"
1492
- }) => {
1493
- const [isOpen, setIsOpen] = React.useState(false);
1494
- const [dialogId, setDialogId] = React.useState(null);
1495
- const { formatMessage } = useIntl();
1496
- const { toggleNotification } = useNotification();
1497
- const isDisabled = actions2.every((action) => action.disabled) || actions2.length === 0;
1498
- const handleClick = (action) => async (e) => {
1499
- const { onClick = () => false, dialog, id } = action;
1500
- const muteDialog = await onClick(e);
1501
- if (dialog && !muteDialog) {
1502
- switch (dialog.type) {
1503
- case "notification":
1504
- toggleNotification({
1505
- title: dialog.title,
1506
- message: dialog.content,
1507
- type: dialog.status,
1508
- timeout: dialog.timeout,
1509
- onClose: dialog.onClose
1510
- });
1511
- break;
1512
- case "dialog":
1513
- case "modal":
1514
- setDialogId(id);
1515
- }
1516
- }
1517
- };
1518
- const handleClose = () => {
1519
- setDialogId(null);
1520
- setIsOpen(false);
1521
- };
1522
- return /* @__PURE__ */ jsxs(Menu.Root, { open: isOpen, onOpenChange: setIsOpen, children: [
1523
- /* @__PURE__ */ jsxs(
1524
- Menu.Trigger,
1525
- {
1526
- disabled: isDisabled,
1527
- size: "S",
1528
- endIcon: null,
1529
- paddingTop: "4px",
1530
- paddingLeft: "7px",
1531
- paddingRight: "7px",
1532
- variant,
1533
- children: [
1534
- /* @__PURE__ */ jsx(More, { "aria-hidden": true, focusable: false }),
1535
- /* @__PURE__ */ jsx(VisuallyHidden, { tag: "span", children: label || formatMessage({
1536
- id: "content-manager.containers.edit.panels.default.more-actions",
1537
- defaultMessage: "More document actions"
1538
- }) })
1539
- ]
1540
- }
1541
- ),
1542
- /* @__PURE__ */ jsxs(Menu.Content, { top: "4px", maxHeight: void 0, popoverPlacement: "bottom-end", children: [
1543
- actions2.map((action) => {
1544
- return /* @__PURE__ */ jsx(
1545
- Menu.Item,
1546
- {
1547
- disabled: action.disabled,
1548
- onSelect: handleClick(action),
1549
- display: "block",
1550
- children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", gap: 4, children: [
1551
- /* @__PURE__ */ jsxs(
1552
- Flex,
1553
- {
1554
- color: !action.disabled ? convertActionVariantToColor(action.variant) : "inherit",
1555
- gap: 2,
1556
- tag: "span",
1557
- children: [
1558
- /* @__PURE__ */ jsx(
1559
- Flex,
1560
- {
1561
- tag: "span",
1562
- color: !action.disabled ? convertActionVariantToIconColor(action.variant) : "inherit",
1563
- children: action.icon
1564
- }
1565
- ),
1566
- action.label
1567
- ]
1568
- }
1569
- ),
1570
- action.id.startsWith("HistoryAction") && /* @__PURE__ */ jsx(
1571
- Flex,
1572
- {
1573
- alignItems: "center",
1574
- background: "alternative100",
1575
- borderStyle: "solid",
1576
- borderColor: "alternative200",
1577
- borderWidth: "1px",
1578
- height: 5,
1579
- paddingLeft: 2,
1580
- paddingRight: 2,
1581
- hasRadius: true,
1582
- color: "alternative600",
1583
- children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", fontWeight: "bold", lineHeight: 1, children: formatMessage({ id: "global.new", defaultMessage: "New" }) })
1584
- }
1585
- )
1586
- ] })
1587
- },
1588
- action.id
1589
- );
1590
- }),
1591
- children
1592
- ] }),
1593
- actions2.map((action) => {
1594
- return /* @__PURE__ */ jsxs(React.Fragment, { children: [
1595
- action.dialog?.type === "dialog" ? /* @__PURE__ */ jsx(
1596
- DocumentActionConfirmDialog,
1597
- {
1598
- ...action.dialog,
1599
- variant: action.variant,
1600
- isOpen: dialogId === action.id,
1601
- onClose: handleClose
1602
- }
1603
- ) : null,
1604
- action.dialog?.type === "modal" ? /* @__PURE__ */ jsx(
1605
- DocumentActionModal,
1606
- {
1607
- ...action.dialog,
1608
- onModalClose: handleClose,
1609
- isOpen: dialogId === action.id
1610
- }
1611
- ) : null
1612
- ] }, action.id);
1613
- })
1614
- ] });
1615
- };
1616
- const convertActionVariantToColor = (variant = "secondary") => {
1617
- switch (variant) {
1618
- case "danger":
1619
- return "danger600";
1620
- case "secondary":
1621
- return void 0;
1622
- case "success":
1623
- return "success600";
1624
- default:
1625
- return "primary600";
1626
- }
1627
- };
1628
- const convertActionVariantToIconColor = (variant = "secondary") => {
1629
- switch (variant) {
1630
- case "danger":
1631
- return "danger600";
1632
- case "secondary":
1633
- return "neutral500";
1634
- case "success":
1635
- return "success600";
1636
- default:
1637
- return "primary600";
1638
- }
1639
- };
1640
- const DocumentActionConfirmDialog = ({
1641
- onClose,
1642
- onCancel,
1643
- onConfirm,
1644
- title,
1645
- content,
1646
- isOpen,
1647
- variant = "secondary"
1648
- }) => {
1649
- const { formatMessage } = useIntl();
1650
- const handleClose = async () => {
1651
- if (onCancel) {
1652
- await onCancel();
1653
- }
1654
- onClose();
1655
- };
1656
- const handleConfirm = async () => {
1657
- if (onConfirm) {
1658
- await onConfirm();
1659
- }
1660
- onClose();
1661
- };
1662
- return /* @__PURE__ */ jsx(Dialog.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxs(Dialog.Content, { children: [
1663
- /* @__PURE__ */ jsx(Dialog.Header, { children: title }),
1664
- /* @__PURE__ */ jsx(Dialog.Body, { children: content }),
1665
- /* @__PURE__ */ jsxs(Dialog.Footer, { children: [
1666
- /* @__PURE__ */ jsx(Dialog.Cancel, { children: /* @__PURE__ */ jsx(Button, { variant: "tertiary", children: formatMessage({
1667
- id: "app.components.Button.cancel",
1668
- defaultMessage: "Cancel"
1669
- }) }) }),
1670
- /* @__PURE__ */ jsx(Button, { onClick: handleConfirm, variant, children: formatMessage({
1671
- id: "app.components.Button.confirm",
1672
- defaultMessage: "Confirm"
1673
- }) })
1674
- ] })
1675
- ] }) });
1676
- };
1677
- const DocumentActionModal = ({
1678
- isOpen,
1679
- title,
1680
- onClose,
1681
- footer: Footer,
1682
- content: Content,
1683
- onModalClose
1684
- }) => {
1685
- const handleClose = () => {
1686
- if (onClose) {
1687
- onClose();
1688
- }
1689
- onModalClose();
1690
- };
1691
- return /* @__PURE__ */ jsx(Modal.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxs(Modal.Content, { children: [
1692
- /* @__PURE__ */ jsx(Modal.Header, { children: /* @__PURE__ */ jsx(Modal.Title, { children: title }) }),
1693
- typeof Content === "function" ? /* @__PURE__ */ jsx(Content, { onClose: handleClose }) : /* @__PURE__ */ jsx(Modal.Body, { children: Content }),
1694
- typeof Footer === "function" ? /* @__PURE__ */ jsx(Footer, { onClose: handleClose }) : Footer
1695
- ] }) });
1696
- };
1697
- const PublishAction$1 = ({
1698
- activeTab,
1699
- documentId,
1700
- model,
1701
- collectionType,
1702
- meta,
1703
- document
1704
- }) => {
1705
- const { schema } = useDoc();
1706
- const navigate = useNavigate();
1707
- const { toggleNotification } = useNotification();
1708
- const { _unstableFormatValidationErrors: formatValidationErrors } = useAPIErrorHandler();
1709
- const isListView = useMatch(LIST_PATH) !== null;
1710
- const isCloning = useMatch(CLONE_PATH) !== null;
1711
- const { formatMessage } = useIntl();
1712
- const canPublish = useDocumentRBAC("PublishAction", ({ canPublish: canPublish2 }) => canPublish2);
1713
- const { publish } = useDocumentActions();
1714
- const [
1715
- countDraftRelations,
1716
- { isLoading: isLoadingDraftRelations, isError: isErrorDraftRelations }
1717
- ] = useLazyGetDraftRelationCountQuery();
1718
- const [localCountOfDraftRelations, setLocalCountOfDraftRelations] = React.useState(0);
1719
- const [serverCountOfDraftRelations, setServerCountOfDraftRelations] = React.useState(0);
1720
- const [{ query, rawQuery }] = useQueryParams();
1721
- const params = React.useMemo(() => buildValidParams(query), [query]);
1722
- const modified = useForm("PublishAction", ({ modified: modified2 }) => modified2);
1723
- const setSubmitting = useForm("PublishAction", ({ setSubmitting: setSubmitting2 }) => setSubmitting2);
1724
- const isSubmitting = useForm("PublishAction", ({ isSubmitting: isSubmitting2 }) => isSubmitting2);
1725
- const validate = useForm("PublishAction", (state) => state.validate);
1726
- const setErrors = useForm("PublishAction", (state) => state.setErrors);
1727
- const formValues = useForm("PublishAction", ({ values }) => values);
1728
- React.useEffect(() => {
1729
- if (isErrorDraftRelations) {
1730
- toggleNotification({
1731
- type: "danger",
1732
- message: formatMessage({
1733
- id: getTranslation("error.records.fetch-draft-relatons"),
1734
- defaultMessage: "An error occurred while fetching draft relations on this document."
1735
- })
1736
- });
1737
- }
1738
- }, [isErrorDraftRelations, toggleNotification, formatMessage]);
1739
- React.useEffect(() => {
1740
- const localDraftRelations = /* @__PURE__ */ new Set();
1741
- const extractDraftRelations = (data) => {
1742
- const relations = data.connect || [];
1743
- relations.forEach((relation) => {
1744
- if (relation.status === "draft") {
1745
- localDraftRelations.add(relation.id);
1746
- }
1747
- });
1748
- };
1749
- const traverseAndExtract = (data) => {
1750
- Object.entries(data).forEach(([key, value]) => {
1751
- if (key === "connect" && Array.isArray(value)) {
1752
- extractDraftRelations({ connect: value });
1753
- } else if (typeof value === "object" && value !== null) {
1754
- traverseAndExtract(value);
1755
- }
1756
- });
1757
- };
1758
- if (!documentId || modified) {
1759
- traverseAndExtract(formValues);
1760
- setLocalCountOfDraftRelations(localDraftRelations.size);
1761
- }
1762
- }, [documentId, modified, formValues, setLocalCountOfDraftRelations]);
1763
- React.useEffect(() => {
1764
- if (!document || !document.documentId || isListView) {
1765
- return;
1766
- }
1767
- const fetchDraftRelationsCount = async () => {
1768
- const { data, error } = await countDraftRelations({
1769
- collectionType,
1770
- model,
1771
- documentId,
1772
- params
1773
- });
1774
- if (error) {
1775
- throw error;
1776
- }
1777
- if (data) {
1778
- setServerCountOfDraftRelations(data.data);
1779
- }
1780
- };
1781
- fetchDraftRelationsCount();
1782
- }, [isListView, document, documentId, countDraftRelations, collectionType, model, params]);
1783
- const isDocumentPublished = (document?.[PUBLISHED_AT_ATTRIBUTE_NAME] || meta?.availableStatus.some((doc) => doc[PUBLISHED_AT_ATTRIBUTE_NAME] !== null)) && document?.status !== "modified";
1784
- if (!schema?.options?.draftAndPublish) {
1785
- return null;
1786
- }
1787
- const performPublish = async () => {
1788
- setSubmitting(true);
1789
- try {
1790
- const { errors } = await validate();
1791
- if (errors) {
1792
- toggleNotification({
1793
- type: "danger",
1794
- message: formatMessage({
1795
- id: "content-manager.validation.error",
1796
- defaultMessage: "There are validation errors in your document. Please fix them before saving."
1775
+ {
1776
+ actions: restActions,
1777
+ label: formatMessage({
1778
+ id: "content-manager.containers.edit.panels.default.more-actions",
1779
+ defaultMessage: "More document actions"
1797
1780
  })
1798
- });
1799
- return;
1781
+ }
1782
+ ) : null
1783
+ ] }),
1784
+ secondaryAction ? /* @__PURE__ */ jsx(
1785
+ DocumentActionButton,
1786
+ {
1787
+ ...secondaryAction,
1788
+ variant: secondaryAction.variant || "secondary"
1800
1789
  }
1801
- const res = await publish(
1802
- {
1803
- collectionType,
1804
- model,
1805
- documentId,
1806
- params
1807
- },
1808
- formValues
1809
- );
1810
- if ("data" in res && collectionType !== SINGLE_TYPES) {
1811
- navigate({
1812
- pathname: `../${collectionType}/${model}/${res.data.documentId}`,
1813
- search: rawQuery
1814
- });
1815
- } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1816
- setErrors(formatValidationErrors(res.error));
1790
+ ) : null
1791
+ ] });
1792
+ };
1793
+ const DocumentActionButton = (action) => {
1794
+ const [dialogId, setDialogId] = React.useState(null);
1795
+ const { toggleNotification } = useNotification();
1796
+ const handleClick = (action2) => async (e) => {
1797
+ const { onClick = () => false, dialog, id } = action2;
1798
+ const muteDialog = await onClick(e);
1799
+ if (dialog && !muteDialog) {
1800
+ switch (dialog.type) {
1801
+ case "notification":
1802
+ toggleNotification({
1803
+ title: dialog.title,
1804
+ message: dialog.content,
1805
+ type: dialog.status,
1806
+ timeout: dialog.timeout,
1807
+ onClose: dialog.onClose
1808
+ });
1809
+ break;
1810
+ case "dialog":
1811
+ case "modal":
1812
+ e.preventDefault();
1813
+ setDialogId(id);
1817
1814
  }
1818
- } finally {
1819
- setSubmitting(false);
1820
1815
  }
1821
1816
  };
1822
- const totalDraftRelations = localCountOfDraftRelations + serverCountOfDraftRelations;
1823
- const enableDraftRelationsCount = false;
1824
- const hasDraftRelations = enableDraftRelationsCount;
1825
- return {
1826
- /**
1827
- * Disabled when:
1828
- * - currently if you're cloning a document we don't support publish & clone at the same time.
1829
- * - the form is submitting
1830
- * - the active tab is the published tab
1831
- * - the document is already published & not modified
1832
- * - the document is being created & not modified
1833
- * - the user doesn't have the permission to publish
1834
- */
1835
- disabled: isCloning || isSubmitting || isLoadingDraftRelations || activeTab === "published" || !modified && isDocumentPublished || !modified && !document?.documentId || !canPublish,
1836
- label: formatMessage({
1837
- id: "app.utils.publish",
1838
- defaultMessage: "Publish"
1839
- }),
1840
- onClick: async () => {
1841
- await performPublish();
1842
- },
1843
- dialog: hasDraftRelations ? {
1844
- type: "dialog",
1845
- variant: "danger",
1846
- footer: null,
1847
- title: formatMessage({
1848
- id: getTranslation(`popUpwarning.warning.bulk-has-draft-relations.title`),
1849
- defaultMessage: "Confirmation"
1850
- }),
1851
- content: formatMessage(
1852
- {
1853
- id: getTranslation(`popUpwarning.warning.bulk-has-draft-relations.message`),
1854
- defaultMessage: "This entry is related to {count, plural, one {# draft entry} other {# draft entries}}. Publishing it could leave broken links in your app."
1855
- },
1856
- {
1857
- count: totalDraftRelations
1858
- }
1859
- ),
1860
- onConfirm: async () => {
1861
- await performPublish();
1862
- }
1863
- } : void 0
1817
+ const handleClose = () => {
1818
+ setDialogId(null);
1864
1819
  };
1820
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1821
+ /* @__PURE__ */ jsx(
1822
+ Button,
1823
+ {
1824
+ flex: "auto",
1825
+ startIcon: action.icon,
1826
+ disabled: action.disabled,
1827
+ onClick: handleClick(action),
1828
+ justifyContent: "center",
1829
+ variant: action.variant || "default",
1830
+ paddingTop: "7px",
1831
+ paddingBottom: "7px",
1832
+ children: action.label
1833
+ }
1834
+ ),
1835
+ action.dialog?.type === "dialog" ? /* @__PURE__ */ jsx(
1836
+ DocumentActionConfirmDialog,
1837
+ {
1838
+ ...action.dialog,
1839
+ variant: action.dialog?.variant ?? action.variant,
1840
+ isOpen: dialogId === action.id,
1841
+ onClose: handleClose
1842
+ }
1843
+ ) : null,
1844
+ action.dialog?.type === "modal" ? /* @__PURE__ */ jsx(
1845
+ DocumentActionModal,
1846
+ {
1847
+ ...action.dialog,
1848
+ onModalClose: handleClose,
1849
+ isOpen: dialogId === action.id
1850
+ }
1851
+ ) : null
1852
+ ] });
1865
1853
  };
1866
- PublishAction$1.type = "publish";
1867
- const UpdateAction = ({
1868
- activeTab,
1869
- documentId,
1870
- model,
1871
- collectionType
1854
+ const DocumentActionsMenu = ({
1855
+ actions: actions2,
1856
+ children,
1857
+ label,
1858
+ variant = "tertiary"
1872
1859
  }) => {
1873
- const navigate = useNavigate();
1874
- const { toggleNotification } = useNotification();
1875
- const { _unstableFormatValidationErrors: formatValidationErrors } = useAPIErrorHandler();
1876
- const cloneMatch = useMatch(CLONE_PATH);
1877
- const isCloning = cloneMatch !== null;
1860
+ const [isOpen, setIsOpen] = React.useState(false);
1861
+ const [dialogId, setDialogId] = React.useState(null);
1878
1862
  const { formatMessage } = useIntl();
1879
- const { create, update, clone } = useDocumentActions();
1880
- const [{ query, rawQuery }] = useQueryParams();
1881
- const params = React.useMemo(() => buildValidParams(query), [query]);
1882
- const isSubmitting = useForm("UpdateAction", ({ isSubmitting: isSubmitting2 }) => isSubmitting2);
1883
- const modified = useForm("UpdateAction", ({ modified: modified2 }) => modified2);
1884
- const setSubmitting = useForm("UpdateAction", ({ setSubmitting: setSubmitting2 }) => setSubmitting2);
1885
- const document = useForm("UpdateAction", ({ values }) => values);
1886
- const validate = useForm("UpdateAction", (state) => state.validate);
1887
- const setErrors = useForm("UpdateAction", (state) => state.setErrors);
1888
- const resetForm = useForm("PublishAction", ({ resetForm: resetForm2 }) => resetForm2);
1889
- return {
1890
- /**
1891
- * Disabled when:
1892
- * - the form is submitting
1893
- * - the document is not modified & we're not cloning (you can save a clone entity straight away)
1894
- * - the active tab is the published tab
1895
- */
1896
- disabled: isSubmitting || !modified && !isCloning || activeTab === "published",
1897
- label: formatMessage({
1898
- id: "content-manager.containers.Edit.save",
1899
- defaultMessage: "Save"
1900
- }),
1901
- onClick: async () => {
1902
- setSubmitting(true);
1903
- try {
1904
- if (activeTab !== "draft") {
1905
- const { errors } = await validate();
1906
- if (errors) {
1907
- toggleNotification({
1908
- type: "danger",
1909
- message: formatMessage({
1910
- id: "content-manager.validation.error",
1911
- defaultMessage: "There are validation errors in your document. Please fix them before saving."
1912
- })
1913
- });
1914
- return;
1915
- }
1916
- }
1917
- if (isCloning) {
1918
- const res = await clone(
1919
- {
1920
- model,
1921
- documentId: cloneMatch.params.origin,
1922
- params
1923
- },
1924
- document
1925
- );
1926
- if ("data" in res) {
1927
- navigate(
1928
- {
1929
- pathname: `../${res.data.documentId}`,
1930
- search: rawQuery
1931
- },
1932
- { relative: "path" }
1933
- );
1934
- } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1935
- setErrors(formatValidationErrors(res.error));
1936
- }
1937
- } else if (documentId || collectionType === SINGLE_TYPES) {
1938
- const res = await update(
1939
- {
1940
- collectionType,
1941
- model,
1942
- documentId,
1943
- params
1944
- },
1945
- document
1946
- );
1947
- if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1948
- setErrors(formatValidationErrors(res.error));
1949
- } else {
1950
- resetForm();
1951
- }
1952
- } else {
1953
- const res = await create(
1954
- {
1955
- model,
1956
- params
1957
- },
1958
- document
1959
- );
1960
- if ("data" in res && collectionType !== SINGLE_TYPES) {
1961
- navigate(
1962
- {
1963
- pathname: `../${res.data.documentId}`,
1964
- search: rawQuery
1965
- },
1966
- { replace: true, relative: "path" }
1967
- );
1968
- } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
1969
- setErrors(formatValidationErrors(res.error));
1970
- }
1971
- }
1972
- } finally {
1973
- setSubmitting(false);
1863
+ const { toggleNotification } = useNotification();
1864
+ const isDisabled = actions2.every((action) => action.disabled) || actions2.length === 0;
1865
+ const handleClick = (action) => async (e) => {
1866
+ const { onClick = () => false, dialog, id } = action;
1867
+ const muteDialog = await onClick(e);
1868
+ if (dialog && !muteDialog) {
1869
+ switch (dialog.type) {
1870
+ case "notification":
1871
+ toggleNotification({
1872
+ title: dialog.title,
1873
+ message: dialog.content,
1874
+ type: dialog.status,
1875
+ timeout: dialog.timeout,
1876
+ onClose: dialog.onClose
1877
+ });
1878
+ break;
1879
+ case "dialog":
1880
+ case "modal":
1881
+ setDialogId(id);
1974
1882
  }
1975
1883
  }
1976
1884
  };
1977
- };
1978
- UpdateAction.type = "update";
1979
- const UNPUBLISH_DRAFT_OPTIONS = {
1980
- KEEP: "keep",
1981
- DISCARD: "discard"
1982
- };
1983
- const UnpublishAction$1 = ({
1984
- activeTab,
1985
- documentId,
1986
- model,
1987
- collectionType,
1988
- document
1989
- }) => {
1990
- const { formatMessage } = useIntl();
1991
- const { schema } = useDoc();
1992
- const canPublish = useDocumentRBAC("UnpublishAction", ({ canPublish: canPublish2 }) => canPublish2);
1993
- const { unpublish } = useDocumentActions();
1994
- const [{ query }] = useQueryParams();
1995
- const params = React.useMemo(() => buildValidParams(query), [query]);
1996
- const { toggleNotification } = useNotification();
1997
- const [shouldKeepDraft, setShouldKeepDraft] = React.useState(true);
1998
- const isDocumentModified = document?.status === "modified";
1999
- const handleChange = (value) => {
2000
- setShouldKeepDraft(value === UNPUBLISH_DRAFT_OPTIONS.KEEP);
1885
+ const handleClose = () => {
1886
+ setDialogId(null);
1887
+ setIsOpen(false);
2001
1888
  };
2002
- if (!schema?.options?.draftAndPublish) {
2003
- return null;
2004
- }
2005
- return {
2006
- disabled: !canPublish || activeTab === "published" || document?.status !== "published" && document?.status !== "modified",
2007
- label: formatMessage({
2008
- id: "app.utils.unpublish",
2009
- defaultMessage: "Unpublish"
2010
- }),
2011
- icon: /* @__PURE__ */ jsx(Cross, {}),
2012
- onClick: async () => {
2013
- if (!documentId && collectionType !== SINGLE_TYPES || isDocumentModified) {
2014
- if (!documentId) {
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
- return;
1889
+ return /* @__PURE__ */ jsxs(Menu.Root, { open: isOpen, onOpenChange: setIsOpen, children: [
1890
+ /* @__PURE__ */ jsxs(
1891
+ Menu.Trigger,
1892
+ {
1893
+ disabled: isDisabled,
1894
+ size: "S",
1895
+ endIcon: null,
1896
+ paddingTop: "4px",
1897
+ paddingLeft: "7px",
1898
+ paddingRight: "7px",
1899
+ variant,
1900
+ children: [
1901
+ /* @__PURE__ */ jsx(More, { "aria-hidden": true, focusable: false }),
1902
+ /* @__PURE__ */ jsx(VisuallyHidden, { tag: "span", children: label || formatMessage({
1903
+ id: "content-manager.containers.edit.panels.default.more-actions",
1904
+ defaultMessage: "More document actions"
1905
+ }) })
1906
+ ]
2027
1907
  }
2028
- await unpublish({
2029
- collectionType,
2030
- model,
2031
- documentId,
2032
- params
2033
- });
2034
- },
2035
- dialog: isDocumentModified ? {
2036
- type: "dialog",
2037
- title: formatMessage({
2038
- id: "app.components.ConfirmDialog.title",
2039
- defaultMessage: "Confirmation"
1908
+ ),
1909
+ /* @__PURE__ */ jsxs(Menu.Content, { maxHeight: void 0, popoverPlacement: "bottom-end", children: [
1910
+ actions2.map((action) => {
1911
+ return /* @__PURE__ */ jsx(
1912
+ Menu.Item,
1913
+ {
1914
+ disabled: action.disabled,
1915
+ onSelect: handleClick(action),
1916
+ display: "block",
1917
+ children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", gap: 4, children: [
1918
+ /* @__PURE__ */ jsxs(
1919
+ Flex,
1920
+ {
1921
+ color: !action.disabled ? convertActionVariantToColor(action.variant) : "inherit",
1922
+ gap: 2,
1923
+ tag: "span",
1924
+ children: [
1925
+ /* @__PURE__ */ jsx(
1926
+ Flex,
1927
+ {
1928
+ tag: "span",
1929
+ color: !action.disabled ? convertActionVariantToIconColor(action.variant) : "inherit",
1930
+ children: action.icon
1931
+ }
1932
+ ),
1933
+ action.label
1934
+ ]
1935
+ }
1936
+ ),
1937
+ action.id.startsWith("HistoryAction") && /* @__PURE__ */ jsx(
1938
+ Flex,
1939
+ {
1940
+ alignItems: "center",
1941
+ background: "alternative100",
1942
+ borderStyle: "solid",
1943
+ borderColor: "alternative200",
1944
+ borderWidth: "1px",
1945
+ height: 5,
1946
+ paddingLeft: 2,
1947
+ paddingRight: 2,
1948
+ hasRadius: true,
1949
+ color: "alternative600",
1950
+ children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", fontWeight: "bold", lineHeight: 1, children: formatMessage({ id: "global.new", defaultMessage: "New" }) })
1951
+ }
1952
+ )
1953
+ ] })
1954
+ },
1955
+ action.id
1956
+ );
2040
1957
  }),
2041
- content: /* @__PURE__ */ jsxs(Flex, { alignItems: "flex-start", direction: "column", gap: 6, children: [
2042
- /* @__PURE__ */ jsxs(Flex, { width: "100%", direction: "column", gap: 2, children: [
2043
- /* @__PURE__ */ jsx(WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
2044
- /* @__PURE__ */ jsx(Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
2045
- id: "content-manager.actions.unpublish.dialog.body",
2046
- defaultMessage: "Are you sure?"
2047
- }) })
2048
- ] }),
2049
- /* @__PURE__ */ jsxs(
2050
- Radio.Group,
1958
+ children
1959
+ ] }),
1960
+ actions2.map((action) => {
1961
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
1962
+ action.dialog?.type === "dialog" ? /* @__PURE__ */ jsx(
1963
+ DocumentActionConfirmDialog,
2051
1964
  {
2052
- defaultValue: UNPUBLISH_DRAFT_OPTIONS.KEEP,
2053
- name: "discard-options",
2054
- "aria-label": formatMessage({
2055
- id: "content-manager.actions.unpublish.dialog.radio-label",
2056
- defaultMessage: "Choose an option to unpublish the document."
2057
- }),
2058
- onValueChange: handleChange,
2059
- children: [
2060
- /* @__PURE__ */ jsx(Radio.Item, { checked: shouldKeepDraft, value: UNPUBLISH_DRAFT_OPTIONS.KEEP, children: formatMessage({
2061
- id: "content-manager.actions.unpublish.dialog.option.keep-draft",
2062
- defaultMessage: "Keep draft"
2063
- }) }),
2064
- /* @__PURE__ */ jsx(Radio.Item, { checked: !shouldKeepDraft, value: UNPUBLISH_DRAFT_OPTIONS.DISCARD, children: formatMessage({
2065
- id: "content-manager.actions.unpublish.dialog.option.replace-draft",
2066
- defaultMessage: "Replace draft"
2067
- }) })
2068
- ]
1965
+ ...action.dialog,
1966
+ variant: action.variant,
1967
+ isOpen: dialogId === action.id,
1968
+ onClose: handleClose
2069
1969
  }
2070
- )
2071
- ] }),
2072
- onConfirm: async () => {
2073
- if (!documentId && collectionType !== SINGLE_TYPES) {
2074
- console.error(
2075
- "You're trying to unpublish a document without an id, this is likely a bug with Strapi. Please open an issue."
2076
- );
2077
- toggleNotification({
2078
- message: formatMessage({
2079
- id: "content-manager.actions.unpublish.error",
2080
- defaultMessage: "An error occurred while trying to unpublish the document."
2081
- }),
2082
- type: "danger"
2083
- });
2084
- }
2085
- await unpublish(
1970
+ ) : null,
1971
+ action.dialog?.type === "modal" ? /* @__PURE__ */ jsx(
1972
+ DocumentActionModal,
2086
1973
  {
2087
- collectionType,
2088
- model,
2089
- documentId,
2090
- params
2091
- },
2092
- !shouldKeepDraft
2093
- );
2094
- }
2095
- } : void 0,
2096
- variant: "danger",
2097
- position: ["panel", "table-row"]
1974
+ ...action.dialog,
1975
+ onModalClose: handleClose,
1976
+ isOpen: dialogId === action.id
1977
+ }
1978
+ ) : null
1979
+ ] }, action.id);
1980
+ })
1981
+ ] });
1982
+ };
1983
+ const convertActionVariantToColor = (variant = "secondary") => {
1984
+ switch (variant) {
1985
+ case "danger":
1986
+ return "danger600";
1987
+ case "secondary":
1988
+ return void 0;
1989
+ case "success":
1990
+ return "success600";
1991
+ default:
1992
+ return "primary600";
1993
+ }
1994
+ };
1995
+ const convertActionVariantToIconColor = (variant = "secondary") => {
1996
+ switch (variant) {
1997
+ case "danger":
1998
+ return "danger600";
1999
+ case "secondary":
2000
+ return "neutral500";
2001
+ case "success":
2002
+ return "success600";
2003
+ default:
2004
+ return "primary600";
2005
+ }
2006
+ };
2007
+ const DocumentActionConfirmDialog = ({
2008
+ onClose,
2009
+ onCancel,
2010
+ onConfirm,
2011
+ title,
2012
+ content,
2013
+ isOpen,
2014
+ variant = "secondary"
2015
+ }) => {
2016
+ const { formatMessage } = useIntl();
2017
+ const handleClose = async () => {
2018
+ if (onCancel) {
2019
+ await onCancel();
2020
+ }
2021
+ onClose();
2022
+ };
2023
+ const handleConfirm = async () => {
2024
+ if (onConfirm) {
2025
+ await onConfirm();
2026
+ }
2027
+ onClose();
2028
+ };
2029
+ return /* @__PURE__ */ jsx(Dialog.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxs(Dialog.Content, { children: [
2030
+ /* @__PURE__ */ jsx(Dialog.Header, { children: title }),
2031
+ /* @__PURE__ */ jsx(Dialog.Body, { children: content }),
2032
+ /* @__PURE__ */ jsxs(Dialog.Footer, { children: [
2033
+ /* @__PURE__ */ jsx(Dialog.Cancel, { children: /* @__PURE__ */ jsx(Button, { variant: "tertiary", fullWidth: true, children: formatMessage({
2034
+ id: "app.components.Button.cancel",
2035
+ defaultMessage: "Cancel"
2036
+ }) }) }),
2037
+ /* @__PURE__ */ jsx(Button, { onClick: handleConfirm, variant, fullWidth: true, children: formatMessage({
2038
+ id: "app.components.Button.confirm",
2039
+ defaultMessage: "Confirm"
2040
+ }) })
2041
+ ] })
2042
+ ] }) });
2043
+ };
2044
+ const DocumentActionModal = ({
2045
+ isOpen,
2046
+ title,
2047
+ onClose,
2048
+ footer: Footer,
2049
+ content: Content,
2050
+ onModalClose
2051
+ }) => {
2052
+ const handleClose = () => {
2053
+ if (onClose) {
2054
+ onClose();
2055
+ }
2056
+ onModalClose();
2098
2057
  };
2058
+ return /* @__PURE__ */ jsx(Modal.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxs(Modal.Content, { children: [
2059
+ /* @__PURE__ */ jsx(Modal.Header, { children: /* @__PURE__ */ jsx(Modal.Title, { children: title }) }),
2060
+ typeof Content === "function" ? /* @__PURE__ */ jsx(Content, { onClose: handleClose }) : /* @__PURE__ */ jsx(Modal.Body, { children: Content }),
2061
+ typeof Footer === "function" ? /* @__PURE__ */ jsx(Footer, { onClose: handleClose }) : Footer
2062
+ ] }) });
2099
2063
  };
2100
- UnpublishAction$1.type = "unpublish";
2101
- const DiscardAction = ({
2064
+ const transformData = (data) => {
2065
+ if (Array.isArray(data)) {
2066
+ return data.map(transformData);
2067
+ }
2068
+ if (typeof data === "object" && data !== null) {
2069
+ if ("apiData" in data) {
2070
+ return data.apiData;
2071
+ }
2072
+ return mapValues(transformData)(data);
2073
+ }
2074
+ return data;
2075
+ };
2076
+ const PublishAction$1 = ({
2102
2077
  activeTab,
2103
2078
  documentId,
2104
2079
  model,
2105
2080
  collectionType,
2081
+ meta,
2106
2082
  document
2107
2083
  }) => {
2108
- const { formatMessage } = useIntl();
2109
2084
  const { schema } = useDoc();
2110
- const canUpdate = useDocumentRBAC("DiscardAction", ({ canUpdate: canUpdate2 }) => canUpdate2);
2111
- const { discard } = useDocumentActions();
2112
- const [{ query }] = useQueryParams();
2113
- const params = React.useMemo(() => buildValidParams(query), [query]);
2114
- if (!schema?.options?.draftAndPublish) {
2115
- return null;
2116
- }
2117
- return {
2118
- disabled: !canUpdate || activeTab === "published" || document?.status !== "modified",
2119
- label: formatMessage({
2120
- id: "content-manager.actions.discard.label",
2121
- defaultMessage: "Discard changes"
2122
- }),
2123
- icon: /* @__PURE__ */ jsx(Cross, {}),
2124
- position: ["panel", "table-row"],
2125
- variant: "danger",
2126
- dialog: {
2127
- type: "dialog",
2128
- title: formatMessage({
2129
- id: "app.components.ConfirmDialog.title",
2130
- defaultMessage: "Confirmation"
2131
- }),
2132
- content: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, children: [
2133
- /* @__PURE__ */ jsx(WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
2134
- /* @__PURE__ */ jsx(Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
2135
- id: "content-manager.actions.discard.dialog.body",
2136
- defaultMessage: "Are you sure?"
2137
- }) })
2138
- ] }),
2139
- onConfirm: async () => {
2140
- await discard({
2141
- collectionType,
2142
- model,
2143
- documentId,
2144
- params
2145
- });
2146
- }
2147
- }
2148
- };
2149
- };
2150
- DiscardAction.type = "discard";
2151
- const DEFAULT_ACTIONS = [PublishAction$1, UpdateAction, UnpublishAction$1, DiscardAction];
2152
- const intervals = ["years", "months", "days", "hours", "minutes", "seconds"];
2153
- const RelativeTime = React.forwardRef(
2154
- ({ timestamp, customIntervals = [], ...restProps }, forwardedRef) => {
2155
- const { formatRelativeTime, formatDate, formatTime } = useIntl();
2156
- const interval = intervalToDuration({
2157
- start: timestamp,
2158
- end: Date.now()
2159
- // see https://github.com/date-fns/date-fns/issues/2891 – No idea why it's all partial it returns it every time.
2160
- });
2161
- const unit = intervals.find((intervalUnit) => {
2162
- return interval[intervalUnit] > 0 && Object.keys(interval).includes(intervalUnit);
2163
- });
2164
- const relativeTime = isPast(timestamp) ? -interval[unit] : interval[unit];
2165
- const customInterval = customIntervals.find(
2166
- (custom) => interval[custom.unit] < custom.threshold
2167
- );
2168
- const displayText = customInterval ? customInterval.text : formatRelativeTime(relativeTime, unit, { numeric: "auto" });
2169
- return /* @__PURE__ */ jsx(
2170
- "time",
2171
- {
2172
- ref: forwardedRef,
2173
- dateTime: timestamp.toISOString(),
2174
- role: "time",
2175
- title: `${formatDate(timestamp)} ${formatTime(timestamp)}`,
2176
- ...restProps,
2177
- children: displayText
2178
- }
2179
- );
2180
- }
2181
- );
2182
- const getDisplayName = ({
2183
- firstname,
2184
- lastname,
2185
- username,
2186
- email
2187
- } = {}) => {
2188
- if (username) {
2189
- return username;
2190
- }
2191
- if (firstname) {
2192
- return `${firstname} ${lastname ?? ""}`.trim();
2193
- }
2194
- return email ?? "";
2195
- };
2196
- const capitalise = (str) => str.charAt(0).toUpperCase() + str.slice(1);
2197
- const DocumentStatus = ({ status = "draft", ...restProps }) => {
2198
- const statusVariant = status === "draft" ? "primary" : status === "published" ? "success" : "alternative";
2199
- return /* @__PURE__ */ jsx(Status, { ...restProps, showBullet: false, size: "S", variant: statusVariant, children: /* @__PURE__ */ jsx(Typography, { tag: "span", variant: "omega", fontWeight: "bold", children: capitalise(status) }) });
2200
- };
2201
- const Header = ({ isCreating, status, title: documentTitle = "Untitled" }) => {
2202
- const { formatMessage } = useIntl();
2085
+ const navigate = useNavigate();
2086
+ const { toggleNotification } = useNotification();
2087
+ const { _unstableFormatValidationErrors: formatValidationErrors } = useAPIErrorHandler();
2088
+ const isListView = useMatch(LIST_PATH) !== null;
2203
2089
  const isCloning = useMatch(CLONE_PATH) !== null;
2204
- const title = isCreating ? formatMessage({
2205
- id: "content-manager.containers.edit.title.new",
2206
- defaultMessage: "Create an entry"
2207
- }) : documentTitle;
2208
- return /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", paddingTop: 6, paddingBottom: 4, gap: 2, children: [
2209
- /* @__PURE__ */ jsx(BackButton, {}),
2210
- /* @__PURE__ */ jsxs(Flex, { width: "100%", justifyContent: "space-between", gap: "80px", alignItems: "flex-start", children: [
2211
- /* @__PURE__ */ jsx(Typography, { variant: "alpha", tag: "h1", children: title }),
2212
- /* @__PURE__ */ jsx(HeaderToolbar, {})
2213
- ] }),
2214
- status ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(DocumentStatus, { status: isCloning ? "draft" : status }) }) : null
2215
- ] });
2216
- };
2217
- const HeaderToolbar = () => {
2218
2090
  const { formatMessage } = useIntl();
2219
- const isCloning = useMatch(CLONE_PATH) !== null;
2091
+ const canPublish = useDocumentRBAC("PublishAction", ({ canPublish: canPublish2 }) => canPublish2);
2092
+ const { publish } = useDocumentActions();
2220
2093
  const [
2221
- {
2222
- query: { status = "draft" }
2094
+ countDraftRelations,
2095
+ { isLoading: isLoadingDraftRelations, isError: isErrorDraftRelations }
2096
+ ] = useLazyGetDraftRelationCountQuery();
2097
+ const [localCountOfDraftRelations, setLocalCountOfDraftRelations] = React.useState(0);
2098
+ const [serverCountOfDraftRelations, setServerCountOfDraftRelations] = React.useState(0);
2099
+ const [{ query, rawQuery }] = useQueryParams();
2100
+ const params = React.useMemo(() => buildValidParams(query), [query]);
2101
+ const modified = useForm("PublishAction", ({ modified: modified2 }) => modified2);
2102
+ const setSubmitting = useForm("PublishAction", ({ setSubmitting: setSubmitting2 }) => setSubmitting2);
2103
+ const isSubmitting = useForm("PublishAction", ({ isSubmitting: isSubmitting2 }) => isSubmitting2);
2104
+ const validate = useForm("PublishAction", (state) => state.validate);
2105
+ const setErrors = useForm("PublishAction", (state) => state.setErrors);
2106
+ const formValues = useForm("PublishAction", ({ values }) => values);
2107
+ React.useEffect(() => {
2108
+ if (isErrorDraftRelations) {
2109
+ toggleNotification({
2110
+ type: "danger",
2111
+ message: formatMessage({
2112
+ id: getTranslation("error.records.fetch-draft-relatons"),
2113
+ defaultMessage: "An error occurred while fetching draft relations on this document."
2114
+ })
2115
+ });
2223
2116
  }
2224
- ] = useQueryParams();
2225
- const { model, id, document, meta, collectionType } = useDoc();
2226
- const plugins = useStrapiApp("HeaderToolbar", (state) => state.plugins);
2227
- return /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
2228
- /* @__PURE__ */ jsx(
2229
- DescriptionComponentRenderer,
2230
- {
2231
- props: {
2232
- activeTab: status,
2233
- model,
2234
- documentId: id,
2235
- document: isCloning ? void 0 : document,
2236
- meta: isCloning ? void 0 : meta,
2237
- collectionType
2238
- },
2239
- descriptions: plugins["content-manager"].apis.getHeaderActions(),
2240
- children: (actions2) => {
2241
- if (actions2.length > 0) {
2242
- return /* @__PURE__ */ jsx(HeaderActions, { actions: actions2 });
2243
- } else {
2244
- return null;
2245
- }
2117
+ }, [isErrorDraftRelations, toggleNotification, formatMessage]);
2118
+ React.useEffect(() => {
2119
+ const localDraftRelations = /* @__PURE__ */ new Set();
2120
+ const extractDraftRelations = (data) => {
2121
+ const relations = data.connect || [];
2122
+ relations.forEach((relation) => {
2123
+ if (relation.status === "draft") {
2124
+ localDraftRelations.add(relation.id);
2246
2125
  }
2247
- }
2248
- ),
2249
- /* @__PURE__ */ jsx(
2250
- DescriptionComponentRenderer,
2251
- {
2252
- props: {
2253
- activeTab: status,
2254
- model,
2255
- documentId: id,
2256
- document: isCloning ? void 0 : document,
2257
- meta: isCloning ? void 0 : meta,
2258
- collectionType
2259
- },
2260
- descriptions: plugins["content-manager"].apis.getDocumentActions(),
2261
- children: (actions2) => {
2262
- const headerActions = actions2.filter((action) => {
2263
- const positions = Array.isArray(action.position) ? action.position : [action.position];
2264
- return positions.includes("header");
2265
- });
2266
- return /* @__PURE__ */ jsx(
2267
- DocumentActionsMenu,
2268
- {
2269
- actions: headerActions,
2270
- label: formatMessage({
2271
- id: "content-manager.containers.edit.header.more-actions",
2272
- defaultMessage: "More actions"
2273
- }),
2274
- children: /* @__PURE__ */ jsx(Information, { activeTab: status })
2275
- }
2276
- );
2126
+ });
2127
+ };
2128
+ const traverseAndExtract = (data) => {
2129
+ Object.entries(data).forEach(([key, value]) => {
2130
+ if (key === "connect" && Array.isArray(value)) {
2131
+ extractDraftRelations({ connect: value });
2132
+ } else if (typeof value === "object" && value !== null) {
2133
+ traverseAndExtract(value);
2277
2134
  }
2135
+ });
2136
+ };
2137
+ if (!documentId || modified) {
2138
+ traverseAndExtract(formValues);
2139
+ setLocalCountOfDraftRelations(localDraftRelations.size);
2140
+ }
2141
+ }, [documentId, modified, formValues, setLocalCountOfDraftRelations]);
2142
+ React.useEffect(() => {
2143
+ if (!document || !document.documentId || isListView) {
2144
+ return;
2145
+ }
2146
+ const fetchDraftRelationsCount = async () => {
2147
+ const { data, error } = await countDraftRelations({
2148
+ collectionType,
2149
+ model,
2150
+ documentId,
2151
+ params
2152
+ });
2153
+ if (error) {
2154
+ throw error;
2278
2155
  }
2279
- )
2280
- ] });
2281
- };
2282
- const Information = ({ activeTab }) => {
2283
- const { formatMessage } = useIntl();
2284
- const { document, meta } = useDoc();
2285
- if (!document || !document.id) {
2156
+ if (data) {
2157
+ setServerCountOfDraftRelations(data.data);
2158
+ }
2159
+ };
2160
+ fetchDraftRelationsCount();
2161
+ }, [isListView, document, documentId, countDraftRelations, collectionType, model, params]);
2162
+ const isDocumentPublished = (document?.[PUBLISHED_AT_ATTRIBUTE_NAME] || meta?.availableStatus.some((doc) => doc[PUBLISHED_AT_ATTRIBUTE_NAME] !== null)) && document?.status !== "modified";
2163
+ if (!schema?.options?.draftAndPublish) {
2286
2164
  return null;
2287
2165
  }
2288
- const createAndUpdateDocument = activeTab === "draft" ? document : meta?.availableStatus.find((status) => status.publishedAt === null);
2289
- const publishDocument = activeTab === "published" ? document : meta?.availableStatus.find((status) => status.publishedAt !== null);
2290
- const creator = createAndUpdateDocument?.[CREATED_BY_ATTRIBUTE_NAME] ? getDisplayName(createAndUpdateDocument[CREATED_BY_ATTRIBUTE_NAME]) : null;
2291
- const updator = createAndUpdateDocument?.[UPDATED_BY_ATTRIBUTE_NAME] ? getDisplayName(createAndUpdateDocument[UPDATED_BY_ATTRIBUTE_NAME]) : null;
2292
- const information = [
2293
- {
2294
- isDisplayed: !!publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME],
2295
- label: formatMessage({
2296
- id: "content-manager.containers.edit.information.last-published.label",
2297
- defaultMessage: "Last published"
2298
- }),
2299
- value: formatMessage(
2166
+ const performPublish = async () => {
2167
+ setSubmitting(true);
2168
+ try {
2169
+ const { errors } = await validate(true, {
2170
+ status: "published"
2171
+ });
2172
+ if (errors) {
2173
+ toggleNotification({
2174
+ type: "danger",
2175
+ message: formatMessage({
2176
+ id: "content-manager.validation.error",
2177
+ defaultMessage: "There are validation errors in your document. Please fix them before saving."
2178
+ })
2179
+ });
2180
+ return;
2181
+ }
2182
+ const res = await publish(
2300
2183
  {
2301
- id: "content-manager.containers.edit.information.last-published.value",
2302
- defaultMessage: `Published {time}{isAnonymous, select, true {} other { by {author}}}`
2184
+ collectionType,
2185
+ model,
2186
+ documentId,
2187
+ params
2303
2188
  },
2304
- {
2305
- time: /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME]) }),
2306
- isAnonymous: !publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME],
2307
- author: publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME] ? getDisplayName(publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME]) : null
2308
- }
2309
- )
2189
+ transformData(formValues)
2190
+ );
2191
+ if ("data" in res && collectionType !== SINGLE_TYPES) {
2192
+ navigate({
2193
+ pathname: `../${collectionType}/${model}/${res.data.documentId}`,
2194
+ search: rawQuery
2195
+ });
2196
+ } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
2197
+ setErrors(formatValidationErrors(res.error));
2198
+ }
2199
+ } finally {
2200
+ setSubmitting(false);
2201
+ }
2202
+ };
2203
+ const totalDraftRelations = localCountOfDraftRelations + serverCountOfDraftRelations;
2204
+ const enableDraftRelationsCount = false;
2205
+ const hasDraftRelations = enableDraftRelationsCount;
2206
+ return {
2207
+ /**
2208
+ * Disabled when:
2209
+ * - currently if you're cloning a document we don't support publish & clone at the same time.
2210
+ * - the form is submitting
2211
+ * - the active tab is the published tab
2212
+ * - the document is already published & not modified
2213
+ * - the document is being created & not modified
2214
+ * - the user doesn't have the permission to publish
2215
+ */
2216
+ disabled: isCloning || isSubmitting || isLoadingDraftRelations || activeTab === "published" || !modified && isDocumentPublished || !modified && !document?.documentId || !canPublish,
2217
+ label: formatMessage({
2218
+ id: "app.utils.publish",
2219
+ defaultMessage: "Publish"
2220
+ }),
2221
+ onClick: async () => {
2222
+ await performPublish();
2310
2223
  },
2311
- {
2312
- isDisplayed: !!createAndUpdateDocument?.[UPDATED_AT_ATTRIBUTE_NAME],
2313
- label: formatMessage({
2314
- id: "content-manager.containers.edit.information.last-draft.label",
2315
- defaultMessage: "Last draft"
2224
+ dialog: hasDraftRelations ? {
2225
+ type: "dialog",
2226
+ variant: "danger",
2227
+ footer: null,
2228
+ title: formatMessage({
2229
+ id: getTranslation(`popUpwarning.warning.bulk-has-draft-relations.title`),
2230
+ defaultMessage: "Confirmation"
2316
2231
  }),
2317
- value: formatMessage(
2232
+ content: formatMessage(
2318
2233
  {
2319
- id: "content-manager.containers.edit.information.last-draft.value",
2320
- defaultMessage: `Modified {time}{isAnonymous, select, true {} other { by {author}}}`
2234
+ id: getTranslation(`popUpwarning.warning.bulk-has-draft-relations.message`),
2235
+ defaultMessage: "This entry is related to {count, plural, one {# draft entry} other {# draft entries}}. Publishing it could leave broken links in your app."
2321
2236
  },
2322
2237
  {
2323
- time: /* @__PURE__ */ jsx(
2324
- RelativeTime,
2325
- {
2326
- timestamp: new Date(createAndUpdateDocument?.[UPDATED_AT_ATTRIBUTE_NAME])
2327
- }
2328
- ),
2329
- isAnonymous: !updator,
2330
- author: updator
2238
+ count: totalDraftRelations
2331
2239
  }
2332
- )
2333
- },
2334
- {
2335
- isDisplayed: !!createAndUpdateDocument?.[CREATED_AT_ATTRIBUTE_NAME],
2336
- label: formatMessage({
2337
- id: "content-manager.containers.edit.information.document.label",
2338
- defaultMessage: "Document"
2339
- }),
2340
- value: formatMessage(
2341
- {
2342
- id: "content-manager.containers.edit.information.document.value",
2343
- defaultMessage: `Created {time}{isAnonymous, select, true {} other { by {author}}}`
2344
- },
2345
- {
2346
- time: /* @__PURE__ */ jsx(
2347
- RelativeTime,
2348
- {
2349
- timestamp: new Date(createAndUpdateDocument?.[CREATED_AT_ATTRIBUTE_NAME])
2350
- }
2351
- ),
2352
- isAnonymous: !creator,
2353
- author: creator
2240
+ ),
2241
+ onConfirm: async () => {
2242
+ await performPublish();
2243
+ }
2244
+ } : void 0
2245
+ };
2246
+ };
2247
+ PublishAction$1.type = "publish";
2248
+ const UpdateAction = ({
2249
+ activeTab,
2250
+ documentId,
2251
+ model,
2252
+ collectionType
2253
+ }) => {
2254
+ const navigate = useNavigate();
2255
+ const { toggleNotification } = useNotification();
2256
+ const { _unstableFormatValidationErrors: formatValidationErrors } = useAPIErrorHandler();
2257
+ const cloneMatch = useMatch(CLONE_PATH);
2258
+ const isCloning = cloneMatch !== null;
2259
+ const { formatMessage } = useIntl();
2260
+ const { create, update, clone } = useDocumentActions();
2261
+ const [{ query, rawQuery }] = useQueryParams();
2262
+ const params = React.useMemo(() => buildValidParams(query), [query]);
2263
+ const isSubmitting = useForm("UpdateAction", ({ isSubmitting: isSubmitting2 }) => isSubmitting2);
2264
+ const modified = useForm("UpdateAction", ({ modified: modified2 }) => modified2);
2265
+ const setSubmitting = useForm("UpdateAction", ({ setSubmitting: setSubmitting2 }) => setSubmitting2);
2266
+ const document = useForm("UpdateAction", ({ values }) => values);
2267
+ const validate = useForm("UpdateAction", (state) => state.validate);
2268
+ const setErrors = useForm("UpdateAction", (state) => state.setErrors);
2269
+ const resetForm = useForm("PublishAction", ({ resetForm: resetForm2 }) => resetForm2);
2270
+ return {
2271
+ /**
2272
+ * Disabled when:
2273
+ * - the form is submitting
2274
+ * - the document is not modified & we're not cloning (you can save a clone entity straight away)
2275
+ * - the active tab is the published tab
2276
+ */
2277
+ disabled: isSubmitting || !modified && !isCloning || activeTab === "published",
2278
+ label: formatMessage({
2279
+ id: "content-manager.containers.Edit.save",
2280
+ defaultMessage: "Save"
2281
+ }),
2282
+ onClick: async () => {
2283
+ setSubmitting(true);
2284
+ try {
2285
+ const { errors } = await validate(true, {
2286
+ status: "draft"
2287
+ });
2288
+ if (errors) {
2289
+ toggleNotification({
2290
+ type: "danger",
2291
+ message: formatMessage({
2292
+ id: "content-manager.validation.error",
2293
+ defaultMessage: "There are validation errors in your document. Please fix them before saving."
2294
+ })
2295
+ });
2296
+ return;
2354
2297
  }
2355
- )
2356
- }
2357
- ].filter((info) => info.isDisplayed);
2358
- return /* @__PURE__ */ jsx(
2359
- Flex,
2360
- {
2361
- borderWidth: "1px 0 0 0",
2362
- borderStyle: "solid",
2363
- borderColor: "neutral150",
2364
- direction: "column",
2365
- marginTop: 2,
2366
- tag: "dl",
2367
- padding: 5,
2368
- gap: 3,
2369
- alignItems: "flex-start",
2370
- marginLeft: "-0.4rem",
2371
- marginRight: "-0.4rem",
2372
- width: "calc(100% + 8px)",
2373
- children: information.map((info) => /* @__PURE__ */ jsxs(Flex, { gap: 1, direction: "column", alignItems: "flex-start", children: [
2374
- /* @__PURE__ */ jsx(Typography, { tag: "dt", variant: "pi", fontWeight: "bold", children: info.label }),
2375
- /* @__PURE__ */ jsx(Typography, { tag: "dd", variant: "pi", textColor: "neutral600", children: info.value })
2376
- ] }, info.label))
2298
+ if (isCloning) {
2299
+ const res = await clone(
2300
+ {
2301
+ model,
2302
+ documentId: cloneMatch.params.origin,
2303
+ params
2304
+ },
2305
+ transformData(document)
2306
+ );
2307
+ if ("data" in res) {
2308
+ navigate(
2309
+ {
2310
+ pathname: `../${res.data.documentId}`,
2311
+ search: rawQuery
2312
+ },
2313
+ { relative: "path" }
2314
+ );
2315
+ } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
2316
+ setErrors(formatValidationErrors(res.error));
2317
+ }
2318
+ } else if (documentId || collectionType === SINGLE_TYPES) {
2319
+ const res = await update(
2320
+ {
2321
+ collectionType,
2322
+ model,
2323
+ documentId,
2324
+ params
2325
+ },
2326
+ transformData(document)
2327
+ );
2328
+ if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
2329
+ setErrors(formatValidationErrors(res.error));
2330
+ } else {
2331
+ resetForm();
2332
+ }
2333
+ } else {
2334
+ const res = await create(
2335
+ {
2336
+ model,
2337
+ params
2338
+ },
2339
+ transformData(document)
2340
+ );
2341
+ if ("data" in res && collectionType !== SINGLE_TYPES) {
2342
+ navigate(
2343
+ {
2344
+ pathname: `../${res.data.documentId}`,
2345
+ search: rawQuery
2346
+ },
2347
+ { replace: true, relative: "path" }
2348
+ );
2349
+ } else if ("error" in res && isBaseQueryError(res.error) && res.error.name === "ValidationError") {
2350
+ setErrors(formatValidationErrors(res.error));
2351
+ }
2352
+ }
2353
+ } finally {
2354
+ setSubmitting(false);
2355
+ }
2377
2356
  }
2378
- );
2357
+ };
2379
2358
  };
2380
- const HeaderActions = ({ actions: actions2 }) => {
2381
- return /* @__PURE__ */ jsx(Flex, { children: actions2.map((action) => {
2382
- if ("options" in action) {
2383
- return /* @__PURE__ */ jsx(
2384
- SingleSelect,
2385
- {
2386
- size: "S",
2387
- disabled: action.disabled,
2388
- "aria-label": action.label,
2389
- onChange: action.onSelect,
2390
- value: action.value,
2391
- children: action.options.map(({ label, ...option }) => /* @__PURE__ */ jsx(SingleSelectOption, { ...option, children: label }, option.value))
2392
- },
2393
- action.id
2394
- );
2395
- } else {
2396
- return null;
2397
- }
2398
- }) });
2359
+ UpdateAction.type = "update";
2360
+ const UNPUBLISH_DRAFT_OPTIONS = {
2361
+ KEEP: "keep",
2362
+ DISCARD: "discard"
2399
2363
  };
2400
- const ConfigureTheViewAction = ({ collectionType, model }) => {
2401
- const navigate = useNavigate();
2364
+ const UnpublishAction$1 = ({
2365
+ activeTab,
2366
+ documentId,
2367
+ model,
2368
+ collectionType,
2369
+ document
2370
+ }) => {
2402
2371
  const { formatMessage } = useIntl();
2403
- return {
2404
- label: formatMessage({
2405
- id: "app.links.configure-view",
2406
- defaultMessage: "Configure the view"
2407
- }),
2408
- icon: /* @__PURE__ */ jsx(ListPlus, {}),
2409
- onClick: () => {
2410
- navigate(`../${collectionType}/${model}/configurations/edit`);
2411
- },
2412
- position: "header"
2372
+ const { schema } = useDoc();
2373
+ const canPublish = useDocumentRBAC("UnpublishAction", ({ canPublish: canPublish2 }) => canPublish2);
2374
+ const { unpublish } = useDocumentActions();
2375
+ const [{ query }] = useQueryParams();
2376
+ const params = React.useMemo(() => buildValidParams(query), [query]);
2377
+ const { toggleNotification } = useNotification();
2378
+ const [shouldKeepDraft, setShouldKeepDraft] = React.useState(true);
2379
+ const isDocumentModified = document?.status === "modified";
2380
+ const handleChange = (value) => {
2381
+ setShouldKeepDraft(value === UNPUBLISH_DRAFT_OPTIONS.KEEP);
2413
2382
  };
2414
- };
2415
- ConfigureTheViewAction.type = "configure-the-view";
2416
- const EditTheModelAction = ({ model }) => {
2417
- const navigate = useNavigate();
2418
- const { formatMessage } = useIntl();
2383
+ if (!schema?.options?.draftAndPublish) {
2384
+ return null;
2385
+ }
2419
2386
  return {
2387
+ disabled: !canPublish || activeTab === "published" || document?.status !== "published" && document?.status !== "modified",
2420
2388
  label: formatMessage({
2421
- id: "content-manager.link-to-ctb",
2422
- defaultMessage: "Edit the model"
2389
+ id: "app.utils.unpublish",
2390
+ defaultMessage: "Unpublish"
2423
2391
  }),
2424
- icon: /* @__PURE__ */ jsx(Pencil, {}),
2425
- onClick: () => {
2426
- navigate(`/plugins/content-type-builder/content-types/${model}`);
2392
+ icon: /* @__PURE__ */ jsx(Cross, {}),
2393
+ onClick: async () => {
2394
+ if (!documentId && collectionType !== SINGLE_TYPES || isDocumentModified) {
2395
+ if (!documentId) {
2396
+ console.error(
2397
+ "You're trying to unpublish a document without an id, this is likely a bug with Strapi. Please open an issue."
2398
+ );
2399
+ toggleNotification({
2400
+ message: formatMessage({
2401
+ id: "content-manager.actions.unpublish.error",
2402
+ defaultMessage: "An error occurred while trying to unpublish the document."
2403
+ }),
2404
+ type: "danger"
2405
+ });
2406
+ }
2407
+ return;
2408
+ }
2409
+ await unpublish({
2410
+ collectionType,
2411
+ model,
2412
+ documentId,
2413
+ params
2414
+ });
2427
2415
  },
2428
- position: "header"
2416
+ dialog: isDocumentModified ? {
2417
+ type: "dialog",
2418
+ title: formatMessage({
2419
+ id: "app.components.ConfirmDialog.title",
2420
+ defaultMessage: "Confirmation"
2421
+ }),
2422
+ content: /* @__PURE__ */ jsxs(Flex, { alignItems: "flex-start", direction: "column", gap: 6, children: [
2423
+ /* @__PURE__ */ jsxs(Flex, { width: "100%", direction: "column", gap: 2, children: [
2424
+ /* @__PURE__ */ jsx(WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
2425
+ /* @__PURE__ */ jsx(Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
2426
+ id: "content-manager.actions.unpublish.dialog.body",
2427
+ defaultMessage: "Are you sure?"
2428
+ }) })
2429
+ ] }),
2430
+ /* @__PURE__ */ jsxs(
2431
+ Radio.Group,
2432
+ {
2433
+ defaultValue: UNPUBLISH_DRAFT_OPTIONS.KEEP,
2434
+ name: "discard-options",
2435
+ "aria-label": formatMessage({
2436
+ id: "content-manager.actions.unpublish.dialog.radio-label",
2437
+ defaultMessage: "Choose an option to unpublish the document."
2438
+ }),
2439
+ onValueChange: handleChange,
2440
+ children: [
2441
+ /* @__PURE__ */ jsx(Radio.Item, { checked: shouldKeepDraft, value: UNPUBLISH_DRAFT_OPTIONS.KEEP, children: formatMessage({
2442
+ id: "content-manager.actions.unpublish.dialog.option.keep-draft",
2443
+ defaultMessage: "Keep draft"
2444
+ }) }),
2445
+ /* @__PURE__ */ jsx(Radio.Item, { checked: !shouldKeepDraft, value: UNPUBLISH_DRAFT_OPTIONS.DISCARD, children: formatMessage({
2446
+ id: "content-manager.actions.unpublish.dialog.option.replace-draft",
2447
+ defaultMessage: "Replace draft"
2448
+ }) })
2449
+ ]
2450
+ }
2451
+ )
2452
+ ] }),
2453
+ onConfirm: async () => {
2454
+ if (!documentId && collectionType !== SINGLE_TYPES) {
2455
+ console.error(
2456
+ "You're trying to unpublish a document without an id, this is likely a bug with Strapi. Please open an issue."
2457
+ );
2458
+ toggleNotification({
2459
+ message: formatMessage({
2460
+ id: "content-manager.actions.unpublish.error",
2461
+ defaultMessage: "An error occurred while trying to unpublish the document."
2462
+ }),
2463
+ type: "danger"
2464
+ });
2465
+ }
2466
+ await unpublish(
2467
+ {
2468
+ collectionType,
2469
+ model,
2470
+ documentId,
2471
+ params
2472
+ },
2473
+ !shouldKeepDraft
2474
+ );
2475
+ }
2476
+ } : void 0,
2477
+ variant: "danger",
2478
+ position: ["panel", "table-row"]
2429
2479
  };
2430
2480
  };
2431
- EditTheModelAction.type = "edit-the-model";
2432
- const DeleteAction$1 = ({ documentId, model, collectionType, document }) => {
2433
- const navigate = useNavigate();
2481
+ UnpublishAction$1.type = "unpublish";
2482
+ const DiscardAction = ({
2483
+ activeTab,
2484
+ documentId,
2485
+ model,
2486
+ collectionType,
2487
+ document
2488
+ }) => {
2434
2489
  const { formatMessage } = useIntl();
2435
- const listViewPathMatch = useMatch(LIST_PATH);
2436
- const canDelete = useDocumentRBAC("DeleteAction", (state) => state.canDelete);
2437
- const { delete: deleteAction } = useDocumentActions();
2438
- const { toggleNotification } = useNotification();
2439
- const setSubmitting = useForm("DeleteAction", (state) => state.setSubmitting);
2490
+ const { schema } = useDoc();
2491
+ const canUpdate = useDocumentRBAC("DiscardAction", ({ canUpdate: canUpdate2 }) => canUpdate2);
2492
+ const { discard } = useDocumentActions();
2493
+ const [{ query }] = useQueryParams();
2494
+ const params = React.useMemo(() => buildValidParams(query), [query]);
2495
+ if (!schema?.options?.draftAndPublish) {
2496
+ return null;
2497
+ }
2440
2498
  return {
2441
- disabled: !canDelete || !document,
2499
+ disabled: !canUpdate || activeTab === "published" || document?.status !== "modified",
2442
2500
  label: formatMessage({
2443
- id: "content-manager.actions.delete.label",
2444
- defaultMessage: "Delete document"
2501
+ id: "content-manager.actions.discard.label",
2502
+ defaultMessage: "Discard changes"
2445
2503
  }),
2446
- icon: /* @__PURE__ */ jsx(Trash, {}),
2504
+ icon: /* @__PURE__ */ jsx(Cross, {}),
2505
+ position: ["panel", "table-row"],
2506
+ variant: "danger",
2447
2507
  dialog: {
2448
2508
  type: "dialog",
2449
2509
  title: formatMessage({
@@ -2453,92 +2513,90 @@ const DeleteAction$1 = ({ documentId, model, collectionType, document }) => {
2453
2513
  content: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, children: [
2454
2514
  /* @__PURE__ */ jsx(WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
2455
2515
  /* @__PURE__ */ jsx(Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
2456
- id: "content-manager.actions.delete.dialog.body",
2516
+ id: "content-manager.actions.discard.dialog.body",
2457
2517
  defaultMessage: "Are you sure?"
2458
2518
  }) })
2459
2519
  ] }),
2460
2520
  onConfirm: async () => {
2461
- if (!listViewPathMatch) {
2462
- setSubmitting(true);
2463
- }
2464
- try {
2465
- if (!documentId && collectionType !== SINGLE_TYPES) {
2466
- console.error(
2467
- "You're trying to delete a document without an id, this is likely a bug with Strapi. Please open an issue."
2468
- );
2469
- toggleNotification({
2470
- message: formatMessage({
2471
- id: "content-manager.actions.delete.error",
2472
- defaultMessage: "An error occurred while trying to delete the document."
2473
- }),
2474
- type: "danger"
2475
- });
2476
- return;
2477
- }
2478
- const res = await deleteAction({
2479
- documentId,
2480
- model,
2481
- collectionType,
2482
- params: {
2483
- locale: "*"
2484
- }
2485
- });
2486
- if (!("error" in res)) {
2487
- navigate({ pathname: `../${collectionType}/${model}` }, { replace: true });
2488
- }
2489
- } finally {
2490
- if (!listViewPathMatch) {
2491
- setSubmitting(false);
2492
- }
2493
- }
2521
+ await discard({
2522
+ collectionType,
2523
+ model,
2524
+ documentId,
2525
+ params
2526
+ });
2527
+ }
2528
+ }
2529
+ };
2530
+ };
2531
+ DiscardAction.type = "discard";
2532
+ const DEFAULT_ACTIONS = [PublishAction$1, UpdateAction, UnpublishAction$1, DiscardAction];
2533
+ const intervals = ["years", "months", "days", "hours", "minutes", "seconds"];
2534
+ const RelativeTime = React.forwardRef(
2535
+ ({ timestamp, customIntervals = [], ...restProps }, forwardedRef) => {
2536
+ const { formatRelativeTime, formatDate, formatTime } = useIntl();
2537
+ const interval = intervalToDuration({
2538
+ start: timestamp,
2539
+ end: Date.now()
2540
+ // see https://github.com/date-fns/date-fns/issues/2891 – No idea why it's all partial it returns it every time.
2541
+ });
2542
+ const unit = intervals.find((intervalUnit) => {
2543
+ return interval[intervalUnit] > 0 && Object.keys(interval).includes(intervalUnit);
2544
+ });
2545
+ const relativeTime = isPast(timestamp) ? -interval[unit] : interval[unit];
2546
+ const customInterval = customIntervals.find(
2547
+ (custom) => interval[custom.unit] < custom.threshold
2548
+ );
2549
+ const displayText = customInterval ? customInterval.text : formatRelativeTime(relativeTime, unit, { numeric: "auto" });
2550
+ return /* @__PURE__ */ jsx(
2551
+ "time",
2552
+ {
2553
+ ref: forwardedRef,
2554
+ dateTime: timestamp.toISOString(),
2555
+ role: "time",
2556
+ title: `${formatDate(timestamp)} ${formatTime(timestamp)}`,
2557
+ ...restProps,
2558
+ children: displayText
2494
2559
  }
2495
- },
2496
- variant: "danger",
2497
- position: ["header", "table-row"]
2498
- };
2560
+ );
2561
+ }
2562
+ );
2563
+ const getDisplayName = ({
2564
+ firstname,
2565
+ lastname,
2566
+ username,
2567
+ email
2568
+ } = {}) => {
2569
+ if (username) {
2570
+ return username;
2571
+ }
2572
+ if (firstname) {
2573
+ return `${firstname} ${lastname ?? ""}`.trim();
2574
+ }
2575
+ return email ?? "";
2499
2576
  };
2500
- DeleteAction$1.type = "delete";
2501
- const DEFAULT_HEADER_ACTIONS = [EditTheModelAction, ConfigureTheViewAction, DeleteAction$1];
2502
- const Panels = () => {
2503
- const isCloning = useMatch(CLONE_PATH) !== null;
2504
- const [
2505
- {
2506
- query: { status }
2507
- }
2508
- ] = useQueryParams({
2509
- status: "draft"
2510
- });
2511
- const { model, id, document, meta, collectionType } = useDoc();
2512
- const plugins = useStrapiApp("Panels", (state) => state.plugins);
2513
- const props = {
2514
- activeTab: status,
2515
- model,
2516
- documentId: id,
2517
- document: isCloning ? void 0 : document,
2518
- meta: isCloning ? void 0 : meta,
2519
- collectionType
2520
- };
2521
- return /* @__PURE__ */ jsx(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: /* @__PURE__ */ jsx(
2522
- DescriptionComponentRenderer,
2523
- {
2524
- props,
2525
- descriptions: plugins["content-manager"].apis.getEditViewSidePanels(),
2526
- children: (panels) => panels.map(({ content, id: id2, ...description }) => /* @__PURE__ */ jsx(Panel, { ...description, children: content }, id2))
2527
- }
2528
- ) });
2577
+ const capitalise = (str) => str.charAt(0).toUpperCase() + str.slice(1);
2578
+ const DocumentStatus = ({ status = "draft", ...restProps }) => {
2579
+ const statusVariant = status === "draft" ? "secondary" : status === "published" ? "success" : "alternative";
2580
+ return /* @__PURE__ */ jsx(Status, { ...restProps, showBullet: false, size: "S", variant: statusVariant, children: /* @__PURE__ */ jsx(Typography, { tag: "span", variant: "omega", fontWeight: "bold", children: capitalise(status) }) });
2529
2581
  };
2530
- const ActionsPanel = () => {
2582
+ const Header = ({ isCreating, status, title: documentTitle = "Untitled" }) => {
2531
2583
  const { formatMessage } = useIntl();
2532
- return {
2533
- title: formatMessage({
2534
- id: "content-manager.containers.edit.panels.default.title",
2535
- defaultMessage: "Document"
2536
- }),
2537
- content: /* @__PURE__ */ jsx(ActionsPanelContent, {})
2538
- };
2584
+ const isCloning = useMatch(CLONE_PATH) !== null;
2585
+ const title = isCreating ? formatMessage({
2586
+ id: "content-manager.containers.edit.title.new",
2587
+ defaultMessage: "Create an entry"
2588
+ }) : documentTitle;
2589
+ return /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", paddingTop: 6, paddingBottom: 4, gap: 2, children: [
2590
+ /* @__PURE__ */ jsx(BackButton, {}),
2591
+ /* @__PURE__ */ jsxs(Flex, { width: "100%", justifyContent: "space-between", gap: "80px", alignItems: "flex-start", children: [
2592
+ /* @__PURE__ */ jsx(Typography, { variant: "alpha", tag: "h1", children: title }),
2593
+ /* @__PURE__ */ jsx(HeaderToolbar, {})
2594
+ ] }),
2595
+ status ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(DocumentStatus, { status: isCloning ? "draft" : status }) }) : null
2596
+ ] });
2539
2597
  };
2540
- ActionsPanel.type = "actions";
2541
- const ActionsPanelContent = () => {
2598
+ const HeaderToolbar = () => {
2599
+ const { formatMessage } = useIntl();
2542
2600
  const isCloning = useMatch(CLONE_PATH) !== null;
2543
2601
  const [
2544
2602
  {
@@ -2546,356 +2604,432 @@ const ActionsPanelContent = () => {
2546
2604
  }
2547
2605
  ] = useQueryParams();
2548
2606
  const { model, id, document, meta, collectionType } = useDoc();
2549
- const plugins = useStrapiApp("ActionsPanel", (state) => state.plugins);
2550
- const props = {
2551
- activeTab: status,
2552
- model,
2553
- documentId: id,
2554
- document: isCloning ? void 0 : document,
2555
- meta: isCloning ? void 0 : meta,
2556
- collectionType
2557
- };
2558
- return /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, width: "100%", children: [
2607
+ const plugins = useStrapiApp("HeaderToolbar", (state) => state.plugins);
2608
+ return /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
2559
2609
  /* @__PURE__ */ jsx(
2560
2610
  DescriptionComponentRenderer,
2561
2611
  {
2562
- props,
2563
- descriptions: plugins["content-manager"].apis.getDocumentActions(),
2564
- children: (actions2) => /* @__PURE__ */ jsx(DocumentActions, { actions: actions2 })
2612
+ props: {
2613
+ activeTab: status,
2614
+ model,
2615
+ documentId: id,
2616
+ document: isCloning ? void 0 : document,
2617
+ meta: isCloning ? void 0 : meta,
2618
+ collectionType
2619
+ },
2620
+ descriptions: plugins["content-manager"].apis.getHeaderActions(),
2621
+ children: (actions2) => {
2622
+ if (actions2.length > 0) {
2623
+ return /* @__PURE__ */ jsx(HeaderActions, { actions: actions2 });
2624
+ } else {
2625
+ return null;
2626
+ }
2627
+ }
2565
2628
  }
2566
2629
  ),
2567
- /* @__PURE__ */ jsx(InjectionZone, { area: "editView.right-links", slug: model })
2630
+ /* @__PURE__ */ jsx(
2631
+ DescriptionComponentRenderer,
2632
+ {
2633
+ props: {
2634
+ activeTab: status,
2635
+ model,
2636
+ documentId: id,
2637
+ document: isCloning ? void 0 : document,
2638
+ meta: isCloning ? void 0 : meta,
2639
+ collectionType
2640
+ },
2641
+ descriptions: plugins["content-manager"].apis.getDocumentActions(),
2642
+ children: (actions2) => {
2643
+ const headerActions = actions2.filter((action) => {
2644
+ const positions = Array.isArray(action.position) ? action.position : [action.position];
2645
+ return positions.includes("header");
2646
+ });
2647
+ return /* @__PURE__ */ jsx(
2648
+ DocumentActionsMenu,
2649
+ {
2650
+ actions: headerActions,
2651
+ label: formatMessage({
2652
+ id: "content-manager.containers.edit.header.more-actions",
2653
+ defaultMessage: "More actions"
2654
+ }),
2655
+ children: /* @__PURE__ */ jsx(Information, { activeTab: status })
2656
+ }
2657
+ );
2658
+ }
2659
+ }
2660
+ )
2568
2661
  ] });
2569
2662
  };
2570
- const Panel = React.forwardRef(({ children, title }, ref) => {
2571
- return /* @__PURE__ */ jsxs(
2572
- Flex,
2663
+ const Information = ({ activeTab }) => {
2664
+ const { formatMessage } = useIntl();
2665
+ const { document, meta } = useDoc();
2666
+ if (!document || !document.id) {
2667
+ return null;
2668
+ }
2669
+ const createAndUpdateDocument = activeTab === "draft" ? document : meta?.availableStatus.find((status) => status.publishedAt === null);
2670
+ const publishDocument = activeTab === "published" ? document : meta?.availableStatus.find((status) => status.publishedAt !== null);
2671
+ const creator = createAndUpdateDocument?.[CREATED_BY_ATTRIBUTE_NAME] ? getDisplayName(createAndUpdateDocument[CREATED_BY_ATTRIBUTE_NAME]) : null;
2672
+ const updator = createAndUpdateDocument?.[UPDATED_BY_ATTRIBUTE_NAME] ? getDisplayName(createAndUpdateDocument[UPDATED_BY_ATTRIBUTE_NAME]) : null;
2673
+ const information = [
2573
2674
  {
2574
- ref,
2575
- tag: "aside",
2576
- "aria-labelledby": "additional-information",
2577
- background: "neutral0",
2578
- borderColor: "neutral150",
2579
- hasRadius: true,
2580
- paddingBottom: 4,
2581
- paddingLeft: 4,
2582
- paddingRight: 4,
2583
- paddingTop: 4,
2584
- shadow: "tableShadow",
2585
- gap: 3,
2586
- direction: "column",
2587
- justifyContent: "stretch",
2588
- alignItems: "flex-start",
2589
- children: [
2590
- /* @__PURE__ */ jsx(Typography, { tag: "h2", variant: "sigma", textTransform: "uppercase", children: title }),
2591
- children
2592
- ]
2593
- }
2594
- );
2595
- });
2596
- const HOOKS = {
2597
- /**
2598
- * Hook that allows to mutate the displayed headers of the list view table
2599
- * @constant
2600
- * @type {string}
2601
- */
2602
- INJECT_COLUMN_IN_TABLE: "Admin/CM/pages/ListView/inject-column-in-table",
2603
- /**
2604
- * Hook that allows to mutate the CM's collection types links pre-set filters
2605
- * @constant
2606
- * @type {string}
2607
- */
2608
- MUTATE_COLLECTION_TYPES_LINKS: "Admin/CM/pages/App/mutate-collection-types-links",
2609
- /**
2610
- * Hook that allows to mutate the CM's edit view layout
2611
- * @constant
2612
- * @type {string}
2613
- */
2614
- MUTATE_EDIT_VIEW_LAYOUT: "Admin/CM/pages/EditView/mutate-edit-view-layout",
2615
- /**
2616
- * Hook that allows to mutate the CM's single types links pre-set filters
2617
- * @constant
2618
- * @type {string}
2619
- */
2620
- MUTATE_SINGLE_TYPES_LINKS: "Admin/CM/pages/App/mutate-single-types-links"
2621
- };
2622
- const contentTypesApi = contentManagerApi.injectEndpoints({
2623
- endpoints: (builder) => ({
2624
- getContentTypeConfiguration: builder.query({
2625
- query: (uid) => ({
2626
- url: `/content-manager/content-types/${uid}/configuration`,
2627
- method: "GET"
2675
+ isDisplayed: !!publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME],
2676
+ label: formatMessage({
2677
+ id: "content-manager.containers.edit.information.last-published.label",
2678
+ defaultMessage: "Published"
2628
2679
  }),
2629
- transformResponse: (response) => response.data,
2630
- providesTags: (_result, _error, uid) => [
2631
- { type: "ContentTypesConfiguration", id: uid },
2632
- { type: "ContentTypeSettings", id: "LIST" }
2633
- ]
2634
- }),
2635
- getAllContentTypeSettings: builder.query({
2636
- query: () => "/content-manager/content-types-settings",
2637
- transformResponse: (response) => response.data,
2638
- providesTags: [{ type: "ContentTypeSettings", id: "LIST" }]
2639
- }),
2640
- updateContentTypeConfiguration: builder.mutation({
2641
- query: ({ uid, ...body }) => ({
2642
- url: `/content-manager/content-types/${uid}/configuration`,
2643
- method: "PUT",
2644
- data: body
2680
+ value: formatMessage(
2681
+ {
2682
+ id: "content-manager.containers.edit.information.last-published.value",
2683
+ defaultMessage: `{time}{isAnonymous, select, true {} other { by {author}}}`
2684
+ },
2685
+ {
2686
+ time: /* @__PURE__ */ jsx(RelativeTime, { timestamp: new Date(publishDocument?.[PUBLISHED_AT_ATTRIBUTE_NAME]) }),
2687
+ isAnonymous: !publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME],
2688
+ author: publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME] ? getDisplayName(publishDocument?.[PUBLISHED_BY_ATTRIBUTE_NAME]) : null
2689
+ }
2690
+ )
2691
+ },
2692
+ {
2693
+ isDisplayed: !!createAndUpdateDocument?.[UPDATED_AT_ATTRIBUTE_NAME],
2694
+ label: formatMessage({
2695
+ id: "content-manager.containers.edit.information.last-draft.label",
2696
+ defaultMessage: "Updated"
2697
+ }),
2698
+ value: formatMessage(
2699
+ {
2700
+ id: "content-manager.containers.edit.information.last-draft.value",
2701
+ defaultMessage: `{time}{isAnonymous, select, true {} other { by {author}}}`
2702
+ },
2703
+ {
2704
+ time: /* @__PURE__ */ jsx(
2705
+ RelativeTime,
2706
+ {
2707
+ timestamp: new Date(createAndUpdateDocument?.[UPDATED_AT_ATTRIBUTE_NAME])
2708
+ }
2709
+ ),
2710
+ isAnonymous: !updator,
2711
+ author: updator
2712
+ }
2713
+ )
2714
+ },
2715
+ {
2716
+ isDisplayed: !!createAndUpdateDocument?.[CREATED_AT_ATTRIBUTE_NAME],
2717
+ label: formatMessage({
2718
+ id: "content-manager.containers.edit.information.document.label",
2719
+ defaultMessage: "Created"
2645
2720
  }),
2646
- transformResponse: (response) => response.data,
2647
- invalidatesTags: (_result, _error, { uid }) => [
2648
- { type: "ContentTypesConfiguration", id: uid },
2649
- { type: "ContentTypeSettings", id: "LIST" },
2650
- // Is this necessary?
2651
- { type: "InitialData" }
2652
- ]
2653
- })
2654
- })
2655
- });
2656
- const {
2657
- useGetContentTypeConfigurationQuery,
2658
- useGetAllContentTypeSettingsQuery,
2659
- useUpdateContentTypeConfigurationMutation
2660
- } = contentTypesApi;
2661
- const checkIfAttributeIsDisplayable = (attribute) => {
2662
- const { type } = attribute;
2663
- if (type === "relation") {
2664
- return !attribute.relation.toLowerCase().includes("morph");
2665
- }
2666
- return !["json", "dynamiczone", "richtext", "password", "blocks"].includes(type) && !!type;
2667
- };
2668
- const getMainField = (attribute, mainFieldName, { schemas, components }) => {
2669
- if (!mainFieldName) {
2670
- return void 0;
2671
- }
2672
- const mainFieldType = attribute.type === "component" ? components[attribute.component].attributes[mainFieldName].type : (
2673
- // @ts-expect-error – `targetModel` does exist on the attribute for a relation.
2674
- schemas.find((schema) => schema.uid === attribute.targetModel)?.attributes[mainFieldName].type
2721
+ value: formatMessage(
2722
+ {
2723
+ id: "content-manager.containers.edit.information.document.value",
2724
+ defaultMessage: `{time}{isAnonymous, select, true {} other { by {author}}}`
2725
+ },
2726
+ {
2727
+ time: /* @__PURE__ */ jsx(
2728
+ RelativeTime,
2729
+ {
2730
+ timestamp: new Date(createAndUpdateDocument?.[CREATED_AT_ATTRIBUTE_NAME])
2731
+ }
2732
+ ),
2733
+ isAnonymous: !creator,
2734
+ author: creator
2735
+ }
2736
+ )
2737
+ }
2738
+ ].filter((info) => info.isDisplayed);
2739
+ return /* @__PURE__ */ jsx(
2740
+ Flex,
2741
+ {
2742
+ borderWidth: "1px 0 0 0",
2743
+ borderStyle: "solid",
2744
+ borderColor: "neutral150",
2745
+ direction: "column",
2746
+ marginTop: 2,
2747
+ tag: "dl",
2748
+ padding: 5,
2749
+ gap: 3,
2750
+ alignItems: "flex-start",
2751
+ marginLeft: "-0.4rem",
2752
+ marginRight: "-0.4rem",
2753
+ width: "calc(100% + 8px)",
2754
+ children: information.map((info) => /* @__PURE__ */ jsxs(Flex, { gap: 1, direction: "column", alignItems: "flex-start", children: [
2755
+ /* @__PURE__ */ jsx(Typography, { tag: "dt", variant: "pi", fontWeight: "bold", children: info.label }),
2756
+ /* @__PURE__ */ jsx(Typography, { tag: "dd", variant: "pi", textColor: "neutral600", children: info.value })
2757
+ ] }, info.label))
2758
+ }
2675
2759
  );
2676
- return {
2677
- name: mainFieldName,
2678
- type: mainFieldType ?? "string"
2679
- };
2680
- };
2681
- const DEFAULT_SETTINGS = {
2682
- bulkable: false,
2683
- filterable: false,
2684
- searchable: false,
2685
- pagination: false,
2686
- defaultSortBy: "",
2687
- defaultSortOrder: "asc",
2688
- mainField: "id",
2689
- pageSize: 10
2690
2760
  };
2691
- const useDocumentLayout = (model) => {
2692
- const { schema, components } = useDocument({ model, collectionType: "" }, { skip: true });
2693
- const [{ query }] = useQueryParams();
2694
- const runHookWaterfall = useStrapiApp("useDocumentLayout", (state) => state.runHookWaterfall);
2695
- const { toggleNotification } = useNotification();
2696
- const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
2697
- const { isLoading: isLoadingSchemas, schemas } = useContentTypeSchema();
2698
- const {
2699
- data,
2700
- isLoading: isLoadingConfigs,
2701
- error,
2702
- isFetching: isFetchingConfigs
2703
- } = useGetContentTypeConfigurationQuery(model);
2704
- const isLoading = isLoadingSchemas || isFetchingConfigs || isLoadingConfigs;
2705
- React.useEffect(() => {
2706
- if (error) {
2707
- toggleNotification({
2708
- type: "danger",
2709
- message: formatAPIError(error)
2710
- });
2761
+ const HeaderActions = ({ actions: actions2 }) => {
2762
+ const [dialogId, setDialogId] = React.useState(null);
2763
+ const handleClick = (action) => async (e) => {
2764
+ if (!("options" in action)) {
2765
+ const { onClick = () => false, dialog, id } = action;
2766
+ const muteDialog = await onClick(e);
2767
+ if (dialog && !muteDialog) {
2768
+ e.preventDefault();
2769
+ setDialogId(id);
2770
+ }
2711
2771
  }
2712
- }, [error, formatAPIError, toggleNotification]);
2713
- const editLayout = React.useMemo(
2714
- () => data && !isLoading ? formatEditLayout(data, { schemas, schema, components }) : {
2715
- layout: [],
2716
- components: {},
2717
- metadatas: {},
2718
- options: {},
2719
- settings: DEFAULT_SETTINGS
2720
- },
2721
- [data, isLoading, schemas, schema, components]
2722
- );
2723
- const listLayout = React.useMemo(() => {
2724
- return data && !isLoading ? formatListLayout(data, { schemas, schema, components }) : {
2725
- layout: [],
2726
- metadatas: {},
2727
- options: {},
2728
- settings: DEFAULT_SETTINGS
2729
- };
2730
- }, [data, isLoading, schemas, schema, components]);
2731
- const { layout: edit } = React.useMemo(
2732
- () => runHookWaterfall(HOOKS.MUTATE_EDIT_VIEW_LAYOUT, {
2733
- layout: editLayout,
2734
- query
2735
- }),
2736
- [editLayout, query, runHookWaterfall]
2737
- );
2738
- return {
2739
- error,
2740
- isLoading,
2741
- edit,
2742
- list: listLayout
2743
2772
  };
2744
- };
2745
- const useDocLayout = () => {
2746
- const { model } = useDoc();
2747
- return useDocumentLayout(model);
2748
- };
2749
- const formatEditLayout = (data, {
2750
- schemas,
2751
- schema,
2752
- components
2753
- }) => {
2754
- let currentPanelIndex = 0;
2755
- const panelledEditAttributes = convertEditLayoutToFieldLayouts(
2756
- data.contentType.layouts.edit,
2757
- schema?.attributes,
2758
- data.contentType.metadatas,
2759
- { configurations: data.components, schemas: components },
2760
- schemas
2761
- ).reduce((panels, row) => {
2762
- if (row.some((field) => field.type === "dynamiczone")) {
2763
- panels.push([row]);
2764
- currentPanelIndex += 2;
2773
+ const handleClose = () => {
2774
+ setDialogId(null);
2775
+ };
2776
+ return /* @__PURE__ */ jsx(Flex, { gap: 1, children: actions2.map((action) => {
2777
+ if (action.options) {
2778
+ return /* @__PURE__ */ jsx(
2779
+ SingleSelect,
2780
+ {
2781
+ size: "S",
2782
+ onChange: action.onSelect,
2783
+ "aria-label": action.label,
2784
+ ...action,
2785
+ children: action.options.map(({ label, ...option }) => /* @__PURE__ */ jsx(SingleSelectOption, { ...option, children: label }, option.value))
2786
+ },
2787
+ action.id
2788
+ );
2765
2789
  } else {
2766
- if (!panels[currentPanelIndex]) {
2767
- panels.push([]);
2790
+ if (action.type === "icon") {
2791
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
2792
+ /* @__PURE__ */ jsx(
2793
+ IconButton,
2794
+ {
2795
+ disabled: action.disabled,
2796
+ label: action.label,
2797
+ size: "S",
2798
+ onClick: handleClick(action),
2799
+ children: action.icon
2800
+ }
2801
+ ),
2802
+ action.dialog ? /* @__PURE__ */ jsx(
2803
+ HeaderActionDialog,
2804
+ {
2805
+ ...action.dialog,
2806
+ isOpen: dialogId === action.id,
2807
+ onClose: handleClose
2808
+ }
2809
+ ) : null
2810
+ ] }, action.id);
2768
2811
  }
2769
- panels[currentPanelIndex].push(row);
2770
2812
  }
2771
- return panels;
2772
- }, []);
2773
- const componentEditAttributes = Object.entries(data.components).reduce(
2774
- (acc, [uid, configuration]) => {
2775
- acc[uid] = {
2776
- layout: convertEditLayoutToFieldLayouts(
2777
- configuration.layouts.edit,
2778
- components[uid].attributes,
2779
- configuration.metadatas,
2780
- { configurations: data.components, schemas: components }
2781
- ),
2782
- settings: {
2783
- ...configuration.settings,
2784
- icon: components[uid].info.icon,
2785
- displayName: components[uid].info.displayName
2786
- }
2787
- };
2788
- return acc;
2813
+ }) });
2814
+ };
2815
+ const HeaderActionDialog = ({
2816
+ onClose,
2817
+ onCancel,
2818
+ title,
2819
+ content: Content,
2820
+ isOpen
2821
+ }) => {
2822
+ const handleClose = async () => {
2823
+ if (onCancel) {
2824
+ await onCancel();
2825
+ }
2826
+ onClose();
2827
+ };
2828
+ return /* @__PURE__ */ jsx(Dialog.Root, { open: isOpen, onOpenChange: handleClose, children: /* @__PURE__ */ jsxs(Dialog.Content, { children: [
2829
+ /* @__PURE__ */ jsx(Dialog.Header, { children: title }),
2830
+ typeof Content === "function" ? /* @__PURE__ */ jsx(Content, { onClose: handleClose }) : Content
2831
+ ] }) });
2832
+ };
2833
+ const ConfigureTheViewAction = ({ collectionType, model }) => {
2834
+ const navigate = useNavigate();
2835
+ const { formatMessage } = useIntl();
2836
+ return {
2837
+ label: formatMessage({
2838
+ id: "app.links.configure-view",
2839
+ defaultMessage: "Configure the view"
2840
+ }),
2841
+ icon: /* @__PURE__ */ jsx(ListPlus, {}),
2842
+ onClick: () => {
2843
+ navigate(`../${collectionType}/${model}/configurations/edit`);
2789
2844
  },
2790
- {}
2791
- );
2792
- const editMetadatas = Object.entries(data.contentType.metadatas).reduce(
2793
- (acc, [attribute, metadata]) => {
2794
- return {
2795
- ...acc,
2796
- [attribute]: metadata.edit
2797
- };
2845
+ position: "header"
2846
+ };
2847
+ };
2848
+ ConfigureTheViewAction.type = "configure-the-view";
2849
+ const EditTheModelAction = ({ model }) => {
2850
+ const navigate = useNavigate();
2851
+ const { formatMessage } = useIntl();
2852
+ return {
2853
+ label: formatMessage({
2854
+ id: "content-manager.link-to-ctb",
2855
+ defaultMessage: "Edit the model"
2856
+ }),
2857
+ icon: /* @__PURE__ */ jsx(Pencil, {}),
2858
+ onClick: () => {
2859
+ navigate(`/plugins/content-type-builder/content-types/${model}`);
2798
2860
  },
2799
- {}
2800
- );
2861
+ position: "header"
2862
+ };
2863
+ };
2864
+ EditTheModelAction.type = "edit-the-model";
2865
+ const DeleteAction$1 = ({ documentId, model, collectionType, document }) => {
2866
+ const navigate = useNavigate();
2867
+ const { formatMessage } = useIntl();
2868
+ const listViewPathMatch = useMatch(LIST_PATH);
2869
+ const canDelete = useDocumentRBAC("DeleteAction", (state) => state.canDelete);
2870
+ const { delete: deleteAction } = useDocumentActions();
2871
+ const { toggleNotification } = useNotification();
2872
+ const setSubmitting = useForm("DeleteAction", (state) => state.setSubmitting);
2873
+ const isLocalized = document?.locale != null;
2801
2874
  return {
2802
- layout: panelledEditAttributes,
2803
- components: componentEditAttributes,
2804
- metadatas: editMetadatas,
2805
- settings: {
2806
- ...data.contentType.settings,
2807
- displayName: schema?.info.displayName
2875
+ disabled: !canDelete || !document,
2876
+ label: formatMessage(
2877
+ {
2878
+ id: "content-manager.actions.delete.label",
2879
+ defaultMessage: "Delete entry{isLocalized, select, true { (all locales)} other {}}"
2880
+ },
2881
+ { isLocalized }
2882
+ ),
2883
+ icon: /* @__PURE__ */ jsx(Trash, {}),
2884
+ dialog: {
2885
+ type: "dialog",
2886
+ title: formatMessage({
2887
+ id: "app.components.ConfirmDialog.title",
2888
+ defaultMessage: "Confirmation"
2889
+ }),
2890
+ content: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, children: [
2891
+ /* @__PURE__ */ jsx(WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
2892
+ /* @__PURE__ */ jsx(Typography, { tag: "p", variant: "omega", textAlign: "center", children: formatMessage({
2893
+ id: "content-manager.actions.delete.dialog.body",
2894
+ defaultMessage: "Are you sure?"
2895
+ }) })
2896
+ ] }),
2897
+ onConfirm: async () => {
2898
+ if (!listViewPathMatch) {
2899
+ setSubmitting(true);
2900
+ }
2901
+ try {
2902
+ if (!documentId && collectionType !== SINGLE_TYPES) {
2903
+ console.error(
2904
+ "You're trying to delete a document without an id, this is likely a bug with Strapi. Please open an issue."
2905
+ );
2906
+ toggleNotification({
2907
+ message: formatMessage({
2908
+ id: "content-manager.actions.delete.error",
2909
+ defaultMessage: "An error occurred while trying to delete the document."
2910
+ }),
2911
+ type: "danger"
2912
+ });
2913
+ return;
2914
+ }
2915
+ const res = await deleteAction({
2916
+ documentId,
2917
+ model,
2918
+ collectionType,
2919
+ params: {
2920
+ locale: "*"
2921
+ }
2922
+ });
2923
+ if (!("error" in res)) {
2924
+ navigate({ pathname: `../${collectionType}/${model}` }, { replace: true });
2925
+ }
2926
+ } finally {
2927
+ if (!listViewPathMatch) {
2928
+ setSubmitting(false);
2929
+ }
2930
+ }
2931
+ }
2808
2932
  },
2809
- options: {
2810
- ...schema?.options,
2811
- ...schema?.pluginOptions,
2812
- ...data.contentType.options
2813
- }
2933
+ variant: "danger",
2934
+ position: ["header", "table-row"]
2814
2935
  };
2815
2936
  };
2816
- const convertEditLayoutToFieldLayouts = (rows, attributes = {}, metadatas, components, schemas = []) => {
2817
- return rows.map(
2818
- (row) => row.map((field) => {
2819
- const attribute = attributes[field.name];
2820
- if (!attribute) {
2821
- return null;
2822
- }
2823
- const { edit: metadata } = metadatas[field.name];
2824
- const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
2825
- return {
2826
- attribute,
2827
- disabled: !metadata.editable,
2828
- hint: metadata.description,
2829
- label: metadata.label ?? "",
2830
- name: field.name,
2831
- // @ts-expect-error – mainField does exist on the metadata for a relation.
2832
- mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
2833
- schemas,
2834
- components: components?.schemas ?? {}
2835
- }),
2836
- placeholder: metadata.placeholder ?? "",
2837
- required: attribute.required ?? false,
2838
- size: field.size,
2839
- unique: "unique" in attribute ? attribute.unique : false,
2840
- visible: metadata.visible ?? true,
2841
- type: attribute.type
2842
- };
2843
- }).filter((field) => field !== null)
2844
- );
2937
+ DeleteAction$1.type = "delete";
2938
+ const DEFAULT_HEADER_ACTIONS = [EditTheModelAction, ConfigureTheViewAction, DeleteAction$1];
2939
+ const Panels = () => {
2940
+ const isCloning = useMatch(CLONE_PATH) !== null;
2941
+ const [
2942
+ {
2943
+ query: { status }
2944
+ }
2945
+ ] = useQueryParams({
2946
+ status: "draft"
2947
+ });
2948
+ const { model, id, document, meta, collectionType } = useDoc();
2949
+ const plugins = useStrapiApp("Panels", (state) => state.plugins);
2950
+ const props = {
2951
+ activeTab: status,
2952
+ model,
2953
+ documentId: id,
2954
+ document: isCloning ? void 0 : document,
2955
+ meta: isCloning ? void 0 : meta,
2956
+ collectionType
2957
+ };
2958
+ return /* @__PURE__ */ jsx(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: /* @__PURE__ */ jsx(
2959
+ DescriptionComponentRenderer,
2960
+ {
2961
+ props,
2962
+ descriptions: plugins["content-manager"].apis.getEditViewSidePanels(),
2963
+ children: (panels) => panels.map(({ content, id: id2, ...description }) => /* @__PURE__ */ jsx(Panel, { ...description, children: content }, id2))
2964
+ }
2965
+ ) });
2845
2966
  };
2846
- const formatListLayout = (data, {
2847
- schemas,
2848
- schema,
2849
- components
2850
- }) => {
2851
- const listMetadatas = Object.entries(data.contentType.metadatas).reduce(
2852
- (acc, [attribute, metadata]) => {
2853
- return {
2854
- ...acc,
2855
- [attribute]: metadata.list
2856
- };
2857
- },
2858
- {}
2859
- );
2860
- const listAttributes = convertListLayoutToFieldLayouts(
2861
- data.contentType.layouts.list,
2862
- schema?.attributes,
2863
- listMetadatas,
2864
- { configurations: data.components, schemas: components },
2865
- schemas
2866
- );
2967
+ const ActionsPanel = () => {
2968
+ const { formatMessage } = useIntl();
2867
2969
  return {
2868
- layout: listAttributes,
2869
- settings: { ...data.contentType.settings, displayName: schema?.info.displayName },
2870
- metadatas: listMetadatas,
2871
- options: {
2872
- ...schema?.options,
2873
- ...schema?.pluginOptions,
2874
- ...data.contentType.options
2875
- }
2970
+ title: formatMessage({
2971
+ id: "content-manager.containers.edit.panels.default.title",
2972
+ defaultMessage: "Entry"
2973
+ }),
2974
+ content: /* @__PURE__ */ jsx(ActionsPanelContent, {})
2876
2975
  };
2877
2976
  };
2878
- const convertListLayoutToFieldLayouts = (columns, attributes = {}, metadatas, components, schemas = []) => {
2879
- return columns.map((name) => {
2880
- const attribute = attributes[name];
2881
- if (!attribute) {
2882
- return null;
2977
+ ActionsPanel.type = "actions";
2978
+ const ActionsPanelContent = () => {
2979
+ const isCloning = useMatch(CLONE_PATH) !== null;
2980
+ const [
2981
+ {
2982
+ query: { status = "draft" }
2883
2983
  }
2884
- const metadata = metadatas[name];
2885
- const settings = attribute.type === "component" && components ? components.configurations[attribute.component].settings : {};
2886
- return {
2887
- attribute,
2888
- label: metadata.label ?? "",
2889
- mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
2890
- schemas,
2891
- components: components?.schemas ?? {}
2892
- }),
2893
- name,
2894
- searchable: metadata.searchable ?? true,
2895
- sortable: metadata.sortable ?? true
2896
- };
2897
- }).filter((field) => field !== null);
2984
+ ] = useQueryParams();
2985
+ const { model, id, document, meta, collectionType } = useDoc();
2986
+ const plugins = useStrapiApp("ActionsPanel", (state) => state.plugins);
2987
+ const props = {
2988
+ activeTab: status,
2989
+ model,
2990
+ documentId: id,
2991
+ document: isCloning ? void 0 : document,
2992
+ meta: isCloning ? void 0 : meta,
2993
+ collectionType
2994
+ };
2995
+ return /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, width: "100%", children: [
2996
+ /* @__PURE__ */ jsx(
2997
+ DescriptionComponentRenderer,
2998
+ {
2999
+ props,
3000
+ descriptions: plugins["content-manager"].apis.getDocumentActions(),
3001
+ children: (actions2) => /* @__PURE__ */ jsx(DocumentActions, { actions: actions2 })
3002
+ }
3003
+ ),
3004
+ /* @__PURE__ */ jsx(InjectionZone, { area: "editView.right-links", slug: model })
3005
+ ] });
2898
3006
  };
3007
+ const Panel = React.forwardRef(({ children, title }, ref) => {
3008
+ return /* @__PURE__ */ jsxs(
3009
+ Flex,
3010
+ {
3011
+ ref,
3012
+ tag: "aside",
3013
+ "aria-labelledby": "additional-information",
3014
+ background: "neutral0",
3015
+ borderColor: "neutral150",
3016
+ hasRadius: true,
3017
+ paddingBottom: 4,
3018
+ paddingLeft: 4,
3019
+ paddingRight: 4,
3020
+ paddingTop: 4,
3021
+ shadow: "tableShadow",
3022
+ gap: 3,
3023
+ direction: "column",
3024
+ justifyContent: "stretch",
3025
+ alignItems: "flex-start",
3026
+ children: [
3027
+ /* @__PURE__ */ jsx(Typography, { tag: "h2", variant: "sigma", textTransform: "uppercase", textColor: "neutral600", children: title }),
3028
+ children
3029
+ ]
3030
+ }
3031
+ );
3032
+ });
2899
3033
  const ConfirmBulkActionDialog = ({
2900
3034
  onToggleDialog,
2901
3035
  isOpen = false,
@@ -3325,8 +3459,7 @@ const PublishAction = ({ documents, model }) => {
3325
3459
  const refetchList = () => {
3326
3460
  contentManagerApi.util.invalidateTags([{ type: "Document", id: `${model}_LIST` }]);
3327
3461
  };
3328
- if (!showPublishButton)
3329
- return null;
3462
+ if (!showPublishButton) return null;
3330
3463
  return {
3331
3464
  actionType: "publish",
3332
3465
  variant: "tertiary",
@@ -3394,8 +3527,7 @@ const DeleteAction = ({ documents, model }) => {
3394
3527
  selectRow([]);
3395
3528
  }
3396
3529
  };
3397
- if (!hasDeletePermission)
3398
- return null;
3530
+ if (!hasDeletePermission) return null;
3399
3531
  return {
3400
3532
  variant: "danger-light",
3401
3533
  label: formatMessage({ id: "global.delete", defaultMessage: "Delete" }),
@@ -3444,8 +3576,7 @@ const UnpublishAction = ({ documents, model }) => {
3444
3576
  }
3445
3577
  };
3446
3578
  const showUnpublishButton = hasDraftAndPublishEnabled && hasPublishPermission && documents.some((entry) => entry.status === "published" || entry.status === "modified");
3447
- if (!showUnpublishButton)
3448
- return null;
3579
+ if (!showUnpublishButton) return null;
3449
3580
  return {
3450
3581
  variant: "tertiary",
3451
3582
  label: formatMessage({ id: "app.utils.unpublish", defaultMessage: "Unpublish" }),
@@ -3781,10 +3912,8 @@ class ContentManagerPlugin {
3781
3912
  const getPrintableType = (value) => {
3782
3913
  const nativeType = typeof value;
3783
3914
  if (nativeType === "object") {
3784
- if (value === null)
3785
- return "null";
3786
- if (Array.isArray(value))
3787
- return "array";
3915
+ if (value === null) return "null";
3916
+ if (Array.isArray(value)) return "array";
3788
3917
  if (value instanceof Object && value.constructor.name !== "Object") {
3789
3918
  return value.constructor.name;
3790
3919
  }
@@ -3873,6 +4002,15 @@ const { setInitialData } = actions;
3873
4002
  const reducer = combineReducers({
3874
4003
  app: reducer$1
3875
4004
  });
4005
+ const FEATURE_ID = "preview";
4006
+ const previewAdmin = {
4007
+ bootstrap(app) {
4008
+ if (!window.strapi.future.isEnabled(FEATURE_ID)) {
4009
+ return {};
4010
+ }
4011
+ console.log("Bootstrapping preview admin");
4012
+ }
4013
+ };
3876
4014
  const index = {
3877
4015
  register(app) {
3878
4016
  const cm = new ContentManagerPlugin();
@@ -3892,7 +4030,7 @@ const index = {
3892
4030
  app.router.addRoute({
3893
4031
  path: "content-manager/*",
3894
4032
  lazy: async () => {
3895
- const { Layout } = await import("./layout-0TY7UtKO.mjs");
4033
+ const { Layout } = await import("./layout-BS7u3JUZ.mjs");
3896
4034
  return {
3897
4035
  Component: Layout
3898
4036
  };
@@ -3905,11 +4043,14 @@ const index = {
3905
4043
  if (typeof historyAdmin.bootstrap === "function") {
3906
4044
  historyAdmin.bootstrap(app);
3907
4045
  }
4046
+ if (typeof previewAdmin.bootstrap === "function") {
4047
+ previewAdmin.bootstrap(app);
4048
+ }
3908
4049
  },
3909
4050
  async registerTrads({ locales }) {
3910
4051
  const importedTrads = await Promise.all(
3911
4052
  locales.map((locale) => {
3912
- 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-DCszE74t.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 }) => {
4053
+ return __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/ar.json": () => import("./ar-CCEVvqGG.mjs"), "./translations/ca.json": () => import("./ca-5U32ON2v.mjs"), "./translations/cs.json": () => import("./cs-CM2aBUar.mjs"), "./translations/de.json": () => import("./de-C72KDNOl.mjs"), "./translations/en.json": () => import("./en-DKV44jRb.mjs"), "./translations/es.json": () => import("./es-CeXiYflN.mjs"), "./translations/eu.json": () => import("./eu-CdALomew.mjs"), "./translations/fr.json": () => import("./fr-CD9VFbPM.mjs"), "./translations/gu.json": () => import("./gu-CNpaMDpH.mjs"), "./translations/hi.json": () => import("./hi-Dwvd04m3.mjs"), "./translations/hu.json": () => import("./hu-CeYvaaO0.mjs"), "./translations/id.json": () => import("./id-BtwA9WJT.mjs"), "./translations/it.json": () => import("./it-BrVPqaf1.mjs"), "./translations/ja.json": () => import("./ja-CtsUxOvk.mjs"), "./translations/ko.json": () => import("./ko-HVQRlfUI.mjs"), "./translations/ml.json": () => import("./ml-BihZwQit.mjs"), "./translations/ms.json": () => import("./ms-m_WjyWx7.mjs"), "./translations/nl.json": () => import("./nl-D4R9gHx5.mjs"), "./translations/pl.json": () => import("./pl-sbx9mSt_.mjs"), "./translations/pt-BR.json": () => import("./pt-BR-C71iDxnh.mjs"), "./translations/pt.json": () => import("./pt-BsaFvS8-.mjs"), "./translations/ru.json": () => import("./ru-BE6A4Exp.mjs"), "./translations/sa.json": () => import("./sa-Dag0k-Z8.mjs"), "./translations/sk.json": () => import("./sk-BFg-R8qJ.mjs"), "./translations/sv.json": () => import("./sv-CUnfWGsh.mjs"), "./translations/th.json": () => import("./th-BqbI8lIT.mjs"), "./translations/tr.json": () => import("./tr-CgeK3wJM.mjs"), "./translations/uk.json": () => import("./uk-CR-zDhAY.mjs"), "./translations/vi.json": () => import("./vi-DUXIk_fw.mjs"), "./translations/zh-Hans.json": () => import("./zh-Hans-BPQcRIyH.mjs"), "./translations/zh.json": () => import("./zh-BWZspA60.mjs") }), `./translations/${locale}.json`, 3).then(({ default: data }) => {
3913
4054
  return {
3914
4055
  data: prefixPluginTranslations(data, PLUGIN_ID),
3915
4056
  locale
@@ -3937,7 +4078,8 @@ export {
3937
4078
  InjectionZone as I,
3938
4079
  useDocument as J,
3939
4080
  index as K,
3940
- useDocumentActions as L,
4081
+ useContentManagerContext as L,
4082
+ useDocumentActions as M,
3941
4083
  Panels as P,
3942
4084
  RelativeTime as R,
3943
4085
  SINGLE_TYPES as S,
@@ -3969,4 +4111,4 @@ export {
3969
4111
  capitalise as y,
3970
4112
  useUpdateContentTypeConfigurationMutation as z
3971
4113
  };
3972
- //# sourceMappingURL=index-BYSWwHBJ.mjs.map
4114
+ //# sourceMappingURL=index-LwSbWlvf.mjs.map