feat: add publish configuration option to collections (#3467)
This commit is contained in:
parent
2f86d6fc36
commit
df33bc64a9
@ -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');
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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')) {
|
||||
|
@ -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 (
|
||||
<ToolbarDropdown
|
||||
dropdownTopOverlap="40px"
|
||||
dropdownWidth="120px"
|
||||
renderButton={() => (
|
||||
<StatusButton>
|
||||
{isUpdatingStatus
|
||||
? t('editor.editorToolbar.updating')
|
||||
: t('editor.editorToolbar.setStatus')}
|
||||
</StatusButton>
|
||||
)}
|
||||
>
|
||||
<StatusDropdownItem
|
||||
label={t('editor.editorToolbar.draft')}
|
||||
onClick={() => onChangeStatus('DRAFT')}
|
||||
icon={currentStatus === status.get('DRAFT') ? 'check' : null}
|
||||
/>
|
||||
<StatusDropdownItem
|
||||
label={t('editor.editorToolbar.inReview')}
|
||||
onClick={() => onChangeStatus('PENDING_REVIEW')}
|
||||
icon={currentStatus === status.get('PENDING_REVIEW') ? 'check' : null}
|
||||
/>
|
||||
{useOpenAuthoring ? (
|
||||
''
|
||||
) : (
|
||||
<StatusDropdownItem
|
||||
label={t('editor.editorToolbar.ready')}
|
||||
onClick={() => onChangeStatus('PENDING_PUBLISH')}
|
||||
icon={currentStatus === status.get('PENDING_PUBLISH') ? 'check' : null}
|
||||
/>
|
||||
)}
|
||||
</ToolbarDropdown>
|
||||
);
|
||||
};
|
||||
|
||||
renderNewEntryWorkflowPublishControls = ({ canCreate, canPublish }) => {
|
||||
const { isPublishing, onPublish, onPublishAndNew, onPublishAndDuplicate, t } = this.props;
|
||||
|
||||
return canPublish ? (
|
||||
<ToolbarDropdown
|
||||
dropdownTopOverlap="40px"
|
||||
dropdownWidth="150px"
|
||||
renderButton={() => (
|
||||
<PublishButton>
|
||||
{isPublishing
|
||||
? t('editor.editorToolbar.publishing')
|
||||
: t('editor.editorToolbar.publish')}
|
||||
</PublishButton>
|
||||
)}
|
||||
>
|
||||
<DropdownItem
|
||||
label={t('editor.editorToolbar.publishNow')}
|
||||
icon="arrow"
|
||||
iconDirection="right"
|
||||
onClick={onPublish}
|
||||
/>
|
||||
{canCreate ? (
|
||||
<>
|
||||
<DropdownItem
|
||||
label={t('editor.editorToolbar.publishAndCreateNew')}
|
||||
icon="add"
|
||||
onClick={onPublishAndNew}
|
||||
/>
|
||||
<DropdownItem
|
||||
label={t('editor.editorToolbar.publishAndDuplicate')}
|
||||
icon="add"
|
||||
onClick={onPublishAndDuplicate}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
</ToolbarDropdown>
|
||||
) : (
|
||||
''
|
||||
);
|
||||
};
|
||||
|
||||
renderExistingEntryWorkflowPublishControls = ({ canCreate, canPublish }) => {
|
||||
const { unPublish, onDuplicate, isPersisting, t } = this.props;
|
||||
|
||||
return canPublish || canCreate ? (
|
||||
<ToolbarDropdown
|
||||
dropdownTopOverlap="40px"
|
||||
dropdownWidth="150px"
|
||||
renderButton={() => (
|
||||
<PublishedToolbarButton>
|
||||
{isPersisting
|
||||
? t('editor.editorToolbar.unpublishing')
|
||||
: t('editor.editorToolbar.published')}
|
||||
</PublishedToolbarButton>
|
||||
)}
|
||||
>
|
||||
{canPublish && (
|
||||
<DropdownItem
|
||||
label={t('editor.editorToolbar.unpublish')}
|
||||
icon="arrow"
|
||||
iconDirection="right"
|
||||
onClick={unPublish}
|
||||
/>
|
||||
)}
|
||||
{canCreate && (
|
||||
<DropdownItem
|
||||
label={t('editor.editorToolbar.duplicate')}
|
||||
icon="add"
|
||||
onClick={onDuplicate}
|
||||
/>
|
||||
)}
|
||||
</ToolbarDropdown>
|
||||
) : (
|
||||
''
|
||||
);
|
||||
};
|
||||
|
||||
renderExistingEntrySimplePublishControls = ({ canCreate }) => {
|
||||
const { onDuplicate, t } = this.props;
|
||||
return canCreate ? (
|
||||
<ToolbarDropdown
|
||||
dropdownTopOverlap="40px"
|
||||
dropdownWidth="150px"
|
||||
renderButton={() => (
|
||||
<PublishedToolbarButton>{t('editor.editorToolbar.published')}</PublishedToolbarButton>
|
||||
)}
|
||||
>
|
||||
{
|
||||
<DropdownItem
|
||||
label={t('editor.editorToolbar.duplicate')}
|
||||
icon="add"
|
||||
onClick={onDuplicate}
|
||||
/>
|
||||
}
|
||||
</ToolbarDropdown>
|
||||
) : (
|
||||
<PublishedButton>{t('editor.editorToolbar.published')}</PublishedButton>
|
||||
);
|
||||
};
|
||||
|
||||
renderNewEntrySimplePublishControls = ({ canCreate }) => {
|
||||
const { onPersist, onPersistAndNew, onPersistAndDuplicate, isPersisting, t } = this.props;
|
||||
|
||||
const canCreate = collection.get('create');
|
||||
if (!isNewEntry && !hasChanged) {
|
||||
return (
|
||||
<>
|
||||
{this.renderDeployPreviewControls(t('editor.editorToolbar.deployButtonLabel'))}
|
||||
{canCreate ? (
|
||||
<ToolbarDropdown
|
||||
dropdownTopOverlap="40px"
|
||||
dropdownWidth="150px"
|
||||
renderButton={() => (
|
||||
<PublishedToolbarButton>
|
||||
{t('editor.editorToolbar.published')}
|
||||
</PublishedToolbarButton>
|
||||
)}
|
||||
>
|
||||
{
|
||||
<DropdownItem
|
||||
label={t('editor.editorToolbar.duplicate')}
|
||||
icon="add"
|
||||
onClick={onDuplicate}
|
||||
/>
|
||||
}
|
||||
</ToolbarDropdown>
|
||||
) : (
|
||||
<PublishedButton>{t('editor.editorToolbar.published')}</PublishedButton>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<ToolbarDropdown
|
||||
@ -381,6 +479,21 @@ class EditorToolbar extends React.Component {
|
||||
);
|
||||
};
|
||||
|
||||
renderSimplePublishControls = () => {
|
||||
const { collection, hasChanged, isNewEntry, t } = this.props;
|
||||
|
||||
const canCreate = collection.get('create');
|
||||
if (!isNewEntry && !hasChanged) {
|
||||
return (
|
||||
<>
|
||||
{this.renderDeployPreviewControls(t('editor.editorToolbar.deployButtonLabel'))}
|
||||
{this.renderExistingEntrySimplePublishControls({ canCreate })}
|
||||
</>
|
||||
);
|
||||
}
|
||||
return this.renderNewEntrySimplePublishControls({ canCreate });
|
||||
};
|
||||
|
||||
renderWorkflowSaveControls = () => {
|
||||
const {
|
||||
onPersist,
|
||||
@ -421,95 +534,17 @@ class EditorToolbar extends React.Component {
|
||||
};
|
||||
|
||||
renderWorkflowPublishControls = () => {
|
||||
const {
|
||||
collection,
|
||||
isUpdatingStatus,
|
||||
isPublishing,
|
||||
onChangeStatus,
|
||||
onPublish,
|
||||
unPublish,
|
||||
onDuplicate,
|
||||
onPublishAndNew,
|
||||
onPublishAndDuplicate,
|
||||
currentStatus,
|
||||
isNewEntry,
|
||||
useOpenAuthoring,
|
||||
isPersisting,
|
||||
t,
|
||||
} = this.props;
|
||||
const { collection, currentStatus, isNewEntry, useOpenAuthoring, t } = this.props;
|
||||
|
||||
const canCreate = collection.get('create');
|
||||
const canPublish = collection.get('publish') && !useOpenAuthoring;
|
||||
|
||||
if (currentStatus) {
|
||||
return (
|
||||
<>
|
||||
{this.renderDeployPreviewControls(t('editor.editorToolbar.deployPreviewButtonLabel'))}
|
||||
<ToolbarDropdown
|
||||
dropdownTopOverlap="40px"
|
||||
dropdownWidth="120px"
|
||||
renderButton={() => (
|
||||
<StatusButton>
|
||||
{isUpdatingStatus
|
||||
? t('editor.editorToolbar.updating')
|
||||
: t('editor.editorToolbar.setStatus')}
|
||||
</StatusButton>
|
||||
)}
|
||||
>
|
||||
<StatusDropdownItem
|
||||
label={t('editor.editorToolbar.draft')}
|
||||
onClick={() => onChangeStatus('DRAFT')}
|
||||
icon={currentStatus === status.get('DRAFT') ? 'check' : null}
|
||||
/>
|
||||
<StatusDropdownItem
|
||||
label={t('editor.editorToolbar.inReview')}
|
||||
onClick={() => onChangeStatus('PENDING_REVIEW')}
|
||||
icon={currentStatus === status.get('PENDING_REVIEW') ? 'check' : null}
|
||||
/>
|
||||
{useOpenAuthoring ? (
|
||||
''
|
||||
) : (
|
||||
<StatusDropdownItem
|
||||
label={t('editor.editorToolbar.ready')}
|
||||
onClick={() => onChangeStatus('PENDING_PUBLISH')}
|
||||
icon={currentStatus === status.get('PENDING_PUBLISH') ? 'check' : null}
|
||||
/>
|
||||
)}
|
||||
</ToolbarDropdown>
|
||||
{useOpenAuthoring ? (
|
||||
''
|
||||
) : (
|
||||
<ToolbarDropdown
|
||||
dropdownTopOverlap="40px"
|
||||
dropdownWidth="150px"
|
||||
renderButton={() => (
|
||||
<PublishButton>
|
||||
{isPublishing
|
||||
? t('editor.editorToolbar.publishing')
|
||||
: t('editor.editorToolbar.publish')}
|
||||
</PublishButton>
|
||||
)}
|
||||
>
|
||||
<DropdownItem
|
||||
label={t('editor.editorToolbar.publishNow')}
|
||||
icon="arrow"
|
||||
iconDirection="right"
|
||||
onClick={onPublish}
|
||||
/>
|
||||
{canCreate ? (
|
||||
<>
|
||||
<DropdownItem
|
||||
label={t('editor.editorToolbar.publishAndCreateNew')}
|
||||
icon="add"
|
||||
onClick={onPublishAndNew}
|
||||
/>
|
||||
<DropdownItem
|
||||
label={t('editor.editorToolbar.publishAndDuplicate')}
|
||||
icon="add"
|
||||
onClick={onPublishAndDuplicate}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
</ToolbarDropdown>
|
||||
)}
|
||||
{this.renderWorkflowStatusControls()}
|
||||
{this.renderNewEntryWorkflowPublishControls({ canCreate, canPublish })}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -521,31 +556,7 @@ class EditorToolbar extends React.Component {
|
||||
return (
|
||||
<>
|
||||
{this.renderDeployPreviewControls(t('editor.editorToolbar.deployButtonLabel'))}
|
||||
<ToolbarDropdown
|
||||
dropdownTopOverlap="40px"
|
||||
dropdownWidth="150px"
|
||||
renderButton={() => (
|
||||
<PublishedToolbarButton>
|
||||
{isPersisting
|
||||
? t('editor.editorToolbar.unpublishing')
|
||||
: t('editor.editorToolbar.published')}
|
||||
</PublishedToolbarButton>
|
||||
)}
|
||||
>
|
||||
<DropdownItem
|
||||
label={t('editor.editorToolbar.unpublish')}
|
||||
icon="arrow"
|
||||
iconDirection="right"
|
||||
onClick={unPublish}
|
||||
/>
|
||||
{canCreate && (
|
||||
<DropdownItem
|
||||
label={t('editor.editorToolbar.duplicate')}
|
||||
icon="add"
|
||||
onClick={onDuplicate}
|
||||
/>
|
||||
)}
|
||||
</ToolbarDropdown>
|
||||
{this.renderExistingEntryWorkflowPublishControls({ canCreate, canPublish })}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ const WorkflowTopDescription = styled.p`
|
||||
|
||||
class Workflow extends Component {
|
||||
static propTypes = {
|
||||
collections: ImmutablePropTypes.orderedMap,
|
||||
collections: ImmutablePropTypes.orderedMap.isRequired,
|
||||
isEditorialWorkflow: PropTypes.bool.isRequired,
|
||||
isOpenAuthoring: PropTypes.bool,
|
||||
isFetching: PropTypes.bool,
|
||||
|
@ -126,6 +126,7 @@ const WorkflowCard = ({
|
||||
editLink,
|
||||
timestamp,
|
||||
onDelete,
|
||||
allowPublish,
|
||||
canPublish,
|
||||
onPublish,
|
||||
t,
|
||||
@ -143,11 +144,13 @@ const WorkflowCard = ({
|
||||
? t('workflow.workflowCard.deleteChanges')
|
||||
: t('workflow.workflowCard.deleteNewEntry')}
|
||||
</DeleteButton>
|
||||
<PublishButton disabled={!canPublish} onClick={onPublish}>
|
||||
{isModification
|
||||
? t('workflow.workflowCard.publishChanges')
|
||||
: t('workflow.workflowCard.publishNewEntry')}
|
||||
</PublishButton>
|
||||
{allowPublish && (
|
||||
<PublishButton disabled={!canPublish} onClick={onPublish}>
|
||||
{isModification
|
||||
? t('workflow.workflowCard.publishChanges')
|
||||
: t('workflow.workflowCard.publishNewEntry')}
|
||||
</PublishButton>
|
||||
)}
|
||||
</CardButtonContainer>
|
||||
</WorkflowCardContainer>
|
||||
);
|
||||
@ -161,6 +164,7 @@ WorkflowCard.propTypes = {
|
||||
editLink: PropTypes.string.isRequired,
|
||||
timestamp: PropTypes.string.isRequired,
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
allowPublish: PropTypes.bool.isRequired,
|
||||
canPublish: PropTypes.bool.isRequired,
|
||||
onPublish: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
|
@ -134,7 +134,7 @@ class WorkflowList extends React.Component {
|
||||
handleDelete: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
isOpenAuthoring: PropTypes.bool,
|
||||
collections: ImmutablePropTypes.orderedMap,
|
||||
collections: ImmutablePropTypes.orderedMap.isRequired,
|
||||
};
|
||||
|
||||
handleChangeStatus = (newStatus, dragProps) => {
|
||||
@ -210,11 +210,15 @@ class WorkflowList extends React.Component {
|
||||
const editLink = `collections/${entry.getIn(['metaData', 'collection'])}/entries/${slug}`;
|
||||
const ownStatus = entry.getIn(['metaData', 'status']);
|
||||
const collectionName = entry.getIn(['metaData', 'collection']);
|
||||
const collectionLabel = collections
|
||||
?.find(collection => collection.get('name') === collectionName)
|
||||
?.get('label');
|
||||
const collection = collections.find(
|
||||
collection => collection.get('name') === collectionName,
|
||||
);
|
||||
const collectionLabel = collection?.get('label');
|
||||
const isModification = entry.get('isModification');
|
||||
|
||||
const allowPublish = collection?.get('publish');
|
||||
const canPublish = ownStatus === status.last() && !entry.get('isPersisting', false);
|
||||
|
||||
return (
|
||||
<DragSource
|
||||
namespace={DNDNamespace}
|
||||
@ -235,6 +239,7 @@ class WorkflowList extends React.Component {
|
||||
editLink={editLink}
|
||||
timestamp={timestamp}
|
||||
onDelete={this.requestDelete.bind(this, collectionName, slug, ownStatus)}
|
||||
allowPublish={allowPublish}
|
||||
canPublish={canPublish}
|
||||
onPublish={this.requestPublish.bind(this, collectionName, slug, ownStatus)}
|
||||
/>
|
||||
|
@ -152,5 +152,17 @@ describe('config', () => {
|
||||
);
|
||||
}).not.toThrowError();
|
||||
});
|
||||
|
||||
it('should throw if collection publish is not a boolean', () => {
|
||||
expect(() => {
|
||||
validateConfig(merge(validConfig, { collections: [{ publish: 'false' }] }));
|
||||
}).toThrowError("'collections[0].publish' should be boolean");
|
||||
});
|
||||
|
||||
it('should not throw if collection publish is a boolean', () => {
|
||||
expect(() => {
|
||||
validateConfig(merge(validConfig, { collections: [{ publish: false }] }));
|
||||
}).not.toThrowError();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -117,6 +117,7 @@ const getConfigSchema = () => ({
|
||||
preview_path: { type: 'string' },
|
||||
preview_path_date_field: { type: 'string' },
|
||||
create: { type: 'boolean' },
|
||||
publish: { type: 'boolean' },
|
||||
editor: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
|
@ -194,6 +194,7 @@ The `collections` setting is the heart of your Netlify CMS configuration, as it
|
||||
* `files` or `folder` (requires one of these): specifies the collection type and location; details in [Collection Types](../collection-types)
|
||||
* `filter`: optional filter for `folder` collections; details in [Collection Types](../collection-types)
|
||||
* `create`: for `folder` collections only; `true` allows users to create new items in the collection; defaults to `false`
|
||||
* `publish`: for `publish_mode: editorial_workflow` only; `false` hides UI publishing controls for a collection; defaults to `true`
|
||||
* `delete`: `false` prevents users from deleting items in a collection; defaults to `true`
|
||||
* `extension`: see detailed description below
|
||||
* `format`: see detailed description below
|
||||
|
Loading…
x
Reference in New Issue
Block a user