Publish a draft

Signed-off-by: HichamELBSI <elabbassih@gmail.com>
This commit is contained in:
HichamELBSI 2020-08-18 13:49:50 +02:00 committed by Pierre Noël
parent 941b381ffb
commit 23ede00fa8
8 changed files with 143 additions and 30 deletions

View File

@ -251,6 +251,7 @@
"app.utils.add-filter": "Add filter",
"app.utils.defaultMessage": " ",
"app.utils.delete": "Delete",
"app.utils.publish": "Publish",
"app.utils.filters": "Filters",
"app.utils.placeholder.defaultMessage": " ",
"app.utils.select-all": "Select all",

View File

@ -6,10 +6,11 @@ const getAttributesToDisplay = contentType => {
// Sometimes timestamps is false
let timestampsArray = Array.isArray(timestamps) ? timestamps : [];
const idsAttributes = ['id', '_id']; // For both SQL and mongo
const unwritableAttributes = [...idsAttributes, ...timestampsArray, 'published_at'];
const schemaAttributes = get(contentType, ['schema', 'attributes'], {});
return Object.keys(schemaAttributes).reduce((acc, current) => {
if (![...idsAttributes, ...timestampsArray].includes(current)) {
if (!unwritableAttributes.includes(current)) {
acc.push({ ...schemaAttributes[current], attributeName: current });
}

View File

@ -10,6 +10,7 @@ describe('ADMIN | utils | getAttributesToDisplay', () => {
description: { type: 'string' },
created_at: { type: 'timestamp' },
updated_at: { type: 'timestamp' },
published_at: { type: 'timestamp' },
},
options: {
timestamps: ['created_at', 'updated_at'],

View File

@ -24,21 +24,26 @@ const Header = () => {
isCreatingEntry,
isSingleType,
isSubmitting,
isPublishing,
layout,
modifiedData,
onPublish,
redirectToPreviousPage,
resetData,
slug,
clearData,
} = useDataManager();
const {
allowedActions: { canDelete, canUpdate, canCreate },
allowedActions: { canDelete, canUpdate, canCreate, canPublish },
} = useEditView();
const currentContentTypeMainField = useMemo(() => get(layout, ['settings', 'mainField'], 'id'), [
layout,
]);
const currentContentTypeName = useMemo(() => get(layout, ['schema', 'info', 'name']), [layout]);
const hasDraftAndPublish = useMemo(() => get(layout, ['schema', 'options', 'draftAndPublish']), [
layout,
]);
const didChangeData = useMemo(() => {
return !isEqual(initialData, modifiedData) || (isCreatingEntry && !isEmpty(modifiedData));
}, [initialData, isCreatingEntry, modifiedData]);
@ -63,22 +68,6 @@ const Header = () => {
if ((isCreatingEntry && canCreate) || (!isCreatingEntry && canUpdate)) {
headerActions = [
{
disabled: !didChangeData,
onClick: () => {
toggleWarningCancel();
},
color: 'cancel',
label: formatMessage({
id: `${pluginId}.containers.Edit.reset`,
}),
type: 'button',
style: {
paddingLeft: 15,
paddingRight: 15,
fontWeight: 600,
},
},
{
disabled: !didChangeData,
color: 'success',
@ -95,6 +84,23 @@ const Header = () => {
];
}
if (hasDraftAndPublish && canPublish) {
headerActions.unshift({
disabled: didChangeData,
label: formatMessage({
id: 'app.utils.publish',
}),
color: 'primary',
onClick: onPublish,
type: 'button',
isLoading: isPublishing,
style: {
minWidth: 150,
fontWeight: 600,
},
});
}
if (!isCreatingEntry && canDelete) {
headerActions.unshift({
label: formatMessage({
@ -114,14 +120,18 @@ const Header = () => {
}
return headerActions;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
canCreate,
canDelete,
canUpdate,
didChangeData,
isCreatingEntry,
isSubmitting,
canCreate,
canUpdate,
hasDraftAndPublish,
canPublish,
canDelete,
didChangeData,
formatMessage,
isSubmitting,
isPublishing,
]);
const headerProps = useMemo(() => {

View File

@ -1,7 +1,14 @@
import React, { useCallback, useEffect, useMemo, useReducer, useState } from 'react';
import { cloneDeep, get, isEmpty, isEqual, pick, set } from 'lodash';
import PropTypes from 'prop-types';
import { Prompt, Redirect, useParams, useLocation, useHistory } from 'react-router-dom';
import {
Prompt,
Redirect,
useParams,
useLocation,
useHistory,
useRouteMatch,
} from 'react-router-dom';
import {
LoadingIndicatorPage,
request,
@ -52,7 +59,11 @@ const EditViewDataManagerProvider = ({
} = reducerState.toJS();
const [isCreatingEntry, setIsCreatingEntry] = useState(id === 'create');
const [isSubmitting, setIsSubmitting] = useState(false);
const [isPublishing, setIsPublishing] = useState(false);
const currentContentTypeLayout = get(allLayoutData, ['contentType'], {});
const {
params: { contentType },
} = useRouteMatch('/plugins/content-manager/:contentType');
// This is used for the readonly mode when updating an entry
const allDynamicZoneFields = useMemo(() => {
const attributes = get(currentContentTypeLayout, ['schema', 'attributes'], {});
@ -406,7 +417,7 @@ const EditViewDataManagerProvider = ({
try {
// Time to actually send the data
await request(
const res = await request(
getRequestUrl(endPoint),
{
method,
@ -426,10 +437,10 @@ const EditViewDataManagerProvider = ({
});
strapi.notification.success(`${pluginId}.success.record.save`);
if (isSingleType) {
setIsCreatingEntry(false);
} else {
redirectToPreviousPage();
setIsCreatingEntry(false);
if (!isSingleType) {
push(`/plugins/${pluginId}/${contentType}/${slug}/${res.id}`);
}
} catch (err) {
console.error({ err });
@ -474,6 +485,85 @@ const EditViewDataManagerProvider = ({
}
};
const handlePublish = async e => {
e.preventDefault();
// Create yup schema
const schema = createYupSchema(
currentContentTypeLayout,
{
components: get(allLayoutData, 'components', {}),
},
isCreatingEntry
);
try {
// Validate the form using yup
await schema.validate(modifiedData, { abortEarly: false });
// Show a loading button in the EditView/Header.js
setIsPublishing(true);
try {
// Time to actually send the data
await request(
getRequestUrl(`${slug}/publish/${id}`),
{
method: 'POST',
signal,
},
false,
false
);
setIsPublishing(false);
dispatch({
type: 'PUBLISH_SUCCESS',
});
strapi.notification.success(`${pluginId}.success.record.publish`);
} catch (err) {
// ---------- @Soupette Is this error handling still mandatory? ----------
// The api error send response.payload.message: 'The error message'.
// There isn't : response.payload.message[0].messages[0].id
console.error({ err });
setIsPublishing(false);
const error = get(
err,
['response', 'payload', 'message', '0', 'messages', '0', 'id'],
'SERVER ERROR'
);
if (error === 'ValidationError') {
const errors = get(err, ['response', 'payload', 'data', '0', 'errors'], {});
const formattedErrors = Object.keys(errors).reduce((acc, current) => {
acc[current] = { id: errors[current][0] };
return acc;
}, {});
dispatch({
type: 'PUBLISH_ERRORS',
errors: formattedErrors,
});
}
const errorMessage = get(err, ['response', 'payload', 'message'], 'SERVER ERROR');
strapi.notification.error(errorMessage);
}
} catch (err) {
console.error({ err });
const errors = getYupInnerErrors(err);
setIsSubmitting(false);
dispatch({
type: 'PUBLISH_ERRORS',
errors,
});
}
};
const shouldCheckDZErrors = useCallback(
dzName => {
const doesDZHaveError = Object.keys(formErrors).some(key => key.split('.')[0] === dzName);
@ -630,6 +720,7 @@ const EditViewDataManagerProvider = ({
isCreatingEntry,
isSingleType,
isSubmitting,
isPublishing,
layout: currentContentTypeLayout,
modifiedData,
moveComponentDown,
@ -637,6 +728,7 @@ const EditViewDataManagerProvider = ({
moveComponentUp,
moveRelation,
onChange: handleChange,
onPublish: handlePublish,
onRemoveRelation,
readActionAllowedFields,
redirectToPreviousPage,
@ -650,7 +742,11 @@ const EditViewDataManagerProvider = ({
}}
>
<>
<OverlayBlocker key="overlayBlocker" isOpen={isSubmitting} {...overlayBlockerParams} />
<OverlayBlocker
key="overlayBlocker"
isOpen={isSubmitting || isPublishing}
{...overlayBlockerParams}
/>
{isLoading ? (
<LoadingIndicatorPage />
) : (

View File

@ -210,9 +210,11 @@ const reducer = (state, action) => {
.update('modifiedDZName', () => null)
.update('formErrors', () => fromJS(action.errors));
case 'SUBMIT_ERRORS':
case 'PUBLISH_ERRORS':
return state
.update('formErrors', () => fromJS(action.errors))
.update('shouldShowLoadingState', () => false);
case 'PUBLISH_SUCCESS':
case 'SUBMIT_SUCCESS':
case 'DELETE_SUCCEEDED':
return state

View File

@ -151,5 +151,6 @@
"popUpWarning.warning.cancelAllSettings": "Are you sure you want to cancel your modifications?",
"popUpWarning.warning.updateAllSettings": "This will modify all your settings",
"success.record.delete": "Deleted",
"success.record.publish": "Published",
"success.record.save": "Saved"
}

View File

@ -4,6 +4,7 @@ const generatePermissionsObject = uid => {
const permissions = {
create: [{ action: 'plugins::content-manager.explorer.create', subject: null }],
delete: [{ action: 'plugins::content-manager.explorer.delete', subject: null }],
publish: [{ action: 'plugins::content-manager.explorer.publish', subject: null }],
read: [{ action: 'plugins::content-manager.explorer.read', subject: null }],
update: [{ action: 'plugins::content-manager.explorer.update', subject: null }],
};