mirror of
https://github.com/strapi/strapi.git
synced 2025-12-05 11:32:13 +00:00
feat(i18n): add bulk unpublish locales edit view action (#21030)
* feat(i18n): add bulk unpublish locales edit view action * fix(e2e): i18n bulk actions * chore(e2e): skip for webkit * chore: clean up * fix(i18n): fixes and e2e test coverage for bulk locale unpublish * fix(e2e): content manager edit view * fix(content-manager): incorporate edit view notification fix * chore(i18n): clean up
This commit is contained in:
parent
aac137c6a2
commit
69590e70d5
@ -553,6 +553,7 @@
|
||||
"app.utils.published": "Published",
|
||||
"app.utils.ready-to-publish": "Ready to publish",
|
||||
"app.utils.ready-to-publish-changes": "Ready to publish changes",
|
||||
"app.utils.ready-to-unpublish-changes": "Ready to unpublish",
|
||||
"app.confirm.body": "Are you sure?",
|
||||
"clearLabel": "Clear",
|
||||
"coming.soon": "This content is currently under construction and will be back in a few weeks!",
|
||||
|
||||
@ -18,7 +18,7 @@ import {
|
||||
Menu,
|
||||
ButtonProps,
|
||||
} from '@strapi/design-system';
|
||||
import { CrossCircle, More, WarningCircle } from '@strapi/icons';
|
||||
import { Cross, More, WarningCircle } from '@strapi/icons';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useMatch, useNavigate } from 'react-router-dom';
|
||||
import { styled, DefaultTheme } from 'styled-components';
|
||||
@ -582,29 +582,29 @@ const PublishAction: DocumentActionComponent = ({
|
||||
}, [documentId, modified, formValues, setLocalCountOfDraftRelations]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (documentId && !isListView) {
|
||||
// We don't want to call count draft relations if in the list view. There is no
|
||||
// use for the response.
|
||||
const fetchDraftRelationsCount = async () => {
|
||||
const { data, error } = await countDraftRelations({
|
||||
collectionType,
|
||||
model,
|
||||
documentId,
|
||||
params,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
setServerCountOfDraftRelations(data.data);
|
||||
}
|
||||
};
|
||||
|
||||
fetchDraftRelationsCount();
|
||||
if (!document || !document.documentId || isListView) {
|
||||
return;
|
||||
}
|
||||
}, [isListView, documentId, countDraftRelations, collectionType, model, params]);
|
||||
|
||||
const fetchDraftRelationsCount = async () => {
|
||||
const { data, error } = await countDraftRelations({
|
||||
collectionType,
|
||||
model,
|
||||
documentId,
|
||||
params,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
setServerCountOfDraftRelations(data.data);
|
||||
}
|
||||
};
|
||||
|
||||
fetchDraftRelationsCount();
|
||||
}, [isListView, document, documentId, countDraftRelations, collectionType, model, params]);
|
||||
|
||||
const isDocumentPublished =
|
||||
(document?.[PUBLISHED_AT_ATTRIBUTE_NAME] ||
|
||||
@ -909,7 +909,7 @@ const UnpublishAction: DocumentActionComponent = ({
|
||||
id: 'app.utils.unpublish',
|
||||
defaultMessage: 'Unpublish',
|
||||
}),
|
||||
icon: <StyledCrossCircle />,
|
||||
icon: <Cross />,
|
||||
onClick: async () => {
|
||||
/**
|
||||
* return if there's no id & we're in a collection type, or the status modified
|
||||
@ -1043,7 +1043,7 @@ const DiscardAction: DocumentActionComponent = ({
|
||||
id: 'content-manager.actions.discard.label',
|
||||
defaultMessage: 'Discard changes',
|
||||
}),
|
||||
icon: <StyledCrossCircle />,
|
||||
icon: <Cross />,
|
||||
position: ['panel', 'table-row'],
|
||||
variant: 'danger',
|
||||
dialog: {
|
||||
@ -1077,16 +1077,6 @@ const DiscardAction: DocumentActionComponent = ({
|
||||
|
||||
DiscardAction.type = 'discard';
|
||||
|
||||
/**
|
||||
* Because the icon system is completely broken, we have to do
|
||||
* this to remove the fill from the cog.
|
||||
*/
|
||||
const StyledCrossCircle = styled(CrossCircle)`
|
||||
path {
|
||||
fill: currentColor;
|
||||
}
|
||||
`;
|
||||
|
||||
const DEFAULT_ACTIONS = [PublishAction, UpdateAction, UnpublishAction, DiscardAction];
|
||||
|
||||
export { DocumentActions, DocumentActionsMenu, DocumentActionButton, DEFAULT_ACTIONS };
|
||||
|
||||
@ -63,6 +63,7 @@
|
||||
"components.SettingsViewWrapper.pluginHeader.description.list-settings": "Define the settings of the list view.",
|
||||
"components.SettingsViewWrapper.pluginHeader.title": "Configure the view — {name}",
|
||||
"bulk-publish.already-published": "Already Published",
|
||||
"bulk-unpublish.already-unpublished": "Already Unpublished",
|
||||
"bulk-publish.modified": "Ready to publish changes",
|
||||
"components.TableDelete.delete": "Delete all",
|
||||
"components.TableDelete.deleteSelected": "Delete selected",
|
||||
@ -104,8 +105,8 @@
|
||||
"containers.list.items": "{number} {number, plural, =0 {items} one {item} other {items}}",
|
||||
"containers.list.table.row-actions": "Row actions",
|
||||
"containers.list.selectedEntriesModal.title": "Publish entries",
|
||||
"containers.list.selectedEntriesModal.selectedCount": "<b>{alreadyPublishedCount}</b> {alreadyPublishedCount, plural, =0 {entries} one {entry} other {entries}} already published. <b>{readyToPublishCount}</b> {readyToPublishCount, plural, =0 {entries} one {entry} other {entries}} ready to publish. <b>{withErrorsCount}</b> {withErrorsCount, plural, =0 {entries} one {entry} other {entries}} waiting for action.",
|
||||
"containers.list.selectedEntriesModal.publishedCount": "<b>{publishedCount}</b> {publishedCount, plural, =0 {entries} one {entry} other {entries}} published. <b>{withErrorsCount}</b> {withErrorsCount, plural, =0 {entries} one {entry} other {entries}} waiting for action.",
|
||||
"containers.list.selectedEntriesModal.selectedCount.publish": "<b>{publishedCount}</b> {publishedCount, plural, =0 {entries} one {entry} other {entries}} already published. <b>{draftCount}</b> {draftCount, plural, =0 {entries} one {entry} other {entries}} ready to publish. <b>{withErrorsCount}</b> {withErrorsCount, plural, =0 {entries} one {entry} other {entries}} waiting for action.",
|
||||
"containers.list.selectedEntriesModal.selectedCount.unpublish": "<b>{draftCount}</b> {draftCount, plural, =0 {entries} one {entry} other {entries}} already unpublished. <b>{publishedCount}</b> {publishedCount, plural, =0 {entries} one {entry} other {entries}} ready to unpublish.",
|
||||
"containers.list.autoCloneModal.header": "Duplicate",
|
||||
"containers.list.autoCloneModal.title": "This entry can't be duplicated directly.",
|
||||
"containers.list.autoCloneModal.description": "A new entry will be created with the same content, but you'll have to change the following fields to save it.",
|
||||
|
||||
@ -510,7 +510,9 @@ export default {
|
||||
return ctx.forbidden();
|
||||
}
|
||||
|
||||
const { locale } = await getDocumentLocaleAndStatus(body, model);
|
||||
const { locale } = await getDocumentLocaleAndStatus(body, model, {
|
||||
allowMultipleLocales: true,
|
||||
});
|
||||
|
||||
const entityPromises = documentIds.map((documentId: any) =>
|
||||
documentManager.findLocales(documentId, model, { locale, isPublished: true })
|
||||
|
||||
@ -315,7 +315,6 @@ export declare namespace BulkPublish {
|
||||
};
|
||||
query: {
|
||||
// If not provided, the default locale will be used
|
||||
// Otherwise specify the locales to publish of the documents
|
||||
locale?: string | string[] | null;
|
||||
};
|
||||
}
|
||||
@ -340,7 +339,10 @@ export declare namespace BulkUnpublish {
|
||||
body: {
|
||||
documentIds: Modules.Documents.ID[];
|
||||
};
|
||||
query: {};
|
||||
query: {
|
||||
// If not provided, the default locale will be used
|
||||
locale?: string | string[] | null;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Params {
|
||||
|
||||
@ -23,6 +23,7 @@ type Status = Modules.Documents.Params.PublicationStatus.Kind | 'modified';
|
||||
interface EntryValidationTextProps {
|
||||
status: Status;
|
||||
validationErrors: FormErrors[string] | null;
|
||||
action: 'bulk-publish' | 'bulk-unpublish';
|
||||
}
|
||||
|
||||
interface TranslationMessage extends MessageDescriptor {
|
||||
@ -35,7 +36,11 @@ const isErrorMessageDescriptor = (object?: string | object): object is Translati
|
||||
);
|
||||
};
|
||||
|
||||
const EntryValidationText = ({ status = 'draft', validationErrors }: EntryValidationTextProps) => {
|
||||
const EntryValidationText = ({
|
||||
status = 'draft',
|
||||
validationErrors,
|
||||
action,
|
||||
}: EntryValidationTextProps) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
/**
|
||||
@ -86,42 +91,67 @@ const EntryValidationText = ({ status = 'draft', validationErrors }: EntryValida
|
||||
);
|
||||
}
|
||||
|
||||
if (status === 'published') {
|
||||
return (
|
||||
<Flex gap={2}>
|
||||
<CheckCircle fill="success600" />
|
||||
<Typography textColor="success600" fontWeight="bold">
|
||||
{formatMessage({
|
||||
const getStatusMessage = () => {
|
||||
if (action === 'bulk-publish') {
|
||||
if (status === 'published') {
|
||||
return {
|
||||
icon: <CheckCircle fill="success600" />,
|
||||
text: formatMessage({
|
||||
id: 'content-manager.bulk-publish.already-published',
|
||||
defaultMessage: 'Already Published',
|
||||
})}
|
||||
</Typography>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
if (status === 'modified') {
|
||||
return (
|
||||
<Flex gap={2}>
|
||||
<ArrowsCounterClockwise fill="alternative600" />
|
||||
<Typography>
|
||||
{formatMessage({
|
||||
}),
|
||||
textColor: 'success600',
|
||||
fontWeight: 'bold',
|
||||
};
|
||||
} else if (status === 'modified') {
|
||||
return {
|
||||
icon: <ArrowsCounterClockwise fill="alternative600" />,
|
||||
text: formatMessage({
|
||||
id: 'app.utils.ready-to-publish-changes',
|
||||
defaultMessage: 'Ready to publish changes',
|
||||
})}
|
||||
</Typography>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
}),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
icon: <CheckCircle fill="success600" />,
|
||||
text: formatMessage({
|
||||
id: 'app.utils.ready-to-publish',
|
||||
defaultMessage: 'Ready to publish',
|
||||
}),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (status === 'draft') {
|
||||
return {
|
||||
icon: <CheckCircle fill="success600" />,
|
||||
text: formatMessage({
|
||||
id: 'content-manager.bulk-unpublish.already-unpublished',
|
||||
defaultMessage: 'Already Unpublished',
|
||||
}),
|
||||
textColor: 'success600',
|
||||
fontWeight: 'bold',
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
icon: <CheckCircle fill="success600" />,
|
||||
text: formatMessage({
|
||||
id: 'app.utils.ready-to-unpublish-changes',
|
||||
defaultMessage: 'Ready to unpublish',
|
||||
}),
|
||||
textColor: 'success600',
|
||||
fontWeight: 'bold',
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const { icon, text, textColor = 'success600', fontWeight = 'normal' } = getStatusMessage();
|
||||
|
||||
return (
|
||||
<Flex gap={2}>
|
||||
<CheckCircle fill="success600" />
|
||||
<Typography>
|
||||
{formatMessage({
|
||||
id: 'app.utils.ready-to-publish',
|
||||
defaultMessage: 'Ready to publish',
|
||||
})}
|
||||
{icon}
|
||||
<Typography textColor={textColor} fontWeight={fontWeight}>
|
||||
{text}
|
||||
</Typography>
|
||||
</Flex>
|
||||
);
|
||||
@ -145,14 +175,15 @@ interface BulkLocaleActionModalProps {
|
||||
}[];
|
||||
localesMetadata: Locale[];
|
||||
validationErrors?: FormErrors;
|
||||
action: 'bulk-publish' | 'bulk-unpublish';
|
||||
}
|
||||
|
||||
const BulkLocaleActionModal = ({
|
||||
headers,
|
||||
rows,
|
||||
localesMetadata,
|
||||
|
||||
validationErrors = {},
|
||||
action,
|
||||
}: BulkLocaleActionModalProps) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
@ -168,11 +199,11 @@ const BulkLocaleActionModal = ({
|
||||
}, {});
|
||||
const localesWithErrors = Object.keys(validationErrors);
|
||||
|
||||
const alreadyPublishedCount = selectedRows.filter(
|
||||
const publishedCount = selectedRows.filter(
|
||||
({ locale }) => currentStatusByLocale[locale] === 'published'
|
||||
).length;
|
||||
|
||||
const readyToPublishCount = selectedRows.filter(
|
||||
const draftCount = selectedRows.filter(
|
||||
({ locale }) =>
|
||||
(currentStatusByLocale[locale] === 'draft' ||
|
||||
currentStatusByLocale[locale] === 'modified') &&
|
||||
@ -180,17 +211,25 @@ const BulkLocaleActionModal = ({
|
||||
).length;
|
||||
|
||||
const withErrorsCount = localesWithErrors.length;
|
||||
const messageId =
|
||||
action === 'bulk-publish'
|
||||
? 'content-manager.containers.list.selectedEntriesModal.selectedCount.publish'
|
||||
: 'content-manager.containers.list.selectedEntriesModal.selectedCount.unpublish';
|
||||
|
||||
const defaultMessage =
|
||||
action === 'bulk-publish'
|
||||
? '<b>{publishedCount}</b> {publishedCount, plural, =0 {entries} one {entry} other {entries}} already published. <b>{draftCount}</b> {draftCount, plural, =0 {entries} one {entry} other {entries}} ready to publish. <b>{withErrorsCount}</b> {withErrorsCount, plural, =0 {entries} one {entry} other {entries}} waiting for action.'
|
||||
: '<b>{draftCount}</b> {draftCount, plural, =0 {entries} one {entry} other {entries}} already unpublished. <b>{publishedCount}</b> {publishedCount, plural, =0 {entries} one {entry} other {entries}} ready to unpublish.';
|
||||
|
||||
return formatMessage(
|
||||
{
|
||||
id: 'content-manager.containers.list.selectedEntriesModal.selectedCount',
|
||||
defaultMessage:
|
||||
'<b>{alreadyPublishedCount}</b> {alreadyPublishedCount, plural, =0 {entries} one {entry} other {entries}} already published. <b>{readyToPublishCount}</b> {readyToPublishCount, plural, =0 {entries} one {entry} other {entries}} ready to publish. <b>{withErrorsCount}</b> {withErrorsCount, plural, =0 {entries} one {entry} other {entries}} waiting for action.',
|
||||
id: messageId,
|
||||
defaultMessage,
|
||||
},
|
||||
{
|
||||
withErrorsCount,
|
||||
readyToPublishCount,
|
||||
alreadyPublishedCount,
|
||||
draftCount,
|
||||
publishedCount,
|
||||
b: BoldChunk,
|
||||
}
|
||||
);
|
||||
@ -243,7 +282,7 @@ const BulkLocaleActionModal = ({
|
||||
</Box>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<EntryValidationText validationErrors={error} status={status} />
|
||||
<EntryValidationText validationErrors={error} status={status} action={action} />
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<IconButton
|
||||
|
||||
@ -11,12 +11,13 @@ import {
|
||||
import {
|
||||
type HeaderActionComponent,
|
||||
type DocumentActionComponent,
|
||||
type DocumentActionProps,
|
||||
unstable_useDocument as useDocument,
|
||||
unstable_useDocumentActions as useDocumentActions,
|
||||
buildValidParams,
|
||||
} from '@strapi/content-manager/strapi-admin';
|
||||
import { Flex, Status, Typography, Button, Modal } from '@strapi/design-system';
|
||||
import { WarningCircle, ListPlus, Trash } from '@strapi/icons';
|
||||
import { WarningCircle, ListPlus, Trash, Cross } from '@strapi/icons';
|
||||
import { Modules } from '@strapi/types';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
@ -239,27 +240,34 @@ const DeleteLocaleAction: DocumentActionComponent = ({
|
||||
};
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------------------------------
|
||||
* BulkPublishAction
|
||||
* -----------------------------------------------------------------------------------------------*/
|
||||
|
||||
export type LocaleStatus = {
|
||||
locale: string;
|
||||
status: Modules.Documents.Params.PublicationStatus.Kind | 'modified';
|
||||
};
|
||||
|
||||
const BulkLocalePublishAction: DocumentActionComponent = ({
|
||||
interface ExtendedDocumentActionProps extends DocumentActionProps {
|
||||
action?: 'bulk-publish' | 'bulk-unpublish';
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------------------------------
|
||||
* BulkLocaleAction
|
||||
*
|
||||
* This component is used to handle bulk publish and unpublish actions on locales.
|
||||
* -----------------------------------------------------------------------------------------------*/
|
||||
|
||||
const BulkLocaleAction: DocumentActionComponent = ({
|
||||
document: baseDocument,
|
||||
documentId,
|
||||
model,
|
||||
collectionType,
|
||||
}) => {
|
||||
action,
|
||||
}: ExtendedDocumentActionProps) => {
|
||||
const baseLocale = baseDocument?.locale ?? null;
|
||||
|
||||
const [{ query }] = useQueryParams<{ status: 'draft' | 'published' }>();
|
||||
|
||||
const params = React.useMemo(() => buildValidParams(query), [query]);
|
||||
const isPublishedTab = query.status === 'published';
|
||||
const isOnPublishedTab = query.status === 'published';
|
||||
|
||||
const { formatMessage } = useIntl();
|
||||
const { hasI18n, canPublish } = useI18n();
|
||||
@ -270,7 +278,9 @@ const BulkLocalePublishAction: DocumentActionComponent = ({
|
||||
const [isDraftRelationConfirmationOpen, setIsDraftRelationConfirmationOpen] =
|
||||
React.useState<boolean>(false);
|
||||
|
||||
const { publishMany: publishManyAction } = useDocumentActions();
|
||||
const { publishMany: publishManyAction, unpublishMany: unpublishManyAction } =
|
||||
useDocumentActions();
|
||||
|
||||
const {
|
||||
document,
|
||||
meta: documentMeta,
|
||||
@ -332,6 +342,7 @@ const BulkLocalePublishAction: DocumentActionComponent = ({
|
||||
|
||||
return { locale, status };
|
||||
});
|
||||
|
||||
rowsFromMeta.unshift({
|
||||
locale: document.locale,
|
||||
status: document.status,
|
||||
@ -355,16 +366,26 @@ const BulkLocalePublishAction: DocumentActionComponent = ({
|
||||
return [rowsFromMeta, errors];
|
||||
}, [document, documentMeta?.availableLocales, validate]);
|
||||
|
||||
const localesToPublish = selectedRows.reduce((acc, selectedRow) => {
|
||||
if (
|
||||
selectedRow.status !== 'published' &&
|
||||
!Object.keys(validationErrors).includes(selectedRow.locale)
|
||||
) {
|
||||
const isBulkPublish = action === 'bulk-publish';
|
||||
const localesForAction = selectedRows.reduce((acc: string[], selectedRow: LocaleStatus) => {
|
||||
const isValidLocale =
|
||||
// Validation errors are irrelevant if we are trying to unpublish
|
||||
!isBulkPublish || !Object.keys(validationErrors).includes(selectedRow.locale);
|
||||
|
||||
const shouldAddLocale = isBulkPublish
|
||||
? selectedRow.status !== 'published' && isValidLocale
|
||||
: selectedRow.status !== 'draft' && isValidLocale;
|
||||
|
||||
if (shouldAddLocale) {
|
||||
acc.push(selectedRow.locale);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
// TODO skipping this for now as there is a bug with the draft relation count that will be worked on separately
|
||||
// see https://www.notion.so/strapi/Count-draft-relations-56901b492efb45ab90d42fe975b32bd8?pvs=4
|
||||
const enableDraftRelationsCount = false;
|
||||
const {
|
||||
data: draftRelationsCount = 0,
|
||||
isLoading: isDraftRelationsLoading,
|
||||
@ -373,10 +394,10 @@ const BulkLocalePublishAction: DocumentActionComponent = ({
|
||||
{
|
||||
model,
|
||||
documentIds: [documentId!],
|
||||
locale: localesToPublish,
|
||||
locale: localesForAction,
|
||||
},
|
||||
{
|
||||
skip: !documentId || localesToPublish.length === 0,
|
||||
skip: !enableDraftRelationsCount || !documentId || localesForAction.length === 0,
|
||||
}
|
||||
);
|
||||
|
||||
@ -410,7 +431,20 @@ const BulkLocalePublishAction: DocumentActionComponent = ({
|
||||
documentIds: [documentId],
|
||||
params: {
|
||||
...params,
|
||||
locale: localesToPublish,
|
||||
locale: localesForAction,
|
||||
},
|
||||
});
|
||||
|
||||
setSelectedRows([]);
|
||||
};
|
||||
|
||||
const unpublish = async () => {
|
||||
await unpublishManyAction({
|
||||
model,
|
||||
documentIds: [documentId],
|
||||
params: {
|
||||
...params,
|
||||
locale: localesForAction,
|
||||
},
|
||||
});
|
||||
|
||||
@ -420,17 +454,13 @@ const BulkLocalePublishAction: DocumentActionComponent = ({
|
||||
const handleAction = async () => {
|
||||
if (draftRelationsCount > 0) {
|
||||
setIsDraftRelationConfirmationOpen(true);
|
||||
} else {
|
||||
} else if (isBulkPublish) {
|
||||
await publish();
|
||||
} else {
|
||||
await unpublish();
|
||||
}
|
||||
};
|
||||
|
||||
const isUnpublish = document?.status === 'published';
|
||||
if (isUnpublish) {
|
||||
// TODO: For now we still proceed so we have the bulk locale publish action in all cases
|
||||
console.warn(['I18N'], 'Bulk locale unpublish modal not implemented');
|
||||
}
|
||||
|
||||
if (isDraftRelationConfirmationOpen) {
|
||||
return {
|
||||
label: formatMessage({
|
||||
@ -480,18 +510,18 @@ const BulkLocalePublishAction: DocumentActionComponent = ({
|
||||
|
||||
return {
|
||||
label: formatMessage({
|
||||
id: getTranslation('CMEditViewBulkLocale.publish-title'),
|
||||
defaultMessage: 'Publish Multiple Locales',
|
||||
id: getTranslation(`CMEditViewBulkLocale.${isBulkPublish ? 'publish' : 'unpublish'}-title`),
|
||||
defaultMessage: `${isBulkPublish ? 'Publish' : 'Unpublish'} Multiple Locales`,
|
||||
}),
|
||||
icon: <ListPlus />,
|
||||
disabled: isPublishedTab || canPublish.length === 0,
|
||||
variant: isBulkPublish ? 'secondary' : 'danger',
|
||||
icon: isBulkPublish ? <ListPlus /> : <Cross />,
|
||||
disabled: isOnPublishedTab || canPublish.length === 0,
|
||||
position: ['panel'],
|
||||
variant: 'secondary',
|
||||
dialog: {
|
||||
type: 'modal',
|
||||
title: formatMessage({
|
||||
id: getTranslation('CMEditViewBulkLocale.publish-title'),
|
||||
defaultMessage: 'Publish Multiple Locales',
|
||||
id: getTranslation(`CMEditViewBulkLocale.${isBulkPublish ? 'publish' : 'unpublish'}-title`),
|
||||
defaultMessage: `${isBulkPublish ? 'Publish' : 'Unpublish'} Multiple Locales`,
|
||||
}),
|
||||
content: () => {
|
||||
return (
|
||||
@ -509,6 +539,7 @@ const BulkLocalePublishAction: DocumentActionComponent = ({
|
||||
headers={headers}
|
||||
rows={rows}
|
||||
localesMetadata={localesMetadata as Locale[]}
|
||||
action={action ?? 'bulk-publish'}
|
||||
/>
|
||||
</Table.Root>
|
||||
);
|
||||
@ -517,13 +548,13 @@ const BulkLocalePublishAction: DocumentActionComponent = ({
|
||||
<Modal.Footer justifyContent="flex-end">
|
||||
<Button
|
||||
loading={isDraftRelationsLoading}
|
||||
disabled={!hasPermission || localesToPublish.length === 0}
|
||||
disabled={!hasPermission || localesForAction.length === 0}
|
||||
variant="default"
|
||||
onClick={handleAction}
|
||||
>
|
||||
{formatMessage({
|
||||
id: 'app.utils.publish',
|
||||
defaultMessage: 'Publish',
|
||||
id: isBulkPublish ? 'app.utils.publish' : 'app.utils.unpublish',
|
||||
defaultMessage: isBulkPublish ? 'Publish' : 'Unpublish',
|
||||
})}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
@ -532,6 +563,20 @@ const BulkLocalePublishAction: DocumentActionComponent = ({
|
||||
};
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------------------------------
|
||||
* BulkLocalePublishAction
|
||||
* -----------------------------------------------------------------------------------------------*/
|
||||
const BulkLocalePublishAction: DocumentActionComponent = (props: ExtendedDocumentActionProps) => {
|
||||
return BulkLocaleAction({ action: 'bulk-publish', ...props });
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------------------------------
|
||||
* BulkLocaleUnpublishAction
|
||||
* -----------------------------------------------------------------------------------------------*/
|
||||
const BulkLocaleUnpublishAction: DocumentActionComponent = (props: ExtendedDocumentActionProps) => {
|
||||
return BulkLocaleAction({ action: 'bulk-unpublish', ...props });
|
||||
};
|
||||
|
||||
/**
|
||||
* Because the icon system is completely broken, we have to do
|
||||
* this to remove the fill from the cog.
|
||||
@ -542,4 +587,9 @@ const StyledTrash = styled(Trash)`
|
||||
}
|
||||
`;
|
||||
|
||||
export { BulkLocalePublishAction, DeleteLocaleAction, LocalePickerAction };
|
||||
export {
|
||||
BulkLocalePublishAction,
|
||||
BulkLocaleUnpublishAction,
|
||||
DeleteLocaleAction,
|
||||
LocalePickerAction,
|
||||
};
|
||||
|
||||
@ -4,6 +4,7 @@ import * as yup from 'yup';
|
||||
import { CheckboxConfirmation } from './components/CheckboxConfirmation';
|
||||
import {
|
||||
BulkLocalePublishAction,
|
||||
BulkLocaleUnpublishAction,
|
||||
DeleteLocaleAction,
|
||||
LocalePickerAction,
|
||||
} from './components/CMHeaderActions';
|
||||
@ -79,6 +80,7 @@ export default {
|
||||
// When enabled the bulk locale publish action should be the first action
|
||||
// in 'More Document Actions' and therefore the third action in the array
|
||||
actions.splice(2, 0, BulkLocalePublishAction);
|
||||
actions.splice(5, 0, BulkLocaleUnpublishAction);
|
||||
return actions;
|
||||
});
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
"CMEditViewCopyLocale.copy-text": "Fill in from another locale",
|
||||
"CMEditViewCopyLocale.submit-text": "Yes, fill in",
|
||||
"CMEditViewBulkLocale.publish-title": "Publish Multiple Locales",
|
||||
"CMEditViewBulkLocale.unpublish-title": "Unpublish Multiple Locales",
|
||||
"CMEditViewBulkLocale.status": "Status",
|
||||
"CMEditViewBulkLocale.publication-status": "Publication Status",
|
||||
"CMEditViewBulkLocale.draft-relation-warning": "Some locales are related to draft entries. Publishing them could leave broken links in your app.",
|
||||
|
||||
@ -137,7 +137,9 @@ test.describe('Edit View', () => {
|
||||
await expect(page.getByRole('button', { name: 'More document actions' })).not.toBeDisabled();
|
||||
|
||||
await page.getByRole('button', { name: 'More document actions' }).click();
|
||||
await expect(page.getByRole('menuitem', { name: 'Unpublish' })).not.toBeDisabled();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Unpublish', exact: true })
|
||||
).not.toBeDisabled();
|
||||
await expect(page.getByRole('menuitem', { name: 'Discard changes' })).toBeDisabled();
|
||||
await page.keyboard.press('Escape'); // close the menu since we're not actioning on it atm.
|
||||
|
||||
@ -330,8 +332,10 @@ test.describe('Edit View', () => {
|
||||
await expect(page.getByRole('tab', { name: 'Published' })).not.toBeDisabled();
|
||||
|
||||
await page.getByRole('button', { name: 'More document actions' }).click();
|
||||
await expect(page.getByRole('menuitem', { name: 'Unpublish' })).not.toBeDisabled();
|
||||
await page.getByRole('menuitem', { name: 'Unpublish' }).click();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Unpublish', exact: true })
|
||||
).not.toBeDisabled();
|
||||
await page.getByRole('menuitem', { name: 'Unpublish', exact: true }).click();
|
||||
|
||||
await findAndClose(page, 'Unpublished Document');
|
||||
|
||||
@ -483,7 +487,9 @@ test.describe('Edit View', () => {
|
||||
await expect(page.getByRole('button', { name: 'More document actions' })).not.toBeDisabled();
|
||||
|
||||
await page.getByRole('button', { name: 'More document actions' }).click();
|
||||
await expect(page.getByRole('menuitem', { name: 'Unpublish' })).not.toBeDisabled();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Unpublish', exact: true })
|
||||
).not.toBeDisabled();
|
||||
await expect(page.getByRole('menuitem', { name: 'Discard changes' })).toBeDisabled();
|
||||
await page.keyboard.press('Escape'); // close the menu since we're not actioning on it atm.
|
||||
|
||||
@ -656,8 +662,10 @@ test.describe('Edit View', () => {
|
||||
await expect(page.getByRole('tab', { name: 'Published' })).not.toBeDisabled();
|
||||
|
||||
await page.getByRole('button', { name: 'More document actions' }).click();
|
||||
await expect(page.getByRole('menuitem', { name: 'Unpublish' })).not.toBeDisabled();
|
||||
await page.getByRole('menuitem', { name: 'Unpublish' }).click();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Unpublish', exact: true })
|
||||
).not.toBeDisabled();
|
||||
await page.getByRole('menuitem', { name: 'Unpublish', exact: true }).click();
|
||||
|
||||
await findAndClose(page, 'Unpublished Document');
|
||||
|
||||
|
||||
@ -327,6 +327,113 @@ test.describe('Edit view', () => {
|
||||
).toBeDisabled();
|
||||
});
|
||||
|
||||
test('As a user I want to unpublish multiple locales of my document', async ({
|
||||
page,
|
||||
browser,
|
||||
}) => {
|
||||
if (browser.browserType().name() === 'webkit') {
|
||||
// See DX-1550
|
||||
return test.fixme();
|
||||
}
|
||||
|
||||
const LIST_URL = /\/admin\/content-manager\/collection-types\/api::article.article(\?.*)?/;
|
||||
const EDIT_URL =
|
||||
/\/admin\/content-manager\/collection-types\/api::article.article\/[^/]+(\?.*)?/;
|
||||
|
||||
/**
|
||||
* Navigate to our articles list-view where there will be one document already made in the `en` locale
|
||||
*/
|
||||
await page.getByRole('link', { name: 'Content Manager' }).click();
|
||||
await page.getByRole('link', { name: 'Article' }).click();
|
||||
await page.waitForURL(LIST_URL);
|
||||
await expect(page.getByRole('heading', { name: 'Article' })).toBeVisible();
|
||||
|
||||
/**
|
||||
* Assert we're on the english locale and our document exists
|
||||
*/
|
||||
await expect(page.getByRole('combobox', { name: 'Select a locale' })).toHaveText(
|
||||
'English (en)'
|
||||
);
|
||||
await expect(
|
||||
page.getByRole('row', { name: 'Why I prefer football over soccer' })
|
||||
).toBeVisible();
|
||||
await page.getByRole('row', { name: 'Why I prefer football over soccer' }).click();
|
||||
|
||||
/**
|
||||
* Create a new spanish draft article
|
||||
*/
|
||||
await page.waitForURL(EDIT_URL);
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Why I prefer football over soccer' })
|
||||
).toBeVisible();
|
||||
|
||||
/**
|
||||
* Publish the english article
|
||||
*/
|
||||
await page.getByRole('button', { name: 'Publish' }).click();
|
||||
await findAndClose(page, 'Success:Published');
|
||||
|
||||
await page.getByRole('combobox', { name: 'Locales' }).click();
|
||||
await page.getByRole('option', { name: 'Spanish (es)' }).click();
|
||||
|
||||
/**
|
||||
* Now we should be on a new document in the `es` locale
|
||||
*/
|
||||
expect(new URL(page.url()).searchParams.get('plugins[i18n][locale]')).toEqual('es');
|
||||
await expect(page.getByRole('heading', { name: 'Untitled' })).toBeVisible();
|
||||
|
||||
/**
|
||||
* This is here because the `fill` method below doesn't immediately update the value
|
||||
* in webkit.
|
||||
*/
|
||||
if (browser.browserType().name() === 'webkit') {
|
||||
await page.getByRole('textbox', { name: 'title' }).press('s');
|
||||
await page.getByRole('textbox', { name: 'title' }).press('Delete');
|
||||
}
|
||||
|
||||
await page.getByRole('textbox', { name: 'title' }).fill('Por qué prefiero el fútbol al fútbol');
|
||||
|
||||
/**
|
||||
* Save the spanish draft
|
||||
*/
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await findAndClose(page, 'Success:Saved');
|
||||
|
||||
/**
|
||||
* Publish the spanish article
|
||||
*/
|
||||
await page.getByRole('button', { name: 'Publish' }).click();
|
||||
await findAndClose(page, 'Success:Published');
|
||||
|
||||
/**
|
||||
* Open the bulk locale unpublish modal
|
||||
*/
|
||||
await page.getByText('More document actions').click();
|
||||
await page.getByRole('menuitem', { name: 'Unpublish Multiple Locales', exact: true }).click();
|
||||
|
||||
// Select all locales, assert there are 2 entries ready to unpublish and unpublish them
|
||||
await page
|
||||
.getByRole('row', { name: 'Select all entries Name' })
|
||||
.getByLabel('Select all entries')
|
||||
.click();
|
||||
|
||||
/**
|
||||
* Unpublish the articles
|
||||
*/
|
||||
await expect(page.getByText('2 entries ready to unpublish')).toBeVisible();
|
||||
await page
|
||||
.getByLabel('Unpublish Multiple Locales')
|
||||
.getByRole('button', { name: 'Unpublish' })
|
||||
.click();
|
||||
|
||||
// Assert that all locales are now unpublished
|
||||
await expect(page.getByRole('gridcell', { name: 'Draft' })).toHaveCount(2);
|
||||
|
||||
await expect(
|
||||
page.getByLabel('Unpublish Multiple Locales').getByRole('button', { name: 'Unpublish' })
|
||||
).toBeDisabled();
|
||||
});
|
||||
|
||||
interface ValidationType {
|
||||
field: string;
|
||||
initialValue: string;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user