diff --git a/packages/strapi-admin/admin/src/translations/en.json b/packages/strapi-admin/admin/src/translations/en.json
index 74800cc4b7..86adefa8b2 100644
--- a/packages/strapi-admin/admin/src/translations/en.json
+++ b/packages/strapi-admin/admin/src/translations/en.json
@@ -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",
diff --git a/packages/strapi-admin/admin/src/utils/getAttributesToDisplay.js b/packages/strapi-admin/admin/src/utils/getAttributesToDisplay.js
index 900bda6ec0..9d5f014100 100644
--- a/packages/strapi-admin/admin/src/utils/getAttributesToDisplay.js
+++ b/packages/strapi-admin/admin/src/utils/getAttributesToDisplay.js
@@ -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 });
}
diff --git a/packages/strapi-admin/admin/src/utils/tests/getAttributesToDisplay.test.js b/packages/strapi-admin/admin/src/utils/tests/getAttributesToDisplay.test.js
index 5b9ecd64a5..76f4c7974e 100644
--- a/packages/strapi-admin/admin/src/utils/tests/getAttributesToDisplay.test.js
+++ b/packages/strapi-admin/admin/src/utils/tests/getAttributesToDisplay.test.js
@@ -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'],
diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/EditView/Header.js b/packages/strapi-plugin-content-manager/admin/src/containers/EditView/Header.js
index c306414a1e..5c7e975060 100644
--- a/packages/strapi-plugin-content-manager/admin/src/containers/EditView/Header.js
+++ b/packages/strapi-plugin-content-manager/admin/src/containers/EditView/Header.js
@@ -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(() => {
diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/EditViewDataManagerProvider/index.js b/packages/strapi-plugin-content-manager/admin/src/containers/EditViewDataManagerProvider/index.js
index 096b99ce37..f0e4f71dc0 100644
--- a/packages/strapi-plugin-content-manager/admin/src/containers/EditViewDataManagerProvider/index.js
+++ b/packages/strapi-plugin-content-manager/admin/src/containers/EditViewDataManagerProvider/index.js
@@ -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 = ({
}}
>
<>
-
+
{isLoading ? (
) : (
diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/EditViewDataManagerProvider/reducer.js b/packages/strapi-plugin-content-manager/admin/src/containers/EditViewDataManagerProvider/reducer.js
index b4de5eafbc..9d5c384c39 100644
--- a/packages/strapi-plugin-content-manager/admin/src/containers/EditViewDataManagerProvider/reducer.js
+++ b/packages/strapi-plugin-content-manager/admin/src/containers/EditViewDataManagerProvider/reducer.js
@@ -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
diff --git a/packages/strapi-plugin-content-manager/admin/src/translations/en.json b/packages/strapi-plugin-content-manager/admin/src/translations/en.json
index 2c92c38659..b4592f063f 100644
--- a/packages/strapi-plugin-content-manager/admin/src/translations/en.json
+++ b/packages/strapi-plugin-content-manager/admin/src/translations/en.json
@@ -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"
}
diff --git a/packages/strapi-plugin-content-manager/admin/src/utils/generatePermissionsObject.js b/packages/strapi-plugin-content-manager/admin/src/utils/generatePermissionsObject.js
index 02186adc88..49a7470b8c 100644
--- a/packages/strapi-plugin-content-manager/admin/src/utils/generatePermissionsObject.js
+++ b/packages/strapi-plugin-content-manager/admin/src/utils/generatePermissionsObject.js
@@ -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 }],
};