@strapi/content-releases 0.0.0-next.e1ede8c55a0e1e22ce20137bf238fc374bd5dd51 → 0.0.0-next.f4ff842a3cb7b83db540bee67554b704e042b042

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 (33) hide show
  1. package/dist/_chunks/App-dLXY5ei3.js +1353 -0
  2. package/dist/_chunks/App-dLXY5ei3.js.map +1 -0
  3. package/dist/_chunks/App-jrh58sXY.mjs +1330 -0
  4. package/dist/_chunks/App-jrh58sXY.mjs.map +1 -0
  5. package/dist/_chunks/PurchaseContentReleases-3tRbmbY3.mjs +51 -0
  6. package/dist/_chunks/PurchaseContentReleases-3tRbmbY3.mjs.map +1 -0
  7. package/dist/_chunks/PurchaseContentReleases-bpIYXOfu.js +51 -0
  8. package/dist/_chunks/PurchaseContentReleases-bpIYXOfu.js.map +1 -0
  9. package/dist/_chunks/{en-haKSQIo8.js → en-HrREghh3.js} +31 -7
  10. package/dist/_chunks/en-HrREghh3.js.map +1 -0
  11. package/dist/_chunks/{en-ngTk74JV.mjs → en-ltT1TlKQ.mjs} +31 -7
  12. package/dist/_chunks/en-ltT1TlKQ.mjs.map +1 -0
  13. package/dist/_chunks/{index-EdBmRHRU.js → index-CVO0Rqdm.js} +551 -64
  14. package/dist/_chunks/index-CVO0Rqdm.js.map +1 -0
  15. package/dist/_chunks/{index-XAQOX_IB.mjs → index-PiOGBETy.mjs} +570 -83
  16. package/dist/_chunks/index-PiOGBETy.mjs.map +1 -0
  17. package/dist/admin/index.js +2 -1
  18. package/dist/admin/index.js.map +1 -1
  19. package/dist/admin/index.mjs +3 -2
  20. package/dist/admin/index.mjs.map +1 -1
  21. package/dist/server/index.js +1192 -299
  22. package/dist/server/index.js.map +1 -1
  23. package/dist/server/index.mjs +1191 -300
  24. package/dist/server/index.mjs.map +1 -1
  25. package/package.json +16 -12
  26. package/dist/_chunks/App-g2P5kbSm.mjs +0 -945
  27. package/dist/_chunks/App-g2P5kbSm.mjs.map +0 -1
  28. package/dist/_chunks/App-o5_WfqR-.js +0 -967
  29. package/dist/_chunks/App-o5_WfqR-.js.map +0 -1
  30. package/dist/_chunks/en-haKSQIo8.js.map +0 -1
  31. package/dist/_chunks/en-ngTk74JV.mjs.map +0 -1
  32. package/dist/_chunks/index-EdBmRHRU.js.map +0 -1
  33. package/dist/_chunks/index-XAQOX_IB.mjs.map +0 -1
@@ -1,17 +1,18 @@
1
- import { getFetchClient, useNotification, useAPIErrorHandler, CheckPermissions, useCMEditViewDataManager, NoContent, prefixPluginTranslations } from "@strapi/helper-plugin";
2
- import { Cross, More, Plus, PaperPlane } from "@strapi/icons";
1
+ import { getFetchClient, useNotification, useAPIErrorHandler, CheckPermissions, useCMEditViewDataManager, NoContent, useRBAC, SortIcon, prefixPluginTranslations } from "@strapi/helper-plugin";
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, Typography, Field, FieldLabel, VisuallyHidden, FieldInput, Box, Button, ModalLayout, ModalHeader, ModalBody, SingleSelect, SingleSelectOption, ModalFooter } from "@strapi/design-system";
7
- import { Menu, LinkButton } from "@strapi/design-system/v2";
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
+ 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 { useParams, Link } 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";
15
+ import { useDispatch, useSelector } from "react-redux";
15
16
  const __variableDynamicImportRuntimeHelper = (glob, path) => {
16
17
  const v = glob[path];
17
18
  if (v) {
@@ -135,7 +136,7 @@ const isAxiosError = (err) => {
135
136
  const releaseApi = createApi({
136
137
  reducerPath: pluginId,
137
138
  baseQuery: axiosBaseQuery,
138
- tagTypes: ["Release", "ReleaseAction"],
139
+ tagTypes: ["Release", "ReleaseAction", "EntriesInRelease"],
139
140
  endpoints: (build) => {
140
141
  return {
141
142
  getReleasesForEntry: build.query({
@@ -250,6 +251,20 @@ const releaseApi = createApi({
250
251
  { type: "ReleaseAction", id: "LIST" }
251
252
  ]
252
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
+ }),
253
268
  updateReleaseAction: build.mutation({
254
269
  query({ body, params }) {
255
270
  return {
@@ -258,7 +273,27 @@ const releaseApi = createApi({
258
273
  data: body
259
274
  };
260
275
  },
261
- 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
+ }
262
297
  }),
263
298
  deleteReleaseAction: build.mutation({
264
299
  query({ params }) {
@@ -267,9 +302,11 @@ const releaseApi = createApi({
267
302
  method: "DELETE"
268
303
  };
269
304
  },
270
- invalidatesTags: [
305
+ invalidatesTags: (result, error, arg) => [
271
306
  { type: "Release", id: "LIST" },
272
- { type: "ReleaseAction", id: "LIST" }
307
+ { type: "Release", id: arg.params.releaseId },
308
+ { type: "ReleaseAction", id: "LIST" },
309
+ { type: "EntriesInRelease" }
273
310
  ]
274
311
  }),
275
312
  publishRelease: build.mutation({
@@ -288,7 +325,22 @@ const releaseApi = createApi({
288
325
  method: "DELETE"
289
326
  };
290
327
  },
291
- 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" }]
292
344
  })
293
345
  };
294
346
  }
@@ -300,43 +352,67 @@ const {
300
352
  useGetReleaseActionsQuery,
301
353
  useCreateReleaseMutation,
302
354
  useCreateReleaseActionMutation,
355
+ useCreateManyReleaseActionsMutation,
303
356
  useUpdateReleaseMutation,
304
357
  useUpdateReleaseActionMutation,
305
358
  usePublishReleaseMutation,
306
359
  useDeleteReleaseActionMutation,
307
- useDeleteReleaseMutation
360
+ useDeleteReleaseMutation,
361
+ useGetMappedEntriesInReleasesQuery
308
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
+ };
379
+ const useTypedDispatch = useDispatch;
380
+ const useTypedSelector = useSelector;
309
381
  const StyledMenuItem = styled(Menu.Item)`
310
382
  &:hover {
311
- background: ${({ theme }) => theme.colors.danger100};
383
+ background: ${({ theme, variant = "neutral" }) => theme.colors[`${variant}100`]};
384
+
385
+ svg {
386
+ path {
387
+ fill: ${({ theme, variant = "neutral" }) => theme.colors[`${variant}600`]};
388
+ }
389
+ }
390
+
391
+ a {
392
+ color: ${({ theme }) => theme.colors.neutral800};
393
+ }
312
394
  }
313
395
 
314
396
  svg {
315
397
  path {
316
- fill: ${({ theme }) => theme.colors.danger600};
398
+ fill: ${({ theme, variant = "neutral" }) => theme.colors[`${variant}600`]};
317
399
  }
318
400
  }
319
401
 
320
- &:hover {
321
- svg {
322
- path {
323
- fill: ${({ theme }) => theme.colors.danger600};
324
- }
325
- }
402
+ a {
403
+ color: ${({ theme }) => theme.colors.neutral800};
404
+ }
405
+
406
+ span,
407
+ a {
408
+ width: 100%;
326
409
  }
327
- `;
328
- const StyledCross = styled(Cross)`
329
- padding: ${({ theme }) => theme.spaces[1]};
330
410
  `;
331
411
  const StyledIconButton = styled(IconButton)`
332
412
  /* Setting this style inline with borderColor will not apply the style */
333
413
  border: ${({ theme }) => `1px solid ${theme.colors.neutral200}`};
334
414
  `;
335
- const ReleaseActionMenu = ({
336
- releaseId,
337
- actionId,
338
- hasTriggerBorder = false
339
- }) => {
415
+ const DeleteReleaseActionItem = ({ releaseId, actionId }) => {
340
416
  const { formatMessage } = useIntl();
341
417
  const toggleNotification = useNotification();
342
418
  const { formatAPIError } = useAPIErrorHandler();
@@ -369,6 +445,73 @@ const ReleaseActionMenu = ({
369
445
  }
370
446
  }
371
447
  };
448
+ return /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.deleteAction, children: /* @__PURE__ */ jsx(StyledMenuItem, { variant: "danger", onSelect: handleDeleteAction, children: /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
449
+ /* @__PURE__ */ jsx(Icon, { as: Cross, width: 3, height: 3 }),
450
+ /* @__PURE__ */ jsx(Typography, { textColor: "danger600", variant: "omega", children: formatMessage({
451
+ id: "content-releases.content-manager-edit-view.remove-from-release",
452
+ defaultMessage: "Remove from release"
453
+ }) })
454
+ ] }) }) });
455
+ };
456
+ const ReleaseActionEntryLinkItem = ({
457
+ contentTypeUid,
458
+ entryId,
459
+ locale
460
+ }) => {
461
+ const { formatMessage } = useIntl();
462
+ const collectionTypePermissions = useTypedSelector(
463
+ (state) => state.rbacProvider.collectionTypesRelatedPermissions
464
+ );
465
+ const updatePermissions = contentTypeUid ? collectionTypePermissions[contentTypeUid]?.["plugin::content-manager.explorer.update"] : [];
466
+ const canUpdateEntryForLocale = Boolean(
467
+ !locale || updatePermissions?.find(
468
+ (permission) => permission.properties?.locales?.includes(locale)
469
+ )
470
+ );
471
+ return /* @__PURE__ */ jsx(
472
+ CheckPermissions,
473
+ {
474
+ permissions: [
475
+ {
476
+ action: "plugin::content-manager.explorer.update",
477
+ subject: contentTypeUid
478
+ }
479
+ ],
480
+ children: canUpdateEntryForLocale && /* @__PURE__ */ jsx(StyledMenuItem, { children: /* @__PURE__ */ jsx(
481
+ Link,
482
+ {
483
+ as: NavLink,
484
+ to: {
485
+ pathname: `/content-manager/collection-types/${contentTypeUid}/${entryId}`,
486
+ search: locale && `?plugins[i18n][locale]=${locale}`
487
+ },
488
+ startIcon: /* @__PURE__ */ jsx(Icon, { as: Pencil, width: 3, height: 3 }),
489
+ children: /* @__PURE__ */ jsx(Typography, { variant: "omega", children: formatMessage({
490
+ id: "content-releases.content-manager-edit-view.edit-entry",
491
+ defaultMessage: "Edit entry"
492
+ }) })
493
+ }
494
+ ) })
495
+ }
496
+ );
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
+ };
513
+ const Root = ({ children, hasTriggerBorder = false }) => {
514
+ const { formatMessage } = useIntl();
372
515
  return (
373
516
  // A user can access the dropdown if they have permissions to delete a release-action OR update a release
374
517
  /* @__PURE__ */ jsx(CheckPermissions, { permissions: [...PERMISSIONS.deleteAction, ...PERMISSIONS.update], children: /* @__PURE__ */ jsxs(Menu.Root, { children: [
@@ -385,16 +528,16 @@ const ReleaseActionMenu = ({
385
528
  icon: /* @__PURE__ */ jsx(More, {})
386
529
  }
387
530
  ),
388
- /* @__PURE__ */ jsx(Menu.Content, { top: 1, popoverPlacement: "bottom-end", children: /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.deleteAction, children: /* @__PURE__ */ jsx(StyledMenuItem, { onSelect: handleDeleteAction, children: /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
389
- /* @__PURE__ */ jsx(StyledCross, {}),
390
- /* @__PURE__ */ jsx(Typography, { textColor: "danger600", variant: "omega", children: formatMessage({
391
- id: "content-releases.content-manager-edit-view.remove-from-release",
392
- defaultMessage: "Remove from release"
393
- }) })
394
- ] }) }) }) })
531
+ /* @__PURE__ */ jsx(Menu.Content, { top: 1, popoverPlacement: "bottom-end", children })
395
532
  ] }) })
396
533
  );
397
534
  };
535
+ const ReleaseActionMenu = {
536
+ Root,
537
+ EditReleaseItem,
538
+ DeleteReleaseActionItem,
539
+ ReleaseActionEntryLinkItem
540
+ };
398
541
  const getBorderLeftRadiusValue = (actionType) => {
399
542
  return actionType === "publish" ? 1 : 0;
400
543
  };
@@ -415,19 +558,40 @@ const FieldWrapper = styled(Field)`
415
558
  text-transform: capitalize;
416
559
  }
417
560
 
418
- &:active,
419
561
  &[data-checked='true'] {
420
- color: ${({ theme }) => theme.colors.primary700};
421
- background-color: ${({ theme }) => theme.colors.primary100};
422
- 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};
423
565
  }
424
566
 
425
567
  &[data-checked='false'] {
426
568
  border-left: ${({ actionType }) => actionType === "unpublish" && "none"};
427
569
  border-right: ${({ actionType }) => actionType === "publish" && "none"};
428
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
+ }
429
587
  `;
430
- const ActionOption = ({ selected, actionType, handleChange, name }) => {
588
+ const ActionOption = ({
589
+ selected,
590
+ actionType,
591
+ handleChange,
592
+ name,
593
+ disabled = false
594
+ }) => {
431
595
  return /* @__PURE__ */ jsx(
432
596
  FieldWrapper,
433
597
  {
@@ -438,6 +602,7 @@ const ActionOption = ({ selected, actionType, handleChange, name }) => {
438
602
  position: "relative",
439
603
  cursor: "pointer",
440
604
  "data-checked": selected === actionType,
605
+ "data-disabled": disabled && selected !== actionType,
441
606
  children: /* @__PURE__ */ jsxs(FieldLabel, { htmlFor: `${name}-${actionType}`, children: [
442
607
  /* @__PURE__ */ jsx(VisuallyHidden, { children: /* @__PURE__ */ jsx(
443
608
  FieldInput,
@@ -447,7 +612,8 @@ const ActionOption = ({ selected, actionType, handleChange, name }) => {
447
612
  name,
448
613
  checked: selected === actionType,
449
614
  onChange: handleChange,
450
- value: actionType
615
+ value: actionType,
616
+ disabled
451
617
  }
452
618
  ) }),
453
619
  actionType
@@ -455,7 +621,12 @@ const ActionOption = ({ selected, actionType, handleChange, name }) => {
455
621
  }
456
622
  );
457
623
  };
458
- const ReleaseActionOptions = ({ selected, handleChange, name }) => {
624
+ const ReleaseActionOptions = ({
625
+ selected,
626
+ handleChange,
627
+ name,
628
+ disabled = false
629
+ }) => {
459
630
  return /* @__PURE__ */ jsxs(Flex, { children: [
460
631
  /* @__PURE__ */ jsx(
461
632
  ActionOption,
@@ -463,7 +634,8 @@ const ReleaseActionOptions = ({ selected, handleChange, name }) => {
463
634
  actionType: "publish",
464
635
  selected,
465
636
  handleChange,
466
- name
637
+ name,
638
+ disabled
467
639
  }
468
640
  ),
469
641
  /* @__PURE__ */ jsx(
@@ -472,7 +644,8 @@ const ReleaseActionOptions = ({ selected, handleChange, name }) => {
472
644
  actionType: "unpublish",
473
645
  selected,
474
646
  handleChange,
475
- name
647
+ name,
648
+ disabled
476
649
  }
477
650
  )
478
651
  ] });
@@ -500,7 +673,7 @@ const NoReleases = () => {
500
673
  to: {
501
674
  pathname: "/plugins/content-releases"
502
675
  },
503
- as: Link,
676
+ as: Link$1,
504
677
  variant: "secondary",
505
678
  children: formatMessage({
506
679
  id: "content-releases.content-manager-edit-view.add-to-release.redirect-button",
@@ -516,6 +689,7 @@ const AddActionToReleaseModal = ({
516
689
  contentTypeUid,
517
690
  entryId
518
691
  }) => {
692
+ const releaseHeaderId = React.useId();
519
693
  const { formatMessage } = useIntl();
520
694
  const toggleNotification = useNotification();
521
695
  const { formatAPIError } = useAPIErrorHandler();
@@ -563,8 +737,8 @@ const AddActionToReleaseModal = ({
563
737
  }
564
738
  }
565
739
  };
566
- return /* @__PURE__ */ jsxs(ModalLayout, { onClose: handleClose, labelledBy: "title", children: [
567
- /* @__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({
568
742
  id: "content-releases.content-manager-edit-view.add-to-release",
569
743
  defaultMessage: "Add to release"
570
744
  }) }) }),
@@ -610,7 +784,7 @@ const AddActionToReleaseModal = ({
610
784
  /* @__PURE__ */ jsx(
611
785
  ModalFooter,
612
786
  {
613
- 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({
614
788
  id: "content-releases.content-manager-edit-view.add-to-release.cancel-button",
615
789
  defaultMessage: "Cancel"
616
790
  }) }),
@@ -619,7 +793,7 @@ const AddActionToReleaseModal = ({
619
793
  * TODO: Ideally we would use isValid from Formik to disable the button, however currently it always returns true
620
794
  * for yup.string().required(), even when the value is falsy (including empty string)
621
795
  */
622
- /* @__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({
623
797
  id: "content-releases.content-manager-edit-view.add-to-release.continue-button",
624
798
  defaultMessage: "Continue"
625
799
  }) })
@@ -634,16 +808,18 @@ const AddActionToReleaseModal = ({
634
808
  };
635
809
  const CMReleasesContainer = () => {
636
810
  const [isModalOpen, setIsModalOpen] = React.useState(false);
637
- const { formatMessage } = useIntl();
811
+ const { formatMessage, formatDate, formatTime } = useIntl();
638
812
  const {
639
813
  isCreatingEntry,
640
- allLayoutData: { contentType }
814
+ hasDraftAndPublish,
815
+ initialData: { id: entryId },
816
+ slug
641
817
  } = useCMEditViewDataManager();
642
- const params = useParams();
643
- const canFetch = params?.id != null && contentType?.uid != null;
818
+ const contentTypeUid = slug;
819
+ const canFetch = entryId != null && contentTypeUid != null;
644
820
  const fetchParams = canFetch ? {
645
- contentTypeUid: contentType.uid,
646
- entryId: params.id,
821
+ contentTypeUid,
822
+ entryId,
647
823
  hasEntryAttached: true
648
824
  } : skipToken;
649
825
  const response = useGetReleasesForEntryQuery(fetchParams);
@@ -651,7 +827,7 @@ const CMReleasesContainer = () => {
651
827
  if (!canFetch) {
652
828
  return null;
653
829
  }
654
- if (isCreatingEntry || !contentType?.options?.draftAndPublish) {
830
+ if (isCreatingEntry || !hasDraftAndPublish) {
655
831
  return null;
656
832
  }
657
833
  const toggleModal = () => setIsModalOpen((prev) => !prev);
@@ -688,7 +864,7 @@ const CMReleasesContainer = () => {
688
864
  alignItems: "start",
689
865
  borderWidth: "1px",
690
866
  borderStyle: "solid",
691
- borderColor: getReleaseColorVariant(release.action.type, "200"),
867
+ borderColor: getReleaseColorVariant(release.actions[0].type, "200"),
692
868
  overflow: "hidden",
693
869
  hasRadius: true,
694
870
  children: [
@@ -699,35 +875,59 @@ const CMReleasesContainer = () => {
699
875
  paddingBottom: 3,
700
876
  paddingLeft: 4,
701
877
  paddingRight: 4,
702
- background: getReleaseColorVariant(release.action.type, "100"),
878
+ background: getReleaseColorVariant(release.actions[0].type, "100"),
703
879
  width: "100%",
704
880
  children: /* @__PURE__ */ jsx(
705
881
  Typography,
706
882
  {
707
883
  fontSize: 1,
708
884
  variant: "pi",
709
- textColor: getReleaseColorVariant(release.action.type, "600"),
885
+ textColor: getReleaseColorVariant(release.actions[0].type, "600"),
710
886
  children: formatMessage(
711
887
  {
712
888
  id: "content-releases.content-manager-edit-view.list-releases.title",
713
889
  defaultMessage: "{isPublish, select, true {Will be published in} other {Will be unpublished in}}"
714
890
  },
715
- { isPublish: release.action.type === "publish" }
891
+ { isPublish: release.actions[0].type === "publish" }
716
892
  )
717
893
  }
718
894
  )
719
895
  }
720
896
  ),
721
- /* @__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: [
722
898
  /* @__PURE__ */ jsx(Typography, { fontSize: 2, fontWeight: "bold", variant: "omega", textColor: "neutral700", children: release.name }),
723
- /* @__PURE__ */ jsx(
724
- ReleaseActionMenu,
899
+ release.scheduledAt && release.timezone && /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: formatMessage(
725
900
  {
726
- releaseId: release.id,
727
- actionId: release.action.id,
728
- hasTriggerBorder: true
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
+ )
729
919
  }
730
- )
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
+ ] }) })
731
931
  ] })
732
932
  ]
733
933
  },
@@ -735,7 +935,7 @@ const CMReleasesContainer = () => {
735
935
  );
736
936
  }),
737
937
  /* @__PURE__ */ jsx(CheckPermissions, { permissions: PERMISSIONS.createAction, children: /* @__PURE__ */ jsx(
738
- Button,
938
+ Button$1,
739
939
  {
740
940
  justifyContent: "center",
741
941
  paddingLeft: 4,
@@ -755,18 +955,281 @@ const CMReleasesContainer = () => {
755
955
  AddActionToReleaseModal,
756
956
  {
757
957
  handleClose: toggleModal,
758
- contentTypeUid: contentType.uid,
759
- entryId: params.id
958
+ contentTypeUid,
959
+ entryId
760
960
  }
761
961
  )
762
962
  ]
763
963
  }
764
964
  ) });
765
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
+ };
766
1228
  const admin = {
767
1229
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
768
1230
  register(app) {
769
- if (window.strapi.features.isEnabled("cms-content-releases") && window.strapi.future.isEnabled("contentReleases")) {
1231
+ app.createHook("ContentReleases/pages/ReleaseDetails/add-locale-in-releases");
1232
+ if (window.strapi.features.isEnabled("cms-content-releases")) {
770
1233
  app.addMenuLink({
771
1234
  to: `/plugins/${pluginId}`,
772
1235
  icon: PaperPlane,
@@ -775,7 +1238,7 @@ const admin = {
775
1238
  defaultMessage: "Releases"
776
1239
  },
777
1240
  async Component() {
778
- const { App } = await import("./App-g2P5kbSm.mjs");
1241
+ const { App } = await import("./App-jrh58sXY.mjs");
779
1242
  return App;
780
1243
  },
781
1244
  permissions: PERMISSIONS.main
@@ -788,12 +1251,33 @@ const admin = {
788
1251
  name: `${pluginId}-link`,
789
1252
  Component: CMReleasesContainer
790
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
+ });
791
1275
  }
792
1276
  },
793
1277
  async registerTrads({ locales }) {
794
1278
  const importedTrads = await Promise.all(
795
1279
  locales.map((locale) => {
796
- return __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => import("./en-ngTk74JV.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 }) => {
797
1281
  return {
798
1282
  data: prefixPluginTranslations(data, "content-releases"),
799
1283
  locale
@@ -812,17 +1296,20 @@ const admin = {
812
1296
  export {
813
1297
  PERMISSIONS as P,
814
1298
  ReleaseActionOptions as R,
815
- useUpdateReleaseMutation as a,
816
- useDeleteReleaseMutation as b,
817
- usePublishReleaseMutation as c,
818
- useGetReleaseActionsQuery as d,
819
- useUpdateReleaseActionMutation as e,
820
- ReleaseActionMenu as f,
821
- useGetReleasesQuery as g,
822
- useCreateReleaseMutation 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,
823
1307
  isAxiosError as i,
824
- admin as j,
1308
+ useUpdateReleaseActionMutation as j,
1309
+ ReleaseActionMenu as k,
1310
+ admin as l,
825
1311
  pluginId as p,
826
- useGetReleaseQuery as u
1312
+ releaseApi as r,
1313
+ useGetReleasesQuery as u
827
1314
  };
828
- //# sourceMappingURL=index-XAQOX_IB.mjs.map
1315
+ //# sourceMappingURL=index-PiOGBETy.mjs.map