@strapi/content-manager 0.0.0-experimental.6d27139261823fc4b18da9f3c10b271d5010dbf0 → 0.0.0-experimental.71ed910bd859c7e558bd1c1042eaadb7d26fd22a

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