@strapi/content-manager 5.0.0-rc.3 → 5.0.0-rc.30

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