@strapi/content-releases 0.0.0-next.1168c576ca50587b1a4377082ee09d2375410204 → 0.0.0-next.16afe21665d27a27f5c86939ba362066eabf1982

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 (31) hide show
  1. package/LICENSE +17 -1
  2. package/dist/_chunks/{App-_20W9dYa.js → App-dLXY5ei3.js} +784 -468
  3. package/dist/_chunks/App-dLXY5ei3.js.map +1 -0
  4. package/dist/_chunks/App-jrh58sXY.mjs +1330 -0
  5. package/dist/_chunks/App-jrh58sXY.mjs.map +1 -0
  6. package/dist/_chunks/PurchaseContentReleases-3tRbmbY3.mjs +51 -0
  7. package/dist/_chunks/PurchaseContentReleases-3tRbmbY3.mjs.map +1 -0
  8. package/dist/_chunks/PurchaseContentReleases-bpIYXOfu.js +51 -0
  9. package/dist/_chunks/PurchaseContentReleases-bpIYXOfu.js.map +1 -0
  10. package/dist/_chunks/{en-gYDqKYFd.js → en-HrREghh3.js} +30 -7
  11. package/dist/_chunks/en-HrREghh3.js.map +1 -0
  12. package/dist/_chunks/{en-MyLPoISH.mjs → en-ltT1TlKQ.mjs} +30 -7
  13. package/dist/_chunks/en-ltT1TlKQ.mjs.map +1 -0
  14. package/dist/_chunks/{index-KJa1Rb5F.js → index-CVO0Rqdm.js} +469 -41
  15. package/dist/_chunks/index-CVO0Rqdm.js.map +1 -0
  16. package/dist/_chunks/{index-c4zRX_sg.mjs → index-PiOGBETy.mjs} +486 -58
  17. package/dist/_chunks/index-PiOGBETy.mjs.map +1 -0
  18. package/dist/admin/index.js +1 -1
  19. package/dist/admin/index.mjs +2 -2
  20. package/dist/server/index.js +1066 -398
  21. package/dist/server/index.js.map +1 -1
  22. package/dist/server/index.mjs +1065 -398
  23. package/dist/server/index.mjs.map +1 -1
  24. package/package.json +16 -13
  25. package/dist/_chunks/App-L1jSxCiL.mjs +0 -1015
  26. package/dist/_chunks/App-L1jSxCiL.mjs.map +0 -1
  27. package/dist/_chunks/App-_20W9dYa.js.map +0 -1
  28. package/dist/_chunks/en-MyLPoISH.mjs.map +0 -1
  29. package/dist/_chunks/en-gYDqKYFd.js.map +0 -1
  30. package/dist/_chunks/index-KJa1Rb5F.js.map +0 -1
  31. package/dist/_chunks/index-c4zRX_sg.mjs.map +0 -1
@@ -1,14 +1,14 @@
1
- import { getFetchClient, useNotification, useAPIErrorHandler, CheckPermissions, useCMEditViewDataManager, NoContent, prefixPluginTranslations } from "@strapi/helper-plugin";
1
+ import { getFetchClient, useNotification, useAPIErrorHandler, CheckPermissions, useCMEditViewDataManager, NoContent, useRBAC, SortIcon, prefixPluginTranslations } from "@strapi/helper-plugin";
2
2
  import { Cross, Pencil, More, Plus, PaperPlane } from "@strapi/icons";
3
3
  import { jsx, jsxs } from "react/jsx-runtime";
4
4
  import * as React from "react";
5
5
  import { skipToken } from "@reduxjs/toolkit/query";
6
- import { IconButton, Flex, Icon, Typography, Field, FieldLabel, VisuallyHidden, FieldInput, Box, Button, ModalLayout, ModalHeader, ModalBody, SingleSelect, SingleSelectOption, ModalFooter } from "@strapi/design-system";
6
+ import { IconButton, Flex, Icon, Typography, Field, FieldLabel, VisuallyHidden, FieldInput, Box, Button as Button$1, ModalLayout, ModalHeader, ModalBody, SingleSelect, SingleSelectOption, ModalFooter, Popover } from "@strapi/design-system";
7
7
  import { Menu, Link, LinkButton } from "@strapi/design-system/v2";
8
8
  import { isAxiosError as isAxiosError$1 } from "axios";
9
9
  import { Formik, Form } from "formik";
10
10
  import { useIntl } from "react-intl";
11
- import { NavLink, useParams, Link as Link$1 } from "react-router-dom";
11
+ import { NavLink, Link as Link$1 } from "react-router-dom";
12
12
  import * as yup from "yup";
13
13
  import { createApi } from "@reduxjs/toolkit/query/react";
14
14
  import styled from "styled-components";
@@ -136,7 +136,7 @@ const isAxiosError = (err) => {
136
136
  const releaseApi = createApi({
137
137
  reducerPath: pluginId,
138
138
  baseQuery: axiosBaseQuery,
139
- tagTypes: ["Release", "ReleaseAction"],
139
+ tagTypes: ["Release", "ReleaseAction", "EntriesInRelease"],
140
140
  endpoints: (build) => {
141
141
  return {
142
142
  getReleasesForEntry: build.query({
@@ -251,6 +251,20 @@ const releaseApi = createApi({
251
251
  { type: "ReleaseAction", id: "LIST" }
252
252
  ]
253
253
  }),
254
+ createManyReleaseActions: build.mutation({
255
+ query({ body, params }) {
256
+ return {
257
+ url: `/content-releases/${params.releaseId}/actions/bulk`,
258
+ method: "POST",
259
+ data: body
260
+ };
261
+ },
262
+ invalidatesTags: [
263
+ { type: "Release", id: "LIST" },
264
+ { type: "ReleaseAction", id: "LIST" },
265
+ { type: "EntriesInRelease" }
266
+ ]
267
+ }),
254
268
  updateReleaseAction: build.mutation({
255
269
  query({ body, params }) {
256
270
  return {
@@ -259,7 +273,27 @@ const releaseApi = createApi({
259
273
  data: body
260
274
  };
261
275
  },
262
- invalidatesTags: () => [{ type: "ReleaseAction", id: "LIST" }]
276
+ invalidatesTags: () => [{ type: "ReleaseAction", id: "LIST" }],
277
+ async onQueryStarted({ body, params, query, actionPath }, { dispatch, queryFulfilled }) {
278
+ const paramsWithoutActionId = {
279
+ releaseId: params.releaseId,
280
+ ...query
281
+ };
282
+ const patchResult = dispatch(
283
+ releaseApi.util.updateQueryData("getReleaseActions", paramsWithoutActionId, (draft) => {
284
+ const [key, index] = actionPath;
285
+ const action = draft.data[key][index];
286
+ if (action) {
287
+ action.type = body.type;
288
+ }
289
+ })
290
+ );
291
+ try {
292
+ await queryFulfilled;
293
+ } catch {
294
+ patchResult.undo();
295
+ }
296
+ }
263
297
  }),
264
298
  deleteReleaseAction: build.mutation({
265
299
  query({ params }) {
@@ -268,9 +302,11 @@ const releaseApi = createApi({
268
302
  method: "DELETE"
269
303
  };
270
304
  },
271
- invalidatesTags: [
305
+ invalidatesTags: (result, error, arg) => [
272
306
  { type: "Release", id: "LIST" },
273
- { type: "ReleaseAction", id: "LIST" }
307
+ { type: "Release", id: arg.params.releaseId },
308
+ { type: "ReleaseAction", id: "LIST" },
309
+ { type: "EntriesInRelease" }
274
310
  ]
275
311
  }),
276
312
  publishRelease: build.mutation({
@@ -289,7 +325,22 @@ const releaseApi = createApi({
289
325
  method: "DELETE"
290
326
  };
291
327
  },
292
- invalidatesTags: (result, error, arg) => [{ type: "Release", id: arg.id }]
328
+ invalidatesTags: () => [{ type: "Release", id: "LIST" }, { type: "EntriesInRelease" }]
329
+ }),
330
+ getMappedEntriesInReleases: build.query({
331
+ query(params) {
332
+ return {
333
+ url: "/content-releases/mapEntriesToReleases",
334
+ method: "GET",
335
+ config: {
336
+ params
337
+ }
338
+ };
339
+ },
340
+ transformResponse(response) {
341
+ return response.data;
342
+ },
343
+ providesTags: [{ type: "EntriesInRelease" }]
293
344
  })
294
345
  };
295
346
  }
@@ -301,12 +352,30 @@ const {
301
352
  useGetReleaseActionsQuery,
302
353
  useCreateReleaseMutation,
303
354
  useCreateReleaseActionMutation,
355
+ useCreateManyReleaseActionsMutation,
304
356
  useUpdateReleaseMutation,
305
357
  useUpdateReleaseActionMutation,
306
358
  usePublishReleaseMutation,
307
359
  useDeleteReleaseActionMutation,
308
- useDeleteReleaseMutation
360
+ useDeleteReleaseMutation,
361
+ useGetMappedEntriesInReleasesQuery
309
362
  } = releaseApi;
363
+ const getTimezoneOffset = (timezone, date) => {
364
+ try {
365
+ const offsetPart = new Intl.DateTimeFormat("en", {
366
+ timeZone: timezone,
367
+ timeZoneName: "longOffset"
368
+ }).formatToParts(date).find((part) => part.type === "timeZoneName");
369
+ const offset = offsetPart ? offsetPart.value : "";
370
+ let utcOffset = offset.replace("GMT", "UTC");
371
+ if (!utcOffset.includes("+") && !utcOffset.includes("-")) {
372
+ utcOffset = `${utcOffset}+00:00`;
373
+ }
374
+ return utcOffset;
375
+ } catch (error) {
376
+ return "";
377
+ }
378
+ };
310
379
  const useTypedDispatch = useDispatch;
311
380
  const useTypedSelector = useSelector;
312
381
  const StyledMenuItem = styled(Menu.Item)`
@@ -377,7 +446,7 @@ const DeleteReleaseActionItem = ({ releaseId, actionId }) => {
377
446
  }
378
447
  };
379
448
  return /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.deleteAction, children: /* @__PURE__ */ jsx(StyledMenuItem, { variant: "danger", onSelect: handleDeleteAction, children: /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
380
- /* @__PURE__ */ jsx(Icon, { as: Cross, padding: 1 }),
449
+ /* @__PURE__ */ jsx(Icon, { as: Cross, width: 3, height: 3 }),
381
450
  /* @__PURE__ */ jsx(Typography, { textColor: "danger600", variant: "omega", children: formatMessage({
382
451
  id: "content-releases.content-manager-edit-view.remove-from-release",
383
452
  defaultMessage: "Remove from release"
@@ -416,7 +485,7 @@ const ReleaseActionEntryLinkItem = ({
416
485
  pathname: `/content-manager/collection-types/${contentTypeUid}/${entryId}`,
417
486
  search: locale && `?plugins[i18n][locale]=${locale}`
418
487
  },
419
- startIcon: /* @__PURE__ */ jsx(Icon, { as: Pencil, padding: 1 }),
488
+ startIcon: /* @__PURE__ */ jsx(Icon, { as: Pencil, width: 3, height: 3 }),
420
489
  children: /* @__PURE__ */ jsx(Typography, { variant: "omega", children: formatMessage({
421
490
  id: "content-releases.content-manager-edit-view.edit-entry",
422
491
  defaultMessage: "Edit entry"
@@ -426,6 +495,21 @@ const ReleaseActionEntryLinkItem = ({
426
495
  }
427
496
  );
428
497
  };
498
+ const EditReleaseItem = ({ releaseId }) => {
499
+ const { formatMessage } = useIntl();
500
+ return /* @__PURE__ */ jsx(StyledMenuItem, { children: /* @__PURE__ */ jsx(
501
+ Link,
502
+ {
503
+ href: `/admin/plugins/content-releases/${releaseId}`,
504
+ startIcon: /* @__PURE__ */ jsx(Icon, { as: Pencil, width: 3, height: 3 }),
505
+ isExternal: false,
506
+ children: /* @__PURE__ */ jsx(Typography, { variant: "omega", children: formatMessage({
507
+ id: "content-releases.content-manager-edit-view.edit-release",
508
+ defaultMessage: "Edit release"
509
+ }) })
510
+ }
511
+ ) });
512
+ };
429
513
  const Root = ({ children, hasTriggerBorder = false }) => {
430
514
  const { formatMessage } = useIntl();
431
515
  return (
@@ -450,6 +534,7 @@ const Root = ({ children, hasTriggerBorder = false }) => {
450
534
  };
451
535
  const ReleaseActionMenu = {
452
536
  Root,
537
+ EditReleaseItem,
453
538
  DeleteReleaseActionItem,
454
539
  ReleaseActionEntryLinkItem
455
540
  };
@@ -473,19 +558,40 @@ const FieldWrapper = styled(Field)`
473
558
  text-transform: capitalize;
474
559
  }
475
560
 
476
- &:active,
477
561
  &[data-checked='true'] {
478
- color: ${({ theme }) => theme.colors.primary700};
479
- background-color: ${({ theme }) => theme.colors.primary100};
480
- border-color: ${({ theme }) => theme.colors.primary700};
562
+ color: ${({ theme, actionType }) => actionType === "publish" ? theme.colors.primary700 : theme.colors.danger600};
563
+ background-color: ${({ theme, actionType }) => actionType === "publish" ? theme.colors.primary100 : theme.colors.danger100};
564
+ border-color: ${({ theme, actionType }) => actionType === "publish" ? theme.colors.primary700 : theme.colors.danger600};
481
565
  }
482
566
 
483
567
  &[data-checked='false'] {
484
568
  border-left: ${({ actionType }) => actionType === "unpublish" && "none"};
485
569
  border-right: ${({ actionType }) => actionType === "publish" && "none"};
486
570
  }
571
+
572
+ &[data-checked='false'][data-disabled='false']:hover {
573
+ color: ${({ theme }) => theme.colors.neutral700};
574
+ background-color: ${({ theme }) => theme.colors.neutral100};
575
+ border-color: ${({ theme }) => theme.colors.neutral200};
576
+
577
+ & > label {
578
+ cursor: pointer;
579
+ }
580
+ }
581
+
582
+ &[data-disabled='true'] {
583
+ color: ${({ theme }) => theme.colors.neutral600};
584
+ background-color: ${({ theme }) => theme.colors.neutral150};
585
+ border-color: ${({ theme }) => theme.colors.neutral300};
586
+ }
487
587
  `;
488
- const ActionOption = ({ selected, actionType, handleChange, name }) => {
588
+ const ActionOption = ({
589
+ selected,
590
+ actionType,
591
+ handleChange,
592
+ name,
593
+ disabled = false
594
+ }) => {
489
595
  return /* @__PURE__ */ jsx(
490
596
  FieldWrapper,
491
597
  {
@@ -496,6 +602,7 @@ const ActionOption = ({ selected, actionType, handleChange, name }) => {
496
602
  position: "relative",
497
603
  cursor: "pointer",
498
604
  "data-checked": selected === actionType,
605
+ "data-disabled": disabled && selected !== actionType,
499
606
  children: /* @__PURE__ */ jsxs(FieldLabel, { htmlFor: `${name}-${actionType}`, children: [
500
607
  /* @__PURE__ */ jsx(VisuallyHidden, { children: /* @__PURE__ */ jsx(
501
608
  FieldInput,
@@ -505,7 +612,8 @@ const ActionOption = ({ selected, actionType, handleChange, name }) => {
505
612
  name,
506
613
  checked: selected === actionType,
507
614
  onChange: handleChange,
508
- value: actionType
615
+ value: actionType,
616
+ disabled
509
617
  }
510
618
  ) }),
511
619
  actionType
@@ -513,7 +621,12 @@ const ActionOption = ({ selected, actionType, handleChange, name }) => {
513
621
  }
514
622
  );
515
623
  };
516
- const ReleaseActionOptions = ({ selected, handleChange, name }) => {
624
+ const ReleaseActionOptions = ({
625
+ selected,
626
+ handleChange,
627
+ name,
628
+ disabled = false
629
+ }) => {
517
630
  return /* @__PURE__ */ jsxs(Flex, { children: [
518
631
  /* @__PURE__ */ jsx(
519
632
  ActionOption,
@@ -521,7 +634,8 @@ const ReleaseActionOptions = ({ selected, handleChange, name }) => {
521
634
  actionType: "publish",
522
635
  selected,
523
636
  handleChange,
524
- name
637
+ name,
638
+ disabled
525
639
  }
526
640
  ),
527
641
  /* @__PURE__ */ jsx(
@@ -530,7 +644,8 @@ const ReleaseActionOptions = ({ selected, handleChange, name }) => {
530
644
  actionType: "unpublish",
531
645
  selected,
532
646
  handleChange,
533
- name
647
+ name,
648
+ disabled
534
649
  }
535
650
  )
536
651
  ] });
@@ -574,6 +689,7 @@ const AddActionToReleaseModal = ({
574
689
  contentTypeUid,
575
690
  entryId
576
691
  }) => {
692
+ const releaseHeaderId = React.useId();
577
693
  const { formatMessage } = useIntl();
578
694
  const toggleNotification = useNotification();
579
695
  const { formatAPIError } = useAPIErrorHandler();
@@ -621,8 +737,8 @@ const AddActionToReleaseModal = ({
621
737
  }
622
738
  }
623
739
  };
624
- return /* @__PURE__ */ jsxs(ModalLayout, { onClose: handleClose, labelledBy: "title", children: [
625
- /* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsx(Typography, { id: "title", fontWeight: "bold", textColor: "neutral800", children: formatMessage({
740
+ return /* @__PURE__ */ jsxs(ModalLayout, { onClose: handleClose, labelledBy: releaseHeaderId, children: [
741
+ /* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsx(Typography, { id: releaseHeaderId, fontWeight: "bold", textColor: "neutral800", children: formatMessage({
626
742
  id: "content-releases.content-manager-edit-view.add-to-release",
627
743
  defaultMessage: "Add to release"
628
744
  }) }) }),
@@ -668,7 +784,7 @@ const AddActionToReleaseModal = ({
668
784
  /* @__PURE__ */ jsx(
669
785
  ModalFooter,
670
786
  {
671
- startActions: /* @__PURE__ */ jsx(Button, { onClick: handleClose, variant: "tertiary", name: "cancel", children: formatMessage({
787
+ startActions: /* @__PURE__ */ jsx(Button$1, { onClick: handleClose, variant: "tertiary", name: "cancel", children: formatMessage({
672
788
  id: "content-releases.content-manager-edit-view.add-to-release.cancel-button",
673
789
  defaultMessage: "Cancel"
674
790
  }) }),
@@ -677,7 +793,7 @@ const AddActionToReleaseModal = ({
677
793
  * TODO: Ideally we would use isValid from Formik to disable the button, however currently it always returns true
678
794
  * for yup.string().required(), even when the value is falsy (including empty string)
679
795
  */
680
- /* @__PURE__ */ jsx(Button, { type: "submit", disabled: !values.releaseId, loading: isLoading, children: formatMessage({
796
+ /* @__PURE__ */ jsx(Button$1, { type: "submit", disabled: !values.releaseId, loading: isLoading, children: formatMessage({
681
797
  id: "content-releases.content-manager-edit-view.add-to-release.continue-button",
682
798
  defaultMessage: "Continue"
683
799
  }) })
@@ -692,16 +808,18 @@ const AddActionToReleaseModal = ({
692
808
  };
693
809
  const CMReleasesContainer = () => {
694
810
  const [isModalOpen, setIsModalOpen] = React.useState(false);
695
- const { formatMessage } = useIntl();
811
+ const { formatMessage, formatDate, formatTime } = useIntl();
696
812
  const {
697
813
  isCreatingEntry,
698
- allLayoutData: { contentType }
814
+ hasDraftAndPublish,
815
+ initialData: { id: entryId },
816
+ slug
699
817
  } = useCMEditViewDataManager();
700
- const params = useParams();
701
- const canFetch = params?.id != null && contentType?.uid != null;
818
+ const contentTypeUid = slug;
819
+ const canFetch = entryId != null && contentTypeUid != null;
702
820
  const fetchParams = canFetch ? {
703
- contentTypeUid: contentType.uid,
704
- entryId: params.id,
821
+ contentTypeUid,
822
+ entryId,
705
823
  hasEntryAttached: true
706
824
  } : skipToken;
707
825
  const response = useGetReleasesForEntryQuery(fetchParams);
@@ -709,7 +827,7 @@ const CMReleasesContainer = () => {
709
827
  if (!canFetch) {
710
828
  return null;
711
829
  }
712
- if (isCreatingEntry || !contentType?.options?.draftAndPublish) {
830
+ if (isCreatingEntry || !hasDraftAndPublish) {
713
831
  return null;
714
832
  }
715
833
  const toggleModal = () => setIsModalOpen((prev) => !prev);
@@ -746,7 +864,7 @@ const CMReleasesContainer = () => {
746
864
  alignItems: "start",
747
865
  borderWidth: "1px",
748
866
  borderStyle: "solid",
749
- borderColor: getReleaseColorVariant(release.action.type, "200"),
867
+ borderColor: getReleaseColorVariant(release.actions[0].type, "200"),
750
868
  overflow: "hidden",
751
869
  hasRadius: true,
752
870
  children: [
@@ -757,34 +875,59 @@ const CMReleasesContainer = () => {
757
875
  paddingBottom: 3,
758
876
  paddingLeft: 4,
759
877
  paddingRight: 4,
760
- background: getReleaseColorVariant(release.action.type, "100"),
878
+ background: getReleaseColorVariant(release.actions[0].type, "100"),
761
879
  width: "100%",
762
880
  children: /* @__PURE__ */ jsx(
763
881
  Typography,
764
882
  {
765
883
  fontSize: 1,
766
884
  variant: "pi",
767
- textColor: getReleaseColorVariant(release.action.type, "600"),
885
+ textColor: getReleaseColorVariant(release.actions[0].type, "600"),
768
886
  children: formatMessage(
769
887
  {
770
888
  id: "content-releases.content-manager-edit-view.list-releases.title",
771
889
  defaultMessage: "{isPublish, select, true {Will be published in} other {Will be unpublished in}}"
772
890
  },
773
- { isPublish: release.action.type === "publish" }
891
+ { isPublish: release.actions[0].type === "publish" }
774
892
  )
775
893
  }
776
894
  )
777
895
  }
778
896
  ),
779
- /* @__PURE__ */ jsxs(Flex, { padding: 4, direction: "column", gap: 3, width: "100%", alignItems: "flex-start", children: [
897
+ /* @__PURE__ */ jsxs(Flex, { padding: 4, direction: "column", gap: 2, width: "100%", alignItems: "flex-start", children: [
780
898
  /* @__PURE__ */ jsx(Typography, { fontSize: 2, fontWeight: "bold", variant: "omega", textColor: "neutral700", children: release.name }),
781
- /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.deleteAction, children: /* @__PURE__ */ jsx(ReleaseActionMenu.Root, { hasTriggerBorder: true, children: /* @__PURE__ */ jsx(
782
- ReleaseActionMenu.DeleteReleaseActionItem,
899
+ release.scheduledAt && release.timezone && /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: formatMessage(
783
900
  {
784
- releaseId: release.id,
785
- actionId: release.action.id
901
+ id: "content-releases.content-manager-edit-view.scheduled.date",
902
+ defaultMessage: "{date} at {time} ({offset})"
903
+ },
904
+ {
905
+ date: formatDate(new Date(release.scheduledAt), {
906
+ day: "2-digit",
907
+ month: "2-digit",
908
+ year: "numeric",
909
+ timeZone: release.timezone
910
+ }),
911
+ time: formatTime(new Date(release.scheduledAt), {
912
+ hourCycle: "h23",
913
+ timeZone: release.timezone
914
+ }),
915
+ offset: getTimezoneOffset(
916
+ release.timezone,
917
+ new Date(release.scheduledAt)
918
+ )
786
919
  }
787
- ) }) })
920
+ ) }),
921
+ /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.deleteAction, children: /* @__PURE__ */ jsxs(ReleaseActionMenu.Root, { hasTriggerBorder: true, children: [
922
+ /* @__PURE__ */ jsx(ReleaseActionMenu.EditReleaseItem, { releaseId: release.id }),
923
+ /* @__PURE__ */ jsx(
924
+ ReleaseActionMenu.DeleteReleaseActionItem,
925
+ {
926
+ releaseId: release.id,
927
+ actionId: release.actions[0].id
928
+ }
929
+ )
930
+ ] }) })
788
931
  ] })
789
932
  ]
790
933
  },
@@ -792,7 +935,7 @@ const CMReleasesContainer = () => {
792
935
  );
793
936
  }),
794
937
  /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.createAction, children: /* @__PURE__ */ jsx(
795
- Button,
938
+ Button$1,
796
939
  {
797
940
  justifyContent: "center",
798
941
  paddingLeft: 4,
@@ -812,17 +955,280 @@ const CMReleasesContainer = () => {
812
955
  AddActionToReleaseModal,
813
956
  {
814
957
  handleClose: toggleModal,
815
- contentTypeUid: contentType.uid,
816
- entryId: params.id
958
+ contentTypeUid,
959
+ entryId
817
960
  }
818
961
  )
819
962
  ]
820
963
  }
821
964
  ) });
822
965
  };
966
+ const getContentPermissions = (subject) => {
967
+ const permissions = {
968
+ publish: [
969
+ {
970
+ action: "plugin::content-manager.explorer.publish",
971
+ subject,
972
+ id: "",
973
+ actionParameters: {},
974
+ properties: {},
975
+ conditions: []
976
+ }
977
+ ]
978
+ };
979
+ return permissions;
980
+ };
981
+ const ReleaseAction = ({ ids, model }) => {
982
+ const { formatMessage } = useIntl();
983
+ const toggleNotification = useNotification();
984
+ const { formatAPIError } = useAPIErrorHandler();
985
+ const { modifiedData } = useCMEditViewDataManager();
986
+ const contentPermissions = getContentPermissions(model);
987
+ const {
988
+ allowedActions: { canPublish }
989
+ } = useRBAC(contentPermissions);
990
+ const {
991
+ allowedActions: { canCreate }
992
+ } = useRBAC(PERMISSIONS);
993
+ const response = useGetReleasesQuery();
994
+ const releases = response.data?.data;
995
+ const [createManyReleaseActions, { isLoading }] = useCreateManyReleaseActionsMutation();
996
+ const handleSubmit = async (values) => {
997
+ const locale = modifiedData.locale;
998
+ const releaseActionEntries = ids.map((id) => ({
999
+ type: values.type,
1000
+ entry: {
1001
+ contentType: model,
1002
+ id,
1003
+ locale
1004
+ }
1005
+ }));
1006
+ const response2 = await createManyReleaseActions({
1007
+ body: releaseActionEntries,
1008
+ params: { releaseId: values.releaseId }
1009
+ });
1010
+ if ("data" in response2) {
1011
+ const notificationMessage = formatMessage(
1012
+ {
1013
+ id: "content-releases.content-manager-list-view.add-to-release.notification.success.message",
1014
+ defaultMessage: "{entriesAlreadyInRelease} out of {totalEntries} entries were already in the release."
1015
+ },
1016
+ {
1017
+ entriesAlreadyInRelease: response2.data.meta.entriesAlreadyInRelease,
1018
+ totalEntries: response2.data.meta.totalEntries
1019
+ }
1020
+ );
1021
+ const notification = {
1022
+ type: "success",
1023
+ title: formatMessage(
1024
+ {
1025
+ id: "content-releases.content-manager-list-view.add-to-release.notification.success.title",
1026
+ defaultMessage: "Successfully added to release."
1027
+ },
1028
+ {
1029
+ entriesAlreadyInRelease: response2.data.meta.entriesAlreadyInRelease,
1030
+ totalEntries: response2.data.meta.totalEntries
1031
+ }
1032
+ ),
1033
+ message: response2.data.meta.entriesAlreadyInRelease ? notificationMessage : ""
1034
+ };
1035
+ toggleNotification(notification);
1036
+ return true;
1037
+ }
1038
+ if ("error" in response2) {
1039
+ if (isAxiosError$1(response2.error)) {
1040
+ toggleNotification({
1041
+ type: "warning",
1042
+ message: formatAPIError(response2.error)
1043
+ });
1044
+ } else {
1045
+ toggleNotification({
1046
+ type: "warning",
1047
+ message: formatMessage({ id: "notification.error", defaultMessage: "An error occurred" })
1048
+ });
1049
+ }
1050
+ }
1051
+ };
1052
+ if (!canCreate || !canPublish)
1053
+ return null;
1054
+ return {
1055
+ actionType: "release",
1056
+ variant: "tertiary",
1057
+ label: formatMessage({
1058
+ id: "content-manager-list-view.add-to-release",
1059
+ defaultMessage: "Add to Release"
1060
+ }),
1061
+ dialog: {
1062
+ type: "modal",
1063
+ title: formatMessage({
1064
+ id: "content-manager-list-view.add-to-release",
1065
+ defaultMessage: "Add to Release"
1066
+ }),
1067
+ content: ({ onClose }) => {
1068
+ return /* @__PURE__ */ jsx(
1069
+ Formik,
1070
+ {
1071
+ onSubmit: async (values) => {
1072
+ const data = await handleSubmit(values);
1073
+ if (data) {
1074
+ return onClose();
1075
+ }
1076
+ },
1077
+ validationSchema: RELEASE_ACTION_FORM_SCHEMA,
1078
+ initialValues: INITIAL_VALUES,
1079
+ children: ({ values, setFieldValue }) => /* @__PURE__ */ jsxs(Form, { children: [
1080
+ releases?.length === 0 ? /* @__PURE__ */ jsx(NoReleases, {}) : /* @__PURE__ */ jsx(ModalBody, { children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
1081
+ /* @__PURE__ */ jsx(Box, { paddingBottom: 6, children: /* @__PURE__ */ jsx(
1082
+ SingleSelect,
1083
+ {
1084
+ required: true,
1085
+ label: formatMessage({
1086
+ id: "content-releases.content-manager-list-view.add-to-release.select-label",
1087
+ defaultMessage: "Select a release"
1088
+ }),
1089
+ placeholder: formatMessage({
1090
+ id: "content-releases.content-manager-list-view.add-to-release.select-placeholder",
1091
+ defaultMessage: "Select"
1092
+ }),
1093
+ onChange: (value) => setFieldValue("releaseId", value),
1094
+ value: values.releaseId,
1095
+ children: releases?.map((release) => /* @__PURE__ */ jsx(SingleSelectOption, { value: release.id, children: release.name }, release.id))
1096
+ }
1097
+ ) }),
1098
+ /* @__PURE__ */ jsx(FieldLabel, { children: formatMessage({
1099
+ id: "content-releases.content-manager-list-view.add-to-release.action-type-label",
1100
+ defaultMessage: "What do you want to do with these entries?"
1101
+ }) }),
1102
+ /* @__PURE__ */ jsx(
1103
+ ReleaseActionOptions,
1104
+ {
1105
+ selected: values.type,
1106
+ handleChange: (e) => setFieldValue("type", e.target.value),
1107
+ name: "type"
1108
+ }
1109
+ )
1110
+ ] }) }),
1111
+ /* @__PURE__ */ jsx(
1112
+ ModalFooter,
1113
+ {
1114
+ startActions: /* @__PURE__ */ jsx(Button$1, { onClick: onClose, variant: "tertiary", name: "cancel", children: formatMessage({
1115
+ id: "content-releases.content-manager-list-view.add-to-release.cancel-button",
1116
+ defaultMessage: "Cancel"
1117
+ }) }),
1118
+ endActions: (
1119
+ /**
1120
+ * TODO: Ideally we would use isValid from Formik to disable the button, however currently it always returns true
1121
+ * for yup.string().required(), even when the value is falsy (including empty string)
1122
+ */
1123
+ /* @__PURE__ */ jsx(Button$1, { type: "submit", disabled: !values.releaseId, loading: isLoading, children: formatMessage({
1124
+ id: "content-releases.content-manager-list-view.add-to-release.continue-button",
1125
+ defaultMessage: "Continue"
1126
+ }) })
1127
+ )
1128
+ }
1129
+ )
1130
+ ] })
1131
+ }
1132
+ );
1133
+ }
1134
+ }
1135
+ };
1136
+ };
1137
+ const Button = styled.button`
1138
+ svg {
1139
+ > g,
1140
+ path {
1141
+ fill: ${({ theme }) => theme.colors.neutral500};
1142
+ }
1143
+ }
1144
+ &:hover {
1145
+ svg {
1146
+ > g,
1147
+ path {
1148
+ fill: ${({ theme }) => theme.colors.neutral600};
1149
+ }
1150
+ }
1151
+ }
1152
+ &:active {
1153
+ svg {
1154
+ > g,
1155
+ path {
1156
+ fill: ${({ theme }) => theme.colors.neutral400};
1157
+ }
1158
+ }
1159
+ }
1160
+ `;
1161
+ const ActionWrapper = styled(Flex)`
1162
+ svg {
1163
+ height: ${4 / 16}rem;
1164
+ }
1165
+ `;
1166
+ const useReleasesList = (entryId) => {
1167
+ const { uid: contentTypeUid } = useTypedSelector(
1168
+ (state) => state["content-manager_listView"].contentType
1169
+ );
1170
+ const listViewData = useTypedSelector((state) => state["content-manager_listView"].data);
1171
+ const entriesIds = listViewData.map((entry) => entry.id);
1172
+ const response = useGetMappedEntriesInReleasesQuery(
1173
+ { contentTypeUid, entriesIds },
1174
+ { skip: !entriesIds || !contentTypeUid || entriesIds.length === 0 }
1175
+ );
1176
+ const mappedEntriesInReleases = response.data || {};
1177
+ return mappedEntriesInReleases?.[entryId] || [];
1178
+ };
1179
+ const addColumnToTableHook = ({ displayedHeaders, layout }) => {
1180
+ const { contentType } = layout;
1181
+ if (!contentType.options?.draftAndPublish) {
1182
+ return { displayedHeaders, layout };
1183
+ }
1184
+ return {
1185
+ displayedHeaders: [
1186
+ ...displayedHeaders,
1187
+ {
1188
+ key: "__release_key__",
1189
+ fieldSchema: { type: "string" },
1190
+ metadatas: { label: "To be released in", searchable: true, sortable: false },
1191
+ name: "releasedAt",
1192
+ cellFormatter: (props) => /* @__PURE__ */ jsx(ReleaseListCell, { ...props })
1193
+ }
1194
+ ],
1195
+ layout
1196
+ };
1197
+ };
1198
+ const ReleaseListCell = ({ id }) => {
1199
+ const releases = useReleasesList(id);
1200
+ const [visible, setVisible] = React.useState(false);
1201
+ const buttonRef = React.useRef(null);
1202
+ const { formatMessage } = useIntl();
1203
+ const handleTogglePopover = () => setVisible((prev) => !prev);
1204
+ return /* @__PURE__ */ jsx(Flex, { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx(Button, { type: "button", onClick: handleTogglePopover, ref: buttonRef, children: /* @__PURE__ */ jsxs(ActionWrapper, { height: "2rem", width: "2rem", children: [
1205
+ /* @__PURE__ */ jsx(Typography, { style: { maxWidth: "252px", cursor: "pointer" }, textColor: "neutral800", children: releases.length > 0 ? formatMessage(
1206
+ {
1207
+ id: "content-releases.content-manager.list-view.releases-number",
1208
+ defaultMessage: "{number} {number, plural, one {release} other {releases}}"
1209
+ },
1210
+ {
1211
+ number: releases.length
1212
+ }
1213
+ ) : "-" }),
1214
+ /* @__PURE__ */ jsxs(Flex, { children: [
1215
+ releases.length > 0 && /* @__PURE__ */ jsx(SortIcon, {}),
1216
+ visible && /* @__PURE__ */ jsx(
1217
+ Popover,
1218
+ {
1219
+ onDismiss: handleTogglePopover,
1220
+ source: buttonRef,
1221
+ spacing: 16,
1222
+ children: /* @__PURE__ */ jsx("ul", { children: releases.map(({ id: id2, name }) => /* @__PURE__ */ jsx(Box, { padding: 3, as: "li", children: /* @__PURE__ */ jsx(Link, { href: `/admin/plugins/content-releases/${id2}`, isExternal: false, children: name }) }, id2)) })
1223
+ }
1224
+ )
1225
+ ] })
1226
+ ] }) }) });
1227
+ };
823
1228
  const admin = {
824
1229
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
825
1230
  register(app) {
1231
+ app.createHook("ContentReleases/pages/ReleaseDetails/add-locale-in-releases");
826
1232
  if (window.strapi.features.isEnabled("cms-content-releases")) {
827
1233
  app.addMenuLink({
828
1234
  to: `/plugins/${pluginId}`,
@@ -832,7 +1238,7 @@ const admin = {
832
1238
  defaultMessage: "Releases"
833
1239
  },
834
1240
  async Component() {
835
- const { App } = await import("./App-L1jSxCiL.mjs");
1241
+ const { App } = await import("./App-jrh58sXY.mjs");
836
1242
  return App;
837
1243
  },
838
1244
  permissions: PERMISSIONS.main
@@ -845,12 +1251,33 @@ const admin = {
845
1251
  name: `${pluginId}-link`,
846
1252
  Component: CMReleasesContainer
847
1253
  });
1254
+ app.plugins["content-manager"].apis.addBulkAction((actions) => {
1255
+ const deleteActionIndex = actions.findIndex((action) => action.name === "DeleteAction");
1256
+ actions.splice(deleteActionIndex, 0, ReleaseAction);
1257
+ return actions;
1258
+ });
1259
+ app.registerHook("Admin/CM/pages/ListView/inject-column-in-table", addColumnToTableHook);
1260
+ } else if (!window.strapi.features.isEnabled("cms-content-releases") && window.strapi?.flags?.promoteEE) {
1261
+ app.addMenuLink({
1262
+ to: `/plugins/purchase-content-releases`,
1263
+ icon: PaperPlane,
1264
+ intlLabel: {
1265
+ id: `${pluginId}.plugin.name`,
1266
+ defaultMessage: "Releases"
1267
+ },
1268
+ async Component() {
1269
+ const { PurchaseContentReleases } = await import("./PurchaseContentReleases-3tRbmbY3.mjs");
1270
+ return PurchaseContentReleases;
1271
+ },
1272
+ lockIcon: true
1273
+ // TODO: to replace with another name in v5
1274
+ });
848
1275
  }
849
1276
  },
850
1277
  async registerTrads({ locales }) {
851
1278
  const importedTrads = await Promise.all(
852
1279
  locales.map((locale) => {
853
- return __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => import("./en-MyLPoISH.mjs") }), `./translations/${locale}.json`).then(({ default: data }) => {
1280
+ return __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => import("./en-ltT1TlKQ.mjs") }), `./translations/${locale}.json`).then(({ default: data }) => {
854
1281
  return {
855
1282
  data: prefixPluginTranslations(data, "content-releases"),
856
1283
  locale
@@ -869,19 +1296,20 @@ const admin = {
869
1296
  export {
870
1297
  PERMISSIONS as P,
871
1298
  ReleaseActionOptions as R,
872
- useUpdateReleaseMutation as a,
873
- useDeleteReleaseMutation as b,
874
- usePublishReleaseMutation as c,
875
- useTypedDispatch as d,
876
- useGetReleaseActionsQuery as e,
877
- useUpdateReleaseActionMutation as f,
878
- ReleaseActionMenu as g,
879
- useGetReleasesQuery as h,
1299
+ useCreateReleaseMutation as a,
1300
+ useGetReleaseQuery as b,
1301
+ useUpdateReleaseMutation as c,
1302
+ useDeleteReleaseMutation as d,
1303
+ usePublishReleaseMutation as e,
1304
+ useTypedDispatch as f,
1305
+ getTimezoneOffset as g,
1306
+ useGetReleaseActionsQuery as h,
880
1307
  isAxiosError as i,
881
- useCreateReleaseMutation as j,
882
- admin as k,
1308
+ useUpdateReleaseActionMutation as j,
1309
+ ReleaseActionMenu as k,
1310
+ admin as l,
883
1311
  pluginId as p,
884
1312
  releaseApi as r,
885
- useGetReleaseQuery as u
1313
+ useGetReleasesQuery as u
886
1314
  };
887
- //# sourceMappingURL=index-c4zRX_sg.mjs.map
1315
+ //# sourceMappingURL=index-PiOGBETy.mjs.map