diff --git a/cypress/integration/editorial_workflow_spec_test_backend.js b/cypress/integration/editorial_workflow_spec_test_backend.js
index 236f8148..555f8ca1 100644
--- a/cypress/integration/editorial_workflow_spec_test_backend.js
+++ b/cypress/integration/editorial_workflow_spec_test_backend.js
@@ -23,6 +23,7 @@ import {
duplicateEntry,
} from '../utils/steps';
import { setting1, setting2, workflowStatus, editorStatus, publishTypes } from '../utils/constants';
+import { fromJS } from 'immutable';
const entry1 = {
title: 'first title',
@@ -145,4 +146,65 @@ describe('Test Backend Editorial Workflow', () => {
publishEntryInEditor(publishTypes.publishNow);
duplicateEntry(entry1);
});
+
+ it('cannot publish when "publish" is false', () => {
+ cy.visit('/', {
+ onBeforeLoad: window => {
+ window.CMS_MANUAL_INIT = true;
+ },
+ onLoad: window => {
+ window.CMS.init({
+ config: fromJS({
+ backend: {
+ name: 'test-repo',
+ },
+ publish_mode: 'editorial_workflow',
+ load_config_file: false,
+ media_folder: 'assets/uploads',
+ collections: [
+ {
+ label: 'Posts',
+ name: 'post',
+ folder: '_posts',
+ label_singular: 'Post',
+ create: true,
+ publish: false,
+ fields: [
+ { label: 'Title', name: 'title', widget: 'string', tagname: 'h1' },
+ {
+ label: 'Publish Date',
+ name: 'date',
+ widget: 'datetime',
+ dateFormat: 'YYYY-MM-DD',
+ timeFormat: 'HH:mm',
+ format: 'YYYY-MM-DD HH:mm',
+ },
+ {
+ label: 'Cover Image',
+ name: 'image',
+ widget: 'image',
+ required: false,
+ tagname: '',
+ },
+ {
+ label: 'Body',
+ name: 'body',
+ widget: 'markdown',
+ hint: 'Main content goes here.',
+ },
+ ],
+ },
+ ],
+ }),
+ });
+ },
+ });
+ cy.contains('button', 'Login').click();
+ createPost(entry1);
+ cy.contains('span', 'Publish').should('not.exist');
+ exitEditor();
+ goToWorkflow();
+ updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.ready);
+ cy.contains('button', 'Publish new entry').should('not.exist');
+ });
});
diff --git a/packages/netlify-cms-backend-test/src/implementation.ts b/packages/netlify-cms-backend-test/src/implementation.ts
index 7b97db2b..112569f8 100644
--- a/packages/netlify-cms-backend-test/src/implementation.ts
+++ b/packages/netlify-cms-backend-test/src/implementation.ts
@@ -1,4 +1,4 @@
-import { attempt, isError, take, unset } from 'lodash';
+import { attempt, isError, take, unset, isEmpty } from 'lodash';
import uuid from 'uuid/v4';
import {
EditorialWorkflowError,
@@ -243,6 +243,7 @@ export default class TestBackend implements Implementation {
},
slug,
mediaFiles: assetProxies.map(this.normalizeAsset),
+ isModification: !isEmpty(getFile(path)),
};
unpubStore.push(unpubEntry);
}
diff --git a/packages/netlify-cms-core/src/actions/__tests__/config.spec.js b/packages/netlify-cms-core/src/actions/__tests__/config.spec.js
index 688726db..76d96a1a 100644
--- a/packages/netlify-cms-core/src/actions/__tests__/config.spec.js
+++ b/packages/netlify-cms-core/src/actions/__tests__/config.spec.js
@@ -55,6 +55,41 @@ describe('config', () => {
});
});
+ describe('slug', () => {
+ it('should set default slug config if not set', () => {
+ expect(applyDefaults(fromJS({ collections: [] })).get('slug')).toEqual(
+ fromJS({ encoding: 'unicode', clean_accents: false, sanitize_replacement: '-' }),
+ );
+ });
+
+ it('should not overwrite slug encoding if set', () => {
+ expect(
+ applyDefaults(fromJS({ collections: [], slug: { encoding: 'ascii' } })).getIn([
+ 'slug',
+ 'encoding',
+ ]),
+ ).toEqual('ascii');
+ });
+
+ it('should not overwrite slug clean_accents if set', () => {
+ expect(
+ applyDefaults(fromJS({ collections: [], slug: { clean_accents: true } })).getIn([
+ 'slug',
+ 'clean_accents',
+ ]),
+ ).toEqual(true);
+ });
+
+ it('should not overwrite slug sanitize_replacement if set', () => {
+ expect(
+ applyDefaults(fromJS({ collections: [], slug: { sanitize_replacement: '_' } })).getIn([
+ 'slug',
+ 'sanitize_replacement',
+ ]),
+ ).toEqual('_');
+ });
+ });
+
describe('collections', () => {
it('should strip leading slashes from collection folder', () => {
expect(
@@ -62,8 +97,8 @@ describe('config', () => {
fromJS({
collections: [{ folder: '/foo' }],
}),
- ).get('collections'),
- ).toEqual(fromJS([{ folder: 'foo' }]));
+ ).getIn(['collections', 0, 'folder']),
+ ).toEqual('foo');
});
it('should strip leading slashes from collection files', () => {
@@ -72,43 +107,8 @@ describe('config', () => {
fromJS({
collections: [{ files: [{ file: '/foo' }] }],
}),
- ).get('collections'),
- ).toEqual(fromJS([{ files: [{ file: 'foo' }] }]));
- });
-
- describe('slug', () => {
- it('should set default slug config if not set', () => {
- expect(applyDefaults(fromJS({ collections: [] })).get('slug')).toEqual(
- fromJS({ encoding: 'unicode', clean_accents: false, sanitize_replacement: '-' }),
- );
- });
-
- it('should not overwrite slug encoding if set', () => {
- expect(
- applyDefaults(fromJS({ collections: [], slug: { encoding: 'ascii' } })).getIn([
- 'slug',
- 'encoding',
- ]),
- ).toEqual('ascii');
- });
-
- it('should not overwrite slug clean_accents if set', () => {
- expect(
- applyDefaults(fromJS({ collections: [], slug: { clean_accents: true } })).getIn([
- 'slug',
- 'clean_accents',
- ]),
- ).toEqual(true);
- });
-
- it('should not overwrite slug sanitize_replacement if set', () => {
- expect(
- applyDefaults(fromJS({ collections: [], slug: { sanitize_replacement: '_' } })).getIn([
- 'slug',
- 'sanitize_replacement',
- ]),
- ).toEqual('_');
- });
+ ).getIn(['collections', 0, 'files', 0, 'file']),
+ ).toEqual('foo');
});
describe('public_folder and media_folder', () => {
@@ -118,16 +118,8 @@ describe('config', () => {
fromJS({
collections: [{ folder: 'foo', media_folder: 'static/images/docs' }],
}),
- ).get('collections'),
- ).toEqual(
- fromJS([
- {
- folder: 'foo',
- media_folder: 'static/images/docs',
- public_folder: 'static/images/docs',
- },
- ]),
- );
+ ).getIn(['collections', 0, 'public_folder']),
+ ).toEqual('static/images/docs');
});
it('should not overwrite collection public_folder if set', () => {
@@ -142,30 +134,42 @@ describe('config', () => {
},
],
}),
- ).get('collections'),
- ).toEqual(
- fromJS([
- {
- folder: 'foo',
- media_folder: 'static/images/docs',
- public_folder: 'images/docs',
- },
- ]),
- );
+ ).getIn(['collections', 0, 'public_folder']),
+ ).toEqual('images/docs');
});
it("should set collection media_folder and public_folder to an empty string when collection path exists, but collection media_folder doesn't", () => {
+ const result = applyDefaults(
+ fromJS({
+ collections: [{ folder: 'foo', path: '{{slug}}/index' }],
+ }),
+ );
+ expect(result.getIn(['collections', 0, 'media_folder'])).toEqual('');
+ expect(result.getIn(['collections', 0, 'public_folder'])).toEqual('');
+ });
+ });
+
+ describe('publish', () => {
+ it('should set publish to true if not set', () => {
expect(
applyDefaults(
fromJS({
- collections: [{ folder: 'foo', path: '{{slug}}/index' }],
+ collections: [{ folder: 'foo', media_folder: 'static/images/docs' }],
}),
- ).get('collections'),
- ).toEqual(
- fromJS([
- { folder: 'foo', path: '{{slug}}/index', media_folder: '', public_folder: '' },
- ]),
- );
+ ).getIn(['collections', 0, 'publish']),
+ ).toEqual(true);
+ });
+
+ it('should not override existing publish config', () => {
+ expect(
+ applyDefaults(
+ fromJS({
+ collections: [
+ { folder: 'foo', media_folder: 'static/images/docs', publish: false },
+ ],
+ }),
+ ).getIn(['collections', 0, 'publish']),
+ ).toEqual(false);
});
});
});
diff --git a/packages/netlify-cms-core/src/actions/config.js b/packages/netlify-cms-core/src/actions/config.js
index 678b448f..fcbadb5a 100644
--- a/packages/netlify-cms-core/src/actions/config.js
+++ b/packages/netlify-cms-core/src/actions/config.js
@@ -58,6 +58,10 @@ export function applyDefaults(config) {
map.set(
'collections',
map.get('collections').map(collection => {
+ if (!collection.has('publish')) {
+ collection = collection.set('publish', true);
+ }
+
const folder = collection.get('folder');
if (folder) {
if (collection.has('path') && !collection.has('media_folder')) {
diff --git a/packages/netlify-cms-core/src/components/Editor/EditorToolbar.js b/packages/netlify-cms-core/src/components/Editor/EditorToolbar.js
index ece3129e..de6333f2 100644
--- a/packages/netlify-cms-core/src/components/Editor/EditorToolbar.js
+++ b/packages/netlify-cms-core/src/components/Editor/EditorToolbar.js
@@ -301,48 +301,146 @@ class EditorToolbar extends React.Component {
);
};
- renderSimplePublishControls = () => {
- const {
- collection,
- onPersist,
- onPersistAndNew,
- onPersistAndDuplicate,
- onDuplicate,
- isPersisting,
- hasChanged,
- isNewEntry,
- t,
- } = this.props;
+ renderWorkflowStatusControls = () => {
+ const { isUpdatingStatus, onChangeStatus, currentStatus, t, useOpenAuthoring } = this.props;
+ return (
+