node_modules ignore

This commit is contained in:
2025-05-08 23:43:47 +02:00
parent e19d52f172
commit 4574544c9f
65041 changed files with 10593536 additions and 0 deletions

17
server/node_modules/@strapi/content-releases/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,17 @@
Copyright (c) 2015-present Strapi Solutions SAS
* All software that resides within this directory and its subdirectories, is licensed under the license defined below.
Enterprise License
If you or the company you represent has entered into a written agreement referencing the Enterprise Edition of the Strapi source code available at
https://github.com/strapi/strapi, then such agreement applies to your use of the Enterprise Edition of the Strapi Software. If you or the company you
represent is using the Enterprise Edition of the Strapi Software in connection with a subscription to our cloud offering, then the agreement you have
agreed to with respect to our cloud offering and the licenses included in such agreement apply to your use of the Enterprise Edition of the Strapi Software.
Otherwise, the Strapi Enterprise Software License Agreement (found here https://strapi.io/enterprise-terms) applies to your use of the Enterprise Edition of the Strapi Software.
BY ACCESSING OR USING THE ENTERPRISE EDITION OF THE STRAPI SOFTWARE, YOU ARE AGREEING TO BE BOUND BY THE RELEVANT REFERENCED AGREEMENT.
IF YOU ARE NOT AUTHORIZED TO ACCEPT THESE TERMS ON BEHALF OF THE COMPANY YOU REPRESENT OR IF YOU DO NOT AGREE TO ALL OF THE RELEVANT TERMS AND CONDITIONS REFERENCED AND YOU
HAVE NOT OTHERWISE EXECUTED A WRITTEN AGREEMENT WITH STRAPI, YOU ARE NOT AUTHORIZED TO ACCESS OR USE OR ALLOW ANY USER TO ACCESS OR USE ANY PART OF
THE ENTERPRISE EDITION OF THE STRAPI SOFTWARE. YOUR ACCESS RIGHTS ARE CONDITIONAL ON YOUR CONSENT TO THE RELEVANT REFERENCED TERMS TO THE EXCLUSION OF ALL OTHER TERMS;
IF THE RELEVANT REFERENCED TERMS ARE CONSIDERED AN OFFER BY YOU, ACCEPTANCE IS EXPRESSLY LIMITED TO THE RELEVANT REFERENCED TERMS.

5
server/node_modules/@strapi/content-releases/README.md generated vendored Executable file
View File

@@ -0,0 +1,5 @@
# Plugin releases
Organize content over time into containers called releases, define actions for each entry (publish, unpublish), and release it all instantly with the click of a button.
Releases is an enterprise edition plugin.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,344 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
require('react');
var strapiAdmin = require('@strapi/content-manager/strapi-admin');
var designSystem = require('@strapi/design-system');
var icons = require('@strapi/icons');
var qs = require('qs');
var reactIntl = require('react-intl');
var reactRouterDom = require('react-router-dom');
var styledComponents = require('styled-components');
const StyledPopoverFlex = styledComponents.styled(designSystem.Flex)`
width: 100%;
max-width: 256px;
& > * {
border-bottom: 1px solid ${({ theme })=>theme.colors.neutral150};
}
& > *:last-child {
border-bottom: none;
}
`;
const EntryStatusTrigger = ({ action, status, hasErrors, requiredStage, entryStage })=>{
const { formatMessage } = reactIntl.useIntl();
if (action === 'publish') {
if (hasErrors || requiredStage && requiredStage.id !== entryStage?.id) {
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.Popover.Trigger, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
variant: "ghost",
startIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.CrossCircle, {
fill: "danger600"
}),
endIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.CaretDown, {}),
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "danger600",
variant: "omega",
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.not-ready',
defaultMessage: 'Not ready to publish'
})
})
})
});
}
if (status === 'draft') {
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.Popover.Trigger, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
variant: "ghost",
startIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.CheckCircle, {
fill: "success600"
}),
endIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.CaretDown, {}),
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "success600",
variant: "omega",
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.ready-to-publish',
defaultMessage: 'Ready to publish'
})
})
})
});
}
if (status === 'modified') {
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.Popover.Trigger, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
variant: "ghost",
startIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.ArrowsCounterClockwise, {
fill: "alternative600"
}),
endIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.CaretDown, {}),
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
variant: "omega",
fontWeight: "bold",
textColor: "alternative600",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.modified',
defaultMessage: 'Ready to publish changes'
})
})
})
});
}
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.Popover.Trigger, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
variant: "ghost",
startIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.CheckCircle, {
fill: "success600"
}),
endIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.CaretDown, {}),
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "success600",
variant: "omega",
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.already-published',
defaultMessage: 'Already published'
})
})
})
});
}
if (status === 'published') {
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.Popover.Trigger, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
variant: "ghost",
startIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.CheckCircle, {
fill: "success600"
}),
endIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.CaretDown, {}),
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "success600",
variant: "omega",
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.ready-to-unpublish',
defaultMessage: 'Ready to unpublish'
})
})
})
});
}
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.Popover.Trigger, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
variant: "ghost",
startIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.CheckCircle, {
fill: "success600"
}),
endIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.CaretDown, {}),
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "success600",
variant: "omega",
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.already-unpublished',
defaultMessage: 'Already unpublished'
})
})
})
});
};
const FieldsValidation = ({ hasErrors, errors, kind, contentTypeUid, documentId, locale })=>{
const { formatMessage } = reactIntl.useIntl();
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
gap: 1,
width: "100%",
padding: 5,
children: [
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 2,
width: "100%",
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.fields',
defaultMessage: 'Fields'
})
}),
hasErrors ? /*#__PURE__*/ jsxRuntime.jsx(icons.CrossCircle, {
fill: "danger600"
}) : /*#__PURE__*/ jsxRuntime.jsx(icons.CheckCircle, {
fill: "success600"
})
]
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
width: "100%",
textColor: "neutral600",
children: hasErrors ? formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.fields.error',
defaultMessage: '{errors} errors on fields.'
}, {
errors: errors ? Object.keys(errors).length : 0
}) : formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.fields.success',
defaultMessage: 'All fields are filled correctly.'
})
}),
hasErrors && /*#__PURE__*/ jsxRuntime.jsx(designSystem.LinkButton, {
tag: reactRouterDom.Link,
to: {
pathname: `/content-manager/${kind === 'collectionType' ? 'collection-types' : 'single-types'}/${contentTypeUid}/${documentId}`,
search: locale ? qs.stringify({
plugins: {
i18n: {
locale
}
}
}) : ''
},
variant: "secondary",
fullWidth: true,
state: {
forceValidation: true
},
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.fields.see-errors',
defaultMessage: 'See errors'
})
})
]
});
};
const getReviewStageIcon = ({ contentTypeHasReviewWorkflow, requiredStage, entryStage })=>{
if (!contentTypeHasReviewWorkflow) {
return /*#__PURE__*/ jsxRuntime.jsx(icons.CheckCircle, {
fill: "neutral200"
});
}
if (requiredStage && requiredStage.id !== entryStage?.id) {
return /*#__PURE__*/ jsxRuntime.jsx(icons.CrossCircle, {
fill: "danger600"
});
}
return /*#__PURE__*/ jsxRuntime.jsx(icons.CheckCircle, {
fill: "success600"
});
};
const getReviewStageMessage = ({ contentTypeHasReviewWorkflow, requiredStage, entryStage, formatMessage })=>{
if (!contentTypeHasReviewWorkflow) {
return formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.review-stage.not-enabled',
defaultMessage: 'This entry is not associated to any workflow.'
});
}
if (requiredStage && requiredStage.id !== entryStage?.id) {
return formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.review-stage.not-ready',
defaultMessage: 'This entry is not at the required stage for publishing. ({stageName})'
}, {
stageName: requiredStage?.name ?? ''
});
}
if (requiredStage && requiredStage.id === entryStage?.id) {
return formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.review-stage.ready',
defaultMessage: 'This entry is at the required stage for publishing. ({stageName})'
}, {
stageName: requiredStage?.name ?? ''
});
}
return formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.review-stage.stage-not-required',
defaultMessage: 'No required stage for publication'
});
};
const ReviewStageValidation = ({ contentTypeHasReviewWorkflow, requiredStage, entryStage })=>{
const { formatMessage } = reactIntl.useIntl();
const Icon = getReviewStageIcon({
contentTypeHasReviewWorkflow,
requiredStage,
entryStage
});
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
gap: 1,
width: "100%",
padding: 5,
children: [
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 2,
width: "100%",
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.review-stage',
defaultMessage: 'Review stage'
})
}),
Icon
]
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "neutral600",
children: getReviewStageMessage({
contentTypeHasReviewWorkflow,
requiredStage,
entryStage,
formatMessage
})
})
]
});
};
const EntryValidationPopover = ({ schema, entry, status, action })=>{
const { validate, isLoading } = strapiAdmin.unstable_useDocument({
collectionType: schema?.kind ?? '',
model: schema?.uid ?? ''
}, {
// useDocument makes a request to get more data about the entry, but we only want to have the validation function so we skip the request
skip: true
});
// Validation errors
const errors = isLoading ? null : validate(entry);
const hasErrors = errors ? Object.keys(errors).length > 0 : false;
// Entry stage
const contentTypeHasReviewWorkflow = schema?.hasReviewWorkflow ?? false;
const requiredStage = schema?.stageRequiredToPublish;
const entryStage = entry.strapi_stage;
if (isLoading) {
return null;
}
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Popover.Root, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(EntryStatusTrigger, {
action: action,
status: status,
hasErrors: hasErrors,
requiredStage: requiredStage,
entryStage: entryStage
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Popover.Content, {
children: /*#__PURE__*/ jsxRuntime.jsxs(StyledPopoverFlex, {
direction: "column",
children: [
/*#__PURE__*/ jsxRuntime.jsx(FieldsValidation, {
hasErrors: hasErrors,
errors: errors,
contentTypeUid: schema?.uid,
kind: schema?.kind,
documentId: entry.documentId,
locale: entry.locale
}),
/*#__PURE__*/ jsxRuntime.jsx(ReviewStageValidation, {
contentTypeHasReviewWorkflow: contentTypeHasReviewWorkflow,
requiredStage: requiredStage,
entryStage: entryStage
})
]
})
})
]
});
};
exports.EntryValidationPopover = EntryValidationPopover;
//# sourceMappingURL=EntryValidationPopover.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,342 @@
import { jsxs, jsx } from 'react/jsx-runtime';
import 'react';
import { unstable_useDocument } from '@strapi/content-manager/strapi-admin';
import { Flex, Popover, Button, Typography, LinkButton } from '@strapi/design-system';
import { CrossCircle, CaretDown, CheckCircle, ArrowsCounterClockwise } from '@strapi/icons';
import { stringify } from 'qs';
import { useIntl } from 'react-intl';
import { Link } from 'react-router-dom';
import { styled } from 'styled-components';
const StyledPopoverFlex = styled(Flex)`
width: 100%;
max-width: 256px;
& > * {
border-bottom: 1px solid ${({ theme })=>theme.colors.neutral150};
}
& > *:last-child {
border-bottom: none;
}
`;
const EntryStatusTrigger = ({ action, status, hasErrors, requiredStage, entryStage })=>{
const { formatMessage } = useIntl();
if (action === 'publish') {
if (hasErrors || requiredStage && requiredStage.id !== entryStage?.id) {
return /*#__PURE__*/ jsx(Popover.Trigger, {
children: /*#__PURE__*/ jsx(Button, {
variant: "ghost",
startIcon: /*#__PURE__*/ jsx(CrossCircle, {
fill: "danger600"
}),
endIcon: /*#__PURE__*/ jsx(CaretDown, {}),
children: /*#__PURE__*/ jsx(Typography, {
textColor: "danger600",
variant: "omega",
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.not-ready',
defaultMessage: 'Not ready to publish'
})
})
})
});
}
if (status === 'draft') {
return /*#__PURE__*/ jsx(Popover.Trigger, {
children: /*#__PURE__*/ jsx(Button, {
variant: "ghost",
startIcon: /*#__PURE__*/ jsx(CheckCircle, {
fill: "success600"
}),
endIcon: /*#__PURE__*/ jsx(CaretDown, {}),
children: /*#__PURE__*/ jsx(Typography, {
textColor: "success600",
variant: "omega",
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.ready-to-publish',
defaultMessage: 'Ready to publish'
})
})
})
});
}
if (status === 'modified') {
return /*#__PURE__*/ jsx(Popover.Trigger, {
children: /*#__PURE__*/ jsx(Button, {
variant: "ghost",
startIcon: /*#__PURE__*/ jsx(ArrowsCounterClockwise, {
fill: "alternative600"
}),
endIcon: /*#__PURE__*/ jsx(CaretDown, {}),
children: /*#__PURE__*/ jsx(Typography, {
variant: "omega",
fontWeight: "bold",
textColor: "alternative600",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.modified',
defaultMessage: 'Ready to publish changes'
})
})
})
});
}
return /*#__PURE__*/ jsx(Popover.Trigger, {
children: /*#__PURE__*/ jsx(Button, {
variant: "ghost",
startIcon: /*#__PURE__*/ jsx(CheckCircle, {
fill: "success600"
}),
endIcon: /*#__PURE__*/ jsx(CaretDown, {}),
children: /*#__PURE__*/ jsx(Typography, {
textColor: "success600",
variant: "omega",
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.already-published',
defaultMessage: 'Already published'
})
})
})
});
}
if (status === 'published') {
return /*#__PURE__*/ jsx(Popover.Trigger, {
children: /*#__PURE__*/ jsx(Button, {
variant: "ghost",
startIcon: /*#__PURE__*/ jsx(CheckCircle, {
fill: "success600"
}),
endIcon: /*#__PURE__*/ jsx(CaretDown, {}),
children: /*#__PURE__*/ jsx(Typography, {
textColor: "success600",
variant: "omega",
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.ready-to-unpublish',
defaultMessage: 'Ready to unpublish'
})
})
})
});
}
return /*#__PURE__*/ jsx(Popover.Trigger, {
children: /*#__PURE__*/ jsx(Button, {
variant: "ghost",
startIcon: /*#__PURE__*/ jsx(CheckCircle, {
fill: "success600"
}),
endIcon: /*#__PURE__*/ jsx(CaretDown, {}),
children: /*#__PURE__*/ jsx(Typography, {
textColor: "success600",
variant: "omega",
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.already-unpublished',
defaultMessage: 'Already unpublished'
})
})
})
});
};
const FieldsValidation = ({ hasErrors, errors, kind, contentTypeUid, documentId, locale })=>{
const { formatMessage } = useIntl();
return /*#__PURE__*/ jsxs(Flex, {
direction: "column",
gap: 1,
width: "100%",
padding: 5,
children: [
/*#__PURE__*/ jsxs(Flex, {
gap: 2,
width: "100%",
children: [
/*#__PURE__*/ jsx(Typography, {
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.fields',
defaultMessage: 'Fields'
})
}),
hasErrors ? /*#__PURE__*/ jsx(CrossCircle, {
fill: "danger600"
}) : /*#__PURE__*/ jsx(CheckCircle, {
fill: "success600"
})
]
}),
/*#__PURE__*/ jsx(Typography, {
width: "100%",
textColor: "neutral600",
children: hasErrors ? formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.fields.error',
defaultMessage: '{errors} errors on fields.'
}, {
errors: errors ? Object.keys(errors).length : 0
}) : formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.fields.success',
defaultMessage: 'All fields are filled correctly.'
})
}),
hasErrors && /*#__PURE__*/ jsx(LinkButton, {
tag: Link,
to: {
pathname: `/content-manager/${kind === 'collectionType' ? 'collection-types' : 'single-types'}/${contentTypeUid}/${documentId}`,
search: locale ? stringify({
plugins: {
i18n: {
locale
}
}
}) : ''
},
variant: "secondary",
fullWidth: true,
state: {
forceValidation: true
},
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.fields.see-errors',
defaultMessage: 'See errors'
})
})
]
});
};
const getReviewStageIcon = ({ contentTypeHasReviewWorkflow, requiredStage, entryStage })=>{
if (!contentTypeHasReviewWorkflow) {
return /*#__PURE__*/ jsx(CheckCircle, {
fill: "neutral200"
});
}
if (requiredStage && requiredStage.id !== entryStage?.id) {
return /*#__PURE__*/ jsx(CrossCircle, {
fill: "danger600"
});
}
return /*#__PURE__*/ jsx(CheckCircle, {
fill: "success600"
});
};
const getReviewStageMessage = ({ contentTypeHasReviewWorkflow, requiredStage, entryStage, formatMessage })=>{
if (!contentTypeHasReviewWorkflow) {
return formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.review-stage.not-enabled',
defaultMessage: 'This entry is not associated to any workflow.'
});
}
if (requiredStage && requiredStage.id !== entryStage?.id) {
return formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.review-stage.not-ready',
defaultMessage: 'This entry is not at the required stage for publishing. ({stageName})'
}, {
stageName: requiredStage?.name ?? ''
});
}
if (requiredStage && requiredStage.id === entryStage?.id) {
return formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.review-stage.ready',
defaultMessage: 'This entry is at the required stage for publishing. ({stageName})'
}, {
stageName: requiredStage?.name ?? ''
});
}
return formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.review-stage.stage-not-required',
defaultMessage: 'No required stage for publication'
});
};
const ReviewStageValidation = ({ contentTypeHasReviewWorkflow, requiredStage, entryStage })=>{
const { formatMessage } = useIntl();
const Icon = getReviewStageIcon({
contentTypeHasReviewWorkflow,
requiredStage,
entryStage
});
return /*#__PURE__*/ jsxs(Flex, {
direction: "column",
gap: 1,
width: "100%",
padding: 5,
children: [
/*#__PURE__*/ jsxs(Flex, {
gap: 2,
width: "100%",
children: [
/*#__PURE__*/ jsx(Typography, {
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.pages.ReleaseDetails.entry-validation.review-stage',
defaultMessage: 'Review stage'
})
}),
Icon
]
}),
/*#__PURE__*/ jsx(Typography, {
textColor: "neutral600",
children: getReviewStageMessage({
contentTypeHasReviewWorkflow,
requiredStage,
entryStage,
formatMessage
})
})
]
});
};
const EntryValidationPopover = ({ schema, entry, status, action })=>{
const { validate, isLoading } = unstable_useDocument({
collectionType: schema?.kind ?? '',
model: schema?.uid ?? ''
}, {
// useDocument makes a request to get more data about the entry, but we only want to have the validation function so we skip the request
skip: true
});
// Validation errors
const errors = isLoading ? null : validate(entry);
const hasErrors = errors ? Object.keys(errors).length > 0 : false;
// Entry stage
const contentTypeHasReviewWorkflow = schema?.hasReviewWorkflow ?? false;
const requiredStage = schema?.stageRequiredToPublish;
const entryStage = entry.strapi_stage;
if (isLoading) {
return null;
}
return /*#__PURE__*/ jsxs(Popover.Root, {
children: [
/*#__PURE__*/ jsx(EntryStatusTrigger, {
action: action,
status: status,
hasErrors: hasErrors,
requiredStage: requiredStage,
entryStage: entryStage
}),
/*#__PURE__*/ jsx(Popover.Content, {
children: /*#__PURE__*/ jsxs(StyledPopoverFlex, {
direction: "column",
children: [
/*#__PURE__*/ jsx(FieldsValidation, {
hasErrors: hasErrors,
errors: errors,
contentTypeUid: schema?.uid,
kind: schema?.kind,
documentId: entry.documentId,
locale: entry.locale
}),
/*#__PURE__*/ jsx(ReviewStageValidation, {
contentTypeHasReviewWorkflow: contentTypeHasReviewWorkflow,
requiredStage: requiredStage,
entryStage: entryStage
})
]
})
})
]
});
};
export { EntryValidationPopover };
//# sourceMappingURL=EntryValidationPopover.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,76 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var dateFns = require('date-fns');
var reactIntl = require('react-intl');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
const intervals = [
'years',
'months',
'days',
'hours',
'minutes',
'seconds'
];
/**
* Displays the relative time between a given timestamp and the current time.
* You can display a custom message for given time intervals by passing an array of custom intervals.
*
* @example
* ```jsx
* <caption>Display "last hour" if the timestamp is less than an hour ago</caption>
* <RelativeTime
* timestamp={new Date('2021-01-01')}
* customIntervals={[
* { unit: 'hours', threshold: 1, text: 'last hour' },
* ]}
* ```
*/ const RelativeTime = /*#__PURE__*/ React__namespace.forwardRef(({ timestamp, customIntervals = [], ...restProps }, forwardedRef)=>{
const { formatRelativeTime, formatDate, formatTime } = reactIntl.useIntl();
/**
* TODO: make this auto-update, like a clock.
*/ const interval = dateFns.intervalToDuration({
start: timestamp,
end: Date.now()
});
const unit = intervals.find((intervalUnit)=>{
return interval[intervalUnit] > 0 && Object.keys(interval).includes(intervalUnit);
});
const relativeTime = dateFns.isPast(timestamp) ? -interval[unit] : interval[unit];
// Display custom text if interval is less than the threshold
const customInterval = customIntervals.find((custom)=>interval[custom.unit] < custom.threshold);
const displayText = customInterval ? customInterval.text : formatRelativeTime(relativeTime, unit, {
numeric: 'auto'
});
return /*#__PURE__*/ jsxRuntime.jsx("time", {
ref: forwardedRef,
dateTime: timestamp.toISOString(),
role: "time",
title: `${formatDate(timestamp)} ${formatTime(timestamp)}`,
...restProps,
children: displayText
});
});
exports.RelativeTime = RelativeTime;
//# sourceMappingURL=RelativeTime.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"RelativeTime.js","sources":["../../../admin/src/components/RelativeTime.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { Duration, intervalToDuration, isPast } from 'date-fns';\nimport { useIntl } from 'react-intl';\n\nconst intervals: Array<keyof Duration> = ['years', 'months', 'days', 'hours', 'minutes', 'seconds'];\n\ninterface CustomInterval {\n unit: keyof Duration;\n text: string;\n threshold: number;\n}\n\ninterface RelativeTimeProps extends React.ComponentPropsWithoutRef<'time'> {\n timestamp: Date;\n customIntervals?: CustomInterval[];\n}\n\n/**\n * Displays the relative time between a given timestamp and the current time.\n * You can display a custom message for given time intervals by passing an array of custom intervals.\n *\n * @example\n * ```jsx\n * <caption>Display \"last hour\" if the timestamp is less than an hour ago</caption>\n * <RelativeTime\n * timestamp={new Date('2021-01-01')}\n * customIntervals={[\n * { unit: 'hours', threshold: 1, text: 'last hour' },\n * ]}\n * ```\n */\nconst RelativeTime = React.forwardRef<HTMLTimeElement, RelativeTimeProps>(\n ({ timestamp, customIntervals = [], ...restProps }, forwardedRef) => {\n const { formatRelativeTime, formatDate, formatTime } = useIntl();\n\n /**\n * TODO: make this auto-update, like a clock.\n */\n const interval = intervalToDuration({\n start: timestamp,\n end: Date.now(),\n // see https://github.com/date-fns/date-fns/issues/2891 No idea why it's all partial it returns it every time.\n }) as Required<Duration>;\n\n const unit = intervals.find((intervalUnit) => {\n return interval[intervalUnit] > 0 && Object.keys(interval).includes(intervalUnit);\n })!;\n\n const relativeTime = isPast(timestamp) ? -interval[unit] : interval[unit];\n\n // Display custom text if interval is less than the threshold\n const customInterval = customIntervals.find(\n (custom) => interval[custom.unit] < custom.threshold\n );\n\n const displayText = customInterval\n ? customInterval.text\n : formatRelativeTime(relativeTime, unit, { numeric: 'auto' });\n\n return (\n <time\n ref={forwardedRef}\n dateTime={timestamp.toISOString()}\n role=\"time\"\n title={`${formatDate(timestamp)} ${formatTime(timestamp)}`}\n {...restProps}\n >\n {displayText}\n </time>\n );\n }\n);\n\nexport { RelativeTime };\nexport type { CustomInterval, RelativeTimeProps };\n"],"names":["intervals","RelativeTime","React","forwardRef","timestamp","customIntervals","restProps","forwardedRef","formatRelativeTime","formatDate","formatTime","useIntl","interval","intervalToDuration","start","end","Date","now","unit","find","intervalUnit","Object","keys","includes","relativeTime","isPast","customInterval","custom","threshold","displayText","text","numeric","_jsx","time","ref","dateTime","toISOString","role","title"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAKA,MAAMA,SAAmC,GAAA;AAAC,IAAA,OAAA;AAAS,IAAA,QAAA;AAAU,IAAA,MAAA;AAAQ,IAAA,OAAA;AAAS,IAAA,SAAA;AAAW,IAAA;AAAU,CAAA;AAanG;;;;;;;;;;;;;AAaC,IACKC,MAAAA,YAAAA,iBAAeC,gBAAMC,CAAAA,UAAU,CACnC,CAAC,EAAEC,SAAS,EAAEC,eAAkB,GAAA,EAAE,EAAE,GAAGC,WAAW,EAAEC,YAAAA,GAAAA;AAClD,IAAA,MAAM,EAAEC,kBAAkB,EAAEC,UAAU,EAAEC,UAAU,EAAE,GAAGC,iBAAAA,EAAAA;AAEvD;;QAGA,MAAMC,WAAWC,0BAAmB,CAAA;QAClCC,KAAOV,EAAAA,SAAAA;AACPW,QAAAA,GAAAA,EAAKC,KAAKC,GAAG;AAEf,KAAA,CAAA;AAEA,IAAA,MAAMC,IAAOlB,GAAAA,SAAAA,CAAUmB,IAAI,CAAC,CAACC,YAAAA,GAAAA;QAC3B,OAAOR,QAAQ,CAACQ,YAAAA,CAAa,GAAG,CAAA,IAAKC,OAAOC,IAAI,CAACV,QAAUW,CAAAA,CAAAA,QAAQ,CAACH,YAAAA,CAAAA;AACtE,KAAA,CAAA;IAEA,MAAMI,YAAAA,GAAeC,cAAOrB,CAAAA,SAAAA,CAAAA,GAAa,CAACQ,QAAQ,CAACM,IAAK,CAAA,GAAGN,QAAQ,CAACM,IAAK,CAAA;;AAGzE,IAAA,MAAMQ,cAAiBrB,GAAAA,eAAAA,CAAgBc,IAAI,CACzC,CAACQ,MAAAA,GAAWf,QAAQ,CAACe,MAAOT,CAAAA,IAAI,CAAC,GAAGS,OAAOC,SAAS,CAAA;AAGtD,IAAA,MAAMC,cAAcH,cAChBA,GAAAA,cAAAA,CAAeI,IAAI,GACnBtB,kBAAAA,CAAmBgB,cAAcN,IAAM,EAAA;QAAEa,OAAS,EAAA;AAAO,KAAA,CAAA;AAE7D,IAAA,qBACEC,cAACC,CAAAA,MAAAA,EAAAA;QACCC,GAAK3B,EAAAA,YAAAA;AACL4B,QAAAA,QAAAA,EAAU/B,UAAUgC,WAAW,EAAA;QAC/BC,IAAK,EAAA,MAAA;QACLC,KAAO,EAAA,CAAC,EAAE7B,UAAWL,CAAAA,SAAAA,CAAAA,CAAW,CAAC,EAAEM,UAAAA,CAAWN,WAAW,CAAC;AACzD,QAAA,GAAGE,SAAS;AAEZuB,QAAAA,QAAAA,EAAAA;;AAGP,CAAA;;;;"}

View File

@@ -0,0 +1,55 @@
import { jsx } from 'react/jsx-runtime';
import * as React from 'react';
import { intervalToDuration, isPast } from 'date-fns';
import { useIntl } from 'react-intl';
const intervals = [
'years',
'months',
'days',
'hours',
'minutes',
'seconds'
];
/**
* Displays the relative time between a given timestamp and the current time.
* You can display a custom message for given time intervals by passing an array of custom intervals.
*
* @example
* ```jsx
* <caption>Display "last hour" if the timestamp is less than an hour ago</caption>
* <RelativeTime
* timestamp={new Date('2021-01-01')}
* customIntervals={[
* { unit: 'hours', threshold: 1, text: 'last hour' },
* ]}
* ```
*/ const RelativeTime = /*#__PURE__*/ React.forwardRef(({ timestamp, customIntervals = [], ...restProps }, forwardedRef)=>{
const { formatRelativeTime, formatDate, formatTime } = useIntl();
/**
* TODO: make this auto-update, like a clock.
*/ const interval = intervalToDuration({
start: timestamp,
end: Date.now()
});
const unit = intervals.find((intervalUnit)=>{
return interval[intervalUnit] > 0 && Object.keys(interval).includes(intervalUnit);
});
const relativeTime = isPast(timestamp) ? -interval[unit] : interval[unit];
// Display custom text if interval is less than the threshold
const customInterval = customIntervals.find((custom)=>interval[custom.unit] < custom.threshold);
const displayText = customInterval ? customInterval.text : formatRelativeTime(relativeTime, unit, {
numeric: 'auto'
});
return /*#__PURE__*/ jsx("time", {
ref: forwardedRef,
dateTime: timestamp.toISOString(),
role: "time",
title: `${formatDate(timestamp)} ${formatTime(timestamp)}`,
...restProps,
children: displayText
});
});
export { RelativeTime };
//# sourceMappingURL=RelativeTime.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"RelativeTime.mjs","sources":["../../../admin/src/components/RelativeTime.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { Duration, intervalToDuration, isPast } from 'date-fns';\nimport { useIntl } from 'react-intl';\n\nconst intervals: Array<keyof Duration> = ['years', 'months', 'days', 'hours', 'minutes', 'seconds'];\n\ninterface CustomInterval {\n unit: keyof Duration;\n text: string;\n threshold: number;\n}\n\ninterface RelativeTimeProps extends React.ComponentPropsWithoutRef<'time'> {\n timestamp: Date;\n customIntervals?: CustomInterval[];\n}\n\n/**\n * Displays the relative time between a given timestamp and the current time.\n * You can display a custom message for given time intervals by passing an array of custom intervals.\n *\n * @example\n * ```jsx\n * <caption>Display \"last hour\" if the timestamp is less than an hour ago</caption>\n * <RelativeTime\n * timestamp={new Date('2021-01-01')}\n * customIntervals={[\n * { unit: 'hours', threshold: 1, text: 'last hour' },\n * ]}\n * ```\n */\nconst RelativeTime = React.forwardRef<HTMLTimeElement, RelativeTimeProps>(\n ({ timestamp, customIntervals = [], ...restProps }, forwardedRef) => {\n const { formatRelativeTime, formatDate, formatTime } = useIntl();\n\n /**\n * TODO: make this auto-update, like a clock.\n */\n const interval = intervalToDuration({\n start: timestamp,\n end: Date.now(),\n // see https://github.com/date-fns/date-fns/issues/2891 No idea why it's all partial it returns it every time.\n }) as Required<Duration>;\n\n const unit = intervals.find((intervalUnit) => {\n return interval[intervalUnit] > 0 && Object.keys(interval).includes(intervalUnit);\n })!;\n\n const relativeTime = isPast(timestamp) ? -interval[unit] : interval[unit];\n\n // Display custom text if interval is less than the threshold\n const customInterval = customIntervals.find(\n (custom) => interval[custom.unit] < custom.threshold\n );\n\n const displayText = customInterval\n ? customInterval.text\n : formatRelativeTime(relativeTime, unit, { numeric: 'auto' });\n\n return (\n <time\n ref={forwardedRef}\n dateTime={timestamp.toISOString()}\n role=\"time\"\n title={`${formatDate(timestamp)} ${formatTime(timestamp)}`}\n {...restProps}\n >\n {displayText}\n </time>\n );\n }\n);\n\nexport { RelativeTime };\nexport type { CustomInterval, RelativeTimeProps };\n"],"names":["intervals","RelativeTime","React","forwardRef","timestamp","customIntervals","restProps","forwardedRef","formatRelativeTime","formatDate","formatTime","useIntl","interval","intervalToDuration","start","end","Date","now","unit","find","intervalUnit","Object","keys","includes","relativeTime","isPast","customInterval","custom","threshold","displayText","text","numeric","_jsx","time","ref","dateTime","toISOString","role","title"],"mappings":";;;;;AAKA,MAAMA,SAAmC,GAAA;AAAC,IAAA,OAAA;AAAS,IAAA,QAAA;AAAU,IAAA,MAAA;AAAQ,IAAA,OAAA;AAAS,IAAA,SAAA;AAAW,IAAA;AAAU,CAAA;AAanG;;;;;;;;;;;;;AAaC,IACKC,MAAAA,YAAAA,iBAAeC,KAAMC,CAAAA,UAAU,CACnC,CAAC,EAAEC,SAAS,EAAEC,eAAkB,GAAA,EAAE,EAAE,GAAGC,WAAW,EAAEC,YAAAA,GAAAA;AAClD,IAAA,MAAM,EAAEC,kBAAkB,EAAEC,UAAU,EAAEC,UAAU,EAAE,GAAGC,OAAAA,EAAAA;AAEvD;;QAGA,MAAMC,WAAWC,kBAAmB,CAAA;QAClCC,KAAOV,EAAAA,SAAAA;AACPW,QAAAA,GAAAA,EAAKC,KAAKC,GAAG;AAEf,KAAA,CAAA;AAEA,IAAA,MAAMC,IAAOlB,GAAAA,SAAAA,CAAUmB,IAAI,CAAC,CAACC,YAAAA,GAAAA;QAC3B,OAAOR,QAAQ,CAACQ,YAAAA,CAAa,GAAG,CAAA,IAAKC,OAAOC,IAAI,CAACV,QAAUW,CAAAA,CAAAA,QAAQ,CAACH,YAAAA,CAAAA;AACtE,KAAA,CAAA;IAEA,MAAMI,YAAAA,GAAeC,MAAOrB,CAAAA,SAAAA,CAAAA,GAAa,CAACQ,QAAQ,CAACM,IAAK,CAAA,GAAGN,QAAQ,CAACM,IAAK,CAAA;;AAGzE,IAAA,MAAMQ,cAAiBrB,GAAAA,eAAAA,CAAgBc,IAAI,CACzC,CAACQ,MAAAA,GAAWf,QAAQ,CAACe,MAAOT,CAAAA,IAAI,CAAC,GAAGS,OAAOC,SAAS,CAAA;AAGtD,IAAA,MAAMC,cAAcH,cAChBA,GAAAA,cAAAA,CAAeI,IAAI,GACnBtB,kBAAAA,CAAmBgB,cAAcN,IAAM,EAAA;QAAEa,OAAS,EAAA;AAAO,KAAA,CAAA;AAE7D,IAAA,qBACEC,GAACC,CAAAA,MAAAA,EAAAA;QACCC,GAAK3B,EAAAA,YAAAA;AACL4B,QAAAA,QAAAA,EAAU/B,UAAUgC,WAAW,EAAA;QAC/BC,IAAK,EAAA,MAAA;QACLC,KAAO,EAAA,CAAC,EAAE7B,UAAWL,CAAAA,SAAAA,CAAAA,CAAW,CAAC,EAAEM,UAAAA,CAAWN,WAAW,CAAC;AACzD,QAAA,GAAGE,SAAS;AAEZuB,QAAAA,QAAAA,EAAAA;;AAGP,CAAA;;;;"}

View File

@@ -0,0 +1,201 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var formik = require('formik');
var reactIntl = require('react-intl');
var constants = require('../constants.js');
var release = require('../services/release.js');
var ReleaseActionModal = require('./ReleaseActionModal.js');
var ReleaseActionOptions = require('./ReleaseActionOptions.js');
const getContentPermissions = (subject)=>{
const permissions = {
publish: [
{
action: 'plugin::content-manager.explorer.publish',
subject,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
]
};
return permissions;
};
const ReleaseAction = ({ documents, model })=>{
const { formatMessage } = reactIntl.useIntl();
const { toggleNotification } = strapiAdmin.useNotification();
const { formatAPIError } = strapiAdmin.useAPIErrorHandler();
const [{ query }] = strapiAdmin.useQueryParams();
const contentPermissions = getContentPermissions(model);
const { allowedActions: { canPublish } } = strapiAdmin.useRBAC(contentPermissions);
const { allowedActions: { canCreate } } = strapiAdmin.useRBAC(constants.PERMISSIONS);
// Get all the releases not published
const response = release.useGetReleasesQuery();
const releases = response.data?.data;
const [createManyReleaseActions, { isLoading }] = release.useCreateManyReleaseActionsMutation();
const documentIds = documents.map((doc)=>doc.documentId);
const handleSubmit = async (values)=>{
const locale = query.plugins?.i18n?.locale;
const releaseActionEntries = documentIds.map((entryDocumentId)=>({
type: values.type,
contentType: model,
entryDocumentId,
locale
}));
const response = await createManyReleaseActions({
body: releaseActionEntries,
params: {
releaseId: values.releaseId
}
});
if ('data' in response) {
// Handle success
const notificationMessage = formatMessage({
id: 'content-releases.content-manager-list-view.add-to-release.notification.success.message',
defaultMessage: '{entriesAlreadyInRelease} out of {totalEntries} entries were already in the release.'
}, {
entriesAlreadyInRelease: response.data.meta.entriesAlreadyInRelease,
totalEntries: response.data.meta.totalEntries
});
const notification = {
type: 'success',
title: formatMessage({
id: 'content-releases.content-manager-list-view.add-to-release.notification.success.title',
defaultMessage: 'Successfully added to release.'
}, {
entriesAlreadyInRelease: response.data.meta.entriesAlreadyInRelease,
totalEntries: response.data.meta.totalEntries
}),
message: response.data.meta.entriesAlreadyInRelease ? notificationMessage : ''
};
toggleNotification(notification);
return true;
}
if ('error' in response) {
if (strapiAdmin.isFetchError(response.error)) {
// Handle fetch error
toggleNotification({
type: 'warning',
message: formatAPIError(response.error)
});
} else {
// Handle generic error
toggleNotification({
type: 'warning',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
}
};
if (!canCreate || !canPublish) return null;
return {
actionType: 'release',
variant: 'tertiary',
label: formatMessage({
id: 'content-manager-list-view.add-to-release',
defaultMessage: 'Add to Release'
}),
dialog: {
type: 'modal',
title: formatMessage({
id: 'content-manager-list-view.add-to-release',
defaultMessage: 'Add to Release'
}),
content: ({ onClose })=>{
return /*#__PURE__*/ jsxRuntime.jsx(formik.Formik, {
onSubmit: async (values)=>{
const data = await handleSubmit(values);
if (data) {
return onClose();
}
},
validationSchema: ReleaseActionModal.RELEASE_ACTION_FORM_SCHEMA,
initialValues: ReleaseActionModal.INITIAL_VALUES,
children: ({ values, setFieldValue })=>/*#__PURE__*/ jsxRuntime.jsxs(formik.Form, {
children: [
releases?.length === 0 ? /*#__PURE__*/ jsxRuntime.jsx(ReleaseActionModal.NoReleases, {}) : /*#__PURE__*/ jsxRuntime.jsx(designSystem.Modal.Body, {
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
alignItems: "stretch",
gap: 2,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
paddingBottom: 6,
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Field.Root, {
required: true,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Label, {
children: formatMessage({
id: 'content-releases.content-manager-list-view.add-to-release.select-label',
defaultMessage: 'Select a release'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.SingleSelect, {
placeholder: formatMessage({
id: 'content-releases.content-manager-list-view.add-to-release.select-placeholder',
defaultMessage: 'Select'
}),
onChange: (value)=>setFieldValue('releaseId', value),
value: values.releaseId,
children: releases?.map((release)=>/*#__PURE__*/ jsxRuntime.jsx(designSystem.SingleSelectOption, {
value: release.id,
children: release.name
}, release.id))
})
]
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Label, {
children: formatMessage({
id: 'content-releases.content-manager-list-view.add-to-release.action-type-label',
defaultMessage: 'What do you want to do with these entries?'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(ReleaseActionOptions.ReleaseActionOptions, {
selected: values.type,
handleChange: (e)=>setFieldValue('type', e.target.value),
name: "type"
})
]
})
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Modal.Footer, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
onClick: onClose,
variant: "tertiary",
name: "cancel",
children: formatMessage({
id: 'content-releases.content-manager-list-view.add-to-release.cancel-button',
defaultMessage: 'Cancel'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
type: "submit",
disabled: !values.releaseId,
loading: isLoading,
children: formatMessage({
id: 'content-releases.content-manager-list-view.add-to-release.continue-button',
defaultMessage: 'Continue'
})
})
]
})
]
})
});
}
}
};
};
exports.ReleaseAction = ReleaseAction;
//# sourceMappingURL=ReleaseAction.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,199 @@
import { jsx, jsxs } from 'react/jsx-runtime';
import 'react';
import { useNotification, useAPIErrorHandler, useQueryParams, useRBAC, isFetchError } from '@strapi/admin/strapi-admin';
import { Modal, Flex, Box, Field, SingleSelect, SingleSelectOption, Button } from '@strapi/design-system';
import { Formik, Form } from 'formik';
import { useIntl } from 'react-intl';
import { PERMISSIONS } from '../constants.mjs';
import { useGetReleasesQuery, useCreateManyReleaseActionsMutation } from '../services/release.mjs';
import { RELEASE_ACTION_FORM_SCHEMA, INITIAL_VALUES, NoReleases } from './ReleaseActionModal.mjs';
import { ReleaseActionOptions } from './ReleaseActionOptions.mjs';
const getContentPermissions = (subject)=>{
const permissions = {
publish: [
{
action: 'plugin::content-manager.explorer.publish',
subject,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
]
};
return permissions;
};
const ReleaseAction = ({ documents, model })=>{
const { formatMessage } = useIntl();
const { toggleNotification } = useNotification();
const { formatAPIError } = useAPIErrorHandler();
const [{ query }] = useQueryParams();
const contentPermissions = getContentPermissions(model);
const { allowedActions: { canPublish } } = useRBAC(contentPermissions);
const { allowedActions: { canCreate } } = useRBAC(PERMISSIONS);
// Get all the releases not published
const response = useGetReleasesQuery();
const releases = response.data?.data;
const [createManyReleaseActions, { isLoading }] = useCreateManyReleaseActionsMutation();
const documentIds = documents.map((doc)=>doc.documentId);
const handleSubmit = async (values)=>{
const locale = query.plugins?.i18n?.locale;
const releaseActionEntries = documentIds.map((entryDocumentId)=>({
type: values.type,
contentType: model,
entryDocumentId,
locale
}));
const response = await createManyReleaseActions({
body: releaseActionEntries,
params: {
releaseId: values.releaseId
}
});
if ('data' in response) {
// Handle success
const notificationMessage = formatMessage({
id: 'content-releases.content-manager-list-view.add-to-release.notification.success.message',
defaultMessage: '{entriesAlreadyInRelease} out of {totalEntries} entries were already in the release.'
}, {
entriesAlreadyInRelease: response.data.meta.entriesAlreadyInRelease,
totalEntries: response.data.meta.totalEntries
});
const notification = {
type: 'success',
title: formatMessage({
id: 'content-releases.content-manager-list-view.add-to-release.notification.success.title',
defaultMessage: 'Successfully added to release.'
}, {
entriesAlreadyInRelease: response.data.meta.entriesAlreadyInRelease,
totalEntries: response.data.meta.totalEntries
}),
message: response.data.meta.entriesAlreadyInRelease ? notificationMessage : ''
};
toggleNotification(notification);
return true;
}
if ('error' in response) {
if (isFetchError(response.error)) {
// Handle fetch error
toggleNotification({
type: 'warning',
message: formatAPIError(response.error)
});
} else {
// Handle generic error
toggleNotification({
type: 'warning',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
}
};
if (!canCreate || !canPublish) return null;
return {
actionType: 'release',
variant: 'tertiary',
label: formatMessage({
id: 'content-manager-list-view.add-to-release',
defaultMessage: 'Add to Release'
}),
dialog: {
type: 'modal',
title: formatMessage({
id: 'content-manager-list-view.add-to-release',
defaultMessage: 'Add to Release'
}),
content: ({ onClose })=>{
return /*#__PURE__*/ jsx(Formik, {
onSubmit: async (values)=>{
const data = await handleSubmit(values);
if (data) {
return onClose();
}
},
validationSchema: RELEASE_ACTION_FORM_SCHEMA,
initialValues: INITIAL_VALUES,
children: ({ values, setFieldValue })=>/*#__PURE__*/ jsxs(Form, {
children: [
releases?.length === 0 ? /*#__PURE__*/ jsx(NoReleases, {}) : /*#__PURE__*/ jsx(Modal.Body, {
children: /*#__PURE__*/ jsxs(Flex, {
direction: "column",
alignItems: "stretch",
gap: 2,
children: [
/*#__PURE__*/ jsx(Box, {
paddingBottom: 6,
children: /*#__PURE__*/ jsxs(Field.Root, {
required: true,
children: [
/*#__PURE__*/ jsx(Field.Label, {
children: formatMessage({
id: 'content-releases.content-manager-list-view.add-to-release.select-label',
defaultMessage: 'Select a release'
})
}),
/*#__PURE__*/ jsx(SingleSelect, {
placeholder: formatMessage({
id: 'content-releases.content-manager-list-view.add-to-release.select-placeholder',
defaultMessage: 'Select'
}),
onChange: (value)=>setFieldValue('releaseId', value),
value: values.releaseId,
children: releases?.map((release)=>/*#__PURE__*/ jsx(SingleSelectOption, {
value: release.id,
children: release.name
}, release.id))
})
]
})
}),
/*#__PURE__*/ jsx(Field.Label, {
children: formatMessage({
id: 'content-releases.content-manager-list-view.add-to-release.action-type-label',
defaultMessage: 'What do you want to do with these entries?'
})
}),
/*#__PURE__*/ jsx(ReleaseActionOptions, {
selected: values.type,
handleChange: (e)=>setFieldValue('type', e.target.value),
name: "type"
})
]
})
}),
/*#__PURE__*/ jsxs(Modal.Footer, {
children: [
/*#__PURE__*/ jsx(Button, {
onClick: onClose,
variant: "tertiary",
name: "cancel",
children: formatMessage({
id: 'content-releases.content-manager-list-view.add-to-release.cancel-button',
defaultMessage: 'Cancel'
})
}),
/*#__PURE__*/ jsx(Button, {
type: "submit",
disabled: !values.releaseId,
loading: isLoading,
children: formatMessage({
id: 'content-releases.content-manager-list-view.add-to-release.continue-button',
defaultMessage: 'Continue'
})
})
]
})
]
})
});
}
}
};
};
export { ReleaseAction };
//# sourceMappingURL=ReleaseAction.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,243 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var icons = require('@strapi/icons');
var reactIntl = require('react-intl');
var reactRouterDom = require('react-router-dom');
var styledComponents = require('styled-components');
var constants = require('../constants.js');
var release = require('../services/release.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
const StyledMenuItem = styledComponents.styled(designSystem.Menu.Item)`
&:hover {
background: ${({ theme, $variant = 'neutral' })=>theme.colors[`${$variant}100`]};
svg {
fill: ${({ theme, $variant = 'neutral' })=>theme.colors[`${$variant}600`]};
}
a {
color: ${({ theme })=>theme.colors.neutral800};
}
}
svg {
color: ${({ theme, $variant = 'neutral' })=>theme.colors[`${$variant}500`]};
}
span {
color: ${({ theme, $variant = 'neutral' })=>theme.colors[`${$variant}800`]};
}
span,
a {
width: 100%;
}
`;
const DeleteReleaseActionItem = ({ releaseId, actionId })=>{
const { formatMessage } = reactIntl.useIntl();
const { toggleNotification } = strapiAdmin.useNotification();
const { formatAPIError } = strapiAdmin.useAPIErrorHandler();
const [deleteReleaseAction] = release.useDeleteReleaseActionMutation();
const { allowedActions: { canDeleteAction } } = strapiAdmin.useRBAC(constants.PERMISSIONS);
const handleDeleteAction = async ()=>{
const response = await deleteReleaseAction({
params: {
releaseId,
actionId
}
});
if ('data' in response) {
// Handle success
toggleNotification({
type: 'success',
message: formatMessage({
id: 'content-releases.content-manager-edit-view.remove-from-release.notification.success',
defaultMessage: 'Entry removed from release'
})
});
return;
}
if ('error' in response) {
if (strapiAdmin.isFetchError(response.error)) {
// Handle fetch error
toggleNotification({
type: 'danger',
message: formatAPIError(response.error)
});
} else {
// Handle generic error
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
}
};
if (!canDeleteAction) {
return null;
}
return /*#__PURE__*/ jsxRuntime.jsx(StyledMenuItem, {
$variant: "danger",
onSelect: handleDeleteAction,
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsxRuntime.jsx(icons.Cross, {
width: "1.6rem",
height: "1.6rem"
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "danger600",
variant: "omega",
children: formatMessage({
id: 'content-releases.content-manager-edit-view.remove-from-release',
defaultMessage: 'Remove from release'
})
})
]
})
});
};
const ReleaseActionEntryLinkItem = ({ contentTypeUid, documentId, locale })=>{
const { formatMessage } = reactIntl.useIntl();
const userPermissions = strapiAdmin.useAuth('ReleaseActionEntryLinkItem', (state)=>state.permissions);
// Confirm user has permissions to access the entry for the given locale
const canUpdateEntryForLocale = React__namespace.useMemo(()=>{
const updatePermissions = userPermissions.find((permission)=>permission.subject === contentTypeUid && permission.action === 'plugin::content-manager.explorer.update');
if (!updatePermissions) {
return false;
}
return Boolean(!locale || updatePermissions.properties?.locales?.includes(locale));
}, [
contentTypeUid,
locale,
userPermissions
]);
const { allowedActions: { canUpdate: canUpdateContentType } } = strapiAdmin.useRBAC({
updateContentType: [
{
action: 'plugin::content-manager.explorer.update',
subject: contentTypeUid
}
]
});
if (!canUpdateContentType || !canUpdateEntryForLocale) {
return null;
}
return /*#__PURE__*/ jsxRuntime.jsx(StyledMenuItem, {
/* @ts-expect-error inference isn't working in DS */ tag: reactRouterDom.NavLink,
isLink: true,
to: {
pathname: `/content-manager/collection-types/${contentTypeUid}/${documentId}`,
search: locale && `?plugins[i18n][locale]=${locale}`
},
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsxRuntime.jsx(icons.Pencil, {
width: "1.6rem",
height: "1.6rem"
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
variant: "omega",
children: formatMessage({
id: 'content-releases.content-manager-edit-view.edit-entry',
defaultMessage: 'Edit entry'
})
})
]
})
});
};
const EditReleaseItem = ({ releaseId })=>{
const { formatMessage } = reactIntl.useIntl();
return /* @ts-expect-error inference isn't working in DS */ /*#__PURE__*/ jsxRuntime.jsx(StyledMenuItem, {
tag: reactRouterDom.NavLink,
isLink: true,
to: `/plugins/content-releases/${releaseId}`,
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsxRuntime.jsx(icons.Pencil, {
width: "1.6rem",
height: "1.6rem"
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "neutral800",
variant: "omega",
children: formatMessage({
id: 'content-releases.content-manager-edit-view.edit-release',
defaultMessage: 'Edit release'
})
})
]
})
});
};
const Root = ({ children })=>{
const { formatMessage } = reactIntl.useIntl();
const { allowedActions } = strapiAdmin.useRBAC(constants.PERMISSIONS);
return(// A user can access the dropdown if they have permissions to delete a release-action OR update a release
allowedActions.canDeleteAction || allowedActions.canUpdate ? /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Menu.Root, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(StyledMoreButton, {
variant: "tertiary",
endIcon: null,
paddingLeft: "7px",
paddingRight: "7px",
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.AccessibleIcon, {
label: formatMessage({
id: 'content-releases.content-manager-edit-view.release-action-menu',
defaultMessage: 'Release action options'
}),
children: /*#__PURE__*/ jsxRuntime.jsx(icons.More, {})
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Menu.Content, {
top: 1,
popoverPlacement: "bottom-end",
children: children
})
]
}) : null);
};
const StyledMoreButton = styledComponents.styled(designSystem.Menu.Trigger)`
& > span {
display: flex;
}
`;
const ReleaseActionMenu = {
Root,
EditReleaseItem,
DeleteReleaseActionItem,
ReleaseActionEntryLinkItem
};
exports.ReleaseActionMenu = ReleaseActionMenu;
//# sourceMappingURL=ReleaseActionMenu.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,222 @@
import { jsx, jsxs } from 'react/jsx-runtime';
import * as React from 'react';
import { useNotification, useAPIErrorHandler, useRBAC, useAuth, isFetchError } from '@strapi/admin/strapi-admin';
import { Menu, Flex, Typography, AccessibleIcon } from '@strapi/design-system';
import { Cross, Pencil, More } from '@strapi/icons';
import { useIntl } from 'react-intl';
import { NavLink } from 'react-router-dom';
import { styled } from 'styled-components';
import { PERMISSIONS } from '../constants.mjs';
import { useDeleteReleaseActionMutation } from '../services/release.mjs';
const StyledMenuItem = styled(Menu.Item)`
&:hover {
background: ${({ theme, $variant = 'neutral' })=>theme.colors[`${$variant}100`]};
svg {
fill: ${({ theme, $variant = 'neutral' })=>theme.colors[`${$variant}600`]};
}
a {
color: ${({ theme })=>theme.colors.neutral800};
}
}
svg {
color: ${({ theme, $variant = 'neutral' })=>theme.colors[`${$variant}500`]};
}
span {
color: ${({ theme, $variant = 'neutral' })=>theme.colors[`${$variant}800`]};
}
span,
a {
width: 100%;
}
`;
const DeleteReleaseActionItem = ({ releaseId, actionId })=>{
const { formatMessage } = useIntl();
const { toggleNotification } = useNotification();
const { formatAPIError } = useAPIErrorHandler();
const [deleteReleaseAction] = useDeleteReleaseActionMutation();
const { allowedActions: { canDeleteAction } } = useRBAC(PERMISSIONS);
const handleDeleteAction = async ()=>{
const response = await deleteReleaseAction({
params: {
releaseId,
actionId
}
});
if ('data' in response) {
// Handle success
toggleNotification({
type: 'success',
message: formatMessage({
id: 'content-releases.content-manager-edit-view.remove-from-release.notification.success',
defaultMessage: 'Entry removed from release'
})
});
return;
}
if ('error' in response) {
if (isFetchError(response.error)) {
// Handle fetch error
toggleNotification({
type: 'danger',
message: formatAPIError(response.error)
});
} else {
// Handle generic error
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
}
};
if (!canDeleteAction) {
return null;
}
return /*#__PURE__*/ jsx(StyledMenuItem, {
$variant: "danger",
onSelect: handleDeleteAction,
children: /*#__PURE__*/ jsxs(Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsx(Cross, {
width: "1.6rem",
height: "1.6rem"
}),
/*#__PURE__*/ jsx(Typography, {
textColor: "danger600",
variant: "omega",
children: formatMessage({
id: 'content-releases.content-manager-edit-view.remove-from-release',
defaultMessage: 'Remove from release'
})
})
]
})
});
};
const ReleaseActionEntryLinkItem = ({ contentTypeUid, documentId, locale })=>{
const { formatMessage } = useIntl();
const userPermissions = useAuth('ReleaseActionEntryLinkItem', (state)=>state.permissions);
// Confirm user has permissions to access the entry for the given locale
const canUpdateEntryForLocale = React.useMemo(()=>{
const updatePermissions = userPermissions.find((permission)=>permission.subject === contentTypeUid && permission.action === 'plugin::content-manager.explorer.update');
if (!updatePermissions) {
return false;
}
return Boolean(!locale || updatePermissions.properties?.locales?.includes(locale));
}, [
contentTypeUid,
locale,
userPermissions
]);
const { allowedActions: { canUpdate: canUpdateContentType } } = useRBAC({
updateContentType: [
{
action: 'plugin::content-manager.explorer.update',
subject: contentTypeUid
}
]
});
if (!canUpdateContentType || !canUpdateEntryForLocale) {
return null;
}
return /*#__PURE__*/ jsx(StyledMenuItem, {
/* @ts-expect-error inference isn't working in DS */ tag: NavLink,
isLink: true,
to: {
pathname: `/content-manager/collection-types/${contentTypeUid}/${documentId}`,
search: locale && `?plugins[i18n][locale]=${locale}`
},
children: /*#__PURE__*/ jsxs(Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsx(Pencil, {
width: "1.6rem",
height: "1.6rem"
}),
/*#__PURE__*/ jsx(Typography, {
variant: "omega",
children: formatMessage({
id: 'content-releases.content-manager-edit-view.edit-entry',
defaultMessage: 'Edit entry'
})
})
]
})
});
};
const EditReleaseItem = ({ releaseId })=>{
const { formatMessage } = useIntl();
return /* @ts-expect-error inference isn't working in DS */ /*#__PURE__*/ jsx(StyledMenuItem, {
tag: NavLink,
isLink: true,
to: `/plugins/content-releases/${releaseId}`,
children: /*#__PURE__*/ jsxs(Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsx(Pencil, {
width: "1.6rem",
height: "1.6rem"
}),
/*#__PURE__*/ jsx(Typography, {
textColor: "neutral800",
variant: "omega",
children: formatMessage({
id: 'content-releases.content-manager-edit-view.edit-release',
defaultMessage: 'Edit release'
})
})
]
})
});
};
const Root = ({ children })=>{
const { formatMessage } = useIntl();
const { allowedActions } = useRBAC(PERMISSIONS);
return(// A user can access the dropdown if they have permissions to delete a release-action OR update a release
allowedActions.canDeleteAction || allowedActions.canUpdate ? /*#__PURE__*/ jsxs(Menu.Root, {
children: [
/*#__PURE__*/ jsx(StyledMoreButton, {
variant: "tertiary",
endIcon: null,
paddingLeft: "7px",
paddingRight: "7px",
children: /*#__PURE__*/ jsx(AccessibleIcon, {
label: formatMessage({
id: 'content-releases.content-manager-edit-view.release-action-menu',
defaultMessage: 'Release action options'
}),
children: /*#__PURE__*/ jsx(More, {})
})
}),
/*#__PURE__*/ jsx(Menu.Content, {
top: 1,
popoverPlacement: "bottom-end",
children: children
})
]
}) : null);
};
const StyledMoreButton = styled(Menu.Trigger)`
& > span {
display: flex;
}
`;
const ReleaseActionMenu = {
Root,
EditReleaseItem,
DeleteReleaseActionItem,
ReleaseActionEntryLinkItem
};
export { ReleaseActionMenu };
//# sourceMappingURL=ReleaseActionMenu.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,268 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var strapiAdmin$1 = require('@strapi/content-manager/strapi-admin');
var designSystem = require('@strapi/design-system');
var icons = require('@strapi/icons');
var symbols = require('@strapi/icons/symbols');
var formik = require('formik');
var reactIntl = require('react-intl');
var reactRouterDom = require('react-router-dom');
var yup = require('yup');
var constants = require('../constants.js');
var release = require('../services/release.js');
var ReleaseActionOptions = require('./ReleaseActionOptions.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var yup__namespace = /*#__PURE__*/_interopNamespaceDefault(yup);
/* -------------------------------------------------------------------------------------------------
* AddActionToReleaseModal
* -----------------------------------------------------------------------------------------------*/ const RELEASE_ACTION_FORM_SCHEMA = yup__namespace.object().shape({
type: yup__namespace.string().oneOf([
'publish',
'unpublish'
]).required(),
releaseId: yup__namespace.string().required()
});
const INITIAL_VALUES = {
type: 'publish',
releaseId: ''
};
const NoReleases = ()=>{
const { formatMessage } = reactIntl.useIntl();
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.EmptyStateLayout, {
icon: /*#__PURE__*/ jsxRuntime.jsx(symbols.EmptyDocuments, {
width: "16rem"
}),
content: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.no-releases-message',
defaultMessage: 'No available releases. Open the list of releases and create a new one from there.'
}),
action: /*#__PURE__*/ jsxRuntime.jsx(designSystem.LinkButton, {
to: {
pathname: '/plugins/content-releases'
},
tag: reactRouterDom.Link,
variant: "secondary",
children: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.redirect-button',
defaultMessage: 'Open the list of releases'
})
}),
shadow: "none"
});
};
const AddActionToReleaseModal = ({ contentType, documentId, onInputChange, values })=>{
const { formatMessage } = reactIntl.useIntl();
const [{ query }] = strapiAdmin.useQueryParams();
const locale = query.plugins?.i18n?.locale;
// Get all 'pending' releases that do not have the entry attached
const response = release.useGetReleasesForEntryQuery({
contentType,
entryDocumentId: documentId,
hasEntryAttached: false,
locale
});
const releases = response.data?.data;
if (releases?.length === 0) {
return /*#__PURE__*/ jsxRuntime.jsx(NoReleases, {});
}
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
alignItems: "stretch",
gap: 2,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
paddingBottom: 6,
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Field.Root, {
required: true,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Label, {
children: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.select-label',
defaultMessage: 'Select a release'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.SingleSelect, {
required: true,
placeholder: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.select-placeholder',
defaultMessage: 'Select'
}),
name: "releaseId",
onChange: (value)=>onInputChange('releaseId', value),
value: values.releaseId,
children: releases?.map((release)=>/*#__PURE__*/ jsxRuntime.jsx(designSystem.SingleSelectOption, {
value: release.id,
children: release.name
}, release.id))
})
]
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Label, {
children: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.action-type-label',
defaultMessage: 'What do you want to do with this entry?'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(ReleaseActionOptions.ReleaseActionOptions, {
selected: values.type,
handleChange: (e)=>onInputChange('type', e.target.value),
name: "type"
})
]
});
};
/* -------------------------------------------------------------------------------------------------
* ReleaseActionModalForm
* -----------------------------------------------------------------------------------------------*/ const ReleaseActionModalForm = ({ documentId, document, model, collectionType })=>{
const { formatMessage } = reactIntl.useIntl();
const { allowedActions } = strapiAdmin.useRBAC(constants.PERMISSIONS);
const { canCreateAction } = allowedActions;
const [createReleaseAction, { isLoading }] = release.useCreateReleaseActionMutation();
const { toggleNotification } = strapiAdmin.useNotification();
const { formatAPIError } = strapiAdmin.useAPIErrorHandler();
const [{ query }] = strapiAdmin.useQueryParams();
const locale = query.plugins?.i18n?.locale;
const handleSubmit = async (e, onClose)=>{
try {
await formik$1.handleSubmit(e);
onClose();
} catch (error) {
if (strapiAdmin.isFetchError(error)) {
// Handle axios error
toggleNotification({
type: 'danger',
message: formatAPIError(error)
});
} else {
// Handle generic error
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
}
};
const formik$1 = formik.useFormik({
initialValues: INITIAL_VALUES,
validationSchema: RELEASE_ACTION_FORM_SCHEMA,
onSubmit: async (values)=>{
if (collectionType === 'collection-types' && !documentId) {
throw new Error('Document id is required');
}
const response = await createReleaseAction({
body: {
type: values.type,
contentType: model,
entryDocumentId: documentId,
locale
},
params: {
releaseId: values.releaseId
}
});
if ('data' in response) {
// Handle success
toggleNotification({
type: 'success',
message: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.notification.success',
defaultMessage: 'Entry added to release'
})
});
return;
}
if ('error' in response) {
throw response.error;
}
}
});
const { edit: { options } } = strapiAdmin$1.unstable_useDocumentLayout(model);
// Project is not EE or contentType does not have draftAndPublish enabled
if (!window.strapi.isEE || !options?.draftAndPublish || !canCreateAction) {
return null;
}
if (collectionType === 'collection-types' && (!documentId || documentId === 'create')) {
return null;
}
return {
label: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release',
defaultMessage: 'Add to release'
}),
icon: /*#__PURE__*/ jsxRuntime.jsx(icons.PaperPlane, {}),
// Entry is creating so we don't want to allow adding it to a release
disabled: !document,
position: [
'panel',
'table-row'
],
dialog: {
type: 'modal',
title: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release',
defaultMessage: 'Add to release'
}),
content: /*#__PURE__*/ jsxRuntime.jsx(AddActionToReleaseModal, {
contentType: model,
documentId: documentId,
onInputChange: formik$1.setFieldValue,
values: formik$1.values
}),
footer: ({ onClose })=>/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Modal.Footer, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
onClick: onClose,
variant: "tertiary",
name: "cancel",
children: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.cancel-button',
defaultMessage: 'Cancel'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
type: "submit",
// @ts-expect-error - formik ReactEvent types don't match button onClick types as they expect a MouseEvent
onClick: (e)=>handleSubmit(e, onClose),
disabled: !formik$1.values.releaseId,
loading: isLoading,
children: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.continue-button',
defaultMessage: 'Continue'
})
})
]
})
}
};
};
exports.INITIAL_VALUES = INITIAL_VALUES;
exports.NoReleases = NoReleases;
exports.RELEASE_ACTION_FORM_SCHEMA = RELEASE_ACTION_FORM_SCHEMA;
exports.ReleaseActionModalForm = ReleaseActionModalForm;
//# sourceMappingURL=ReleaseActionModal.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,244 @@
import { jsx, jsxs } from 'react/jsx-runtime';
import 'react';
import { useRBAC, useNotification, useAPIErrorHandler, useQueryParams, isFetchError } from '@strapi/admin/strapi-admin';
import { unstable_useDocumentLayout } from '@strapi/content-manager/strapi-admin';
import { Modal, Button, EmptyStateLayout, LinkButton, Flex, Box, Field, SingleSelect, SingleSelectOption } from '@strapi/design-system';
import { PaperPlane } from '@strapi/icons';
import { EmptyDocuments } from '@strapi/icons/symbols';
import { useFormik } from 'formik';
import { useIntl } from 'react-intl';
import { Link } from 'react-router-dom';
import * as yup from 'yup';
import { PERMISSIONS } from '../constants.mjs';
import { useCreateReleaseActionMutation, useGetReleasesForEntryQuery } from '../services/release.mjs';
import { ReleaseActionOptions } from './ReleaseActionOptions.mjs';
/* -------------------------------------------------------------------------------------------------
* AddActionToReleaseModal
* -----------------------------------------------------------------------------------------------*/ const RELEASE_ACTION_FORM_SCHEMA = yup.object().shape({
type: yup.string().oneOf([
'publish',
'unpublish'
]).required(),
releaseId: yup.string().required()
});
const INITIAL_VALUES = {
type: 'publish',
releaseId: ''
};
const NoReleases = ()=>{
const { formatMessage } = useIntl();
return /*#__PURE__*/ jsx(EmptyStateLayout, {
icon: /*#__PURE__*/ jsx(EmptyDocuments, {
width: "16rem"
}),
content: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.no-releases-message',
defaultMessage: 'No available releases. Open the list of releases and create a new one from there.'
}),
action: /*#__PURE__*/ jsx(LinkButton, {
to: {
pathname: '/plugins/content-releases'
},
tag: Link,
variant: "secondary",
children: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.redirect-button',
defaultMessage: 'Open the list of releases'
})
}),
shadow: "none"
});
};
const AddActionToReleaseModal = ({ contentType, documentId, onInputChange, values })=>{
const { formatMessage } = useIntl();
const [{ query }] = useQueryParams();
const locale = query.plugins?.i18n?.locale;
// Get all 'pending' releases that do not have the entry attached
const response = useGetReleasesForEntryQuery({
contentType,
entryDocumentId: documentId,
hasEntryAttached: false,
locale
});
const releases = response.data?.data;
if (releases?.length === 0) {
return /*#__PURE__*/ jsx(NoReleases, {});
}
return /*#__PURE__*/ jsxs(Flex, {
direction: "column",
alignItems: "stretch",
gap: 2,
children: [
/*#__PURE__*/ jsx(Box, {
paddingBottom: 6,
children: /*#__PURE__*/ jsxs(Field.Root, {
required: true,
children: [
/*#__PURE__*/ jsx(Field.Label, {
children: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.select-label',
defaultMessage: 'Select a release'
})
}),
/*#__PURE__*/ jsx(SingleSelect, {
required: true,
placeholder: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.select-placeholder',
defaultMessage: 'Select'
}),
name: "releaseId",
onChange: (value)=>onInputChange('releaseId', value),
value: values.releaseId,
children: releases?.map((release)=>/*#__PURE__*/ jsx(SingleSelectOption, {
value: release.id,
children: release.name
}, release.id))
})
]
})
}),
/*#__PURE__*/ jsx(Field.Label, {
children: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.action-type-label',
defaultMessage: 'What do you want to do with this entry?'
})
}),
/*#__PURE__*/ jsx(ReleaseActionOptions, {
selected: values.type,
handleChange: (e)=>onInputChange('type', e.target.value),
name: "type"
})
]
});
};
/* -------------------------------------------------------------------------------------------------
* ReleaseActionModalForm
* -----------------------------------------------------------------------------------------------*/ const ReleaseActionModalForm = ({ documentId, document, model, collectionType })=>{
const { formatMessage } = useIntl();
const { allowedActions } = useRBAC(PERMISSIONS);
const { canCreateAction } = allowedActions;
const [createReleaseAction, { isLoading }] = useCreateReleaseActionMutation();
const { toggleNotification } = useNotification();
const { formatAPIError } = useAPIErrorHandler();
const [{ query }] = useQueryParams();
const locale = query.plugins?.i18n?.locale;
const handleSubmit = async (e, onClose)=>{
try {
await formik.handleSubmit(e);
onClose();
} catch (error) {
if (isFetchError(error)) {
// Handle axios error
toggleNotification({
type: 'danger',
message: formatAPIError(error)
});
} else {
// Handle generic error
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
}
};
const formik = useFormik({
initialValues: INITIAL_VALUES,
validationSchema: RELEASE_ACTION_FORM_SCHEMA,
onSubmit: async (values)=>{
if (collectionType === 'collection-types' && !documentId) {
throw new Error('Document id is required');
}
const response = await createReleaseAction({
body: {
type: values.type,
contentType: model,
entryDocumentId: documentId,
locale
},
params: {
releaseId: values.releaseId
}
});
if ('data' in response) {
// Handle success
toggleNotification({
type: 'success',
message: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.notification.success',
defaultMessage: 'Entry added to release'
})
});
return;
}
if ('error' in response) {
throw response.error;
}
}
});
const { edit: { options } } = unstable_useDocumentLayout(model);
// Project is not EE or contentType does not have draftAndPublish enabled
if (!window.strapi.isEE || !options?.draftAndPublish || !canCreateAction) {
return null;
}
if (collectionType === 'collection-types' && (!documentId || documentId === 'create')) {
return null;
}
return {
label: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release',
defaultMessage: 'Add to release'
}),
icon: /*#__PURE__*/ jsx(PaperPlane, {}),
// Entry is creating so we don't want to allow adding it to a release
disabled: !document,
position: [
'panel',
'table-row'
],
dialog: {
type: 'modal',
title: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release',
defaultMessage: 'Add to release'
}),
content: /*#__PURE__*/ jsx(AddActionToReleaseModal, {
contentType: model,
documentId: documentId,
onInputChange: formik.setFieldValue,
values: formik.values
}),
footer: ({ onClose })=>/*#__PURE__*/ jsxs(Modal.Footer, {
children: [
/*#__PURE__*/ jsx(Button, {
onClick: onClose,
variant: "tertiary",
name: "cancel",
children: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.cancel-button',
defaultMessage: 'Cancel'
})
}),
/*#__PURE__*/ jsx(Button, {
type: "submit",
// @ts-expect-error - formik ReactEvent types don't match button onClick types as they expect a MouseEvent
onClick: (e)=>handleSubmit(e, onClose),
disabled: !formik.values.releaseId,
loading: isLoading,
children: formatMessage({
id: 'content-releases.content-manager-edit-view.add-to-release.continue-button',
defaultMessage: 'Continue'
})
})
]
})
}
};
};
export { INITIAL_VALUES, NoReleases, RELEASE_ACTION_FORM_SCHEMA, ReleaseActionModalForm };
//# sourceMappingURL=ReleaseActionModal.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,104 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
require('react');
var designSystem = require('@strapi/design-system');
var styledComponents = require('styled-components');
const getBorderLeftRadiusValue = (actionType)=>{
return actionType === 'publish' ? 1 : 0;
};
const getBorderRightRadiusValue = (actionType)=>{
return actionType === 'publish' ? 0 : 1;
};
const FieldWrapper = styledComponents.styled(designSystem.Field.Root)`
border-top-left-radius: ${({ $actionType, theme })=>theme.spaces[getBorderLeftRadiusValue($actionType)]};
border-bottom-left-radius: ${({ $actionType, theme })=>theme.spaces[getBorderLeftRadiusValue($actionType)]};
border-top-right-radius: ${({ $actionType, theme })=>theme.spaces[getBorderRightRadiusValue($actionType)]};
border-bottom-right-radius: ${({ $actionType, theme })=>theme.spaces[getBorderRightRadiusValue($actionType)]};
> label {
color: inherit;
padding: ${({ theme })=>`${theme.spaces[2]} ${theme.spaces[3]}`};
text-align: center;
vertical-align: middle;
text-transform: capitalize;
}
&[data-checked='true'] {
color: ${({ theme, $actionType })=>$actionType === 'publish' ? theme.colors.primary700 : theme.colors.danger600};
background-color: ${({ theme, $actionType })=>$actionType === 'publish' ? theme.colors.primary100 : theme.colors.danger100};
border-color: ${({ theme, $actionType })=>$actionType === 'publish' ? theme.colors.primary700 : theme.colors.danger600};
}
&[data-checked='false'] {
border-left: ${({ $actionType })=>$actionType === 'unpublish' && 'none'};
border-right: ${({ $actionType })=>$actionType === 'publish' && 'none'};
}
&[data-checked='false'][data-disabled='false']:hover {
color: ${({ theme })=>theme.colors.neutral700};
background-color: ${({ theme })=>theme.colors.neutral100};
border-color: ${({ theme })=>theme.colors.neutral200};
& > label {
cursor: pointer;
}
}
&[data-disabled='true'] {
color: ${({ theme })=>theme.colors.neutral600};
background-color: ${({ theme })=>theme.colors.neutral150};
border-color: ${({ theme })=>theme.colors.neutral300};
}
`;
const ActionOption = ({ selected, actionType, handleChange, name, disabled = false })=>{
return /*#__PURE__*/ jsxRuntime.jsx(FieldWrapper, {
$actionType: actionType,
background: "primary0",
borderColor: "neutral200",
color: selected === actionType ? 'primary600' : 'neutral600',
position: "relative",
cursor: "pointer",
"data-checked": selected === actionType,
"data-disabled": disabled && selected !== actionType,
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Field.Label, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.VisuallyHidden, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Input, {
type: "radio",
name: name,
checked: selected === actionType,
onChange: handleChange,
value: actionType,
disabled: disabled
})
}),
actionType
]
})
});
};
const ReleaseActionOptions = ({ selected, handleChange, name, disabled = false })=>{
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(ActionOption, {
actionType: "publish",
selected: selected,
handleChange: handleChange,
name: name,
disabled: disabled
}),
/*#__PURE__*/ jsxRuntime.jsx(ActionOption, {
actionType: "unpublish",
selected: selected,
handleChange: handleChange,
name: name,
disabled: disabled
})
]
});
};
exports.ReleaseActionOptions = ReleaseActionOptions;
//# sourceMappingURL=ReleaseActionOptions.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,102 @@
import { jsxs, jsx } from 'react/jsx-runtime';
import 'react';
import { Field, Flex, VisuallyHidden } from '@strapi/design-system';
import { styled } from 'styled-components';
const getBorderLeftRadiusValue = (actionType)=>{
return actionType === 'publish' ? 1 : 0;
};
const getBorderRightRadiusValue = (actionType)=>{
return actionType === 'publish' ? 0 : 1;
};
const FieldWrapper = styled(Field.Root)`
border-top-left-radius: ${({ $actionType, theme })=>theme.spaces[getBorderLeftRadiusValue($actionType)]};
border-bottom-left-radius: ${({ $actionType, theme })=>theme.spaces[getBorderLeftRadiusValue($actionType)]};
border-top-right-radius: ${({ $actionType, theme })=>theme.spaces[getBorderRightRadiusValue($actionType)]};
border-bottom-right-radius: ${({ $actionType, theme })=>theme.spaces[getBorderRightRadiusValue($actionType)]};
> label {
color: inherit;
padding: ${({ theme })=>`${theme.spaces[2]} ${theme.spaces[3]}`};
text-align: center;
vertical-align: middle;
text-transform: capitalize;
}
&[data-checked='true'] {
color: ${({ theme, $actionType })=>$actionType === 'publish' ? theme.colors.primary700 : theme.colors.danger600};
background-color: ${({ theme, $actionType })=>$actionType === 'publish' ? theme.colors.primary100 : theme.colors.danger100};
border-color: ${({ theme, $actionType })=>$actionType === 'publish' ? theme.colors.primary700 : theme.colors.danger600};
}
&[data-checked='false'] {
border-left: ${({ $actionType })=>$actionType === 'unpublish' && 'none'};
border-right: ${({ $actionType })=>$actionType === 'publish' && 'none'};
}
&[data-checked='false'][data-disabled='false']:hover {
color: ${({ theme })=>theme.colors.neutral700};
background-color: ${({ theme })=>theme.colors.neutral100};
border-color: ${({ theme })=>theme.colors.neutral200};
& > label {
cursor: pointer;
}
}
&[data-disabled='true'] {
color: ${({ theme })=>theme.colors.neutral600};
background-color: ${({ theme })=>theme.colors.neutral150};
border-color: ${({ theme })=>theme.colors.neutral300};
}
`;
const ActionOption = ({ selected, actionType, handleChange, name, disabled = false })=>{
return /*#__PURE__*/ jsx(FieldWrapper, {
$actionType: actionType,
background: "primary0",
borderColor: "neutral200",
color: selected === actionType ? 'primary600' : 'neutral600',
position: "relative",
cursor: "pointer",
"data-checked": selected === actionType,
"data-disabled": disabled && selected !== actionType,
children: /*#__PURE__*/ jsxs(Field.Label, {
children: [
/*#__PURE__*/ jsx(VisuallyHidden, {
children: /*#__PURE__*/ jsx(Field.Input, {
type: "radio",
name: name,
checked: selected === actionType,
onChange: handleChange,
value: actionType,
disabled: disabled
})
}),
actionType
]
})
});
};
const ReleaseActionOptions = ({ selected, handleChange, name, disabled = false })=>{
return /*#__PURE__*/ jsxs(Flex, {
children: [
/*#__PURE__*/ jsx(ActionOption, {
actionType: "publish",
selected: selected,
handleChange: handleChange,
name: name,
disabled: disabled
}),
/*#__PURE__*/ jsx(ActionOption, {
actionType: "unpublish",
selected: selected,
handleChange: handleChange,
name: name,
disabled: disabled
})
]
});
};
export { ReleaseActionOptions };
//# sourceMappingURL=ReleaseActionOptions.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,103 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var icons = require('@strapi/icons');
var reactIntl = require('react-intl');
var release = require('../services/release.js');
const useReleasesList = (contentTypeUid, documentId)=>{
const listViewData = strapiAdmin.useTable('ListView', (state)=>state.rows);
const documentIds = listViewData.map((entry)=>entry.documentId);
const [{ query }] = strapiAdmin.useQueryParams();
const locale = query?.plugins?.i18n?.locale || undefined;
const response = release.useGetMappedEntriesInReleasesQuery({
contentTypeUid,
documentIds,
locale
}, {
skip: !documentIds || !contentTypeUid || documentIds.length === 0
});
const mappedEntriesInReleases = response.data || {};
return mappedEntriesInReleases?.[documentId] || [];
};
const addColumnToTableHook = ({ displayedHeaders, layout })=>{
const { options } = layout;
if (!options?.draftAndPublish) {
return {
displayedHeaders,
layout
};
}
return {
displayedHeaders: [
...displayedHeaders,
{
searchable: false,
sortable: false,
name: 'releases',
label: {
id: 'content-releases.content-manager.list-view.releases.header',
defaultMessage: 'To be released in'
},
cellFormatter: (props, _, { model })=>/*#__PURE__*/ jsxRuntime.jsx(ReleaseListCell, {
...props,
model: model
})
}
],
layout
};
};
const ReleaseListCell = ({ documentId, model })=>{
const releases = useReleasesList(model, documentId);
const { formatMessage } = reactIntl.useIntl();
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Popover.Root, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Popover.Trigger, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
variant: "ghost",
onClick: (e)=>e.stopPropagation(),
// TODO: find a way in the DS to define the widht and height of the icon
endIcon: releases.length > 0 ? /*#__PURE__*/ jsxRuntime.jsx(icons.CaretDown, {
width: "1.2rem",
height: "1.2rem"
}) : null,
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
style: {
maxWidth: '252px',
cursor: 'pointer'
},
textColor: "neutral800",
fontWeight: "regular",
children: releases.length > 0 ? formatMessage({
id: 'content-releases.content-manager.list-view.releases-number',
defaultMessage: '{number} {number, plural, one {release} other {releases}}'
}, {
number: releases.length
}) : '-'
})
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Popover.Content, {
children: /*#__PURE__*/ jsxRuntime.jsx("ul", {
children: releases.map(({ id, name })=>/*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
padding: 3,
tag: "li",
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Link, {
href: `/admin/plugins/content-releases/${id}`,
isExternal: false,
children: name
})
}, id))
})
})
]
});
};
exports.ReleaseListCell = ReleaseListCell;
exports.addColumnToTableHook = addColumnToTableHook;
//# sourceMappingURL=ReleaseListCell.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,100 @@
import { jsx, jsxs } from 'react/jsx-runtime';
import 'react';
import { useTable, useQueryParams } from '@strapi/admin/strapi-admin';
import { Popover, Button, Typography, Box, Link } from '@strapi/design-system';
import { CaretDown } from '@strapi/icons';
import { useIntl } from 'react-intl';
import { useGetMappedEntriesInReleasesQuery } from '../services/release.mjs';
const useReleasesList = (contentTypeUid, documentId)=>{
const listViewData = useTable('ListView', (state)=>state.rows);
const documentIds = listViewData.map((entry)=>entry.documentId);
const [{ query }] = useQueryParams();
const locale = query?.plugins?.i18n?.locale || undefined;
const response = useGetMappedEntriesInReleasesQuery({
contentTypeUid,
documentIds,
locale
}, {
skip: !documentIds || !contentTypeUid || documentIds.length === 0
});
const mappedEntriesInReleases = response.data || {};
return mappedEntriesInReleases?.[documentId] || [];
};
const addColumnToTableHook = ({ displayedHeaders, layout })=>{
const { options } = layout;
if (!options?.draftAndPublish) {
return {
displayedHeaders,
layout
};
}
return {
displayedHeaders: [
...displayedHeaders,
{
searchable: false,
sortable: false,
name: 'releases',
label: {
id: 'content-releases.content-manager.list-view.releases.header',
defaultMessage: 'To be released in'
},
cellFormatter: (props, _, { model })=>/*#__PURE__*/ jsx(ReleaseListCell, {
...props,
model: model
})
}
],
layout
};
};
const ReleaseListCell = ({ documentId, model })=>{
const releases = useReleasesList(model, documentId);
const { formatMessage } = useIntl();
return /*#__PURE__*/ jsxs(Popover.Root, {
children: [
/*#__PURE__*/ jsx(Popover.Trigger, {
children: /*#__PURE__*/ jsx(Button, {
variant: "ghost",
onClick: (e)=>e.stopPropagation(),
// TODO: find a way in the DS to define the widht and height of the icon
endIcon: releases.length > 0 ? /*#__PURE__*/ jsx(CaretDown, {
width: "1.2rem",
height: "1.2rem"
}) : null,
children: /*#__PURE__*/ jsx(Typography, {
style: {
maxWidth: '252px',
cursor: 'pointer'
},
textColor: "neutral800",
fontWeight: "regular",
children: releases.length > 0 ? formatMessage({
id: 'content-releases.content-manager.list-view.releases-number',
defaultMessage: '{number} {number, plural, one {release} other {releases}}'
}, {
number: releases.length
}) : '-'
})
})
}),
/*#__PURE__*/ jsx(Popover.Content, {
children: /*#__PURE__*/ jsx("ul", {
children: releases.map(({ id, name })=>/*#__PURE__*/ jsx(Box, {
padding: 3,
tag: "li",
children: /*#__PURE__*/ jsx(Link, {
href: `/admin/plugins/content-releases/${id}`,
isExternal: false,
children: name
})
}, id))
})
})
]
});
};
export { ReleaseListCell, addColumnToTableHook };
//# sourceMappingURL=ReleaseListCell.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,323 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var designSystem = require('@strapi/design-system');
var dateFns = require('date-fns');
var dateFnsTz = require('date-fns-tz');
var formik = require('formik');
var reactIntl = require('react-intl');
var reactRouterDom = require('react-router-dom');
var pluginId = require('../pluginId.js');
var time = require('../utils/time.js');
var schemas = require('../validation/schemas.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
const ReleaseModal = ({ handleClose, open, handleSubmit, initialValues, isLoading = false })=>{
const { formatMessage } = reactIntl.useIntl();
const { pathname } = reactRouterDom.useLocation();
const isCreatingRelease = pathname === `/plugins/${pluginId.pluginId}`;
// Set default first timezone from the list if no system timezone detected
const { timezoneList, systemTimezone = {
value: 'UTC+00:00-Africa/Abidjan '
} } = time.getTimezones(initialValues.scheduledAt ? new Date(initialValues.scheduledAt) : new Date());
/**
* Generate scheduled time using selected date, time and timezone
*/ const getScheduledTimestamp = (values)=>{
const { date, time, timezone } = values;
if (!date || !time || !timezone) return null;
const timezoneWithoutOffset = timezone.split('&')[1];
return dateFnsTz.zonedTimeToUtc(`${date} ${time}`, timezoneWithoutOffset);
};
/**
* Get timezone with offset to show the selected value in the dropdown
*/ const getTimezoneWithOffset = ()=>{
const currentTimezone = timezoneList.find((timezone)=>timezone.value.split('&')[1] === initialValues.timezone);
return currentTimezone?.value || systemTimezone.value;
};
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.Modal.Root, {
open: open,
onOpenChange: handleClose,
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Modal.Content, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Modal.Header, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Modal.Title, {
children: formatMessage({
id: 'content-releases.modal.title',
defaultMessage: '{isCreatingRelease, select, true {New release} other {Edit release}}'
}, {
isCreatingRelease: isCreatingRelease
})
})
}),
/*#__PURE__*/ jsxRuntime.jsx(formik.Formik, {
onSubmit: (values)=>{
handleSubmit({
...values,
timezone: values.timezone ? values.timezone.split('&')[1] : null,
scheduledAt: values.isScheduled ? getScheduledTimestamp(values) : null
});
},
initialValues: {
...initialValues,
timezone: initialValues.timezone ? getTimezoneWithOffset() : systemTimezone.value
},
validationSchema: schemas.RELEASE_SCHEMA,
validateOnChange: false,
children: ({ values, errors, handleChange, setFieldValue })=>{
return /*#__PURE__*/ jsxRuntime.jsxs(formik.Form, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Modal.Body, {
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
alignItems: "stretch",
gap: 6,
children: [
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Field.Root, {
name: "name",
error: errors.name && formatMessage({
id: errors.name,
defaultMessage: errors.name
}),
required: true,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Label, {
children: formatMessage({
id: 'content-releases.modal.form.input.label.release-name',
defaultMessage: 'Name'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.TextInput, {
value: values.name,
onChange: handleChange
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Error, {})
]
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
width: "max-content",
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Checkbox, {
name: "isScheduled",
checked: values.isScheduled,
onCheckedChange: (checked)=>{
setFieldValue('isScheduled', checked);
if (!checked) {
// Clear scheduling info from a release on unchecking schedule release, which reset scheduling info in DB
setFieldValue('date', null);
setFieldValue('time', '');
setFieldValue('timezone', null);
} else {
// On ticking back schedule release date, time and timezone should be restored to the initial state
setFieldValue('date', initialValues.date);
setFieldValue('time', initialValues.time);
setFieldValue('timezone', initialValues.timezone ?? systemTimezone?.value);
}
},
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: values.isScheduled ? 'primary600' : 'neutral800',
fontWeight: values.isScheduled ? 'semiBold' : 'regular',
children: formatMessage({
id: 'modal.form.input.label.schedule-release',
defaultMessage: 'Schedule release'
})
})
})
}),
values.isScheduled && /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
children: [
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 4,
alignItems: "start",
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
width: "100%",
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Field.Root, {
name: "date",
error: errors.date && formatMessage({
id: errors.date,
defaultMessage: errors.date
}),
required: true,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Label, {
children: formatMessage({
id: 'content-releases.modal.form.input.label.date',
defaultMessage: 'Date'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.DatePicker, {
onChange: (date)=>{
const isoFormatDate = date ? dateFns.formatISO(date, {
representation: 'date'
}) : null;
setFieldValue('date', isoFormatDate);
},
clearLabel: formatMessage({
id: 'content-releases.modal.form.input.clearLabel',
defaultMessage: 'Clear'
}),
onClear: ()=>{
setFieldValue('date', null);
},
value: values.date ? new Date(values.date) : new Date(),
minDate: dateFnsTz.utcToZonedTime(new Date(), values.timezone.split('&')[1])
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Error, {})
]
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
width: "100%",
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Field.Root, {
name: "time",
error: errors.time && formatMessage({
id: errors.time,
defaultMessage: errors.time
}),
required: true,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Label, {
children: formatMessage({
id: 'content-releases.modal.form.input.label.time',
defaultMessage: 'Time'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.TimePicker, {
onChange: (time)=>{
setFieldValue('time', time);
},
clearLabel: formatMessage({
id: 'content-releases.modal.form.input.clearLabel',
defaultMessage: 'Clear'
}),
onClear: ()=>{
setFieldValue('time', '');
},
value: values.time || undefined
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Error, {})
]
})
})
]
}),
/*#__PURE__*/ jsxRuntime.jsx(TimezoneComponent, {
timezoneOptions: timezoneList
})
]
})
]
})
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Modal.Footer, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Modal.Close, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
variant: "tertiary",
name: "cancel",
children: formatMessage({
id: 'cancel',
defaultMessage: 'Cancel'
})
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
name: "submit",
loading: isLoading,
type: "submit",
children: formatMessage({
id: 'content-releases.modal.form.button.submit',
defaultMessage: '{isCreatingRelease, select, true {Continue} other {Save}}'
}, {
isCreatingRelease: isCreatingRelease
})
})
]
})
]
});
}
})
]
})
});
};
const TimezoneComponent = ({ timezoneOptions })=>{
const { values, errors, setFieldValue } = formik.useFormikContext();
const { formatMessage } = reactIntl.useIntl();
const [timezoneList, setTimezoneList] = React__namespace.useState(timezoneOptions);
React__namespace.useEffect(()=>{
if (values.date) {
// Update the timezone offset which varies with DST based on the date selected
const { timezoneList } = time.getTimezones(new Date(values.date));
setTimezoneList(timezoneList);
const updatedTimezone = values.timezone && timezoneList.find((tz)=>tz.value.split('&')[1] === values.timezone.split('&')[1]);
if (updatedTimezone) {
setFieldValue('timezone', updatedTimezone.value);
}
}
}, [
setFieldValue,
values.date,
values.timezone
]);
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Field.Root, {
name: "timezone",
error: errors.timezone && formatMessage({
id: errors.timezone,
defaultMessage: errors.timezone
}),
required: true,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Label, {
children: formatMessage({
id: 'content-releases.modal.form.input.label.timezone',
defaultMessage: 'Timezone'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Combobox, {
autocomplete: {
type: 'list',
filter: 'contains'
},
value: values.timezone || undefined,
textValue: values.timezone ? values.timezone.replace(/&/, ' ') : undefined,
onChange: (timezone)=>{
setFieldValue('timezone', timezone);
},
onTextValueChange: (timezone)=>{
setFieldValue('timezone', timezone);
},
onClear: ()=>{
setFieldValue('timezone', '');
},
children: timezoneList.map((timezone)=>/*#__PURE__*/ jsxRuntime.jsx(designSystem.ComboboxOption, {
value: timezone.value,
children: timezone.value.replace(/&/, ' ')
}, timezone.value))
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Error, {})
]
});
};
exports.ReleaseModal = ReleaseModal;
//# sourceMappingURL=ReleaseModal.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,302 @@
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import * as React from 'react';
import { Modal, Flex, Field, TextInput, Box, Checkbox, Typography, DatePicker, TimePicker, Button, Combobox, ComboboxOption } from '@strapi/design-system';
import { formatISO } from 'date-fns';
import { utcToZonedTime, zonedTimeToUtc } from 'date-fns-tz';
import { Formik, Form, useFormikContext } from 'formik';
import { useIntl } from 'react-intl';
import { useLocation } from 'react-router-dom';
import { pluginId } from '../pluginId.mjs';
import { getTimezones } from '../utils/time.mjs';
import { RELEASE_SCHEMA } from '../validation/schemas.mjs';
const ReleaseModal = ({ handleClose, open, handleSubmit, initialValues, isLoading = false })=>{
const { formatMessage } = useIntl();
const { pathname } = useLocation();
const isCreatingRelease = pathname === `/plugins/${pluginId}`;
// Set default first timezone from the list if no system timezone detected
const { timezoneList, systemTimezone = {
value: 'UTC+00:00-Africa/Abidjan '
} } = getTimezones(initialValues.scheduledAt ? new Date(initialValues.scheduledAt) : new Date());
/**
* Generate scheduled time using selected date, time and timezone
*/ const getScheduledTimestamp = (values)=>{
const { date, time, timezone } = values;
if (!date || !time || !timezone) return null;
const timezoneWithoutOffset = timezone.split('&')[1];
return zonedTimeToUtc(`${date} ${time}`, timezoneWithoutOffset);
};
/**
* Get timezone with offset to show the selected value in the dropdown
*/ const getTimezoneWithOffset = ()=>{
const currentTimezone = timezoneList.find((timezone)=>timezone.value.split('&')[1] === initialValues.timezone);
return currentTimezone?.value || systemTimezone.value;
};
return /*#__PURE__*/ jsx(Modal.Root, {
open: open,
onOpenChange: handleClose,
children: /*#__PURE__*/ jsxs(Modal.Content, {
children: [
/*#__PURE__*/ jsx(Modal.Header, {
children: /*#__PURE__*/ jsx(Modal.Title, {
children: formatMessage({
id: 'content-releases.modal.title',
defaultMessage: '{isCreatingRelease, select, true {New release} other {Edit release}}'
}, {
isCreatingRelease: isCreatingRelease
})
})
}),
/*#__PURE__*/ jsx(Formik, {
onSubmit: (values)=>{
handleSubmit({
...values,
timezone: values.timezone ? values.timezone.split('&')[1] : null,
scheduledAt: values.isScheduled ? getScheduledTimestamp(values) : null
});
},
initialValues: {
...initialValues,
timezone: initialValues.timezone ? getTimezoneWithOffset() : systemTimezone.value
},
validationSchema: RELEASE_SCHEMA,
validateOnChange: false,
children: ({ values, errors, handleChange, setFieldValue })=>{
return /*#__PURE__*/ jsxs(Form, {
children: [
/*#__PURE__*/ jsx(Modal.Body, {
children: /*#__PURE__*/ jsxs(Flex, {
direction: "column",
alignItems: "stretch",
gap: 6,
children: [
/*#__PURE__*/ jsxs(Field.Root, {
name: "name",
error: errors.name && formatMessage({
id: errors.name,
defaultMessage: errors.name
}),
required: true,
children: [
/*#__PURE__*/ jsx(Field.Label, {
children: formatMessage({
id: 'content-releases.modal.form.input.label.release-name',
defaultMessage: 'Name'
})
}),
/*#__PURE__*/ jsx(TextInput, {
value: values.name,
onChange: handleChange
}),
/*#__PURE__*/ jsx(Field.Error, {})
]
}),
/*#__PURE__*/ jsx(Box, {
width: "max-content",
children: /*#__PURE__*/ jsx(Checkbox, {
name: "isScheduled",
checked: values.isScheduled,
onCheckedChange: (checked)=>{
setFieldValue('isScheduled', checked);
if (!checked) {
// Clear scheduling info from a release on unchecking schedule release, which reset scheduling info in DB
setFieldValue('date', null);
setFieldValue('time', '');
setFieldValue('timezone', null);
} else {
// On ticking back schedule release date, time and timezone should be restored to the initial state
setFieldValue('date', initialValues.date);
setFieldValue('time', initialValues.time);
setFieldValue('timezone', initialValues.timezone ?? systemTimezone?.value);
}
},
children: /*#__PURE__*/ jsx(Typography, {
textColor: values.isScheduled ? 'primary600' : 'neutral800',
fontWeight: values.isScheduled ? 'semiBold' : 'regular',
children: formatMessage({
id: 'modal.form.input.label.schedule-release',
defaultMessage: 'Schedule release'
})
})
})
}),
values.isScheduled && /*#__PURE__*/ jsxs(Fragment, {
children: [
/*#__PURE__*/ jsxs(Flex, {
gap: 4,
alignItems: "start",
children: [
/*#__PURE__*/ jsx(Box, {
width: "100%",
children: /*#__PURE__*/ jsxs(Field.Root, {
name: "date",
error: errors.date && formatMessage({
id: errors.date,
defaultMessage: errors.date
}),
required: true,
children: [
/*#__PURE__*/ jsx(Field.Label, {
children: formatMessage({
id: 'content-releases.modal.form.input.label.date',
defaultMessage: 'Date'
})
}),
/*#__PURE__*/ jsx(DatePicker, {
onChange: (date)=>{
const isoFormatDate = date ? formatISO(date, {
representation: 'date'
}) : null;
setFieldValue('date', isoFormatDate);
},
clearLabel: formatMessage({
id: 'content-releases.modal.form.input.clearLabel',
defaultMessage: 'Clear'
}),
onClear: ()=>{
setFieldValue('date', null);
},
value: values.date ? new Date(values.date) : new Date(),
minDate: utcToZonedTime(new Date(), values.timezone.split('&')[1])
}),
/*#__PURE__*/ jsx(Field.Error, {})
]
})
}),
/*#__PURE__*/ jsx(Box, {
width: "100%",
children: /*#__PURE__*/ jsxs(Field.Root, {
name: "time",
error: errors.time && formatMessage({
id: errors.time,
defaultMessage: errors.time
}),
required: true,
children: [
/*#__PURE__*/ jsx(Field.Label, {
children: formatMessage({
id: 'content-releases.modal.form.input.label.time',
defaultMessage: 'Time'
})
}),
/*#__PURE__*/ jsx(TimePicker, {
onChange: (time)=>{
setFieldValue('time', time);
},
clearLabel: formatMessage({
id: 'content-releases.modal.form.input.clearLabel',
defaultMessage: 'Clear'
}),
onClear: ()=>{
setFieldValue('time', '');
},
value: values.time || undefined
}),
/*#__PURE__*/ jsx(Field.Error, {})
]
})
})
]
}),
/*#__PURE__*/ jsx(TimezoneComponent, {
timezoneOptions: timezoneList
})
]
})
]
})
}),
/*#__PURE__*/ jsxs(Modal.Footer, {
children: [
/*#__PURE__*/ jsx(Modal.Close, {
children: /*#__PURE__*/ jsx(Button, {
variant: "tertiary",
name: "cancel",
children: formatMessage({
id: 'cancel',
defaultMessage: 'Cancel'
})
})
}),
/*#__PURE__*/ jsx(Button, {
name: "submit",
loading: isLoading,
type: "submit",
children: formatMessage({
id: 'content-releases.modal.form.button.submit',
defaultMessage: '{isCreatingRelease, select, true {Continue} other {Save}}'
}, {
isCreatingRelease: isCreatingRelease
})
})
]
})
]
});
}
})
]
})
});
};
const TimezoneComponent = ({ timezoneOptions })=>{
const { values, errors, setFieldValue } = useFormikContext();
const { formatMessage } = useIntl();
const [timezoneList, setTimezoneList] = React.useState(timezoneOptions);
React.useEffect(()=>{
if (values.date) {
// Update the timezone offset which varies with DST based on the date selected
const { timezoneList } = getTimezones(new Date(values.date));
setTimezoneList(timezoneList);
const updatedTimezone = values.timezone && timezoneList.find((tz)=>tz.value.split('&')[1] === values.timezone.split('&')[1]);
if (updatedTimezone) {
setFieldValue('timezone', updatedTimezone.value);
}
}
}, [
setFieldValue,
values.date,
values.timezone
]);
return /*#__PURE__*/ jsxs(Field.Root, {
name: "timezone",
error: errors.timezone && formatMessage({
id: errors.timezone,
defaultMessage: errors.timezone
}),
required: true,
children: [
/*#__PURE__*/ jsx(Field.Label, {
children: formatMessage({
id: 'content-releases.modal.form.input.label.timezone',
defaultMessage: 'Timezone'
})
}),
/*#__PURE__*/ jsx(Combobox, {
autocomplete: {
type: 'list',
filter: 'contains'
},
value: values.timezone || undefined,
textValue: values.timezone ? values.timezone.replace(/&/, ' ') : undefined,
onChange: (timezone)=>{
setFieldValue('timezone', timezone);
},
onTextValueChange: (timezone)=>{
setFieldValue('timezone', timezone);
},
onClear: ()=>{
setFieldValue('timezone', '');
},
children: timezoneList.map((timezone)=>/*#__PURE__*/ jsx(ComboboxOption, {
value: timezone.value,
children: timezone.value.replace(/&/, ' ')
}, timezone.value))
}),
/*#__PURE__*/ jsx(Field.Error, {})
]
});
};
export { ReleaseModal };
//# sourceMappingURL=ReleaseModal.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,138 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var strapiAdmin$1 = require('@strapi/content-manager/strapi-admin');
var designSystem = require('@strapi/design-system');
var reactIntl = require('react-intl');
var constants = require('../constants.js');
var release = require('../services/release.js');
var time = require('../utils/time.js');
var ReleaseActionMenu = require('./ReleaseActionMenu.js');
const Panel = ({ model, document, documentId, collectionType })=>{
const [{ query }] = strapiAdmin.useQueryParams();
const locale = query.plugins?.i18n?.locale;
const { edit: { options } } = strapiAdmin$1.unstable_useDocumentLayout(model);
const { formatMessage, formatDate, formatTime } = reactIntl.useIntl();
const { allowedActions } = strapiAdmin.useRBAC(constants.PERMISSIONS);
const { canRead, canDeleteAction } = allowedActions;
const response = release.useGetReleasesForEntryQuery({
contentType: model,
entryDocumentId: documentId,
locale,
hasEntryAttached: true
}, {
skip: !document
});
const releases = response.data?.data;
const getReleaseColorVariant = (actionType, shade)=>{
if (actionType === 'unpublish') {
return `secondary${shade}`;
}
return `success${shade}`;
};
// Project is not EE or contentType does not have draftAndPublish enabled
if (!window.strapi.isEE || !options?.draftAndPublish || !canRead) {
return null;
}
if (collectionType === 'collection-types' && (!documentId || documentId === 'create')) {
return null;
}
if (!releases || releases.length === 0) {
return null;
}
return {
title: formatMessage({
id: 'content-releases.plugin.name',
defaultMessage: 'Releases'
}),
content: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Flex, {
direction: "column",
alignItems: "stretch",
gap: 3,
width: "100%",
children: releases?.map((release)=>/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
alignItems: "start",
borderWidth: "1px",
borderStyle: "solid",
borderColor: getReleaseColorVariant(release.actions[0].type, '200'),
overflow: "hidden",
hasRadius: true,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
paddingTop: 3,
paddingBottom: 3,
paddingLeft: 4,
paddingRight: 4,
background: getReleaseColorVariant(release.actions[0].type, '100'),
width: "100%",
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
fontSize: 1,
variant: "pi",
textColor: getReleaseColorVariant(release.actions[0].type, '600'),
children: formatMessage({
id: 'content-releases.content-manager-edit-view.list-releases.title',
defaultMessage: '{isPublish, select, true {Will be published in} other {Will be unpublished in}}'
}, {
isPublish: release.actions[0].type === 'publish'
})
})
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
padding: 4,
direction: "column",
gap: 2,
width: "100%",
alignItems: "flex-start",
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
fontSize: 2,
fontWeight: "bold",
variant: "omega",
textColor: "neutral700",
children: release.name
}),
release.scheduledAt && release.timezone && /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
variant: "pi",
textColor: "neutral600",
children: formatMessage({
id: 'content-releases.content-manager-edit-view.scheduled.date',
defaultMessage: '{date} at {time} ({offset})'
}, {
date: formatDate(new Date(release.scheduledAt), {
day: '2-digit',
month: '2-digit',
year: 'numeric',
timeZone: release.timezone
}),
time: formatTime(new Date(release.scheduledAt), {
hourCycle: 'h23',
timeZone: release.timezone
}),
offset: time.getTimezoneOffset(release.timezone, new Date(release.scheduledAt))
})
}),
canDeleteAction ? /*#__PURE__*/ jsxRuntime.jsxs(ReleaseActionMenu.ReleaseActionMenu.Root, {
hasTriggerBorder: true,
children: [
/*#__PURE__*/ jsxRuntime.jsx(ReleaseActionMenu.ReleaseActionMenu.EditReleaseItem, {
releaseId: release.id
}),
/*#__PURE__*/ jsxRuntime.jsx(ReleaseActionMenu.ReleaseActionMenu.DeleteReleaseActionItem, {
releaseId: release.id,
actionId: release.actions[0].id
})
]
}) : null
]
})
]
}, release.id))
})
};
};
exports.Panel = Panel;
//# sourceMappingURL=ReleasesPanel.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,136 @@
import { jsx, jsxs } from 'react/jsx-runtime';
import { useQueryParams, useRBAC } from '@strapi/admin/strapi-admin';
import { unstable_useDocumentLayout } from '@strapi/content-manager/strapi-admin';
import { Flex, Box, Typography } from '@strapi/design-system';
import { useIntl } from 'react-intl';
import { PERMISSIONS } from '../constants.mjs';
import { useGetReleasesForEntryQuery } from '../services/release.mjs';
import { getTimezoneOffset } from '../utils/time.mjs';
import { ReleaseActionMenu } from './ReleaseActionMenu.mjs';
const Panel = ({ model, document, documentId, collectionType })=>{
const [{ query }] = useQueryParams();
const locale = query.plugins?.i18n?.locale;
const { edit: { options } } = unstable_useDocumentLayout(model);
const { formatMessage, formatDate, formatTime } = useIntl();
const { allowedActions } = useRBAC(PERMISSIONS);
const { canRead, canDeleteAction } = allowedActions;
const response = useGetReleasesForEntryQuery({
contentType: model,
entryDocumentId: documentId,
locale,
hasEntryAttached: true
}, {
skip: !document
});
const releases = response.data?.data;
const getReleaseColorVariant = (actionType, shade)=>{
if (actionType === 'unpublish') {
return `secondary${shade}`;
}
return `success${shade}`;
};
// Project is not EE or contentType does not have draftAndPublish enabled
if (!window.strapi.isEE || !options?.draftAndPublish || !canRead) {
return null;
}
if (collectionType === 'collection-types' && (!documentId || documentId === 'create')) {
return null;
}
if (!releases || releases.length === 0) {
return null;
}
return {
title: formatMessage({
id: 'content-releases.plugin.name',
defaultMessage: 'Releases'
}),
content: /*#__PURE__*/ jsx(Flex, {
direction: "column",
alignItems: "stretch",
gap: 3,
width: "100%",
children: releases?.map((release)=>/*#__PURE__*/ jsxs(Flex, {
direction: "column",
alignItems: "start",
borderWidth: "1px",
borderStyle: "solid",
borderColor: getReleaseColorVariant(release.actions[0].type, '200'),
overflow: "hidden",
hasRadius: true,
children: [
/*#__PURE__*/ jsx(Box, {
paddingTop: 3,
paddingBottom: 3,
paddingLeft: 4,
paddingRight: 4,
background: getReleaseColorVariant(release.actions[0].type, '100'),
width: "100%",
children: /*#__PURE__*/ jsx(Typography, {
fontSize: 1,
variant: "pi",
textColor: getReleaseColorVariant(release.actions[0].type, '600'),
children: formatMessage({
id: 'content-releases.content-manager-edit-view.list-releases.title',
defaultMessage: '{isPublish, select, true {Will be published in} other {Will be unpublished in}}'
}, {
isPublish: release.actions[0].type === 'publish'
})
})
}),
/*#__PURE__*/ jsxs(Flex, {
padding: 4,
direction: "column",
gap: 2,
width: "100%",
alignItems: "flex-start",
children: [
/*#__PURE__*/ jsx(Typography, {
fontSize: 2,
fontWeight: "bold",
variant: "omega",
textColor: "neutral700",
children: release.name
}),
release.scheduledAt && release.timezone && /*#__PURE__*/ jsx(Typography, {
variant: "pi",
textColor: "neutral600",
children: formatMessage({
id: 'content-releases.content-manager-edit-view.scheduled.date',
defaultMessage: '{date} at {time} ({offset})'
}, {
date: formatDate(new Date(release.scheduledAt), {
day: '2-digit',
month: '2-digit',
year: 'numeric',
timeZone: release.timezone
}),
time: formatTime(new Date(release.scheduledAt), {
hourCycle: 'h23',
timeZone: release.timezone
}),
offset: getTimezoneOffset(release.timezone, new Date(release.scheduledAt))
})
}),
canDeleteAction ? /*#__PURE__*/ jsxs(ReleaseActionMenu.Root, {
hasTriggerBorder: true,
children: [
/*#__PURE__*/ jsx(ReleaseActionMenu.EditReleaseItem, {
releaseId: release.id
}),
/*#__PURE__*/ jsx(ReleaseActionMenu.DeleteReleaseActionItem, {
releaseId: release.id,
actionId: release.actions[0].id
})
]
}) : null
]
})
]
}, release.id))
})
};
};
export { Panel };
//# sourceMappingURL=ReleasesPanel.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,77 @@
'use strict';
const PERMISSIONS = {
main: [
{
action: 'plugin::content-releases.read',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
],
create: [
{
action: 'plugin::content-releases.create',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
],
update: [
{
action: 'plugin::content-releases.update',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
],
delete: [
{
action: 'plugin::content-releases.delete',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
],
createAction: [
{
action: 'plugin::content-releases.create-action',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
],
deleteAction: [
{
action: 'plugin::content-releases.delete-action',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
],
publish: [
{
action: 'plugin::content-releases.publish',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
]
};
exports.PERMISSIONS = PERMISSIONS;
//# sourceMappingURL=constants.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"constants.js","sources":["../../admin/src/constants.ts"],"sourcesContent":["import type { Permission as StrapiPermission } from '@strapi/admin/strapi-admin';\n\nexport const PERMISSIONS = {\n main: [\n {\n action: 'plugin::content-releases.read',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n create: [\n {\n action: 'plugin::content-releases.create',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n update: [\n {\n action: 'plugin::content-releases.update',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n delete: [\n {\n action: 'plugin::content-releases.delete',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n createAction: [\n {\n action: 'plugin::content-releases.create-action',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n deleteAction: [\n {\n action: 'plugin::content-releases.delete-action',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n publish: [\n {\n action: 'plugin::content-releases.publish',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n} satisfies Record<string, StrapiPermission[]>;\n\nexport const PERMISSIONS_SETTINGS = {\n read: [\n {\n action: 'plugin::content-releases.settings.read',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n update: [\n {\n action: 'plugin::content-releases.settings.update',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n} satisfies Record<string, StrapiPermission[]>;\n"],"names":["PERMISSIONS","main","action","subject","id","actionParameters","properties","conditions","create","update","delete","createAction","deleteAction","publish"],"mappings":";;MAEaA,WAAc,GAAA;IACzBC,IAAM,EAAA;AACJ,QAAA;YACEC,MAAQ,EAAA,+BAAA;YACRC,OAAS,EAAA,IAAA;YACTC,EAAI,EAAA,EAAA;AACJC,YAAAA,gBAAAA,EAAkB,EAAC;AACnBC,YAAAA,UAAAA,EAAY,EAAC;AACbC,YAAAA,UAAAA,EAAY;AACd;AACD,KAAA;IACDC,MAAQ,EAAA;AACN,QAAA;YACEN,MAAQ,EAAA,iCAAA;YACRC,OAAS,EAAA,IAAA;YACTC,EAAI,EAAA,EAAA;AACJC,YAAAA,gBAAAA,EAAkB,EAAC;AACnBC,YAAAA,UAAAA,EAAY,EAAC;AACbC,YAAAA,UAAAA,EAAY;AACd;AACD,KAAA;IACDE,MAAQ,EAAA;AACN,QAAA;YACEP,MAAQ,EAAA,iCAAA;YACRC,OAAS,EAAA,IAAA;YACTC,EAAI,EAAA,EAAA;AACJC,YAAAA,gBAAAA,EAAkB,EAAC;AACnBC,YAAAA,UAAAA,EAAY,EAAC;AACbC,YAAAA,UAAAA,EAAY;AACd;AACD,KAAA;IACDG,MAAQ,EAAA;AACN,QAAA;YACER,MAAQ,EAAA,iCAAA;YACRC,OAAS,EAAA,IAAA;YACTC,EAAI,EAAA,EAAA;AACJC,YAAAA,gBAAAA,EAAkB,EAAC;AACnBC,YAAAA,UAAAA,EAAY,EAAC;AACbC,YAAAA,UAAAA,EAAY;AACd;AACD,KAAA;IACDI,YAAc,EAAA;AACZ,QAAA;YACET,MAAQ,EAAA,wCAAA;YACRC,OAAS,EAAA,IAAA;YACTC,EAAI,EAAA,EAAA;AACJC,YAAAA,gBAAAA,EAAkB,EAAC;AACnBC,YAAAA,UAAAA,EAAY,EAAC;AACbC,YAAAA,UAAAA,EAAY;AACd;AACD,KAAA;IACDK,YAAc,EAAA;AACZ,QAAA;YACEV,MAAQ,EAAA,wCAAA;YACRC,OAAS,EAAA,IAAA;YACTC,EAAI,EAAA,EAAA;AACJC,YAAAA,gBAAAA,EAAkB,EAAC;AACnBC,YAAAA,UAAAA,EAAY,EAAC;AACbC,YAAAA,UAAAA,EAAY;AACd;AACD,KAAA;IACDM,OAAS,EAAA;AACP,QAAA;YACEX,MAAQ,EAAA,kCAAA;YACRC,OAAS,EAAA,IAAA;YACTC,EAAI,EAAA,EAAA;AACJC,YAAAA,gBAAAA,EAAkB,EAAC;AACnBC,YAAAA,UAAAA,EAAY,EAAC;AACbC,YAAAA,UAAAA,EAAY;AACd;AACD;AACH;;;;"}

View File

@@ -0,0 +1,75 @@
const PERMISSIONS = {
main: [
{
action: 'plugin::content-releases.read',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
],
create: [
{
action: 'plugin::content-releases.create',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
],
update: [
{
action: 'plugin::content-releases.update',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
],
delete: [
{
action: 'plugin::content-releases.delete',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
],
createAction: [
{
action: 'plugin::content-releases.create-action',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
],
deleteAction: [
{
action: 'plugin::content-releases.delete-action',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
],
publish: [
{
action: 'plugin::content-releases.publish',
subject: null,
id: '',
actionParameters: {},
properties: {},
conditions: []
}
]
};
export { PERMISSIONS };
//# sourceMappingURL=constants.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"constants.mjs","sources":["../../admin/src/constants.ts"],"sourcesContent":["import type { Permission as StrapiPermission } from '@strapi/admin/strapi-admin';\n\nexport const PERMISSIONS = {\n main: [\n {\n action: 'plugin::content-releases.read',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n create: [\n {\n action: 'plugin::content-releases.create',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n update: [\n {\n action: 'plugin::content-releases.update',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n delete: [\n {\n action: 'plugin::content-releases.delete',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n createAction: [\n {\n action: 'plugin::content-releases.create-action',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n deleteAction: [\n {\n action: 'plugin::content-releases.delete-action',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n publish: [\n {\n action: 'plugin::content-releases.publish',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n} satisfies Record<string, StrapiPermission[]>;\n\nexport const PERMISSIONS_SETTINGS = {\n read: [\n {\n action: 'plugin::content-releases.settings.read',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n update: [\n {\n action: 'plugin::content-releases.settings.update',\n subject: null,\n id: '',\n actionParameters: {},\n properties: {},\n conditions: [],\n },\n ],\n} satisfies Record<string, StrapiPermission[]>;\n"],"names":["PERMISSIONS","main","action","subject","id","actionParameters","properties","conditions","create","update","delete","createAction","deleteAction","publish"],"mappings":"MAEaA,WAAc,GAAA;IACzBC,IAAM,EAAA;AACJ,QAAA;YACEC,MAAQ,EAAA,+BAAA;YACRC,OAAS,EAAA,IAAA;YACTC,EAAI,EAAA,EAAA;AACJC,YAAAA,gBAAAA,EAAkB,EAAC;AACnBC,YAAAA,UAAAA,EAAY,EAAC;AACbC,YAAAA,UAAAA,EAAY;AACd;AACD,KAAA;IACDC,MAAQ,EAAA;AACN,QAAA;YACEN,MAAQ,EAAA,iCAAA;YACRC,OAAS,EAAA,IAAA;YACTC,EAAI,EAAA,EAAA;AACJC,YAAAA,gBAAAA,EAAkB,EAAC;AACnBC,YAAAA,UAAAA,EAAY,EAAC;AACbC,YAAAA,UAAAA,EAAY;AACd;AACD,KAAA;IACDE,MAAQ,EAAA;AACN,QAAA;YACEP,MAAQ,EAAA,iCAAA;YACRC,OAAS,EAAA,IAAA;YACTC,EAAI,EAAA,EAAA;AACJC,YAAAA,gBAAAA,EAAkB,EAAC;AACnBC,YAAAA,UAAAA,EAAY,EAAC;AACbC,YAAAA,UAAAA,EAAY;AACd;AACD,KAAA;IACDG,MAAQ,EAAA;AACN,QAAA;YACER,MAAQ,EAAA,iCAAA;YACRC,OAAS,EAAA,IAAA;YACTC,EAAI,EAAA,EAAA;AACJC,YAAAA,gBAAAA,EAAkB,EAAC;AACnBC,YAAAA,UAAAA,EAAY,EAAC;AACbC,YAAAA,UAAAA,EAAY;AACd;AACD,KAAA;IACDI,YAAc,EAAA;AACZ,QAAA;YACET,MAAQ,EAAA,wCAAA;YACRC,OAAS,EAAA,IAAA;YACTC,EAAI,EAAA,EAAA;AACJC,YAAAA,gBAAAA,EAAkB,EAAC;AACnBC,YAAAA,UAAAA,EAAY,EAAC;AACbC,YAAAA,UAAAA,EAAY;AACd;AACD,KAAA;IACDK,YAAc,EAAA;AACZ,QAAA;YACEV,MAAQ,EAAA,wCAAA;YACRC,OAAS,EAAA,IAAA;YACTC,EAAI,EAAA,EAAA;AACJC,YAAAA,gBAAAA,EAAkB,EAAC;AACnBC,YAAAA,UAAAA,EAAY,EAAC;AACbC,YAAAA,UAAAA,EAAY;AACd;AACD,KAAA;IACDM,OAAS,EAAA;AACP,QAAA;YACEX,MAAQ,EAAA,kCAAA;YACRC,OAAS,EAAA,IAAA;YACTC,EAAI,EAAA,EAAA;AACJC,YAAAA,gBAAAA,EAAkB,EAAC;AACnBC,YAAAA,UAAAA,EAAY,EAAC;AACbC,YAAAA,UAAAA,EAAY;AACd;AACD;AACH;;;;"}

View File

@@ -0,0 +1,126 @@
'use strict';
var icons = require('@strapi/icons');
var ReleaseAction = require('./components/ReleaseAction.js');
var ReleaseActionModal = require('./components/ReleaseActionModal.js');
var ReleaseListCell = require('./components/ReleaseListCell.js');
var ReleasesPanel = require('./components/ReleasesPanel.js');
var constants = require('./constants.js');
var pluginId = require('./pluginId.js');
var prefixPluginTranslations = require('./utils/prefixPluginTranslations.js');
function _interopNamespaceDefaultOnly (e) { return Object.freeze({ __proto__: null, default: e }); }
function __variableDynamicImportRuntime3__(path) {
switch (path) {
case './translations/en.json': return Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespaceDefaultOnly(require('./translations/en.json.js')); });
case './translations/uk.json': return Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespaceDefaultOnly(require('./translations/uk.json.js')); });
default: return new Promise(function(resolve, reject) {
(typeof queueMicrotask === 'function' ? queueMicrotask : setTimeout)(
reject.bind(null, new Error("Unknown variable dynamic import: " + path))
);
})
}
}
// eslint-disable-next-line import/no-default-export
const admin = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
register (app) {
/**
* Hook that adds the locale column in the Release Details table
* @constant
* @type {string}
*/ app.createHook('ContentReleases/pages/ReleaseDetails/add-locale-in-releases');
if (window.strapi.features.isEnabled('cms-content-releases')) {
app.addMenuLink({
to: `plugins/${pluginId.pluginId}`,
icon: icons.PaperPlane,
intlLabel: {
id: `${pluginId.pluginId}.plugin.name`,
defaultMessage: 'Releases'
},
Component: ()=>Promise.resolve().then(function () { return require('./pages/App.js'); }).then((mod)=>({
default: mod.App
})),
permissions: constants.PERMISSIONS.main,
position: 2
});
// Insert the releases container into the CM's sidebar on the Edit View
const contentManagerPluginApis = app.getPlugin('content-manager').apis;
if ('addEditViewSidePanel' in contentManagerPluginApis && typeof contentManagerPluginApis.addEditViewSidePanel === 'function') {
contentManagerPluginApis.addEditViewSidePanel([
ReleasesPanel.Panel
]);
}
// Insert the "add to release" action into the CM's Edit View
if ('addDocumentAction' in contentManagerPluginApis && typeof contentManagerPluginApis.addDocumentAction === 'function') {
contentManagerPluginApis.addDocumentAction((actions)=>{
const indexOfDeleteAction = actions.findIndex((action)=>action.type === 'unpublish');
actions.splice(indexOfDeleteAction, 0, ReleaseActionModal.ReleaseActionModalForm);
return actions;
});
}
app.addSettingsLink('global', {
id: pluginId.pluginId,
to: 'releases',
intlLabel: {
id: `${pluginId.pluginId}.plugin.name`,
defaultMessage: 'Releases'
},
permissions: [],
async Component () {
const { ProtectedReleasesSettingsPage } = await Promise.resolve().then(function () { return require('./pages/ReleasesSettingsPage.js'); });
return {
default: ProtectedReleasesSettingsPage
};
}
});
if ('addBulkAction' in contentManagerPluginApis && typeof contentManagerPluginApis.addBulkAction === 'function') {
contentManagerPluginApis.addBulkAction((actions)=>{
// We want to add this action to just before the delete action all the time
const deleteActionIndex = actions.findIndex((action)=>action.type === 'delete');
actions.splice(deleteActionIndex, 0, ReleaseAction.ReleaseAction);
return actions;
});
}
// Hook that adds a column into the CM's LV table
app.registerHook('Admin/CM/pages/ListView/inject-column-in-table', ReleaseListCell.addColumnToTableHook);
} else if (!window.strapi.features.isEnabled('cms-content-releases') && window.strapi?.flags?.promoteEE) {
app.addSettingsLink('global', {
id: pluginId.pluginId,
to: '/plugins/purchase-content-releases',
intlLabel: {
id: `${pluginId.pluginId}.plugin.name`,
defaultMessage: 'Releases'
},
permissions: [],
async Component () {
const { PurchaseContentReleases } = await Promise.resolve().then(function () { return require('./pages/PurchaseContentReleases.js'); });
return {
default: PurchaseContentReleases
};
},
licenseOnly: true
});
}
},
async registerTrads ({ locales }) {
const importedTrads = await Promise.all(locales.map((locale)=>{
return __variableDynamicImportRuntime3__(`./translations/${locale}.json`).then(({ default: data })=>{
return {
data: prefixPluginTranslations.prefixPluginTranslations(data, 'content-releases'),
locale
};
}).catch(()=>{
return {
data: {},
locale
};
});
}));
return Promise.resolve(importedTrads);
}
};
module.exports = admin;
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,122 @@
import { PaperPlane } from '@strapi/icons';
import { ReleaseAction } from './components/ReleaseAction.mjs';
import { ReleaseActionModalForm } from './components/ReleaseActionModal.mjs';
import { addColumnToTableHook } from './components/ReleaseListCell.mjs';
import { Panel } from './components/ReleasesPanel.mjs';
import { PERMISSIONS } from './constants.mjs';
import { pluginId } from './pluginId.mjs';
import { prefixPluginTranslations } from './utils/prefixPluginTranslations.mjs';
function __variableDynamicImportRuntime3__(path) {
switch (path) {
case './translations/en.json': return import('./translations/en.json.mjs');
case './translations/uk.json': return import('./translations/uk.json.mjs');
default: return new Promise(function(resolve, reject) {
(typeof queueMicrotask === 'function' ? queueMicrotask : setTimeout)(
reject.bind(null, new Error("Unknown variable dynamic import: " + path))
);
})
}
}
// eslint-disable-next-line import/no-default-export
const admin = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
register (app) {
/**
* Hook that adds the locale column in the Release Details table
* @constant
* @type {string}
*/ app.createHook('ContentReleases/pages/ReleaseDetails/add-locale-in-releases');
if (window.strapi.features.isEnabled('cms-content-releases')) {
app.addMenuLink({
to: `plugins/${pluginId}`,
icon: PaperPlane,
intlLabel: {
id: `${pluginId}.plugin.name`,
defaultMessage: 'Releases'
},
Component: ()=>import('./pages/App.mjs').then((mod)=>({
default: mod.App
})),
permissions: PERMISSIONS.main,
position: 2
});
// Insert the releases container into the CM's sidebar on the Edit View
const contentManagerPluginApis = app.getPlugin('content-manager').apis;
if ('addEditViewSidePanel' in contentManagerPluginApis && typeof contentManagerPluginApis.addEditViewSidePanel === 'function') {
contentManagerPluginApis.addEditViewSidePanel([
Panel
]);
}
// Insert the "add to release" action into the CM's Edit View
if ('addDocumentAction' in contentManagerPluginApis && typeof contentManagerPluginApis.addDocumentAction === 'function') {
contentManagerPluginApis.addDocumentAction((actions)=>{
const indexOfDeleteAction = actions.findIndex((action)=>action.type === 'unpublish');
actions.splice(indexOfDeleteAction, 0, ReleaseActionModalForm);
return actions;
});
}
app.addSettingsLink('global', {
id: pluginId,
to: 'releases',
intlLabel: {
id: `${pluginId}.plugin.name`,
defaultMessage: 'Releases'
},
permissions: [],
async Component () {
const { ProtectedReleasesSettingsPage } = await import('./pages/ReleasesSettingsPage.mjs');
return {
default: ProtectedReleasesSettingsPage
};
}
});
if ('addBulkAction' in contentManagerPluginApis && typeof contentManagerPluginApis.addBulkAction === 'function') {
contentManagerPluginApis.addBulkAction((actions)=>{
// We want to add this action to just before the delete action all the time
const deleteActionIndex = actions.findIndex((action)=>action.type === 'delete');
actions.splice(deleteActionIndex, 0, ReleaseAction);
return actions;
});
}
// Hook that adds a column into the CM's LV table
app.registerHook('Admin/CM/pages/ListView/inject-column-in-table', addColumnToTableHook);
} else if (!window.strapi.features.isEnabled('cms-content-releases') && window.strapi?.flags?.promoteEE) {
app.addSettingsLink('global', {
id: pluginId,
to: '/plugins/purchase-content-releases',
intlLabel: {
id: `${pluginId}.plugin.name`,
defaultMessage: 'Releases'
},
permissions: [],
async Component () {
const { PurchaseContentReleases } = await import('./pages/PurchaseContentReleases.mjs');
return {
default: PurchaseContentReleases
};
},
licenseOnly: true
});
}
},
async registerTrads ({ locales }) {
const importedTrads = await Promise.all(locales.map((locale)=>{
return __variableDynamicImportRuntime3__(`./translations/${locale}.json`).then(({ default: data })=>{
return {
data: prefixPluginTranslations(data, 'content-releases'),
locale
};
}).catch(()=>{
return {
data: {},
locale
};
});
}));
return Promise.resolve(importedTrads);
}
};
export { admin as default };
//# sourceMappingURL=index.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,8 @@
'use strict';
var reactRedux = require('react-redux');
const useTypedSelector = reactRedux.useSelector;
exports.useTypedSelector = useTypedSelector;
//# sourceMappingURL=hooks.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"hooks.js","sources":["../../../admin/src/modules/hooks.ts"],"sourcesContent":["import { Dispatch } from '@reduxjs/toolkit';\nimport { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';\n\nimport type { Store } from '@strapi/admin/strapi-admin';\n\ntype RootState = ReturnType<Store['getState']>;\n\nconst useTypedDispatch: () => Dispatch = useDispatch;\nconst useTypedSelector: TypedUseSelectorHook<RootState> = useSelector;\n\nexport { useTypedSelector, useTypedDispatch };\n"],"names":["useTypedSelector","useSelector"],"mappings":";;;;AAQA,MAAMA,gBAAoDC,GAAAA;;;;"}

View File

@@ -0,0 +1,6 @@
import { useSelector } from 'react-redux';
const useTypedSelector = useSelector;
export { useTypedSelector };
//# sourceMappingURL=hooks.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"hooks.mjs","sources":["../../../admin/src/modules/hooks.ts"],"sourcesContent":["import { Dispatch } from '@reduxjs/toolkit';\nimport { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';\n\nimport type { Store } from '@strapi/admin/strapi-admin';\n\ntype RootState = ReturnType<Store['getState']>;\n\nconst useTypedDispatch: () => Dispatch = useDispatch;\nconst useTypedSelector: TypedUseSelectorHook<RootState> = useSelector;\n\nexport { useTypedSelector, useTypedDispatch };\n"],"names":["useTypedSelector","useSelector"],"mappings":";;AAQA,MAAMA,gBAAoDC,GAAAA;;;;"}

View File

@@ -0,0 +1,29 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var reactRouterDom = require('react-router-dom');
var constants = require('../constants.js');
var ReleaseDetailsPage = require('./ReleaseDetailsPage.js');
var ReleasesPage = require('./ReleasesPage.js');
const App = ()=>{
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Protect, {
permissions: constants.PERMISSIONS.main,
children: /*#__PURE__*/ jsxRuntime.jsxs(reactRouterDom.Routes, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(reactRouterDom.Route, {
index: true,
element: /*#__PURE__*/ jsxRuntime.jsx(ReleasesPage.ReleasesPage, {})
}),
/*#__PURE__*/ jsxRuntime.jsx(reactRouterDom.Route, {
path: ':releaseId',
element: /*#__PURE__*/ jsxRuntime.jsx(ReleaseDetailsPage.ReleaseDetailsPage, {})
})
]
})
});
};
exports.App = App;
//# sourceMappingURL=App.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"App.js","sources":["../../../admin/src/pages/App.tsx"],"sourcesContent":["import { Page } from '@strapi/admin/strapi-admin';\nimport { Route, Routes } from 'react-router-dom';\n\nimport { PERMISSIONS } from '../constants';\n\nimport { ReleaseDetailsPage } from './ReleaseDetailsPage';\nimport { ReleasesPage } from './ReleasesPage';\n\nexport const App = () => {\n return (\n <Page.Protect permissions={PERMISSIONS.main}>\n <Routes>\n <Route index element={<ReleasesPage />} />\n <Route path={':releaseId'} element={<ReleaseDetailsPage />} />\n </Routes>\n </Page.Protect>\n );\n};\n"],"names":["App","_jsx","Page","Protect","permissions","PERMISSIONS","main","_jsxs","Routes","Route","index","element","ReleasesPage","path","ReleaseDetailsPage"],"mappings":";;;;;;;;;MAQaA,GAAM,GAAA,IAAA;IACjB,qBACEC,cAAA,CAACC,iBAAKC,OAAO,EAAA;AAACC,QAAAA,WAAAA,EAAaC,sBAAYC,IAAI;AACzC,QAAA,QAAA,gBAAAC,eAACC,CAAAA,qBAAAA,EAAAA;;8BACCP,cAACQ,CAAAA,oBAAAA,EAAAA;oBAAMC,KAAK,EAAA,IAAA;AAACC,oBAAAA,OAAAA,gBAASV,cAACW,CAAAA,yBAAAA,EAAAA,EAAAA;;8BACvBX,cAACQ,CAAAA,oBAAAA,EAAAA;oBAAMI,IAAM,EAAA,YAAA;AAAcF,oBAAAA,OAAAA,gBAASV,cAACa,CAAAA,qCAAAA,EAAAA,EAAAA;;;;;AAI7C;;;;"}

View File

@@ -0,0 +1,27 @@
import { jsx, jsxs } from 'react/jsx-runtime';
import { Page } from '@strapi/admin/strapi-admin';
import { Routes, Route } from 'react-router-dom';
import { PERMISSIONS } from '../constants.mjs';
import { ReleaseDetailsPage } from './ReleaseDetailsPage.mjs';
import { ReleasesPage } from './ReleasesPage.mjs';
const App = ()=>{
return /*#__PURE__*/ jsx(Page.Protect, {
permissions: PERMISSIONS.main,
children: /*#__PURE__*/ jsxs(Routes, {
children: [
/*#__PURE__*/ jsx(Route, {
index: true,
element: /*#__PURE__*/ jsx(ReleasesPage, {})
}),
/*#__PURE__*/ jsx(Route, {
path: ':releaseId',
element: /*#__PURE__*/ jsx(ReleaseDetailsPage, {})
})
]
})
});
};
export { App };
//# sourceMappingURL=App.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"App.mjs","sources":["../../../admin/src/pages/App.tsx"],"sourcesContent":["import { Page } from '@strapi/admin/strapi-admin';\nimport { Route, Routes } from 'react-router-dom';\n\nimport { PERMISSIONS } from '../constants';\n\nimport { ReleaseDetailsPage } from './ReleaseDetailsPage';\nimport { ReleasesPage } from './ReleasesPage';\n\nexport const App = () => {\n return (\n <Page.Protect permissions={PERMISSIONS.main}>\n <Routes>\n <Route index element={<ReleasesPage />} />\n <Route path={':releaseId'} element={<ReleaseDetailsPage />} />\n </Routes>\n </Page.Protect>\n );\n};\n"],"names":["App","_jsx","Page","Protect","permissions","PERMISSIONS","main","_jsxs","Routes","Route","index","element","ReleasesPage","path","ReleaseDetailsPage"],"mappings":";;;;;;;MAQaA,GAAM,GAAA,IAAA;IACjB,qBACEC,GAAA,CAACC,KAAKC,OAAO,EAAA;AAACC,QAAAA,WAAAA,EAAaC,YAAYC,IAAI;AACzC,QAAA,QAAA,gBAAAC,IAACC,CAAAA,MAAAA,EAAAA;;8BACCP,GAACQ,CAAAA,KAAAA,EAAAA;oBAAMC,KAAK,EAAA,IAAA;AAACC,oBAAAA,OAAAA,gBAASV,GAACW,CAAAA,YAAAA,EAAAA,EAAAA;;8BACvBX,GAACQ,CAAAA,KAAAA,EAAAA;oBAAMI,IAAM,EAAA,YAAA;AAAcF,oBAAAA,OAAAA,gBAASV,GAACa,CAAAA,kBAAAA,EAAAA,EAAAA;;;;;AAI7C;;;;"}

View File

@@ -0,0 +1,192 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var icons = require('@strapi/icons');
var reactIntl = require('react-intl');
var purchasePageIllustrationDark = require('../assets/purchase-page-illustration-dark.svg.js');
var purchasePageIllustrationLight = require('../assets/purchase-page-illustration-light.svg.js');
var hooks = require('../modules/hooks.js');
const PurchaseContentReleases = ()=>{
const { formatMessage } = reactIntl.useIntl();
const currentTheme = hooks.useTypedSelector((state)=>state.admin_app.theme.currentTheme);
const illustration = currentTheme === 'light' ? purchasePageIllustrationLight : purchasePageIllustrationDark;
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Layouts.Root, {
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Main, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Layouts.Header, {
title: formatMessage({
id: 'content-releases.pages.Releases.title',
defaultMessage: 'Releases'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
marginLeft: 10,
marginRight: 10,
shadow: "filterShadow",
hasRadius: true,
background: "neutral0",
borderColor: 'neutral150',
overflow: 'hidden',
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Grid.Root, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Grid.Item, {
col: 6,
s: 12,
alignItems: 'flex-start',
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
alignItems: "flex-start",
padding: 7,
width: '100%',
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Flex, {
children: /*#__PURE__*/ jsxRuntime.jsx(icons.PaperPlane, {
fill: "primary600",
width: `24px`,
height: `24px`
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Flex, {
paddingTop: 3,
paddingBottom: 4,
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
variant: "beta",
fontWeight: "bold",
children: formatMessage({
id: 'pages.PurchaseRelease.description',
defaultMessage: 'Group content and publish updates together'
})
})
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
alignItems: 'flex-start',
gap: 2,
children: [
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsxRuntime.jsx(icons.Check, {
fill: "success500",
width: `16px`,
height: `16px`,
style: {
flexShrink: 0
}
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "neutral700",
children: formatMessage({
id: 'pages.PurchaseRelease.perks1',
defaultMessage: 'Add many entries to releases'
})
})
]
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsxRuntime.jsx(icons.Check, {
fill: "success500",
width: `16px`,
height: `16px`,
style: {
flexShrink: 0
}
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "neutral700",
children: formatMessage({
id: 'pages.PurchaseRelease.perks2',
defaultMessage: 'Quickly identify entries containing errors'
})
})
]
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsxRuntime.jsx(icons.Check, {
fill: "success500",
width: `16px`,
height: `16px`,
style: {
flexShrink: 0
}
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "neutral700",
children: formatMessage({
id: 'pages.PurchaseRelease.perks3',
defaultMessage: 'Schedule their publication, or publish them manually'
})
})
]
})
]
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 2,
marginTop: 7,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.LinkButton, {
variant: "default",
href: "https://strapi.io/pricing-self-hosted?utm_campaign=In-Product-CTA&utm_source=Releases",
children: formatMessage({
id: 'Settings.page.purchase.upgrade.cta',
defaultMessage: 'Upgrade'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.LinkButton, {
variant: "tertiary",
endIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.ExternalLink, {}),
href: "https://strapi.io/features/releases?utm_campaign=In-Product-CTA&utm_source=Releases",
children: formatMessage({
id: 'Settings.page.purchase.learn-more.cta',
defaultMessage: 'Learn more'
})
})
]
})
]
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Grid.Item, {
col: 6,
s: 12,
background: "primary100",
minHeight: '280px',
children: /*#__PURE__*/ jsxRuntime.jsx("div", {
style: {
position: 'relative',
width: '100%',
height: '100%'
},
children: /*#__PURE__*/ jsxRuntime.jsx("img", {
src: illustration,
alt: "purchase-page-content-releases-illustration",
style: {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
objectFit: 'cover',
objectPosition: 'top left'
}
})
})
})
]
})
})
]
})
});
};
exports.PurchaseContentReleases = PurchaseContentReleases;
//# sourceMappingURL=PurchaseContentReleases.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,190 @@
import { jsx, jsxs } from 'react/jsx-runtime';
import { Layouts } from '@strapi/admin/strapi-admin';
import { Main, Box, Grid, Flex, Typography, LinkButton } from '@strapi/design-system';
import { PaperPlane, Check, ExternalLink } from '@strapi/icons';
import { useIntl } from 'react-intl';
import img$1 from '../assets/purchase-page-illustration-dark.svg.mjs';
import img from '../assets/purchase-page-illustration-light.svg.mjs';
import { useTypedSelector } from '../modules/hooks.mjs';
const PurchaseContentReleases = ()=>{
const { formatMessage } = useIntl();
const currentTheme = useTypedSelector((state)=>state.admin_app.theme.currentTheme);
const illustration = currentTheme === 'light' ? img : img$1;
return /*#__PURE__*/ jsx(Layouts.Root, {
children: /*#__PURE__*/ jsxs(Main, {
children: [
/*#__PURE__*/ jsx(Layouts.Header, {
title: formatMessage({
id: 'content-releases.pages.Releases.title',
defaultMessage: 'Releases'
})
}),
/*#__PURE__*/ jsx(Box, {
marginLeft: 10,
marginRight: 10,
shadow: "filterShadow",
hasRadius: true,
background: "neutral0",
borderColor: 'neutral150',
overflow: 'hidden',
children: /*#__PURE__*/ jsxs(Grid.Root, {
children: [
/*#__PURE__*/ jsx(Grid.Item, {
col: 6,
s: 12,
alignItems: 'flex-start',
children: /*#__PURE__*/ jsxs(Flex, {
direction: "column",
alignItems: "flex-start",
padding: 7,
width: '100%',
children: [
/*#__PURE__*/ jsx(Flex, {
children: /*#__PURE__*/ jsx(PaperPlane, {
fill: "primary600",
width: `24px`,
height: `24px`
})
}),
/*#__PURE__*/ jsx(Flex, {
paddingTop: 3,
paddingBottom: 4,
children: /*#__PURE__*/ jsx(Typography, {
variant: "beta",
fontWeight: "bold",
children: formatMessage({
id: 'pages.PurchaseRelease.description',
defaultMessage: 'Group content and publish updates together'
})
})
}),
/*#__PURE__*/ jsxs(Flex, {
direction: "column",
alignItems: 'flex-start',
gap: 2,
children: [
/*#__PURE__*/ jsxs(Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsx(Check, {
fill: "success500",
width: `16px`,
height: `16px`,
style: {
flexShrink: 0
}
}),
/*#__PURE__*/ jsx(Typography, {
textColor: "neutral700",
children: formatMessage({
id: 'pages.PurchaseRelease.perks1',
defaultMessage: 'Add many entries to releases'
})
})
]
}),
/*#__PURE__*/ jsxs(Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsx(Check, {
fill: "success500",
width: `16px`,
height: `16px`,
style: {
flexShrink: 0
}
}),
/*#__PURE__*/ jsx(Typography, {
textColor: "neutral700",
children: formatMessage({
id: 'pages.PurchaseRelease.perks2',
defaultMessage: 'Quickly identify entries containing errors'
})
})
]
}),
/*#__PURE__*/ jsxs(Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsx(Check, {
fill: "success500",
width: `16px`,
height: `16px`,
style: {
flexShrink: 0
}
}),
/*#__PURE__*/ jsx(Typography, {
textColor: "neutral700",
children: formatMessage({
id: 'pages.PurchaseRelease.perks3',
defaultMessage: 'Schedule their publication, or publish them manually'
})
})
]
})
]
}),
/*#__PURE__*/ jsxs(Flex, {
gap: 2,
marginTop: 7,
children: [
/*#__PURE__*/ jsx(LinkButton, {
variant: "default",
href: "https://strapi.io/pricing-self-hosted?utm_campaign=In-Product-CTA&utm_source=Releases",
children: formatMessage({
id: 'Settings.page.purchase.upgrade.cta',
defaultMessage: 'Upgrade'
})
}),
/*#__PURE__*/ jsx(LinkButton, {
variant: "tertiary",
endIcon: /*#__PURE__*/ jsx(ExternalLink, {}),
href: "https://strapi.io/features/releases?utm_campaign=In-Product-CTA&utm_source=Releases",
children: formatMessage({
id: 'Settings.page.purchase.learn-more.cta',
defaultMessage: 'Learn more'
})
})
]
})
]
})
}),
/*#__PURE__*/ jsx(Grid.Item, {
col: 6,
s: 12,
background: "primary100",
minHeight: '280px',
children: /*#__PURE__*/ jsx("div", {
style: {
position: 'relative',
width: '100%',
height: '100%'
},
children: /*#__PURE__*/ jsx("img", {
src: illustration,
alt: "purchase-page-content-releases-illustration",
style: {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
objectFit: 'cover',
objectPosition: 'top left'
}
})
})
})
]
})
})
]
})
});
};
export { PurchaseContentReleases };
//# sourceMappingURL=PurchaseContentReleases.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,821 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var icons = require('@strapi/icons');
var symbols = require('@strapi/icons/symbols');
var format = require('date-fns/format');
var dateFnsTz = require('date-fns-tz');
var reactIntl = require('react-intl');
var reactRouterDom = require('react-router-dom');
var styledComponents = require('styled-components');
var EntryValidationPopover = require('../components/EntryValidationPopover.js');
var RelativeTime = require('../components/RelativeTime.js');
var ReleaseActionMenu = require('../components/ReleaseActionMenu.js');
var ReleaseActionOptions = require('../components/ReleaseActionOptions.js');
var ReleaseModal = require('../components/ReleaseModal.js');
var constants = require('../constants.js');
var release = require('../services/release.js');
var hooks = require('../store/hooks.js');
var api = require('../utils/api.js');
var time = require('../utils/time.js');
var ReleasesPage = require('./ReleasesPage.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
/* -------------------------------------------------------------------------------------------------
* ReleaseDetailsLayout
* -----------------------------------------------------------------------------------------------*/ const ReleaseInfoWrapper = styledComponents.styled(designSystem.Flex)`
align-self: stretch;
border-bottom-right-radius: ${({ theme })=>theme.borderRadius};
border-bottom-left-radius: ${({ theme })=>theme.borderRadius};
border-top: 1px solid ${({ theme })=>theme.colors.neutral150};
`;
const StyledMenuItem = styledComponents.styled(designSystem.MenuItem)`
svg path {
fill: ${({ theme, disabled })=>disabled && theme.colors.neutral500};
}
span {
color: ${({ theme, disabled })=>disabled && theme.colors.neutral500};
}
&:hover {
background: ${({ theme, $variant = 'neutral' })=>theme.colors[`${$variant}100`]};
}
`;
const PencilIcon = styledComponents.styled(icons.Pencil)`
width: ${({ theme })=>theme.spaces[4]};
height: ${({ theme })=>theme.spaces[4]};
path {
fill: ${({ theme })=>theme.colors.neutral600};
}
`;
const TrashIcon = styledComponents.styled(icons.Trash)`
width: ${({ theme })=>theme.spaces[4]};
height: ${({ theme })=>theme.spaces[4]};
path {
fill: ${({ theme })=>theme.colors.danger600};
}
`;
const ReleaseDetailsLayout = ({ toggleEditReleaseModal, toggleWarningSubmit, children })=>{
const { formatMessage, formatDate, formatTime } = reactIntl.useIntl();
const { releaseId } = reactRouterDom.useParams();
const { data, isLoading: isLoadingDetails, error } = release.useGetReleaseQuery({
id: releaseId
}, {
skip: !releaseId
});
const [publishRelease, { isLoading: isPublishing }] = release.usePublishReleaseMutation();
const { toggleNotification } = strapiAdmin.useNotification();
const { formatAPIError } = strapiAdmin.useAPIErrorHandler();
const { allowedActions } = strapiAdmin.useRBAC(constants.PERMISSIONS);
const { canUpdate, canDelete, canPublish } = allowedActions;
const dispatch = hooks.useTypedDispatch();
const { trackUsage } = strapiAdmin.useTracking();
const release$1 = data?.data;
const handlePublishRelease = (id)=>async ()=>{
const response = await publishRelease({
id
});
if ('data' in response) {
// When the response returns an object with 'data', handle success
toggleNotification({
type: 'success',
message: formatMessage({
id: 'content-releases.pages.ReleaseDetails.publish-notification-success',
defaultMessage: 'Release was published successfully.'
})
});
const { totalEntries, totalPublishedEntries, totalUnpublishedEntries } = response.data.meta;
trackUsage('didPublishRelease', {
totalEntries,
totalPublishedEntries,
totalUnpublishedEntries
});
} else if (strapiAdmin.isFetchError(response.error)) {
// When the response returns an object with 'error', handle fetch error
toggleNotification({
type: 'danger',
message: formatAPIError(response.error)
});
} else {
// Otherwise, the response returns an object with 'error', handle a generic error
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
};
const handleRefresh = ()=>{
dispatch(release.releaseApi.util.invalidateTags([
{
type: 'ReleaseAction',
id: 'LIST'
},
{
type: 'Release',
id: releaseId
}
]));
};
const getCreatedByUser = ()=>{
if (!release$1?.createdBy) {
return null;
}
// Favor the username
if (release$1.createdBy.username) {
return release$1.createdBy.username;
}
// Firstname may not exist if created with SSO
if (release$1.createdBy.firstname) {
return `${release$1.createdBy.firstname} ${release$1.createdBy.lastname || ''}`.trim();
}
// All users must have at least an email
return release$1.createdBy.email;
};
if (isLoadingDetails) {
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Loading, {});
}
if (api.isBaseQueryError(error) && 'code' in error || !release$1) {
return /*#__PURE__*/ jsxRuntime.jsx(reactRouterDom.Navigate, {
to: "..",
state: {
errors: [
{
// @ts-expect-error TODO: fix this weird error flow
code: error?.code
}
]
}
});
}
const totalEntries = release$1.actions.meta.count || 0;
const hasCreatedByUser = Boolean(getCreatedByUser());
const isScheduled = release$1.scheduledAt && release$1.timezone;
const numberOfEntriesText = formatMessage({
id: 'content-releases.pages.Details.header-subtitle',
defaultMessage: '{number, plural, =0 {No entries} one {# entry} other {# entries}}'
}, {
number: totalEntries
});
const scheduledText = isScheduled ? formatMessage({
id: 'content-releases.pages.ReleaseDetails.header-subtitle.scheduled',
defaultMessage: 'Scheduled for {date} at {time} ({offset})'
}, {
date: formatDate(new Date(release$1.scheduledAt), {
weekday: 'long',
day: 'numeric',
month: 'long',
year: 'numeric',
timeZone: release$1.timezone
}),
time: formatTime(new Date(release$1.scheduledAt), {
timeZone: release$1.timezone,
hourCycle: 'h23'
}),
offset: time.getTimezoneOffset(release$1.timezone, new Date(release$1.scheduledAt))
}) : '';
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Main, {
"aria-busy": isLoadingDetails,
children: [
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Layouts.Header, {
title: release$1.name,
subtitle: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 2,
lineHeight: 6,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "neutral600",
variant: "epsilon",
children: numberOfEntriesText + (isScheduled ? ` - ${scheduledText}` : '')
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Badge, {
...ReleasesPage.getBadgeProps(release$1.status),
children: release$1.status
})
]
}),
navigationAction: /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.BackButton, {
fallback: ".."
}),
primaryAction: !release$1.releasedAt && /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsxRuntime.jsxs(SimpleMenuButton, {
label: /*#__PURE__*/ jsxRuntime.jsx(icons.More, {}),
variant: "tertiary",
endIcon: null,
paddingLeft: "7px",
paddingRight: "7px",
"aria-label": formatMessage({
id: 'content-releases.header.actions.open-release-actions',
defaultMessage: 'Release edit and delete menu'
}),
popoverPlacement: "bottom-end",
children: [
/*#__PURE__*/ jsxRuntime.jsx(StyledMenuItem, {
disabled: !canUpdate,
onSelect: toggleEditReleaseModal,
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
alignItems: "center",
gap: 2,
hasRadius: true,
width: "100%",
children: [
/*#__PURE__*/ jsxRuntime.jsx(PencilIcon, {}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
ellipsis: true,
children: formatMessage({
id: 'content-releases.header.actions.edit',
defaultMessage: 'Edit'
})
})
]
})
}),
/*#__PURE__*/ jsxRuntime.jsx(StyledMenuItem, {
disabled: !canDelete,
onSelect: toggleWarningSubmit,
$variant: "danger",
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
alignItems: "center",
gap: 2,
hasRadius: true,
width: "100%",
children: [
/*#__PURE__*/ jsxRuntime.jsx(TrashIcon, {}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
ellipsis: true,
textColor: "danger600",
children: formatMessage({
id: 'content-releases.header.actions.delete',
defaultMessage: 'Delete'
})
})
]
})
}),
/*#__PURE__*/ jsxRuntime.jsxs(ReleaseInfoWrapper, {
direction: "column",
justifyContent: "center",
alignItems: "flex-start",
gap: 1,
padding: 4,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
variant: "pi",
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.header.actions.created',
defaultMessage: 'Created'
})
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Typography, {
variant: "pi",
color: "neutral300",
children: [
/*#__PURE__*/ jsxRuntime.jsx(RelativeTime.RelativeTime, {
timestamp: new Date(release$1.createdAt)
}),
formatMessage({
id: 'content-releases.header.actions.created.description',
defaultMessage: '{hasCreatedByUser, select, true { by {createdBy}} other { by deleted user}}'
}, {
createdBy: getCreatedByUser(),
hasCreatedByUser
})
]
})
]
})
]
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
size: "S",
variant: "tertiary",
onClick: handleRefresh,
children: formatMessage({
id: 'content-releases.header.actions.refresh',
defaultMessage: 'Refresh'
})
}),
canPublish ? /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
size: "S",
variant: "default",
onClick: handlePublishRelease(release$1.id.toString()),
loading: isPublishing,
disabled: release$1.actions.meta.count === 0,
children: formatMessage({
id: 'content-releases.header.actions.publish',
defaultMessage: 'Publish'
})
}) : null
]
})
}),
children
]
});
};
const SimpleMenuButton = styledComponents.styled(designSystem.SimpleMenu)`
& > span {
display: flex;
}
`;
/* -------------------------------------------------------------------------------------------------
* ReleaseDetailsBody
* -----------------------------------------------------------------------------------------------*/ const GROUP_BY_OPTIONS = [
'contentType',
'locale',
'action'
];
const GROUP_BY_OPTIONS_NO_LOCALE = [
'contentType',
'action'
];
const getGroupByOptionLabel = (value)=>{
if (value === 'locale') {
return {
id: 'content-releases.pages.ReleaseDetails.groupBy.option.locales',
defaultMessage: 'Locales'
};
}
if (value === 'action') {
return {
id: 'content-releases.pages.ReleaseDetails.groupBy.option.actions',
defaultMessage: 'Actions'
};
}
return {
id: 'content-releases.pages.ReleaseDetails.groupBy.option.content-type',
defaultMessage: 'Content-Types'
};
};
const ReleaseDetailsBody = ({ releaseId })=>{
const { formatMessage } = reactIntl.useIntl();
const [{ query }, setQuery] = strapiAdmin.useQueryParams();
const { toggleNotification } = strapiAdmin.useNotification();
const { formatAPIError } = strapiAdmin.useAPIErrorHandler();
const { data: releaseData, isLoading: isReleaseLoading, error: releaseError } = release.useGetReleaseQuery({
id: releaseId
});
const { allowedActions: { canUpdate } } = strapiAdmin.useRBAC(constants.PERMISSIONS);
const runHookWaterfall = strapiAdmin.useStrapiApp('ReleaseDetailsPage', (state)=>state.runHookWaterfall);
// TODO: Migrated displayedHeader to v5
const { displayedHeaders, hasI18nEnabled } = runHookWaterfall('ContentReleases/pages/ReleaseDetails/add-locale-in-releases', {
displayedHeaders: [
{
label: {
id: 'content-releases.page.ReleaseDetails.table.header.label.name',
defaultMessage: 'name'
},
name: 'name'
}
],
hasI18nEnabled: false
});
const release$1 = releaseData?.data;
const selectedGroupBy = query?.groupBy || 'contentType';
const { isLoading, isFetching, isError, data, error: releaseActionsError } = release.useGetReleaseActionsQuery({
...query,
releaseId
});
const [updateReleaseAction] = release.useUpdateReleaseActionMutation();
const handleChangeType = async (e, actionId, actionPath)=>{
const response = await updateReleaseAction({
params: {
releaseId,
actionId
},
body: {
type: e.target.value
},
query,
actionPath
});
if ('error' in response) {
if (strapiAdmin.isFetchError(response.error)) {
// When the response returns an object with 'error', handle fetch error
toggleNotification({
type: 'danger',
message: formatAPIError(response.error)
});
} else {
// Otherwise, the response returns an object with 'error', handle a generic error
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
}
};
if (isLoading || isReleaseLoading) {
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Loading, {});
}
const releaseActions = data?.data;
const releaseMeta = data?.meta;
const contentTypes = releaseMeta?.contentTypes || {};
releaseMeta?.components || {};
if (api.isBaseQueryError(releaseError) || !release$1) {
const errorsArray = [];
if (releaseError && 'code' in releaseError) {
errorsArray.push({
code: releaseError.code
});
}
if (releaseActionsError && 'code' in releaseActionsError) {
errorsArray.push({
code: releaseActionsError.code
});
}
return /*#__PURE__*/ jsxRuntime.jsx(reactRouterDom.Navigate, {
to: "..",
state: {
errors: errorsArray
}
});
}
if (isError || !releaseActions) {
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Error, {});
}
if (Object.keys(releaseActions).length === 0) {
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Layouts.Content, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.EmptyStateLayout, {
action: /*#__PURE__*/ jsxRuntime.jsx(designSystem.LinkButton, {
tag: reactRouterDom.Link,
to: {
pathname: '/content-manager'
},
style: {
textDecoration: 'none'
},
variant: "secondary",
children: formatMessage({
id: 'content-releases.page.Details.button.openContentManager',
defaultMessage: 'Open the Content Manager'
})
}),
icon: /*#__PURE__*/ jsxRuntime.jsx(symbols.EmptyDocuments, {
width: "16rem"
}),
content: formatMessage({
id: 'content-releases.pages.Details.tab.emptyEntries',
defaultMessage: 'This release is empty. Open the Content Manager, select an entry and add it to the release.'
})
})
});
}
const groupByLabel = formatMessage({
id: 'content-releases.pages.ReleaseDetails.groupBy.aria-label',
defaultMessage: 'Group by'
});
const headers = [
...displayedHeaders,
{
label: {
id: 'content-releases.page.ReleaseDetails.table.header.label.content-type',
defaultMessage: 'content-type'
},
name: 'content-type'
},
{
label: {
id: 'content-releases.page.ReleaseDetails.table.header.label.action',
defaultMessage: 'action'
},
name: 'action'
},
...!release$1.releasedAt ? [
{
label: {
id: 'content-releases.page.ReleaseDetails.table.header.label.status',
defaultMessage: 'status'
},
name: 'status'
}
] : []
];
const options = hasI18nEnabled ? GROUP_BY_OPTIONS : GROUP_BY_OPTIONS_NO_LOCALE;
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Layouts.Content, {
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 8,
direction: "column",
alignItems: "stretch",
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Flex, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.SingleSelect, {
placeholder: groupByLabel,
"aria-label": groupByLabel,
customizeContent: (value)=>formatMessage({
id: `content-releases.pages.ReleaseDetails.groupBy.label`,
defaultMessage: `Group by {groupBy}`
}, {
groupBy: value
}),
value: formatMessage(getGroupByOptionLabel(selectedGroupBy)),
onChange: (value)=>setQuery({
groupBy: value
}),
children: options.map((option)=>/*#__PURE__*/ jsxRuntime.jsx(designSystem.SingleSelectOption, {
value: option,
children: formatMessage(getGroupByOptionLabel(option))
}, option))
})
}),
Object.keys(releaseActions).map((key)=>/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 4,
direction: "column",
alignItems: "stretch",
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Flex, {
role: "separator",
"aria-label": key,
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Badge, {
children: key
})
}),
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Table.Root, {
rows: releaseActions[key].map((item)=>({
...item,
id: Number(item.entry.id)
})),
headers: headers,
isLoading: isLoading || isFetching,
children: /*#__PURE__*/ jsxRuntime.jsxs(strapiAdmin.Table.Content, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Table.Head, {
children: headers.map(({ label, name })=>/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Table.HeaderCell, {
label: formatMessage(label),
name: name
}, name))
}),
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Table.Loading, {}),
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Table.Body, {
children: releaseActions[key].map(({ id, contentType, locale, type, entry, status }, actionIndex)=>/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Tr, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Td, {
width: "25%",
maxWidth: "200px",
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
ellipsis: true,
children: `${contentType.mainFieldValue || entry.id}`
})
}),
hasI18nEnabled && /*#__PURE__*/ jsxRuntime.jsx(designSystem.Td, {
width: "10%",
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
children: `${locale?.name ? locale.name : '-'}`
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Td, {
width: "10%",
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
children: contentType.displayName || ''
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Td, {
width: "20%",
children: release$1.releasedAt ? /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
children: formatMessage({
id: 'content-releases.page.ReleaseDetails.table.action-published',
defaultMessage: 'This entry was <b>{isPublish, select, true {published} other {unpublished}}</b>.'
}, {
isPublish: type === 'publish',
b: (children)=>/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
fontWeight: "bold",
children: children
})
})
}) : /*#__PURE__*/ jsxRuntime.jsx(ReleaseActionOptions.ReleaseActionOptions, {
selected: type,
handleChange: (e)=>handleChangeType(e, id, [
key,
actionIndex
]),
name: `release-action-${id}-type`,
disabled: !canUpdate
})
}),
!release$1.releasedAt && /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Td, {
width: "20%",
minWidth: "200px",
children: /*#__PURE__*/ jsxRuntime.jsx(EntryValidationPopover.EntryValidationPopover, {
action: type,
schema: contentTypes?.[contentType.uid],
entry: entry,
status: status
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Td, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Flex, {
justifyContent: "flex-end",
children: /*#__PURE__*/ jsxRuntime.jsxs(ReleaseActionMenu.ReleaseActionMenu.Root, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(ReleaseActionMenu.ReleaseActionMenu.ReleaseActionEntryLinkItem, {
contentTypeUid: contentType.uid,
documentId: entry.documentId,
locale: locale?.code
}),
/*#__PURE__*/ jsxRuntime.jsx(ReleaseActionMenu.ReleaseActionMenu.DeleteReleaseActionItem, {
releaseId: release$1.id,
actionId: id
})
]
})
})
})
]
})
]
}, id))
})
]
})
})
]
}, `releases-group-${key}`)),
/*#__PURE__*/ jsxRuntime.jsxs(strapiAdmin.Pagination.Root, {
...releaseMeta?.pagination,
defaultPageSize: releaseMeta?.pagination?.pageSize,
children: [
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Pagination.PageSize, {}),
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Pagination.Links, {})
]
})
]
})
});
};
/* -------------------------------------------------------------------------------------------------
* ReleaseDetailsPage
* -----------------------------------------------------------------------------------------------*/ const ReleaseDetailsPage = ()=>{
const { formatMessage } = reactIntl.useIntl();
const { releaseId } = reactRouterDom.useParams();
const { toggleNotification } = strapiAdmin.useNotification();
const { formatAPIError } = strapiAdmin.useAPIErrorHandler();
const navigate = reactRouterDom.useNavigate();
const [releaseModalShown, setReleaseModalShown] = React__namespace.useState(false);
const [showWarningSubmit, setWarningSubmit] = React__namespace.useState(false);
const { isLoading: isLoadingDetails, data, isSuccess: isSuccessDetails } = release.useGetReleaseQuery({
id: releaseId
}, {
skip: !releaseId
});
const { data: dataTimezone, isLoading: isLoadingTimezone } = release.useGetReleaseSettingsQuery();
const [updateRelease, { isLoading: isSubmittingForm }] = release.useUpdateReleaseMutation();
const [deleteRelease] = release.useDeleteReleaseMutation();
const toggleEditReleaseModal = ()=>{
setReleaseModalShown((prev)=>!prev);
};
const getTimezoneValue = ()=>{
if (releaseData?.timezone) {
return releaseData.timezone;
} else {
if (dataTimezone?.data.defaultTimezone) {
return dataTimezone.data.defaultTimezone;
}
return null;
}
};
const toggleWarningSubmit = ()=>setWarningSubmit((prevState)=>!prevState);
if (isLoadingDetails || isLoadingTimezone) {
return /*#__PURE__*/ jsxRuntime.jsx(ReleaseDetailsLayout, {
toggleEditReleaseModal: toggleEditReleaseModal,
toggleWarningSubmit: toggleWarningSubmit,
children: /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Loading, {})
});
}
if (!releaseId) {
return /*#__PURE__*/ jsxRuntime.jsx(reactRouterDom.Navigate, {
to: ".."
});
}
const releaseData = isSuccessDetails && data?.data || null;
const title = releaseData?.name || '';
const timezone = getTimezoneValue();
const scheduledAt = releaseData?.scheduledAt && timezone ? dateFnsTz.utcToZonedTime(releaseData.scheduledAt, timezone) : null;
// Just get the date and time to display without considering updated timezone time
const date = scheduledAt ? format(scheduledAt, 'yyyy-MM-dd') : undefined;
const time = scheduledAt ? format(scheduledAt, 'HH:mm') : '';
const handleEditRelease = async (values)=>{
const response = await updateRelease({
id: releaseId,
name: values.name,
scheduledAt: values.scheduledAt,
timezone: values.timezone
});
if ('data' in response) {
// When the response returns an object with 'data', handle success
toggleNotification({
type: 'success',
message: formatMessage({
id: 'content-releases.modal.release-updated-notification-success',
defaultMessage: 'Release updated.'
})
});
toggleEditReleaseModal();
} else if (strapiAdmin.isFetchError(response.error)) {
// When the response returns an object with 'error', handle fetch error
toggleNotification({
type: 'danger',
message: formatAPIError(response.error)
});
} else {
// Otherwise, the response returns an object with 'error', handle a generic error
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
};
const handleDeleteRelease = async ()=>{
const response = await deleteRelease({
id: releaseId
});
if ('data' in response) {
navigate('..');
} else if (strapiAdmin.isFetchError(response.error)) {
// When the response returns an object with 'error', handle fetch error
toggleNotification({
type: 'danger',
message: formatAPIError(response.error)
});
} else {
// Otherwise, the response returns an object with 'error', handle a generic error
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
};
return /*#__PURE__*/ jsxRuntime.jsxs(ReleaseDetailsLayout, {
toggleEditReleaseModal: toggleEditReleaseModal,
toggleWarningSubmit: toggleWarningSubmit,
children: [
/*#__PURE__*/ jsxRuntime.jsx(ReleaseDetailsBody, {
releaseId: releaseId
}),
/*#__PURE__*/ jsxRuntime.jsx(ReleaseModal.ReleaseModal, {
open: releaseModalShown,
handleClose: toggleEditReleaseModal,
handleSubmit: handleEditRelease,
isLoading: isLoadingDetails || isSubmittingForm,
initialValues: {
name: title || '',
scheduledAt,
date,
time,
isScheduled: Boolean(scheduledAt),
timezone
}
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Dialog.Root, {
open: showWarningSubmit,
onOpenChange: toggleWarningSubmit,
children: /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.ConfirmDialog, {
onConfirm: handleDeleteRelease,
children: formatMessage({
id: 'content-releases.dialog.confirmation-message',
defaultMessage: 'Are you sure you want to delete this release?'
})
})
})
]
});
};
exports.ReleaseDetailsPage = ReleaseDetailsPage;
//# sourceMappingURL=ReleaseDetailsPage.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,800 @@
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import * as React from 'react';
import { useNotification, useAPIErrorHandler, Page, ConfirmDialog, useRBAC, useTracking, Layouts, BackButton, useQueryParams, useStrapiApp, Table, Pagination, isFetchError } from '@strapi/admin/strapi-admin';
import { Flex, MenuItem, SimpleMenu, Dialog, Main, Typography, Badge, Button, EmptyStateLayout, LinkButton, SingleSelect, SingleSelectOption, Tr, Td } from '@strapi/design-system';
import { Pencil, Trash, More } from '@strapi/icons';
import { EmptyDocuments } from '@strapi/icons/symbols';
import format from 'date-fns/format';
import { utcToZonedTime } from 'date-fns-tz';
import { useIntl } from 'react-intl';
import { useParams, useNavigate, Navigate, Link } from 'react-router-dom';
import { styled } from 'styled-components';
import { EntryValidationPopover } from '../components/EntryValidationPopover.mjs';
import { RelativeTime } from '../components/RelativeTime.mjs';
import { ReleaseActionMenu } from '../components/ReleaseActionMenu.mjs';
import { ReleaseActionOptions } from '../components/ReleaseActionOptions.mjs';
import { ReleaseModal } from '../components/ReleaseModal.mjs';
import { PERMISSIONS } from '../constants.mjs';
import { useGetReleaseQuery, useGetReleaseSettingsQuery, useUpdateReleaseMutation, useDeleteReleaseMutation, usePublishReleaseMutation, useGetReleaseActionsQuery, useUpdateReleaseActionMutation, releaseApi } from '../services/release.mjs';
import { useTypedDispatch } from '../store/hooks.mjs';
import { isBaseQueryError } from '../utils/api.mjs';
import { getTimezoneOffset } from '../utils/time.mjs';
import { getBadgeProps } from './ReleasesPage.mjs';
/* -------------------------------------------------------------------------------------------------
* ReleaseDetailsLayout
* -----------------------------------------------------------------------------------------------*/ const ReleaseInfoWrapper = styled(Flex)`
align-self: stretch;
border-bottom-right-radius: ${({ theme })=>theme.borderRadius};
border-bottom-left-radius: ${({ theme })=>theme.borderRadius};
border-top: 1px solid ${({ theme })=>theme.colors.neutral150};
`;
const StyledMenuItem = styled(MenuItem)`
svg path {
fill: ${({ theme, disabled })=>disabled && theme.colors.neutral500};
}
span {
color: ${({ theme, disabled })=>disabled && theme.colors.neutral500};
}
&:hover {
background: ${({ theme, $variant = 'neutral' })=>theme.colors[`${$variant}100`]};
}
`;
const PencilIcon = styled(Pencil)`
width: ${({ theme })=>theme.spaces[4]};
height: ${({ theme })=>theme.spaces[4]};
path {
fill: ${({ theme })=>theme.colors.neutral600};
}
`;
const TrashIcon = styled(Trash)`
width: ${({ theme })=>theme.spaces[4]};
height: ${({ theme })=>theme.spaces[4]};
path {
fill: ${({ theme })=>theme.colors.danger600};
}
`;
const ReleaseDetailsLayout = ({ toggleEditReleaseModal, toggleWarningSubmit, children })=>{
const { formatMessage, formatDate, formatTime } = useIntl();
const { releaseId } = useParams();
const { data, isLoading: isLoadingDetails, error } = useGetReleaseQuery({
id: releaseId
}, {
skip: !releaseId
});
const [publishRelease, { isLoading: isPublishing }] = usePublishReleaseMutation();
const { toggleNotification } = useNotification();
const { formatAPIError } = useAPIErrorHandler();
const { allowedActions } = useRBAC(PERMISSIONS);
const { canUpdate, canDelete, canPublish } = allowedActions;
const dispatch = useTypedDispatch();
const { trackUsage } = useTracking();
const release = data?.data;
const handlePublishRelease = (id)=>async ()=>{
const response = await publishRelease({
id
});
if ('data' in response) {
// When the response returns an object with 'data', handle success
toggleNotification({
type: 'success',
message: formatMessage({
id: 'content-releases.pages.ReleaseDetails.publish-notification-success',
defaultMessage: 'Release was published successfully.'
})
});
const { totalEntries, totalPublishedEntries, totalUnpublishedEntries } = response.data.meta;
trackUsage('didPublishRelease', {
totalEntries,
totalPublishedEntries,
totalUnpublishedEntries
});
} else if (isFetchError(response.error)) {
// When the response returns an object with 'error', handle fetch error
toggleNotification({
type: 'danger',
message: formatAPIError(response.error)
});
} else {
// Otherwise, the response returns an object with 'error', handle a generic error
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
};
const handleRefresh = ()=>{
dispatch(releaseApi.util.invalidateTags([
{
type: 'ReleaseAction',
id: 'LIST'
},
{
type: 'Release',
id: releaseId
}
]));
};
const getCreatedByUser = ()=>{
if (!release?.createdBy) {
return null;
}
// Favor the username
if (release.createdBy.username) {
return release.createdBy.username;
}
// Firstname may not exist if created with SSO
if (release.createdBy.firstname) {
return `${release.createdBy.firstname} ${release.createdBy.lastname || ''}`.trim();
}
// All users must have at least an email
return release.createdBy.email;
};
if (isLoadingDetails) {
return /*#__PURE__*/ jsx(Page.Loading, {});
}
if (isBaseQueryError(error) && 'code' in error || !release) {
return /*#__PURE__*/ jsx(Navigate, {
to: "..",
state: {
errors: [
{
// @ts-expect-error TODO: fix this weird error flow
code: error?.code
}
]
}
});
}
const totalEntries = release.actions.meta.count || 0;
const hasCreatedByUser = Boolean(getCreatedByUser());
const isScheduled = release.scheduledAt && release.timezone;
const numberOfEntriesText = formatMessage({
id: 'content-releases.pages.Details.header-subtitle',
defaultMessage: '{number, plural, =0 {No entries} one {# entry} other {# entries}}'
}, {
number: totalEntries
});
const scheduledText = isScheduled ? formatMessage({
id: 'content-releases.pages.ReleaseDetails.header-subtitle.scheduled',
defaultMessage: 'Scheduled for {date} at {time} ({offset})'
}, {
date: formatDate(new Date(release.scheduledAt), {
weekday: 'long',
day: 'numeric',
month: 'long',
year: 'numeric',
timeZone: release.timezone
}),
time: formatTime(new Date(release.scheduledAt), {
timeZone: release.timezone,
hourCycle: 'h23'
}),
offset: getTimezoneOffset(release.timezone, new Date(release.scheduledAt))
}) : '';
return /*#__PURE__*/ jsxs(Main, {
"aria-busy": isLoadingDetails,
children: [
/*#__PURE__*/ jsx(Layouts.Header, {
title: release.name,
subtitle: /*#__PURE__*/ jsxs(Flex, {
gap: 2,
lineHeight: 6,
children: [
/*#__PURE__*/ jsx(Typography, {
textColor: "neutral600",
variant: "epsilon",
children: numberOfEntriesText + (isScheduled ? ` - ${scheduledText}` : '')
}),
/*#__PURE__*/ jsx(Badge, {
...getBadgeProps(release.status),
children: release.status
})
]
}),
navigationAction: /*#__PURE__*/ jsx(BackButton, {
fallback: ".."
}),
primaryAction: !release.releasedAt && /*#__PURE__*/ jsxs(Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsxs(SimpleMenuButton, {
label: /*#__PURE__*/ jsx(More, {}),
variant: "tertiary",
endIcon: null,
paddingLeft: "7px",
paddingRight: "7px",
"aria-label": formatMessage({
id: 'content-releases.header.actions.open-release-actions',
defaultMessage: 'Release edit and delete menu'
}),
popoverPlacement: "bottom-end",
children: [
/*#__PURE__*/ jsx(StyledMenuItem, {
disabled: !canUpdate,
onSelect: toggleEditReleaseModal,
children: /*#__PURE__*/ jsxs(Flex, {
alignItems: "center",
gap: 2,
hasRadius: true,
width: "100%",
children: [
/*#__PURE__*/ jsx(PencilIcon, {}),
/*#__PURE__*/ jsx(Typography, {
ellipsis: true,
children: formatMessage({
id: 'content-releases.header.actions.edit',
defaultMessage: 'Edit'
})
})
]
})
}),
/*#__PURE__*/ jsx(StyledMenuItem, {
disabled: !canDelete,
onSelect: toggleWarningSubmit,
$variant: "danger",
children: /*#__PURE__*/ jsxs(Flex, {
alignItems: "center",
gap: 2,
hasRadius: true,
width: "100%",
children: [
/*#__PURE__*/ jsx(TrashIcon, {}),
/*#__PURE__*/ jsx(Typography, {
ellipsis: true,
textColor: "danger600",
children: formatMessage({
id: 'content-releases.header.actions.delete',
defaultMessage: 'Delete'
})
})
]
})
}),
/*#__PURE__*/ jsxs(ReleaseInfoWrapper, {
direction: "column",
justifyContent: "center",
alignItems: "flex-start",
gap: 1,
padding: 4,
children: [
/*#__PURE__*/ jsx(Typography, {
variant: "pi",
fontWeight: "bold",
children: formatMessage({
id: 'content-releases.header.actions.created',
defaultMessage: 'Created'
})
}),
/*#__PURE__*/ jsxs(Typography, {
variant: "pi",
color: "neutral300",
children: [
/*#__PURE__*/ jsx(RelativeTime, {
timestamp: new Date(release.createdAt)
}),
formatMessage({
id: 'content-releases.header.actions.created.description',
defaultMessage: '{hasCreatedByUser, select, true { by {createdBy}} other { by deleted user}}'
}, {
createdBy: getCreatedByUser(),
hasCreatedByUser
})
]
})
]
})
]
}),
/*#__PURE__*/ jsx(Button, {
size: "S",
variant: "tertiary",
onClick: handleRefresh,
children: formatMessage({
id: 'content-releases.header.actions.refresh',
defaultMessage: 'Refresh'
})
}),
canPublish ? /*#__PURE__*/ jsx(Button, {
size: "S",
variant: "default",
onClick: handlePublishRelease(release.id.toString()),
loading: isPublishing,
disabled: release.actions.meta.count === 0,
children: formatMessage({
id: 'content-releases.header.actions.publish',
defaultMessage: 'Publish'
})
}) : null
]
})
}),
children
]
});
};
const SimpleMenuButton = styled(SimpleMenu)`
& > span {
display: flex;
}
`;
/* -------------------------------------------------------------------------------------------------
* ReleaseDetailsBody
* -----------------------------------------------------------------------------------------------*/ const GROUP_BY_OPTIONS = [
'contentType',
'locale',
'action'
];
const GROUP_BY_OPTIONS_NO_LOCALE = [
'contentType',
'action'
];
const getGroupByOptionLabel = (value)=>{
if (value === 'locale') {
return {
id: 'content-releases.pages.ReleaseDetails.groupBy.option.locales',
defaultMessage: 'Locales'
};
}
if (value === 'action') {
return {
id: 'content-releases.pages.ReleaseDetails.groupBy.option.actions',
defaultMessage: 'Actions'
};
}
return {
id: 'content-releases.pages.ReleaseDetails.groupBy.option.content-type',
defaultMessage: 'Content-Types'
};
};
const ReleaseDetailsBody = ({ releaseId })=>{
const { formatMessage } = useIntl();
const [{ query }, setQuery] = useQueryParams();
const { toggleNotification } = useNotification();
const { formatAPIError } = useAPIErrorHandler();
const { data: releaseData, isLoading: isReleaseLoading, error: releaseError } = useGetReleaseQuery({
id: releaseId
});
const { allowedActions: { canUpdate } } = useRBAC(PERMISSIONS);
const runHookWaterfall = useStrapiApp('ReleaseDetailsPage', (state)=>state.runHookWaterfall);
// TODO: Migrated displayedHeader to v5
const { displayedHeaders, hasI18nEnabled } = runHookWaterfall('ContentReleases/pages/ReleaseDetails/add-locale-in-releases', {
displayedHeaders: [
{
label: {
id: 'content-releases.page.ReleaseDetails.table.header.label.name',
defaultMessage: 'name'
},
name: 'name'
}
],
hasI18nEnabled: false
});
const release = releaseData?.data;
const selectedGroupBy = query?.groupBy || 'contentType';
const { isLoading, isFetching, isError, data, error: releaseActionsError } = useGetReleaseActionsQuery({
...query,
releaseId
});
const [updateReleaseAction] = useUpdateReleaseActionMutation();
const handleChangeType = async (e, actionId, actionPath)=>{
const response = await updateReleaseAction({
params: {
releaseId,
actionId
},
body: {
type: e.target.value
},
query,
actionPath
});
if ('error' in response) {
if (isFetchError(response.error)) {
// When the response returns an object with 'error', handle fetch error
toggleNotification({
type: 'danger',
message: formatAPIError(response.error)
});
} else {
// Otherwise, the response returns an object with 'error', handle a generic error
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
}
};
if (isLoading || isReleaseLoading) {
return /*#__PURE__*/ jsx(Page.Loading, {});
}
const releaseActions = data?.data;
const releaseMeta = data?.meta;
const contentTypes = releaseMeta?.contentTypes || {};
releaseMeta?.components || {};
if (isBaseQueryError(releaseError) || !release) {
const errorsArray = [];
if (releaseError && 'code' in releaseError) {
errorsArray.push({
code: releaseError.code
});
}
if (releaseActionsError && 'code' in releaseActionsError) {
errorsArray.push({
code: releaseActionsError.code
});
}
return /*#__PURE__*/ jsx(Navigate, {
to: "..",
state: {
errors: errorsArray
}
});
}
if (isError || !releaseActions) {
return /*#__PURE__*/ jsx(Page.Error, {});
}
if (Object.keys(releaseActions).length === 0) {
return /*#__PURE__*/ jsx(Layouts.Content, {
children: /*#__PURE__*/ jsx(EmptyStateLayout, {
action: /*#__PURE__*/ jsx(LinkButton, {
tag: Link,
to: {
pathname: '/content-manager'
},
style: {
textDecoration: 'none'
},
variant: "secondary",
children: formatMessage({
id: 'content-releases.page.Details.button.openContentManager',
defaultMessage: 'Open the Content Manager'
})
}),
icon: /*#__PURE__*/ jsx(EmptyDocuments, {
width: "16rem"
}),
content: formatMessage({
id: 'content-releases.pages.Details.tab.emptyEntries',
defaultMessage: 'This release is empty. Open the Content Manager, select an entry and add it to the release.'
})
})
});
}
const groupByLabel = formatMessage({
id: 'content-releases.pages.ReleaseDetails.groupBy.aria-label',
defaultMessage: 'Group by'
});
const headers = [
...displayedHeaders,
{
label: {
id: 'content-releases.page.ReleaseDetails.table.header.label.content-type',
defaultMessage: 'content-type'
},
name: 'content-type'
},
{
label: {
id: 'content-releases.page.ReleaseDetails.table.header.label.action',
defaultMessage: 'action'
},
name: 'action'
},
...!release.releasedAt ? [
{
label: {
id: 'content-releases.page.ReleaseDetails.table.header.label.status',
defaultMessage: 'status'
},
name: 'status'
}
] : []
];
const options = hasI18nEnabled ? GROUP_BY_OPTIONS : GROUP_BY_OPTIONS_NO_LOCALE;
return /*#__PURE__*/ jsx(Layouts.Content, {
children: /*#__PURE__*/ jsxs(Flex, {
gap: 8,
direction: "column",
alignItems: "stretch",
children: [
/*#__PURE__*/ jsx(Flex, {
children: /*#__PURE__*/ jsx(SingleSelect, {
placeholder: groupByLabel,
"aria-label": groupByLabel,
customizeContent: (value)=>formatMessage({
id: `content-releases.pages.ReleaseDetails.groupBy.label`,
defaultMessage: `Group by {groupBy}`
}, {
groupBy: value
}),
value: formatMessage(getGroupByOptionLabel(selectedGroupBy)),
onChange: (value)=>setQuery({
groupBy: value
}),
children: options.map((option)=>/*#__PURE__*/ jsx(SingleSelectOption, {
value: option,
children: formatMessage(getGroupByOptionLabel(option))
}, option))
})
}),
Object.keys(releaseActions).map((key)=>/*#__PURE__*/ jsxs(Flex, {
gap: 4,
direction: "column",
alignItems: "stretch",
children: [
/*#__PURE__*/ jsx(Flex, {
role: "separator",
"aria-label": key,
children: /*#__PURE__*/ jsx(Badge, {
children: key
})
}),
/*#__PURE__*/ jsx(Table.Root, {
rows: releaseActions[key].map((item)=>({
...item,
id: Number(item.entry.id)
})),
headers: headers,
isLoading: isLoading || isFetching,
children: /*#__PURE__*/ jsxs(Table.Content, {
children: [
/*#__PURE__*/ jsx(Table.Head, {
children: headers.map(({ label, name })=>/*#__PURE__*/ jsx(Table.HeaderCell, {
label: formatMessage(label),
name: name
}, name))
}),
/*#__PURE__*/ jsx(Table.Loading, {}),
/*#__PURE__*/ jsx(Table.Body, {
children: releaseActions[key].map(({ id, contentType, locale, type, entry, status }, actionIndex)=>/*#__PURE__*/ jsxs(Tr, {
children: [
/*#__PURE__*/ jsx(Td, {
width: "25%",
maxWidth: "200px",
children: /*#__PURE__*/ jsx(Typography, {
ellipsis: true,
children: `${contentType.mainFieldValue || entry.id}`
})
}),
hasI18nEnabled && /*#__PURE__*/ jsx(Td, {
width: "10%",
children: /*#__PURE__*/ jsx(Typography, {
children: `${locale?.name ? locale.name : '-'}`
})
}),
/*#__PURE__*/ jsx(Td, {
width: "10%",
children: /*#__PURE__*/ jsx(Typography, {
children: contentType.displayName || ''
})
}),
/*#__PURE__*/ jsx(Td, {
width: "20%",
children: release.releasedAt ? /*#__PURE__*/ jsx(Typography, {
children: formatMessage({
id: 'content-releases.page.ReleaseDetails.table.action-published',
defaultMessage: 'This entry was <b>{isPublish, select, true {published} other {unpublished}}</b>.'
}, {
isPublish: type === 'publish',
b: (children)=>/*#__PURE__*/ jsx(Typography, {
fontWeight: "bold",
children: children
})
})
}) : /*#__PURE__*/ jsx(ReleaseActionOptions, {
selected: type,
handleChange: (e)=>handleChangeType(e, id, [
key,
actionIndex
]),
name: `release-action-${id}-type`,
disabled: !canUpdate
})
}),
!release.releasedAt && /*#__PURE__*/ jsxs(Fragment, {
children: [
/*#__PURE__*/ jsx(Td, {
width: "20%",
minWidth: "200px",
children: /*#__PURE__*/ jsx(EntryValidationPopover, {
action: type,
schema: contentTypes?.[contentType.uid],
entry: entry,
status: status
})
}),
/*#__PURE__*/ jsx(Td, {
children: /*#__PURE__*/ jsx(Flex, {
justifyContent: "flex-end",
children: /*#__PURE__*/ jsxs(ReleaseActionMenu.Root, {
children: [
/*#__PURE__*/ jsx(ReleaseActionMenu.ReleaseActionEntryLinkItem, {
contentTypeUid: contentType.uid,
documentId: entry.documentId,
locale: locale?.code
}),
/*#__PURE__*/ jsx(ReleaseActionMenu.DeleteReleaseActionItem, {
releaseId: release.id,
actionId: id
})
]
})
})
})
]
})
]
}, id))
})
]
})
})
]
}, `releases-group-${key}`)),
/*#__PURE__*/ jsxs(Pagination.Root, {
...releaseMeta?.pagination,
defaultPageSize: releaseMeta?.pagination?.pageSize,
children: [
/*#__PURE__*/ jsx(Pagination.PageSize, {}),
/*#__PURE__*/ jsx(Pagination.Links, {})
]
})
]
})
});
};
/* -------------------------------------------------------------------------------------------------
* ReleaseDetailsPage
* -----------------------------------------------------------------------------------------------*/ const ReleaseDetailsPage = ()=>{
const { formatMessage } = useIntl();
const { releaseId } = useParams();
const { toggleNotification } = useNotification();
const { formatAPIError } = useAPIErrorHandler();
const navigate = useNavigate();
const [releaseModalShown, setReleaseModalShown] = React.useState(false);
const [showWarningSubmit, setWarningSubmit] = React.useState(false);
const { isLoading: isLoadingDetails, data, isSuccess: isSuccessDetails } = useGetReleaseQuery({
id: releaseId
}, {
skip: !releaseId
});
const { data: dataTimezone, isLoading: isLoadingTimezone } = useGetReleaseSettingsQuery();
const [updateRelease, { isLoading: isSubmittingForm }] = useUpdateReleaseMutation();
const [deleteRelease] = useDeleteReleaseMutation();
const toggleEditReleaseModal = ()=>{
setReleaseModalShown((prev)=>!prev);
};
const getTimezoneValue = ()=>{
if (releaseData?.timezone) {
return releaseData.timezone;
} else {
if (dataTimezone?.data.defaultTimezone) {
return dataTimezone.data.defaultTimezone;
}
return null;
}
};
const toggleWarningSubmit = ()=>setWarningSubmit((prevState)=>!prevState);
if (isLoadingDetails || isLoadingTimezone) {
return /*#__PURE__*/ jsx(ReleaseDetailsLayout, {
toggleEditReleaseModal: toggleEditReleaseModal,
toggleWarningSubmit: toggleWarningSubmit,
children: /*#__PURE__*/ jsx(Page.Loading, {})
});
}
if (!releaseId) {
return /*#__PURE__*/ jsx(Navigate, {
to: ".."
});
}
const releaseData = isSuccessDetails && data?.data || null;
const title = releaseData?.name || '';
const timezone = getTimezoneValue();
const scheduledAt = releaseData?.scheduledAt && timezone ? utcToZonedTime(releaseData.scheduledAt, timezone) : null;
// Just get the date and time to display without considering updated timezone time
const date = scheduledAt ? format(scheduledAt, 'yyyy-MM-dd') : undefined;
const time = scheduledAt ? format(scheduledAt, 'HH:mm') : '';
const handleEditRelease = async (values)=>{
const response = await updateRelease({
id: releaseId,
name: values.name,
scheduledAt: values.scheduledAt,
timezone: values.timezone
});
if ('data' in response) {
// When the response returns an object with 'data', handle success
toggleNotification({
type: 'success',
message: formatMessage({
id: 'content-releases.modal.release-updated-notification-success',
defaultMessage: 'Release updated.'
})
});
toggleEditReleaseModal();
} else if (isFetchError(response.error)) {
// When the response returns an object with 'error', handle fetch error
toggleNotification({
type: 'danger',
message: formatAPIError(response.error)
});
} else {
// Otherwise, the response returns an object with 'error', handle a generic error
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
};
const handleDeleteRelease = async ()=>{
const response = await deleteRelease({
id: releaseId
});
if ('data' in response) {
navigate('..');
} else if (isFetchError(response.error)) {
// When the response returns an object with 'error', handle fetch error
toggleNotification({
type: 'danger',
message: formatAPIError(response.error)
});
} else {
// Otherwise, the response returns an object with 'error', handle a generic error
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
};
return /*#__PURE__*/ jsxs(ReleaseDetailsLayout, {
toggleEditReleaseModal: toggleEditReleaseModal,
toggleWarningSubmit: toggleWarningSubmit,
children: [
/*#__PURE__*/ jsx(ReleaseDetailsBody, {
releaseId: releaseId
}),
/*#__PURE__*/ jsx(ReleaseModal, {
open: releaseModalShown,
handleClose: toggleEditReleaseModal,
handleSubmit: handleEditRelease,
isLoading: isLoadingDetails || isSubmittingForm,
initialValues: {
name: title || '',
scheduledAt,
date,
time,
isScheduled: Boolean(scheduledAt),
timezone
}
}),
/*#__PURE__*/ jsx(Dialog.Root, {
open: showWarningSubmit,
onOpenChange: toggleWarningSubmit,
children: /*#__PURE__*/ jsx(ConfirmDialog, {
onConfirm: handleDeleteRelease,
children: formatMessage({
id: 'content-releases.dialog.confirmation-message',
defaultMessage: 'Are you sure you want to delete this release?'
})
})
})
]
});
};
export { ReleaseDetailsPage };
//# sourceMappingURL=ReleaseDetailsPage.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,397 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var ee = require('@strapi/admin/strapi-admin/ee');
var designSystem = require('@strapi/design-system');
var icons = require('@strapi/icons');
var symbols = require('@strapi/icons/symbols');
var dateFns = require('date-fns');
var reactIntl = require('react-intl');
var reactRouterDom = require('react-router-dom');
var styledComponents = require('styled-components');
var RelativeTime$1 = require('../components/RelativeTime.js');
var ReleaseModal = require('../components/ReleaseModal.js');
var constants = require('../constants.js');
var release = require('../services/release.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
const LinkCard = styledComponents.styled(designSystem.Link)`
display: block;
`;
const RelativeTime = styledComponents.styled(RelativeTime$1.RelativeTime)`
display: inline-block;
&::first-letter {
text-transform: uppercase;
}
`;
const getBadgeProps = (status)=>{
let color;
switch(status){
case 'ready':
color = 'success';
break;
case 'blocked':
color = 'warning';
break;
case 'failed':
color = 'danger';
break;
case 'done':
color = 'primary';
break;
case 'empty':
default:
color = 'neutral';
}
return {
textColor: `${color}600`,
backgroundColor: `${color}100`,
borderColor: `${color}200`
};
};
const ReleasesGrid = ({ sectionTitle, releases = [], isError = false })=>{
const { formatMessage } = reactIntl.useIntl();
if (isError) {
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Error, {});
}
if (releases?.length === 0) {
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.EmptyStateLayout, {
content: formatMessage({
id: 'content-releases.page.Releases.tab.emptyEntries',
defaultMessage: 'No releases'
}, {
target: sectionTitle
}),
icon: /*#__PURE__*/ jsxRuntime.jsx(symbols.EmptyDocuments, {
width: "16rem"
})
});
}
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.Grid.Root, {
gap: 4,
children: releases.map(({ id, name, scheduledAt, status })=>/*#__PURE__*/ jsxRuntime.jsx(designSystem.Grid.Item, {
col: 3,
s: 6,
xs: 12,
direction: "column",
alignItems: "stretch",
children: /*#__PURE__*/ jsxRuntime.jsx(LinkCard, {
tag: reactRouterDom.NavLink,
to: `${id}`,
isExternal: false,
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
justifyContent: "space-between",
padding: 4,
hasRadius: true,
background: "neutral0",
shadow: "tableShadow",
height: "100%",
width: "100%",
alignItems: "start",
gap: 4,
children: [
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
alignItems: "start",
gap: 1,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "neutral800",
tag: "h3",
variant: "delta",
fontWeight: "bold",
children: name
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
variant: "pi",
textColor: "neutral600",
children: scheduledAt ? /*#__PURE__*/ jsxRuntime.jsx(RelativeTime, {
timestamp: new Date(scheduledAt)
}) : formatMessage({
id: 'content-releases.pages.Releases.not-scheduled',
defaultMessage: 'Not scheduled'
})
})
]
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Badge, {
...getBadgeProps(status),
children: status
})
]
})
})
}, id))
});
};
/* -------------------------------------------------------------------------------------------------
* ReleasesPage
* -----------------------------------------------------------------------------------------------*/ const StyledAlert = styledComponents.styled(designSystem.Alert)`
button {
display: none;
}
p + div {
margin-left: auto;
}
`;
const INITIAL_FORM_VALUES = {
name: '',
date: dateFns.format(new Date(), 'yyyy-MM-dd'),
time: '',
isScheduled: true,
scheduledAt: null,
timezone: null
};
const ReleasesPage = ()=>{
const location = reactRouterDom.useLocation();
const [releaseModalShown, setReleaseModalShown] = React__namespace.useState(false);
const { toggleNotification } = strapiAdmin.useNotification();
const { formatMessage } = reactIntl.useIntl();
const navigate = reactRouterDom.useNavigate();
const { formatAPIError } = strapiAdmin.useAPIErrorHandler();
const [{ query }, setQuery] = strapiAdmin.useQueryParams();
const response = release.useGetReleasesQuery(query);
const { data, isLoading: isLoadingSettings } = release.useGetReleaseSettingsQuery();
const [createRelease, { isLoading: isSubmittingForm }] = release.useCreateReleaseMutation();
const { getFeature } = ee.useLicenseLimits();
const { maximumReleases = 3 } = getFeature('cms-content-releases');
const { trackUsage } = strapiAdmin.useTracking();
const { allowedActions: { canCreate } } = strapiAdmin.useRBAC(constants.PERMISSIONS);
const { isLoading: isLoadingReleases, isSuccess, isError } = response;
const activeTab = response?.currentData?.meta?.activeTab || 'pending';
// Check if we have some errors and show a notification to the user to explain the error
React__namespace.useEffect(()=>{
if (location?.state?.errors) {
toggleNotification({
type: 'danger',
title: formatMessage({
id: 'content-releases.pages.Releases.notification.error.title',
defaultMessage: 'Your request could not be processed.'
}),
message: formatMessage({
id: 'content-releases.pages.Releases.notification.error.message',
defaultMessage: 'Please try again or open another release.'
})
});
navigate('', {
replace: true,
state: null
});
}
}, [
formatMessage,
location?.state?.errors,
navigate,
toggleNotification
]);
const toggleAddReleaseModal = ()=>{
setReleaseModalShown((prev)=>!prev);
};
if (isLoadingReleases || isLoadingSettings) {
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Loading, {});
}
const totalPendingReleases = isSuccess && response.currentData?.meta?.pendingReleasesCount || 0;
const hasReachedMaximumPendingReleases = totalPendingReleases >= maximumReleases;
const handleTabChange = (tabValue)=>{
setQuery({
...query,
page: 1,
pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
filters: {
releasedAt: {
$notNull: tabValue !== 'pending'
}
}
});
};
const handleAddRelease = async ({ name, scheduledAt, timezone })=>{
const response = await createRelease({
name,
scheduledAt,
timezone
});
if ('data' in response) {
// When the response returns an object with 'data', handle success
toggleNotification({
type: 'success',
message: formatMessage({
id: 'content-releases.modal.release-created-notification-success',
defaultMessage: 'Release created.'
})
});
trackUsage('didCreateRelease');
navigate(response.data.data.id.toString());
} else if (strapiAdmin.isFetchError(response.error)) {
// When the response returns an object with 'error', handle fetch error
toggleNotification({
type: 'danger',
message: formatAPIError(response.error)
});
} else {
// Otherwise, the response returns an object with 'error', handle a generic error
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
};
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Main, {
"aria-busy": isLoadingReleases || isLoadingSettings,
children: [
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Layouts.Header, {
title: formatMessage({
id: 'content-releases.pages.Releases.title',
defaultMessage: 'Releases'
}),
subtitle: formatMessage({
id: 'content-releases.pages.Releases.header-subtitle',
defaultMessage: 'Create and manage content updates'
}),
primaryAction: canCreate ? /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
startIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.Plus, {}),
onClick: toggleAddReleaseModal,
disabled: hasReachedMaximumPendingReleases,
children: formatMessage({
id: 'content-releases.header.actions.add-release',
defaultMessage: 'New release'
})
}) : null
}),
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Layouts.Content, {
children: /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
children: [
hasReachedMaximumPendingReleases && /*#__PURE__*/ jsxRuntime.jsx(StyledAlert, {
marginBottom: 6,
action: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Link, {
href: "https://strapi.io/pricing-cloud",
isExternal: true,
children: formatMessage({
id: 'content-releases.pages.Releases.max-limit-reached.action',
defaultMessage: 'Explore plans'
})
}),
title: formatMessage({
id: 'content-releases.pages.Releases.max-limit-reached.title',
defaultMessage: 'You have reached the {number} pending {number, plural, one {release} other {releases}} limit.'
}, {
number: maximumReleases
}),
onClose: ()=>{},
closeLabel: "",
children: formatMessage({
id: 'content-releases.pages.Releases.max-limit-reached.message',
defaultMessage: 'Upgrade to manage an unlimited number of releases.'
})
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Tabs.Root, {
variant: "simple",
onValueChange: handleTabChange,
value: activeTab,
children: [
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Box, {
paddingBottom: 8,
children: [
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Tabs.List, {
"aria-label": formatMessage({
id: 'content-releases.pages.Releases.tab-group.label',
defaultMessage: 'Releases list'
}),
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Tabs.Trigger, {
value: "pending",
children: formatMessage({
id: 'content-releases.pages.Releases.tab.pending',
defaultMessage: 'Pending ({count})'
}, {
count: totalPendingReleases
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Tabs.Trigger, {
value: "done",
children: formatMessage({
id: 'content-releases.pages.Releases.tab.done',
defaultMessage: 'Done'
})
})
]
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Divider, {})
]
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Tabs.Content, {
value: "pending",
children: /*#__PURE__*/ jsxRuntime.jsx(ReleasesGrid, {
sectionTitle: "pending",
releases: response?.currentData?.data,
isError: isError
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Tabs.Content, {
value: "done",
children: /*#__PURE__*/ jsxRuntime.jsx(ReleasesGrid, {
sectionTitle: "done",
releases: response?.currentData?.data,
isError: isError
})
})
]
}),
/*#__PURE__*/ jsxRuntime.jsxs(strapiAdmin.Pagination.Root, {
...response?.currentData?.meta?.pagination,
defaultPageSize: response?.currentData?.meta?.pagination?.pageSize,
children: [
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Pagination.PageSize, {
options: [
'8',
'16',
'32',
'64'
]
}),
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Pagination.Links, {})
]
})
]
})
}),
/*#__PURE__*/ jsxRuntime.jsx(ReleaseModal.ReleaseModal, {
open: releaseModalShown,
handleClose: toggleAddReleaseModal,
handleSubmit: handleAddRelease,
isLoading: isSubmittingForm,
initialValues: {
...INITIAL_FORM_VALUES,
timezone: data?.data.defaultTimezone ? data.data.defaultTimezone.split('&')[1] : null
}
})
]
});
};
exports.ReleasesPage = ReleasesPage;
exports.getBadgeProps = getBadgeProps;
//# sourceMappingURL=ReleasesPage.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,375 @@
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import * as React from 'react';
import { useNotification, useAPIErrorHandler, useQueryParams, useTracking, useRBAC, Page, Layouts, Pagination, isFetchError } from '@strapi/admin/strapi-admin';
import { useLicenseLimits } from '@strapi/admin/strapi-admin/ee';
import { Link, Alert, Main, Button, Tabs, Box, Divider, EmptyStateLayout, Grid, Flex, Typography, Badge } from '@strapi/design-system';
import { Plus } from '@strapi/icons';
import { EmptyDocuments } from '@strapi/icons/symbols';
import { format } from 'date-fns';
import { useIntl } from 'react-intl';
import { useLocation, useNavigate, NavLink } from 'react-router-dom';
import { styled } from 'styled-components';
import { RelativeTime as RelativeTime$1 } from '../components/RelativeTime.mjs';
import { ReleaseModal } from '../components/ReleaseModal.mjs';
import { PERMISSIONS } from '../constants.mjs';
import { useGetReleasesQuery, useGetReleaseSettingsQuery, useCreateReleaseMutation } from '../services/release.mjs';
const LinkCard = styled(Link)`
display: block;
`;
const RelativeTime = styled(RelativeTime$1)`
display: inline-block;
&::first-letter {
text-transform: uppercase;
}
`;
const getBadgeProps = (status)=>{
let color;
switch(status){
case 'ready':
color = 'success';
break;
case 'blocked':
color = 'warning';
break;
case 'failed':
color = 'danger';
break;
case 'done':
color = 'primary';
break;
case 'empty':
default:
color = 'neutral';
}
return {
textColor: `${color}600`,
backgroundColor: `${color}100`,
borderColor: `${color}200`
};
};
const ReleasesGrid = ({ sectionTitle, releases = [], isError = false })=>{
const { formatMessage } = useIntl();
if (isError) {
return /*#__PURE__*/ jsx(Page.Error, {});
}
if (releases?.length === 0) {
return /*#__PURE__*/ jsx(EmptyStateLayout, {
content: formatMessage({
id: 'content-releases.page.Releases.tab.emptyEntries',
defaultMessage: 'No releases'
}, {
target: sectionTitle
}),
icon: /*#__PURE__*/ jsx(EmptyDocuments, {
width: "16rem"
})
});
}
return /*#__PURE__*/ jsx(Grid.Root, {
gap: 4,
children: releases.map(({ id, name, scheduledAt, status })=>/*#__PURE__*/ jsx(Grid.Item, {
col: 3,
s: 6,
xs: 12,
direction: "column",
alignItems: "stretch",
children: /*#__PURE__*/ jsx(LinkCard, {
tag: NavLink,
to: `${id}`,
isExternal: false,
children: /*#__PURE__*/ jsxs(Flex, {
direction: "column",
justifyContent: "space-between",
padding: 4,
hasRadius: true,
background: "neutral0",
shadow: "tableShadow",
height: "100%",
width: "100%",
alignItems: "start",
gap: 4,
children: [
/*#__PURE__*/ jsxs(Flex, {
direction: "column",
alignItems: "start",
gap: 1,
children: [
/*#__PURE__*/ jsx(Typography, {
textColor: "neutral800",
tag: "h3",
variant: "delta",
fontWeight: "bold",
children: name
}),
/*#__PURE__*/ jsx(Typography, {
variant: "pi",
textColor: "neutral600",
children: scheduledAt ? /*#__PURE__*/ jsx(RelativeTime, {
timestamp: new Date(scheduledAt)
}) : formatMessage({
id: 'content-releases.pages.Releases.not-scheduled',
defaultMessage: 'Not scheduled'
})
})
]
}),
/*#__PURE__*/ jsx(Badge, {
...getBadgeProps(status),
children: status
})
]
})
})
}, id))
});
};
/* -------------------------------------------------------------------------------------------------
* ReleasesPage
* -----------------------------------------------------------------------------------------------*/ const StyledAlert = styled(Alert)`
button {
display: none;
}
p + div {
margin-left: auto;
}
`;
const INITIAL_FORM_VALUES = {
name: '',
date: format(new Date(), 'yyyy-MM-dd'),
time: '',
isScheduled: true,
scheduledAt: null,
timezone: null
};
const ReleasesPage = ()=>{
const location = useLocation();
const [releaseModalShown, setReleaseModalShown] = React.useState(false);
const { toggleNotification } = useNotification();
const { formatMessage } = useIntl();
const navigate = useNavigate();
const { formatAPIError } = useAPIErrorHandler();
const [{ query }, setQuery] = useQueryParams();
const response = useGetReleasesQuery(query);
const { data, isLoading: isLoadingSettings } = useGetReleaseSettingsQuery();
const [createRelease, { isLoading: isSubmittingForm }] = useCreateReleaseMutation();
const { getFeature } = useLicenseLimits();
const { maximumReleases = 3 } = getFeature('cms-content-releases');
const { trackUsage } = useTracking();
const { allowedActions: { canCreate } } = useRBAC(PERMISSIONS);
const { isLoading: isLoadingReleases, isSuccess, isError } = response;
const activeTab = response?.currentData?.meta?.activeTab || 'pending';
// Check if we have some errors and show a notification to the user to explain the error
React.useEffect(()=>{
if (location?.state?.errors) {
toggleNotification({
type: 'danger',
title: formatMessage({
id: 'content-releases.pages.Releases.notification.error.title',
defaultMessage: 'Your request could not be processed.'
}),
message: formatMessage({
id: 'content-releases.pages.Releases.notification.error.message',
defaultMessage: 'Please try again or open another release.'
})
});
navigate('', {
replace: true,
state: null
});
}
}, [
formatMessage,
location?.state?.errors,
navigate,
toggleNotification
]);
const toggleAddReleaseModal = ()=>{
setReleaseModalShown((prev)=>!prev);
};
if (isLoadingReleases || isLoadingSettings) {
return /*#__PURE__*/ jsx(Page.Loading, {});
}
const totalPendingReleases = isSuccess && response.currentData?.meta?.pendingReleasesCount || 0;
const hasReachedMaximumPendingReleases = totalPendingReleases >= maximumReleases;
const handleTabChange = (tabValue)=>{
setQuery({
...query,
page: 1,
pageSize: response?.currentData?.meta?.pagination?.pageSize || 16,
filters: {
releasedAt: {
$notNull: tabValue !== 'pending'
}
}
});
};
const handleAddRelease = async ({ name, scheduledAt, timezone })=>{
const response = await createRelease({
name,
scheduledAt,
timezone
});
if ('data' in response) {
// When the response returns an object with 'data', handle success
toggleNotification({
type: 'success',
message: formatMessage({
id: 'content-releases.modal.release-created-notification-success',
defaultMessage: 'Release created.'
})
});
trackUsage('didCreateRelease');
navigate(response.data.data.id.toString());
} else if (isFetchError(response.error)) {
// When the response returns an object with 'error', handle fetch error
toggleNotification({
type: 'danger',
message: formatAPIError(response.error)
});
} else {
// Otherwise, the response returns an object with 'error', handle a generic error
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
};
return /*#__PURE__*/ jsxs(Main, {
"aria-busy": isLoadingReleases || isLoadingSettings,
children: [
/*#__PURE__*/ jsx(Layouts.Header, {
title: formatMessage({
id: 'content-releases.pages.Releases.title',
defaultMessage: 'Releases'
}),
subtitle: formatMessage({
id: 'content-releases.pages.Releases.header-subtitle',
defaultMessage: 'Create and manage content updates'
}),
primaryAction: canCreate ? /*#__PURE__*/ jsx(Button, {
startIcon: /*#__PURE__*/ jsx(Plus, {}),
onClick: toggleAddReleaseModal,
disabled: hasReachedMaximumPendingReleases,
children: formatMessage({
id: 'content-releases.header.actions.add-release',
defaultMessage: 'New release'
})
}) : null
}),
/*#__PURE__*/ jsx(Layouts.Content, {
children: /*#__PURE__*/ jsxs(Fragment, {
children: [
hasReachedMaximumPendingReleases && /*#__PURE__*/ jsx(StyledAlert, {
marginBottom: 6,
action: /*#__PURE__*/ jsx(Link, {
href: "https://strapi.io/pricing-cloud",
isExternal: true,
children: formatMessage({
id: 'content-releases.pages.Releases.max-limit-reached.action',
defaultMessage: 'Explore plans'
})
}),
title: formatMessage({
id: 'content-releases.pages.Releases.max-limit-reached.title',
defaultMessage: 'You have reached the {number} pending {number, plural, one {release} other {releases}} limit.'
}, {
number: maximumReleases
}),
onClose: ()=>{},
closeLabel: "",
children: formatMessage({
id: 'content-releases.pages.Releases.max-limit-reached.message',
defaultMessage: 'Upgrade to manage an unlimited number of releases.'
})
}),
/*#__PURE__*/ jsxs(Tabs.Root, {
variant: "simple",
onValueChange: handleTabChange,
value: activeTab,
children: [
/*#__PURE__*/ jsxs(Box, {
paddingBottom: 8,
children: [
/*#__PURE__*/ jsxs(Tabs.List, {
"aria-label": formatMessage({
id: 'content-releases.pages.Releases.tab-group.label',
defaultMessage: 'Releases list'
}),
children: [
/*#__PURE__*/ jsx(Tabs.Trigger, {
value: "pending",
children: formatMessage({
id: 'content-releases.pages.Releases.tab.pending',
defaultMessage: 'Pending ({count})'
}, {
count: totalPendingReleases
})
}),
/*#__PURE__*/ jsx(Tabs.Trigger, {
value: "done",
children: formatMessage({
id: 'content-releases.pages.Releases.tab.done',
defaultMessage: 'Done'
})
})
]
}),
/*#__PURE__*/ jsx(Divider, {})
]
}),
/*#__PURE__*/ jsx(Tabs.Content, {
value: "pending",
children: /*#__PURE__*/ jsx(ReleasesGrid, {
sectionTitle: "pending",
releases: response?.currentData?.data,
isError: isError
})
}),
/*#__PURE__*/ jsx(Tabs.Content, {
value: "done",
children: /*#__PURE__*/ jsx(ReleasesGrid, {
sectionTitle: "done",
releases: response?.currentData?.data,
isError: isError
})
})
]
}),
/*#__PURE__*/ jsxs(Pagination.Root, {
...response?.currentData?.meta?.pagination,
defaultPageSize: response?.currentData?.meta?.pagination?.pageSize,
children: [
/*#__PURE__*/ jsx(Pagination.PageSize, {
options: [
'8',
'16',
'32',
'64'
]
}),
/*#__PURE__*/ jsx(Pagination.Links, {})
]
})
]
})
}),
/*#__PURE__*/ jsx(ReleaseModal, {
open: releaseModalShown,
handleClose: toggleAddReleaseModal,
handleSubmit: handleAddRelease,
isLoading: isSubmittingForm,
initialValues: {
...INITIAL_FORM_VALUES,
timezone: data?.data.defaultTimezone ? data.data.defaultTimezone.split('&')[1] : null
}
})
]
});
};
export { ReleasesPage, getBadgeProps };
//# sourceMappingURL=ReleasesPage.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,199 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var icons = require('@strapi/icons');
var reactIntl = require('react-intl');
var hooks = require('../modules/hooks.js');
var release = require('../services/release.js');
var time = require('../utils/time.js');
var schemas = require('../validation/schemas.js');
const ReleasesSettingsPage = ()=>{
const { formatMessage } = reactIntl.useIntl();
const { formatAPIError } = strapiAdmin.useAPIErrorHandler();
const { toggleNotification } = strapiAdmin.useNotification();
const { data, isLoading: isLoadingSettings } = release.useGetReleaseSettingsQuery();
const [updateReleaseSettings, { isLoading: isSubmittingForm }] = release.useUpdateReleaseSettingsMutation();
const permissions = hooks.useTypedSelector((state)=>state.admin_app.permissions['settings']?.['releases']);
const { allowedActions: { canUpdate } } = strapiAdmin.useRBAC(permissions);
const { timezoneList } = time.getTimezones(new Date());
const handleSubmit = async (body)=>{
const { defaultTimezone } = body;
const isBodyTimezoneValid = timezoneList.some((timezone)=>timezone.value === defaultTimezone);
const newBody = !defaultTimezone || !isBodyTimezoneValid ? {
defaultTimezone: null
} : {
...body
};
try {
const response = await updateReleaseSettings(newBody);
if ('data' in response) {
toggleNotification({
type: 'success',
message: formatMessage({
id: 'content-releases.pages.Settings.releases.setting.default-timezone-notification-success',
defaultMessage: 'Default timezone updated.'
})
});
} else if (strapiAdmin.isFetchError(response.error)) {
toggleNotification({
type: 'danger',
message: formatAPIError(response.error)
});
} else {
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
} catch (error) {
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
};
if (isLoadingSettings) {
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Loading, {});
}
return /*#__PURE__*/ jsxRuntime.jsxs(strapiAdmin.Layouts.Root, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Title, {
children: formatMessage({
id: 'Settings.PageTitle',
defaultMessage: 'Settings - {name}'
}, {
name: 'Releases'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Main, {
"aria-busy": isLoadingSettings,
tabIndex: -1,
children: /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Form, {
method: "PUT",
initialValues: {
defaultTimezone: data?.data.defaultTimezone
},
onSubmit: handleSubmit,
validationSchema: schemas.SETTINGS_SCHEMA,
children: ({ modified, isSubmitting })=>{
return /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Layouts.Header, {
primaryAction: canUpdate ? /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
disabled: !modified || isSubmittingForm,
loading: isSubmitting,
startIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.Check, {}),
type: "submit",
children: formatMessage({
id: 'global.save',
defaultMessage: 'Save'
})
}) : null,
title: formatMessage({
id: 'content-releases.pages.Settings.releases.title',
defaultMessage: 'Releases'
}),
subtitle: formatMessage({
id: 'content-releases.pages.Settings.releases.description',
defaultMessage: 'Create and manage content updates'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Layouts.Content, {
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
background: "neutral0",
alignItems: "stretch",
padding: 6,
gap: 6,
shadow: "filterShadow",
hasRadius: true,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
variant: "delta",
tag: "h2",
children: formatMessage({
id: 'content-releases.pages.Settings.releases.preferences.title',
defaultMessage: 'Preferences'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Grid.Root, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Grid.Item, {
col: 6,
s: 12,
direction: "column",
alignItems: "stretch",
children: /*#__PURE__*/ jsxRuntime.jsx(TimezoneDropdown, {})
})
})
]
})
})
]
});
}
})
})
]
});
};
const TimezoneDropdown = ()=>{
const permissions = hooks.useTypedSelector((state)=>state.admin_app.permissions['settings']?.['releases']);
const { allowedActions: { canUpdate } } = strapiAdmin.useRBAC(permissions);
const { formatMessage } = reactIntl.useIntl();
const { timezoneList } = time.getTimezones(new Date());
const field = strapiAdmin.useField('defaultTimezone');
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Field.Root, {
name: "defaultTimezone",
hint: formatMessage({
id: 'content-releases.pages.Settings.releases.timezone.hint',
defaultMessage: 'The timezone of every release can still be changed individually. '
}),
error: field.error,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Label, {
children: formatMessage({
id: 'content-releases.pages.Settings.releases.timezone.label',
defaultMessage: 'Default timezone'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Combobox, {
autocomplete: {
type: 'list',
filter: 'contains'
},
onChange: (value)=>field.onChange('defaultTimezone', value),
onTextValueChange: (value)=>field.onChange('defaultTimezone', value),
onClear: ()=>field.onChange('defaultTimezone', ''),
value: field.value,
disabled: !canUpdate,
children: timezoneList.map((timezone)=>/*#__PURE__*/ jsxRuntime.jsx(designSystem.ComboboxOption, {
value: timezone.value,
children: timezone.value.replace(/&/, ' ')
}, timezone.value))
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Hint, {}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Error, {})
]
});
};
/* -------------------------------------------------------------------------------------------------
* ProtectedSettingsPage
* -----------------------------------------------------------------------------------------------*/ const ProtectedReleasesSettingsPage = ()=>{
const permissions = hooks.useTypedSelector((state)=>state.admin_app.permissions['settings']?.['releases']?.read);
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Protect, {
permissions: permissions,
children: /*#__PURE__*/ jsxRuntime.jsx(ReleasesSettingsPage, {})
});
};
exports.ProtectedReleasesSettingsPage = ProtectedReleasesSettingsPage;
//# sourceMappingURL=ReleasesSettingsPage.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,197 @@
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import { Page, useAPIErrorHandler, useNotification, useRBAC, Layouts, Form, useField, isFetchError } from '@strapi/admin/strapi-admin';
import { Button, Flex, Typography, Grid, Field, Combobox, ComboboxOption } from '@strapi/design-system';
import { Check } from '@strapi/icons';
import { useIntl } from 'react-intl';
import { useTypedSelector } from '../modules/hooks.mjs';
import { useGetReleaseSettingsQuery, useUpdateReleaseSettingsMutation } from '../services/release.mjs';
import { getTimezones } from '../utils/time.mjs';
import { SETTINGS_SCHEMA } from '../validation/schemas.mjs';
const ReleasesSettingsPage = ()=>{
const { formatMessage } = useIntl();
const { formatAPIError } = useAPIErrorHandler();
const { toggleNotification } = useNotification();
const { data, isLoading: isLoadingSettings } = useGetReleaseSettingsQuery();
const [updateReleaseSettings, { isLoading: isSubmittingForm }] = useUpdateReleaseSettingsMutation();
const permissions = useTypedSelector((state)=>state.admin_app.permissions['settings']?.['releases']);
const { allowedActions: { canUpdate } } = useRBAC(permissions);
const { timezoneList } = getTimezones(new Date());
const handleSubmit = async (body)=>{
const { defaultTimezone } = body;
const isBodyTimezoneValid = timezoneList.some((timezone)=>timezone.value === defaultTimezone);
const newBody = !defaultTimezone || !isBodyTimezoneValid ? {
defaultTimezone: null
} : {
...body
};
try {
const response = await updateReleaseSettings(newBody);
if ('data' in response) {
toggleNotification({
type: 'success',
message: formatMessage({
id: 'content-releases.pages.Settings.releases.setting.default-timezone-notification-success',
defaultMessage: 'Default timezone updated.'
})
});
} else if (isFetchError(response.error)) {
toggleNotification({
type: 'danger',
message: formatAPIError(response.error)
});
} else {
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
} catch (error) {
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
};
if (isLoadingSettings) {
return /*#__PURE__*/ jsx(Page.Loading, {});
}
return /*#__PURE__*/ jsxs(Layouts.Root, {
children: [
/*#__PURE__*/ jsx(Page.Title, {
children: formatMessage({
id: 'Settings.PageTitle',
defaultMessage: 'Settings - {name}'
}, {
name: 'Releases'
})
}),
/*#__PURE__*/ jsx(Page.Main, {
"aria-busy": isLoadingSettings,
tabIndex: -1,
children: /*#__PURE__*/ jsx(Form, {
method: "PUT",
initialValues: {
defaultTimezone: data?.data.defaultTimezone
},
onSubmit: handleSubmit,
validationSchema: SETTINGS_SCHEMA,
children: ({ modified, isSubmitting })=>{
return /*#__PURE__*/ jsxs(Fragment, {
children: [
/*#__PURE__*/ jsx(Layouts.Header, {
primaryAction: canUpdate ? /*#__PURE__*/ jsx(Button, {
disabled: !modified || isSubmittingForm,
loading: isSubmitting,
startIcon: /*#__PURE__*/ jsx(Check, {}),
type: "submit",
children: formatMessage({
id: 'global.save',
defaultMessage: 'Save'
})
}) : null,
title: formatMessage({
id: 'content-releases.pages.Settings.releases.title',
defaultMessage: 'Releases'
}),
subtitle: formatMessage({
id: 'content-releases.pages.Settings.releases.description',
defaultMessage: 'Create and manage content updates'
})
}),
/*#__PURE__*/ jsx(Layouts.Content, {
children: /*#__PURE__*/ jsxs(Flex, {
direction: "column",
background: "neutral0",
alignItems: "stretch",
padding: 6,
gap: 6,
shadow: "filterShadow",
hasRadius: true,
children: [
/*#__PURE__*/ jsx(Typography, {
variant: "delta",
tag: "h2",
children: formatMessage({
id: 'content-releases.pages.Settings.releases.preferences.title',
defaultMessage: 'Preferences'
})
}),
/*#__PURE__*/ jsx(Grid.Root, {
children: /*#__PURE__*/ jsx(Grid.Item, {
col: 6,
s: 12,
direction: "column",
alignItems: "stretch",
children: /*#__PURE__*/ jsx(TimezoneDropdown, {})
})
})
]
})
})
]
});
}
})
})
]
});
};
const TimezoneDropdown = ()=>{
const permissions = useTypedSelector((state)=>state.admin_app.permissions['settings']?.['releases']);
const { allowedActions: { canUpdate } } = useRBAC(permissions);
const { formatMessage } = useIntl();
const { timezoneList } = getTimezones(new Date());
const field = useField('defaultTimezone');
return /*#__PURE__*/ jsxs(Field.Root, {
name: "defaultTimezone",
hint: formatMessage({
id: 'content-releases.pages.Settings.releases.timezone.hint',
defaultMessage: 'The timezone of every release can still be changed individually. '
}),
error: field.error,
children: [
/*#__PURE__*/ jsx(Field.Label, {
children: formatMessage({
id: 'content-releases.pages.Settings.releases.timezone.label',
defaultMessage: 'Default timezone'
})
}),
/*#__PURE__*/ jsx(Combobox, {
autocomplete: {
type: 'list',
filter: 'contains'
},
onChange: (value)=>field.onChange('defaultTimezone', value),
onTextValueChange: (value)=>field.onChange('defaultTimezone', value),
onClear: ()=>field.onChange('defaultTimezone', ''),
value: field.value,
disabled: !canUpdate,
children: timezoneList.map((timezone)=>/*#__PURE__*/ jsx(ComboboxOption, {
value: timezone.value,
children: timezone.value.replace(/&/, ' ')
}, timezone.value))
}),
/*#__PURE__*/ jsx(Field.Hint, {}),
/*#__PURE__*/ jsx(Field.Error, {})
]
});
};
/* -------------------------------------------------------------------------------------------------
* ProtectedSettingsPage
* -----------------------------------------------------------------------------------------------*/ const ProtectedReleasesSettingsPage = ()=>{
const permissions = useTypedSelector((state)=>state.admin_app.permissions['settings']?.['releases']?.read);
return /*#__PURE__*/ jsx(Page.Protect, {
permissions: permissions,
children: /*#__PURE__*/ jsx(ReleasesSettingsPage, {})
});
};
export { ProtectedReleasesSettingsPage };
//# sourceMappingURL=ReleasesSettingsPage.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,6 @@
'use strict';
const pluginId = 'content-releases';
exports.pluginId = pluginId;
//# sourceMappingURL=pluginId.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"pluginId.js","sources":["../../admin/src/pluginId.ts"],"sourcesContent":["export const pluginId = 'content-releases';\n"],"names":["pluginId"],"mappings":";;AAAO,MAAMA,WAAW;;;;"}

View File

@@ -0,0 +1,4 @@
const pluginId = 'content-releases';
export { pluginId };
//# sourceMappingURL=pluginId.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"pluginId.mjs","sources":["../../admin/src/pluginId.ts"],"sourcesContent":["export const pluginId = 'content-releases';\n"],"names":["pluginId"],"mappings":"AAAO,MAAMA,WAAW;;;;"}

View File

@@ -0,0 +1,464 @@
'use strict';
var strapiAdmin = require('@strapi/admin/strapi-admin');
// TODO: move this into the admin code & expose an improved version of enhanceEndpoints or a new function
const extendInvalidatesTags = (endpoint, extraTags)=>{
if (!endpoint) {
return;
}
const originalInvalidatesTags = endpoint.invalidatesTags;
const newInvalidatesTags = (result, err, args, meta)=>{
const originalTags = typeof originalInvalidatesTags === 'function' ? originalInvalidatesTags(result, err, args, meta) : originalInvalidatesTags;
return [
...originalTags ?? [],
...extraTags
];
};
Object.assign(endpoint, {
invalidatesTags: newInvalidatesTags
});
};
const releaseApi = strapiAdmin.adminApi.enhanceEndpoints({
addTagTypes: [
'Release',
'ReleaseAction',
'EntriesInRelease',
'ReleaseSettings',
'Document'
],
endpoints: {
updateDocument (endpoint) {
extendInvalidatesTags(endpoint, [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]);
},
deleteDocument (endpoint) {
extendInvalidatesTags(endpoint, [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]);
},
deleteManyDocuments (endpoint) {
extendInvalidatesTags(endpoint, [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]);
},
discardDocument (endpoint) {
extendInvalidatesTags(endpoint, [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]);
},
createWorkflow (endpoint) {
extendInvalidatesTags(endpoint, [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]);
},
updateWorkflow (endpoint) {
extendInvalidatesTags(endpoint, [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]);
},
deleteWorkflow (endpoint) {
extendInvalidatesTags(endpoint, [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]);
}
}
}).injectEndpoints({
endpoints: (build)=>{
return {
getReleasesForEntry: build.query({
query (params) {
return {
url: '/content-releases/getByDocumentAttached',
method: 'GET',
config: {
params
}
};
},
providesTags: (result)=>result ? [
...result.data.map(({ id })=>({
type: 'Release',
id
})),
{
type: 'Release',
id: 'LIST'
}
] : []
}),
getReleases: build.query({
query ({ page, pageSize, filters } = {
page: 1,
pageSize: 16,
filters: {
releasedAt: {
$notNull: false
}
}
}) {
return {
url: '/content-releases',
method: 'GET',
config: {
params: {
page: page || 1,
pageSize: pageSize || 16,
filters: filters || {
releasedAt: {
$notNull: false
}
}
}
}
};
},
transformResponse (response, meta, arg) {
const releasedAtValue = arg?.filters?.releasedAt?.$notNull;
const isActiveDoneTab = releasedAtValue === 'true';
const newResponse = {
...response,
meta: {
...response.meta,
activeTab: isActiveDoneTab ? 'done' : 'pending'
}
};
return newResponse;
},
providesTags: (result)=>result ? [
...result.data.map(({ id })=>({
type: 'Release',
id
})),
{
type: 'Release',
id: 'LIST'
}
] : [
{
type: 'Release',
id: 'LIST'
}
]
}),
getRelease: build.query({
query ({ id }) {
return {
url: `/content-releases/${id}`,
method: 'GET'
};
},
providesTags: (result, error, arg)=>[
{
type: 'Release',
id: 'LIST'
},
{
type: 'Release',
id: arg.id
}
]
}),
getReleaseActions: build.query({
query ({ releaseId, ...params }) {
return {
url: `/content-releases/${releaseId}/actions`,
method: 'GET',
config: {
params
}
};
},
providesTags: [
{
type: 'ReleaseAction',
id: 'LIST'
}
]
}),
createRelease: build.mutation({
query (data) {
return {
url: '/content-releases',
method: 'POST',
data
};
},
invalidatesTags: [
{
type: 'Release',
id: 'LIST'
}
]
}),
updateRelease: build.mutation({
query ({ id, ...data }) {
return {
url: `/content-releases/${id}`,
method: 'PUT',
data
};
},
invalidatesTags: (result, error, arg)=>[
{
type: 'Release',
id: arg.id
}
]
}),
createReleaseAction: build.mutation({
query ({ body, params }) {
return {
url: `/content-releases/${params.releaseId}/actions`,
method: 'POST',
data: body
};
},
invalidatesTags: [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]
}),
createManyReleaseActions: build.mutation({
query ({ body, params }) {
return {
url: `/content-releases/${params.releaseId}/actions/bulk`,
method: 'POST',
data: body
};
},
invalidatesTags: [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
},
{
type: 'EntriesInRelease'
}
]
}),
updateReleaseAction: build.mutation({
query ({ body, params }) {
return {
url: `/content-releases/${params.releaseId}/actions/${params.actionId}`,
method: 'PUT',
data: body
};
},
invalidatesTags: (res, error, arg)=>[
{
type: 'ReleaseAction',
id: 'LIST'
},
{
type: 'Release',
id: 'LIST'
},
{
type: 'Release',
id: arg.params.releaseId
}
],
async onQueryStarted ({ body, params, query, actionPath }, { dispatch, queryFulfilled }) {
// We need to mimic the same params received by the getReleaseActions query
const paramsWithoutActionId = {
releaseId: params.releaseId,
...query
};
const patchResult = dispatch(releaseApi.util.updateQueryData('getReleaseActions', paramsWithoutActionId, (draft)=>{
const [key, index] = actionPath;
const action = draft.data[key][index];
if (action) {
action.type = body.type;
}
}));
try {
await queryFulfilled;
} catch {
patchResult.undo();
}
}
}),
deleteReleaseAction: build.mutation({
query ({ params }) {
return {
url: `/content-releases/${params.releaseId}/actions/${params.actionId}`,
method: 'DELETE'
};
},
invalidatesTags: (result, error, arg)=>[
{
type: 'Release',
id: 'LIST'
},
{
type: 'Release',
id: arg.params.releaseId
},
{
type: 'ReleaseAction',
id: 'LIST'
},
{
type: 'EntriesInRelease'
}
]
}),
publishRelease: build.mutation({
query ({ id }) {
return {
url: `/content-releases/${id}/publish`,
method: 'POST'
};
},
invalidatesTags: (result, error, arg)=>[
{
type: 'Release',
id: arg.id
},
{
type: 'Document',
id: `ALL_LIST`
}
]
}),
deleteRelease: build.mutation({
query ({ id }) {
return {
url: `/content-releases/${id}`,
method: 'DELETE'
};
},
invalidatesTags: ()=>[
{
type: 'Release',
id: 'LIST'
},
{
type: 'EntriesInRelease'
}
]
}),
getMappedEntriesInReleases: build.query({
query (params) {
return {
url: '/content-releases/mapEntriesToReleases',
method: 'GET',
config: {
params
}
};
},
transformResponse (response) {
return response.data;
},
providesTags: [
{
type: 'EntriesInRelease'
}
]
}),
getReleaseSettings: build.query({
query: ()=>'/content-releases/settings',
providesTags: [
{
type: 'ReleaseSettings'
}
]
}),
updateReleaseSettings: build.mutation({
query (data) {
return {
url: '/content-releases/settings',
method: 'PUT',
data
};
},
invalidatesTags: [
{
type: 'ReleaseSettings'
}
]
})
};
}
});
const { useGetReleasesQuery, useGetReleasesForEntryQuery, useGetReleaseQuery, useGetReleaseActionsQuery, useCreateReleaseMutation, useCreateReleaseActionMutation, useCreateManyReleaseActionsMutation, useUpdateReleaseMutation, useUpdateReleaseActionMutation, usePublishReleaseMutation, useDeleteReleaseActionMutation, useDeleteReleaseMutation, useGetMappedEntriesInReleasesQuery, useGetReleaseSettingsQuery, useUpdateReleaseSettingsMutation } = releaseApi;
exports.releaseApi = releaseApi;
exports.useCreateManyReleaseActionsMutation = useCreateManyReleaseActionsMutation;
exports.useCreateReleaseActionMutation = useCreateReleaseActionMutation;
exports.useCreateReleaseMutation = useCreateReleaseMutation;
exports.useDeleteReleaseActionMutation = useDeleteReleaseActionMutation;
exports.useDeleteReleaseMutation = useDeleteReleaseMutation;
exports.useGetMappedEntriesInReleasesQuery = useGetMappedEntriesInReleasesQuery;
exports.useGetReleaseActionsQuery = useGetReleaseActionsQuery;
exports.useGetReleaseQuery = useGetReleaseQuery;
exports.useGetReleaseSettingsQuery = useGetReleaseSettingsQuery;
exports.useGetReleasesForEntryQuery = useGetReleasesForEntryQuery;
exports.useGetReleasesQuery = useGetReleasesQuery;
exports.usePublishReleaseMutation = usePublishReleaseMutation;
exports.useUpdateReleaseActionMutation = useUpdateReleaseActionMutation;
exports.useUpdateReleaseMutation = useUpdateReleaseMutation;
exports.useUpdateReleaseSettingsMutation = useUpdateReleaseSettingsMutation;
//# sourceMappingURL=release.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,447 @@
import { adminApi } from '@strapi/admin/strapi-admin';
// TODO: move this into the admin code & expose an improved version of enhanceEndpoints or a new function
const extendInvalidatesTags = (endpoint, extraTags)=>{
if (!endpoint) {
return;
}
const originalInvalidatesTags = endpoint.invalidatesTags;
const newInvalidatesTags = (result, err, args, meta)=>{
const originalTags = typeof originalInvalidatesTags === 'function' ? originalInvalidatesTags(result, err, args, meta) : originalInvalidatesTags;
return [
...originalTags ?? [],
...extraTags
];
};
Object.assign(endpoint, {
invalidatesTags: newInvalidatesTags
});
};
const releaseApi = adminApi.enhanceEndpoints({
addTagTypes: [
'Release',
'ReleaseAction',
'EntriesInRelease',
'ReleaseSettings',
'Document'
],
endpoints: {
updateDocument (endpoint) {
extendInvalidatesTags(endpoint, [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]);
},
deleteDocument (endpoint) {
extendInvalidatesTags(endpoint, [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]);
},
deleteManyDocuments (endpoint) {
extendInvalidatesTags(endpoint, [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]);
},
discardDocument (endpoint) {
extendInvalidatesTags(endpoint, [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]);
},
createWorkflow (endpoint) {
extendInvalidatesTags(endpoint, [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]);
},
updateWorkflow (endpoint) {
extendInvalidatesTags(endpoint, [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]);
},
deleteWorkflow (endpoint) {
extendInvalidatesTags(endpoint, [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]);
}
}
}).injectEndpoints({
endpoints: (build)=>{
return {
getReleasesForEntry: build.query({
query (params) {
return {
url: '/content-releases/getByDocumentAttached',
method: 'GET',
config: {
params
}
};
},
providesTags: (result)=>result ? [
...result.data.map(({ id })=>({
type: 'Release',
id
})),
{
type: 'Release',
id: 'LIST'
}
] : []
}),
getReleases: build.query({
query ({ page, pageSize, filters } = {
page: 1,
pageSize: 16,
filters: {
releasedAt: {
$notNull: false
}
}
}) {
return {
url: '/content-releases',
method: 'GET',
config: {
params: {
page: page || 1,
pageSize: pageSize || 16,
filters: filters || {
releasedAt: {
$notNull: false
}
}
}
}
};
},
transformResponse (response, meta, arg) {
const releasedAtValue = arg?.filters?.releasedAt?.$notNull;
const isActiveDoneTab = releasedAtValue === 'true';
const newResponse = {
...response,
meta: {
...response.meta,
activeTab: isActiveDoneTab ? 'done' : 'pending'
}
};
return newResponse;
},
providesTags: (result)=>result ? [
...result.data.map(({ id })=>({
type: 'Release',
id
})),
{
type: 'Release',
id: 'LIST'
}
] : [
{
type: 'Release',
id: 'LIST'
}
]
}),
getRelease: build.query({
query ({ id }) {
return {
url: `/content-releases/${id}`,
method: 'GET'
};
},
providesTags: (result, error, arg)=>[
{
type: 'Release',
id: 'LIST'
},
{
type: 'Release',
id: arg.id
}
]
}),
getReleaseActions: build.query({
query ({ releaseId, ...params }) {
return {
url: `/content-releases/${releaseId}/actions`,
method: 'GET',
config: {
params
}
};
},
providesTags: [
{
type: 'ReleaseAction',
id: 'LIST'
}
]
}),
createRelease: build.mutation({
query (data) {
return {
url: '/content-releases',
method: 'POST',
data
};
},
invalidatesTags: [
{
type: 'Release',
id: 'LIST'
}
]
}),
updateRelease: build.mutation({
query ({ id, ...data }) {
return {
url: `/content-releases/${id}`,
method: 'PUT',
data
};
},
invalidatesTags: (result, error, arg)=>[
{
type: 'Release',
id: arg.id
}
]
}),
createReleaseAction: build.mutation({
query ({ body, params }) {
return {
url: `/content-releases/${params.releaseId}/actions`,
method: 'POST',
data: body
};
},
invalidatesTags: [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
}
]
}),
createManyReleaseActions: build.mutation({
query ({ body, params }) {
return {
url: `/content-releases/${params.releaseId}/actions/bulk`,
method: 'POST',
data: body
};
},
invalidatesTags: [
{
type: 'Release',
id: 'LIST'
},
{
type: 'ReleaseAction',
id: 'LIST'
},
{
type: 'EntriesInRelease'
}
]
}),
updateReleaseAction: build.mutation({
query ({ body, params }) {
return {
url: `/content-releases/${params.releaseId}/actions/${params.actionId}`,
method: 'PUT',
data: body
};
},
invalidatesTags: (res, error, arg)=>[
{
type: 'ReleaseAction',
id: 'LIST'
},
{
type: 'Release',
id: 'LIST'
},
{
type: 'Release',
id: arg.params.releaseId
}
],
async onQueryStarted ({ body, params, query, actionPath }, { dispatch, queryFulfilled }) {
// We need to mimic the same params received by the getReleaseActions query
const paramsWithoutActionId = {
releaseId: params.releaseId,
...query
};
const patchResult = dispatch(releaseApi.util.updateQueryData('getReleaseActions', paramsWithoutActionId, (draft)=>{
const [key, index] = actionPath;
const action = draft.data[key][index];
if (action) {
action.type = body.type;
}
}));
try {
await queryFulfilled;
} catch {
patchResult.undo();
}
}
}),
deleteReleaseAction: build.mutation({
query ({ params }) {
return {
url: `/content-releases/${params.releaseId}/actions/${params.actionId}`,
method: 'DELETE'
};
},
invalidatesTags: (result, error, arg)=>[
{
type: 'Release',
id: 'LIST'
},
{
type: 'Release',
id: arg.params.releaseId
},
{
type: 'ReleaseAction',
id: 'LIST'
},
{
type: 'EntriesInRelease'
}
]
}),
publishRelease: build.mutation({
query ({ id }) {
return {
url: `/content-releases/${id}/publish`,
method: 'POST'
};
},
invalidatesTags: (result, error, arg)=>[
{
type: 'Release',
id: arg.id
},
{
type: 'Document',
id: `ALL_LIST`
}
]
}),
deleteRelease: build.mutation({
query ({ id }) {
return {
url: `/content-releases/${id}`,
method: 'DELETE'
};
},
invalidatesTags: ()=>[
{
type: 'Release',
id: 'LIST'
},
{
type: 'EntriesInRelease'
}
]
}),
getMappedEntriesInReleases: build.query({
query (params) {
return {
url: '/content-releases/mapEntriesToReleases',
method: 'GET',
config: {
params
}
};
},
transformResponse (response) {
return response.data;
},
providesTags: [
{
type: 'EntriesInRelease'
}
]
}),
getReleaseSettings: build.query({
query: ()=>'/content-releases/settings',
providesTags: [
{
type: 'ReleaseSettings'
}
]
}),
updateReleaseSettings: build.mutation({
query (data) {
return {
url: '/content-releases/settings',
method: 'PUT',
data
};
},
invalidatesTags: [
{
type: 'ReleaseSettings'
}
]
})
};
}
});
const { useGetReleasesQuery, useGetReleasesForEntryQuery, useGetReleaseQuery, useGetReleaseActionsQuery, useCreateReleaseMutation, useCreateReleaseActionMutation, useCreateManyReleaseActionsMutation, useUpdateReleaseMutation, useUpdateReleaseActionMutation, usePublishReleaseMutation, useDeleteReleaseActionMutation, useDeleteReleaseMutation, useGetMappedEntriesInReleasesQuery, useGetReleaseSettingsQuery, useUpdateReleaseSettingsMutation } = releaseApi;
export { releaseApi, useCreateManyReleaseActionsMutation, useCreateReleaseActionMutation, useCreateReleaseMutation, useDeleteReleaseActionMutation, useDeleteReleaseMutation, useGetMappedEntriesInReleasesQuery, useGetReleaseActionsQuery, useGetReleaseQuery, useGetReleaseSettingsQuery, useGetReleasesForEntryQuery, useGetReleasesQuery, usePublishReleaseMutation, useUpdateReleaseActionMutation, useUpdateReleaseMutation, useUpdateReleaseSettingsMutation };
//# sourceMappingURL=release.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,13 @@
import type { ReleaseAction, ReleaseActionEntry, Stage } from '../../../shared/contracts/release-actions';
import type { Struct } from '@strapi/types';
interface EntryValidationPopoverProps {
action: ReleaseAction['type'];
schema?: Struct.ContentTypeSchema & {
hasReviewWorkflow: boolean;
stageRequiredToPublish?: Stage;
};
entry: ReleaseActionEntry;
status: ReleaseAction['status'];
}
export declare const EntryValidationPopover: ({ schema, entry, status, action, }: EntryValidationPopoverProps) => import("react/jsx-runtime").JSX.Element | null;
export {};

View File

@@ -0,0 +1,28 @@
import * as React from 'react';
import { Duration } from 'date-fns';
interface CustomInterval {
unit: keyof Duration;
text: string;
threshold: number;
}
interface RelativeTimeProps extends React.ComponentPropsWithoutRef<'time'> {
timestamp: Date;
customIntervals?: CustomInterval[];
}
/**
* Displays the relative time between a given timestamp and the current time.
* You can display a custom message for given time intervals by passing an array of custom intervals.
*
* @example
* ```jsx
* <caption>Display "last hour" if the timestamp is less than an hour ago</caption>
* <RelativeTime
* timestamp={new Date('2021-01-01')}
* customIntervals={[
* { unit: 'hours', threshold: 1, text: 'last hour' },
* ]}
* ```
*/
declare const RelativeTime: React.ForwardRefExoticComponent<RelativeTimeProps & React.RefAttributes<HTMLTimeElement>>;
export { RelativeTime };
export type { CustomInterval, RelativeTimeProps };

View File

@@ -0,0 +1,3 @@
import type { BulkActionComponent } from '@strapi/content-manager/strapi-admin';
declare const ReleaseAction: BulkActionComponent;
export { ReleaseAction };

View File

@@ -0,0 +1,26 @@
import * as React from 'react';
import { DeleteReleaseAction, ReleaseAction } from '../../../shared/contracts/release-actions';
import { Release } from '../../../shared/contracts/releases';
interface DeleteReleaseActionItemProps {
releaseId: DeleteReleaseAction.Request['params']['releaseId'];
actionId: DeleteReleaseAction.Request['params']['actionId'];
}
interface ReleaseActionEntryLinkItemProps {
contentTypeUid: ReleaseAction['contentType'];
documentId: ReleaseAction['entry']['documentId'];
locale: ReleaseAction['locale'];
}
interface EditReleaseItemProps {
releaseId: Release['id'];
}
interface RootProps {
children: React.ReactNode;
hasTriggerBorder?: boolean;
}
export declare const ReleaseActionMenu: {
Root: ({ children }: RootProps) => import("react/jsx-runtime").JSX.Element | null;
EditReleaseItem: ({ releaseId }: EditReleaseItemProps) => import("react/jsx-runtime").JSX.Element;
DeleteReleaseActionItem: ({ releaseId, actionId }: DeleteReleaseActionItemProps) => import("react/jsx-runtime").JSX.Element | null;
ReleaseActionEntryLinkItem: ({ contentTypeUid, documentId, locale, }: ReleaseActionEntryLinkItemProps) => import("react/jsx-runtime").JSX.Element | null;
};
export {};

View File

@@ -0,0 +1,24 @@
import * as yup from 'yup';
import { CreateReleaseAction } from '../../../shared/contracts/release-actions';
import type { DocumentActionComponent } from '@strapi/content-manager/strapi-admin';
export declare const RELEASE_ACTION_FORM_SCHEMA: yup.default<import("yup/lib/object").Assign<import("yup/lib/object").ObjectShape, {
type: import("yup/lib/string").RequiredStringSchema<string | undefined, Record<string, any>>;
releaseId: import("yup/lib/string").RequiredStringSchema<string | undefined, Record<string, any>>;
}>, Record<string, any>, import("yup/lib/object").TypeOfShape<import("yup/lib/object").Assign<import("yup/lib/object").ObjectShape, {
type: import("yup/lib/string").RequiredStringSchema<string | undefined, Record<string, any>>;
releaseId: import("yup/lib/string").RequiredStringSchema<string | undefined, Record<string, any>>;
}>>, import("yup/lib/object").AssertsShape<import("yup/lib/object").Assign<import("yup/lib/object").ObjectShape, {
type: import("yup/lib/string").RequiredStringSchema<string | undefined, Record<string, any>>;
releaseId: import("yup/lib/string").RequiredStringSchema<string | undefined, Record<string, any>>;
}>>>;
export interface FormValues {
type: CreateReleaseAction.Request['body']['type'];
releaseId: CreateReleaseAction.Request['params']['releaseId'];
}
export declare const INITIAL_VALUES: {
type: "publish";
releaseId: string;
};
export declare const NoReleases: () => import("react/jsx-runtime").JSX.Element;
declare const ReleaseActionModalForm: DocumentActionComponent;
export { ReleaseActionModalForm };

View File

@@ -0,0 +1,9 @@
import * as React from 'react';
interface ActionOptionProps {
selected: 'publish' | 'unpublish';
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
name: string;
disabled?: boolean;
}
export declare const ReleaseActionOptions: ({ selected, handleChange, name, disabled, }: ActionOptionProps) => import("react/jsx-runtime").JSX.Element;
export {};

View File

@@ -0,0 +1,28 @@
import { ListFieldLayout, ListLayout } from '@strapi/content-manager/strapi-admin';
import type { Modules, UID } from '@strapi/types';
interface AddColumnToTableHookArgs {
layout: ListLayout;
displayedHeaders: ListFieldLayout[];
}
declare const addColumnToTableHook: ({ displayedHeaders, layout }: AddColumnToTableHookArgs) => {
displayedHeaders: (ListFieldLayout | {
searchable: boolean;
sortable: boolean;
name: string;
label: {
id: string;
defaultMessage: string;
};
cellFormatter: (props: Modules.Documents.AnyDocument, _: any, { model }: {
model: UID.ContentType;
}) => import("react/jsx-runtime").JSX.Element;
})[];
layout: ListLayout;
};
interface ReleaseListCellProps extends Modules.Documents.AnyDocument {
documentId: Modules.Documents.ID;
model: UID.ContentType;
}
declare const ReleaseListCell: ({ documentId, model }: ReleaseListCellProps) => import("react/jsx-runtime").JSX.Element;
export { ReleaseListCell, addColumnToTableHook };
export type { ReleaseListCellProps };

View File

@@ -0,0 +1,17 @@
export interface FormValues {
name: string;
date?: string;
time: string;
timezone: string | null;
isScheduled?: boolean;
scheduledAt: Date | null;
}
interface ReleaseModalProps {
handleClose: () => void;
handleSubmit: (values: FormValues) => void;
isLoading?: boolean;
initialValues: FormValues;
open?: boolean;
}
export declare const ReleaseModal: ({ handleClose, open, handleSubmit, initialValues, isLoading, }: ReleaseModalProps) => import("react/jsx-runtime").JSX.Element;
export {};

View File

@@ -0,0 +1,3 @@
import type { PanelComponent } from '@strapi/content-manager/strapi-admin';
declare const Panel: PanelComponent;
export { Panel };

View File

@@ -0,0 +1,76 @@
export declare const PERMISSIONS: {
main: {
action: string;
subject: null;
id: string;
actionParameters: {};
properties: {};
conditions: never[];
}[];
create: {
action: string;
subject: null;
id: string;
actionParameters: {};
properties: {};
conditions: never[];
}[];
update: {
action: string;
subject: null;
id: string;
actionParameters: {};
properties: {};
conditions: never[];
}[];
delete: {
action: string;
subject: null;
id: string;
actionParameters: {};
properties: {};
conditions: never[];
}[];
createAction: {
action: string;
subject: null;
id: string;
actionParameters: {};
properties: {};
conditions: never[];
}[];
deleteAction: {
action: string;
subject: null;
id: string;
actionParameters: {};
properties: {};
conditions: never[];
}[];
publish: {
action: string;
subject: null;
id: string;
actionParameters: {};
properties: {};
conditions: never[];
}[];
};
export declare const PERMISSIONS_SETTINGS: {
read: {
action: string;
subject: null;
id: string;
actionParameters: {};
properties: {};
conditions: never[];
}[];
update: {
action: string;
subject: null;
id: string;
actionParameters: {};
properties: {};
conditions: never[];
}[];
};

View File

@@ -0,0 +1,3 @@
import type { Plugin } from '@strapi/types';
declare const admin: Plugin.Config.AdminInput;
export default admin;

View File

@@ -0,0 +1,7 @@
import { Dispatch } from '@reduxjs/toolkit';
import { TypedUseSelectorHook } from 'react-redux';
import type { Store } from '@strapi/admin/strapi-admin';
type RootState = ReturnType<Store['getState']>;
declare const useTypedDispatch: () => Dispatch;
declare const useTypedSelector: TypedUseSelectorHook<RootState>;
export { useTypedSelector, useTypedDispatch };

View File

@@ -0,0 +1 @@
export declare const App: () => import("react/jsx-runtime").JSX.Element;

View File

@@ -0,0 +1,2 @@
declare const PurchaseContentReleases: () => import("react/jsx-runtime").JSX.Element;
export { PurchaseContentReleases };

Some files were not shown because too many files have changed in this diff Show More