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