feat: duplicate entry (#2956)
This commit is contained in:
parent
9ee5d4f66f
commit
d180bffb44
@ -19,8 +19,10 @@ import {
|
|||||||
validateNestedObjectFieldsAndExit,
|
validateNestedObjectFieldsAndExit,
|
||||||
validateListFieldsAndExit,
|
validateListFieldsAndExit,
|
||||||
unpublishEntry,
|
unpublishEntry,
|
||||||
|
publishEntryInEditor,
|
||||||
|
duplicateEntry,
|
||||||
} from '../utils/steps';
|
} from '../utils/steps';
|
||||||
import { setting1, setting2, workflowStatus, editorStatus } from '../utils/constants';
|
import { setting1, setting2, workflowStatus, editorStatus, publishTypes } from '../utils/constants';
|
||||||
|
|
||||||
const entry1 = {
|
const entry1 = {
|
||||||
title: 'first title',
|
title: 'first title',
|
||||||
@ -135,4 +137,12 @@ describe('Test Backend Editorial Workflow', () => {
|
|||||||
// then unpublish it
|
// then unpublish it
|
||||||
unpublishEntry(entry1);
|
unpublishEntry(entry1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('can duplicate an existing entry', () => {
|
||||||
|
login();
|
||||||
|
createPost(entry1);
|
||||||
|
updateWorkflowStatusInEditor(editorStatus.ready);
|
||||||
|
publishEntryInEditor(publishTypes.publishNow);
|
||||||
|
duplicateEntry(entry1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -2,6 +2,7 @@ const workflowStatus = { draft: 'Drafts', review: 'In Review', ready: 'Ready' };
|
|||||||
const editorStatus = { draft: 'Draft', review: 'In review', ready: 'Ready' };
|
const editorStatus = { draft: 'Draft', review: 'In review', ready: 'Ready' };
|
||||||
const setting1 = { limit: 10, author: 'John Doe' };
|
const setting1 = { limit: 10, author: 'John Doe' };
|
||||||
const setting2 = { name: 'Andrew Wommack', description: 'A Gospel Teacher' };
|
const setting2 = { name: 'Andrew Wommack', description: 'A Gospel Teacher' };
|
||||||
|
const publishTypes = { publishNow: 'Publish now' };
|
||||||
const notifications = {
|
const notifications = {
|
||||||
saved: 'Entry saved',
|
saved: 'Entry saved',
|
||||||
published: 'Entry published',
|
published: 'Entry published',
|
||||||
@ -25,4 +26,5 @@ module.exports = {
|
|||||||
setting1,
|
setting1,
|
||||||
setting2,
|
setting2,
|
||||||
notifications,
|
notifications,
|
||||||
|
publishTypes,
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const { notifications, workflowStatus } = require('./constants');
|
const { notifications, workflowStatus, editorStatus, publishTypes } = require('./constants');
|
||||||
|
|
||||||
function login(user) {
|
function login(user) {
|
||||||
cy.viewport(1200, 1200);
|
cy.viewport(1200, 1200);
|
||||||
@ -147,14 +147,23 @@ function assertWorkflowStatus({ title }, status) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateWorkflowStatusInEditor(newStatus) {
|
function updateWorkflowStatusInEditor(newStatus) {
|
||||||
cy.contains('[role="button"]', 'Set status').as('setStatusButton');
|
selectDropdownItem('Set status', newStatus);
|
||||||
cy.get('@setStatusButton')
|
assertNotification(notifications.updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
function publishEntryInEditor(publishType) {
|
||||||
|
selectDropdownItem('Publish', publishType);
|
||||||
|
assertNotification(notifications.published);
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectDropdownItem(label, item){
|
||||||
|
cy.contains('[role="button"]', label).as('dropDownButton');
|
||||||
|
cy.get('@dropDownButton')
|
||||||
.parent()
|
.parent()
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.get('@setStatusButton').click();
|
cy.get('@dropDownButton').click();
|
||||||
cy.contains('[role="menuitem"] span', newStatus).click();
|
cy.contains('[role="menuitem"] span', item).click();
|
||||||
});
|
});
|
||||||
assertNotification(notifications.updated);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function populateEntry(entry) {
|
function populateEntry(entry) {
|
||||||
@ -217,18 +226,25 @@ function unpublishEntry(entry) {
|
|||||||
cy.contains('h2', entry.title)
|
cy.contains('h2', entry.title)
|
||||||
.parent()
|
.parent()
|
||||||
.click({ force: true });
|
.click({ force: true });
|
||||||
cy.contains('[role="button"]', 'Published').as('publishedButton');
|
selectDropdownItem('Published', 'Unpublish');
|
||||||
cy.get('@publishedButton')
|
|
||||||
.parent()
|
|
||||||
.within(() => {
|
|
||||||
cy.get('@publishedButton').click();
|
|
||||||
cy.contains('[role="menuitem"] span', 'Unpublish').click();
|
|
||||||
});
|
|
||||||
assertNotification(notifications.unpublished);
|
assertNotification(notifications.unpublished);
|
||||||
goToWorkflow();
|
goToWorkflow();
|
||||||
assertWorkflowStatus(entry, workflowStatus.ready);
|
assertWorkflowStatus(entry, workflowStatus.ready);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function duplicateEntry(entry) {
|
||||||
|
selectDropdownItem('Published', 'Duplicate');
|
||||||
|
cy.url().should('contain', '/#/collections/posts/new');
|
||||||
|
cy.contains('button', 'Save').click();
|
||||||
|
updateWorkflowStatusInEditor(editorStatus.ready);
|
||||||
|
publishEntryInEditor(publishTypes.publishNow);
|
||||||
|
exitEditor();
|
||||||
|
cy.get('a h2').should(($h2s) => {
|
||||||
|
expect($h2s.eq(0)).to.contain(entry.title);
|
||||||
|
expect($h2s.eq(1)).to.contain(entry.title);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function validateObjectFields({ limit, author }) {
|
function validateObjectFields({ limit, author }) {
|
||||||
cy.get('a[href^="#/collections/settings"]').click();
|
cy.get('a[href^="#/collections/settings"]').click();
|
||||||
cy.get('a[href^="#/collections/settings/entries/general"]').click();
|
cy.get('a[href^="#/collections/settings/entries/general"]').click();
|
||||||
@ -321,4 +337,6 @@ module.exports = {
|
|||||||
validateNestedObjectFieldsAndExit,
|
validateNestedObjectFieldsAndExit,
|
||||||
validateListFieldsAndExit,
|
validateListFieldsAndExit,
|
||||||
unpublishEntry,
|
unpublishEntry,
|
||||||
|
publishEntryInEditor,
|
||||||
|
duplicateEntry,
|
||||||
};
|
};
|
||||||
|
@ -36,6 +36,7 @@ export const DRAFT_VALIDATION_ERRORS = 'DRAFT_VALIDATION_ERRORS';
|
|||||||
export const DRAFT_CLEAR_ERRORS = 'DRAFT_CLEAR_ERRORS';
|
export const DRAFT_CLEAR_ERRORS = 'DRAFT_CLEAR_ERRORS';
|
||||||
export const DRAFT_LOCAL_BACKUP_RETRIEVED = 'DRAFT_LOCAL_BACKUP_RETRIEVED';
|
export const DRAFT_LOCAL_BACKUP_RETRIEVED = 'DRAFT_LOCAL_BACKUP_RETRIEVED';
|
||||||
export const DRAFT_CREATE_FROM_LOCAL_BACKUP = 'DRAFT_CREATE_FROM_LOCAL_BACKUP';
|
export const DRAFT_CREATE_FROM_LOCAL_BACKUP = 'DRAFT_CREATE_FROM_LOCAL_BACKUP';
|
||||||
|
export const DRAFT_CREATE_DUPLICATE_FROM_ENTRY = 'DRAFT_CREATE_DUPLICATE_FROM_ENTRY';
|
||||||
|
|
||||||
export const ENTRY_PERSIST_REQUEST = 'ENTRY_PERSIST_REQUEST';
|
export const ENTRY_PERSIST_REQUEST = 'ENTRY_PERSIST_REQUEST';
|
||||||
export const ENTRY_PERSIST_SUCCESS = 'ENTRY_PERSIST_SUCCESS';
|
export const ENTRY_PERSIST_SUCCESS = 'ENTRY_PERSIST_SUCCESS';
|
||||||
@ -200,6 +201,13 @@ export function createDraftFromEntry(entry, metadata, mediaFiles) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createDraftDuplicateFromEntry(entry) {
|
||||||
|
return {
|
||||||
|
type: DRAFT_CREATE_DUPLICATE_FROM_ENTRY,
|
||||||
|
payload: createEntry(entry.get('collection'), '', '', { data: entry.get('data') }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function discardDraft() {
|
export function discardDraft() {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
loadEntry,
|
loadEntry,
|
||||||
loadEntries,
|
loadEntries,
|
||||||
createDraftFromEntry,
|
createDraftFromEntry,
|
||||||
|
createDraftDuplicateFromEntry,
|
||||||
createEmptyDraft,
|
createEmptyDraft,
|
||||||
discardDraft,
|
discardDraft,
|
||||||
changeDraftField,
|
changeDraftField,
|
||||||
@ -49,6 +50,7 @@ export class Editor extends React.Component {
|
|||||||
changeDraftFieldValidation: PropTypes.func.isRequired,
|
changeDraftFieldValidation: PropTypes.func.isRequired,
|
||||||
collection: ImmutablePropTypes.map.isRequired,
|
collection: ImmutablePropTypes.map.isRequired,
|
||||||
createDraftFromEntry: PropTypes.func.isRequired,
|
createDraftFromEntry: PropTypes.func.isRequired,
|
||||||
|
createDraftDuplicateFromEntry: PropTypes.func.isRequired,
|
||||||
createEmptyDraft: PropTypes.func.isRequired,
|
createEmptyDraft: PropTypes.func.isRequired,
|
||||||
discardDraft: PropTypes.func.isRequired,
|
discardDraft: PropTypes.func.isRequired,
|
||||||
entry: ImmutablePropTypes.map,
|
entry: ImmutablePropTypes.map,
|
||||||
@ -255,7 +257,7 @@ export class Editor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handlePersistEntry = async (opts = {}) => {
|
handlePersistEntry = async (opts = {}) => {
|
||||||
const { createNew = false } = opts;
|
const { createNew = false, duplicate = false } = opts;
|
||||||
const {
|
const {
|
||||||
persistEntry,
|
persistEntry,
|
||||||
collection,
|
collection,
|
||||||
@ -263,7 +265,8 @@ export class Editor extends React.Component {
|
|||||||
hasWorkflow,
|
hasWorkflow,
|
||||||
loadEntry,
|
loadEntry,
|
||||||
slug,
|
slug,
|
||||||
createEmptyDraft,
|
createDraftDuplicateFromEntry,
|
||||||
|
entryDraft,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
await persistEntry(collection);
|
await persistEntry(collection);
|
||||||
@ -272,15 +275,23 @@ export class Editor extends React.Component {
|
|||||||
|
|
||||||
if (createNew) {
|
if (createNew) {
|
||||||
navigateToNewEntry(collection.get('name'));
|
navigateToNewEntry(collection.get('name'));
|
||||||
createEmptyDraft(collection);
|
duplicate && createDraftDuplicateFromEntry(entryDraft.get('entry'));
|
||||||
} else if (slug && hasWorkflow && !currentStatus) {
|
} else if (slug && hasWorkflow && !currentStatus) {
|
||||||
loadEntry(collection, slug);
|
loadEntry(collection, slug);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
handlePublishEntry = async (opts = {}) => {
|
handlePublishEntry = async (opts = {}) => {
|
||||||
const { createNew = false } = opts;
|
const { createNew = false, duplicate = false } = opts;
|
||||||
const { publishUnpublishedEntry, entryDraft, collection, slug, currentStatus, t } = this.props;
|
const {
|
||||||
|
publishUnpublishedEntry,
|
||||||
|
createDraftDuplicateFromEntry,
|
||||||
|
entryDraft,
|
||||||
|
collection,
|
||||||
|
slug,
|
||||||
|
currentStatus,
|
||||||
|
t,
|
||||||
|
} = this.props;
|
||||||
if (currentStatus !== status.last()) {
|
if (currentStatus !== status.last()) {
|
||||||
window.alert(t('editor.editor.onPublishingNotReady'));
|
window.alert(t('editor.editor.onPublishingNotReady'));
|
||||||
return;
|
return;
|
||||||
@ -298,6 +309,8 @@ export class Editor extends React.Component {
|
|||||||
if (createNew) {
|
if (createNew) {
|
||||||
navigateToNewEntry(collection.get('name'));
|
navigateToNewEntry(collection.get('name'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
duplicate && createDraftDuplicateFromEntry(entryDraft.get('entry'));
|
||||||
};
|
};
|
||||||
|
|
||||||
handleUnpublishEntry = async () => {
|
handleUnpublishEntry = async () => {
|
||||||
@ -309,6 +322,13 @@ export class Editor extends React.Component {
|
|||||||
return navigateToCollection(collection.get('name'));
|
return navigateToCollection(collection.get('name'));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleDuplicateEntry = async () => {
|
||||||
|
const { createDraftDuplicateFromEntry, collection, entryDraft } = this.props;
|
||||||
|
|
||||||
|
await navigateToNewEntry(collection.get('name'));
|
||||||
|
createDraftDuplicateFromEntry(entryDraft.get('entry'));
|
||||||
|
};
|
||||||
|
|
||||||
handleDeleteEntry = () => {
|
handleDeleteEntry = () => {
|
||||||
const { entryDraft, newEntry, collection, deleteEntry, slug, t } = this.props;
|
const { entryDraft, newEntry, collection, deleteEntry, slug, t } = this.props;
|
||||||
if (entryDraft.get('hasChanged')) {
|
if (entryDraft.get('hasChanged')) {
|
||||||
@ -415,6 +435,7 @@ export class Editor extends React.Component {
|
|||||||
onChangeStatus={this.handleChangeStatus}
|
onChangeStatus={this.handleChangeStatus}
|
||||||
onPublish={this.handlePublishEntry}
|
onPublish={this.handlePublishEntry}
|
||||||
unPublish={this.handleUnpublishEntry}
|
unPublish={this.handleUnpublishEntry}
|
||||||
|
onDuplicate={this.handleDuplicateEntry}
|
||||||
showDelete={this.props.showDelete}
|
showDelete={this.props.showDelete}
|
||||||
user={user}
|
user={user}
|
||||||
hasChanged={hasChanged}
|
hasChanged={hasChanged}
|
||||||
@ -486,6 +507,7 @@ export default connect(mapStateToProps, {
|
|||||||
persistLocalBackup,
|
persistLocalBackup,
|
||||||
deleteLocalBackup,
|
deleteLocalBackup,
|
||||||
createDraftFromEntry,
|
createDraftFromEntry,
|
||||||
|
createDraftDuplicateFromEntry,
|
||||||
createEmptyDraft,
|
createEmptyDraft,
|
||||||
discardDraft,
|
discardDraft,
|
||||||
persistEntry,
|
persistEntry,
|
||||||
|
@ -123,15 +123,15 @@ class EditorInterface extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
handleOnPersist = (opts = {}) => {
|
handleOnPersist = (opts = {}) => {
|
||||||
const { createNew = false } = opts;
|
const { createNew = false, duplicate = false } = opts;
|
||||||
this.controlPaneRef.validate();
|
this.controlPaneRef.validate();
|
||||||
this.props.onPersist({ createNew });
|
this.props.onPersist({ createNew, duplicate });
|
||||||
};
|
};
|
||||||
|
|
||||||
handleOnPublish = (opts = {}) => {
|
handleOnPublish = (opts = {}) => {
|
||||||
const { createNew = false } = opts;
|
const { createNew = false, duplicate = false } = opts;
|
||||||
this.controlPaneRef.validate();
|
this.controlPaneRef.validate();
|
||||||
this.props.onPublish({ createNew });
|
this.props.onPublish({ createNew, duplicate });
|
||||||
};
|
};
|
||||||
|
|
||||||
handleTogglePreview = () => {
|
handleTogglePreview = () => {
|
||||||
@ -161,6 +161,7 @@ class EditorInterface extends Component {
|
|||||||
onChangeStatus,
|
onChangeStatus,
|
||||||
onPublish,
|
onPublish,
|
||||||
unPublish,
|
unPublish,
|
||||||
|
onDuplicate,
|
||||||
onValidate,
|
onValidate,
|
||||||
user,
|
user,
|
||||||
hasChanged,
|
hasChanged,
|
||||||
@ -230,13 +231,16 @@ class EditorInterface extends Component {
|
|||||||
isDeleting={entry.get('isDeleting')}
|
isDeleting={entry.get('isDeleting')}
|
||||||
onPersist={this.handleOnPersist}
|
onPersist={this.handleOnPersist}
|
||||||
onPersistAndNew={() => this.handleOnPersist({ createNew: true })}
|
onPersistAndNew={() => this.handleOnPersist({ createNew: true })}
|
||||||
|
onPersistAndDuplicate={() => this.handleOnPersist({ createNew: true, duplicate: true })}
|
||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
onDeleteUnpublishedChanges={onDeleteUnpublishedChanges}
|
onDeleteUnpublishedChanges={onDeleteUnpublishedChanges}
|
||||||
onChangeStatus={onChangeStatus}
|
onChangeStatus={onChangeStatus}
|
||||||
showDelete={showDelete}
|
showDelete={showDelete}
|
||||||
onPublish={onPublish}
|
onPublish={onPublish}
|
||||||
unPublish={unPublish}
|
unPublish={unPublish}
|
||||||
|
onDuplicate={onDuplicate}
|
||||||
onPublishAndNew={() => this.handleOnPublish({ createNew: true })}
|
onPublishAndNew={() => this.handleOnPublish({ createNew: true })}
|
||||||
|
onPublishAndDuplicate={() => this.handleOnPublish({ createNew: true, duplicate: true })}
|
||||||
user={user}
|
user={user}
|
||||||
hasChanged={hasChanged}
|
hasChanged={hasChanged}
|
||||||
displayUrl={displayUrl}
|
displayUrl={displayUrl}
|
||||||
@ -294,6 +298,7 @@ EditorInterface.propTypes = {
|
|||||||
onDeleteUnpublishedChanges: PropTypes.func.isRequired,
|
onDeleteUnpublishedChanges: PropTypes.func.isRequired,
|
||||||
onPublish: PropTypes.func.isRequired,
|
onPublish: PropTypes.func.isRequired,
|
||||||
unPublish: PropTypes.func.isRequired,
|
unPublish: PropTypes.func.isRequired,
|
||||||
|
onDuplicate: PropTypes.func.isRequired,
|
||||||
onChangeStatus: PropTypes.func.isRequired,
|
onChangeStatus: PropTypes.func.isRequired,
|
||||||
user: ImmutablePropTypes.map.isRequired,
|
user: ImmutablePropTypes.map.isRequired,
|
||||||
hasChanged: PropTypes.bool,
|
hasChanged: PropTypes.bool,
|
||||||
|
@ -15,7 +15,6 @@ import {
|
|||||||
colors,
|
colors,
|
||||||
components,
|
components,
|
||||||
buttons,
|
buttons,
|
||||||
lengths,
|
|
||||||
} from 'netlify-cms-ui-default';
|
} from 'netlify-cms-ui-default';
|
||||||
import { status } from 'Constants/publishModes';
|
import { status } from 'Constants/publishModes';
|
||||||
import SettingsDropdown from 'UI/SettingsDropdown';
|
import SettingsDropdown from 'UI/SettingsDropdown';
|
||||||
@ -141,24 +140,11 @@ const SaveButton = styled(ToolbarButton)`
|
|||||||
${buttons.lightBlue};
|
${buttons.lightBlue};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const UnpublishButton = styled(StyledDropdownButton)`
|
const PublishedButton = styled(StyledDropdownButton)`
|
||||||
background-color: ${colorsRaw.tealLight};
|
background-color: ${colorsRaw.tealLight};
|
||||||
color: ${colorsRaw.teal};
|
color: ${colorsRaw.teal};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StatusPublished = styled.div`
|
|
||||||
${styles.buttonMargin};
|
|
||||||
border: 1px solid ${colors.textFieldBorder};
|
|
||||||
border-radius: ${lengths.borderRadius};
|
|
||||||
background-color: ${colorsRaw.white};
|
|
||||||
color: ${colorsRaw.teal};
|
|
||||||
padding: 0 24px;
|
|
||||||
line-height: 36px;
|
|
||||||
cursor: default;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const PublishButton = styled(StyledDropdownButton)`
|
const PublishButton = styled(StyledDropdownButton)`
|
||||||
background-color: ${colorsRaw.teal};
|
background-color: ${colorsRaw.teal};
|
||||||
`;
|
`;
|
||||||
@ -212,13 +198,16 @@ class EditorToolbar extends React.Component {
|
|||||||
isDeleting: PropTypes.bool,
|
isDeleting: PropTypes.bool,
|
||||||
onPersist: PropTypes.func.isRequired,
|
onPersist: PropTypes.func.isRequired,
|
||||||
onPersistAndNew: PropTypes.func.isRequired,
|
onPersistAndNew: PropTypes.func.isRequired,
|
||||||
|
onPersistAndDuplicate: PropTypes.func.isRequired,
|
||||||
showDelete: PropTypes.bool.isRequired,
|
showDelete: PropTypes.bool.isRequired,
|
||||||
onDelete: PropTypes.func.isRequired,
|
onDelete: PropTypes.func.isRequired,
|
||||||
onDeleteUnpublishedChanges: PropTypes.func.isRequired,
|
onDeleteUnpublishedChanges: PropTypes.func.isRequired,
|
||||||
onChangeStatus: PropTypes.func.isRequired,
|
onChangeStatus: PropTypes.func.isRequired,
|
||||||
onPublish: PropTypes.func.isRequired,
|
onPublish: PropTypes.func.isRequired,
|
||||||
unPublish: PropTypes.func.isRequired,
|
unPublish: PropTypes.func.isRequired,
|
||||||
|
onDuplicate: PropTypes.func.isRequired,
|
||||||
onPublishAndNew: PropTypes.func.isRequired,
|
onPublishAndNew: PropTypes.func.isRequired,
|
||||||
|
onPublishAndDuplicate: PropTypes.func.isRequired,
|
||||||
user: ImmutablePropTypes.map.isRequired,
|
user: ImmutablePropTypes.map.isRequired,
|
||||||
hasChanged: PropTypes.bool,
|
hasChanged: PropTypes.bool,
|
||||||
displayUrl: PropTypes.string,
|
displayUrl: PropTypes.string,
|
||||||
@ -293,6 +282,8 @@ class EditorToolbar extends React.Component {
|
|||||||
collection,
|
collection,
|
||||||
onPersist,
|
onPersist,
|
||||||
onPersistAndNew,
|
onPersistAndNew,
|
||||||
|
onPersistAndDuplicate,
|
||||||
|
onDuplicate,
|
||||||
isPersisting,
|
isPersisting,
|
||||||
hasChanged,
|
hasChanged,
|
||||||
isNewEntry,
|
isNewEntry,
|
||||||
@ -302,7 +293,19 @@ class EditorToolbar extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{this.renderDeployPreviewControls(t('editor.editorToolbar.deployButtonLabel'))}
|
{this.renderDeployPreviewControls(t('editor.editorToolbar.deployButtonLabel'))}
|
||||||
<StatusPublished>{t('editor.editorToolbar.published')}</StatusPublished>
|
<ToolbarDropdown
|
||||||
|
dropdownTopOverlap="40px"
|
||||||
|
dropdownWidth="150px"
|
||||||
|
renderButton={() => (
|
||||||
|
<PublishedButton>{t('editor.editorToolbar.published')}</PublishedButton>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<DropdownItem
|
||||||
|
label={t('editor.editorToolbar.duplicate')}
|
||||||
|
icon="add"
|
||||||
|
onClick={onDuplicate}
|
||||||
|
/>
|
||||||
|
</ToolbarDropdown>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -320,17 +323,24 @@ class EditorToolbar extends React.Component {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
label="Publish now"
|
label={t('editor.editorToolbar.publishNow')}
|
||||||
icon="arrow"
|
icon="arrow"
|
||||||
iconDirection="right"
|
iconDirection="right"
|
||||||
onClick={onPersist}
|
onClick={onPersist}
|
||||||
/>
|
/>
|
||||||
{collection.get('create') ? (
|
{collection.get('create') ? (
|
||||||
|
<>
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
label={t('editor.editorToolbar.publishAndCreateNew')}
|
label={t('editor.editorToolbar.publishAndCreateNew')}
|
||||||
icon="add"
|
icon="add"
|
||||||
onClick={onPersistAndNew}
|
onClick={onPersistAndNew}
|
||||||
/>
|
/>
|
||||||
|
<DropdownItem
|
||||||
|
label={t('editor.editorToolbar.publishAndDuplicate')}
|
||||||
|
icon="add"
|
||||||
|
onClick={onPersistAndDuplicate}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
</ToolbarDropdown>
|
</ToolbarDropdown>
|
||||||
</div>
|
</div>
|
||||||
@ -384,7 +394,9 @@ class EditorToolbar extends React.Component {
|
|||||||
onChangeStatus,
|
onChangeStatus,
|
||||||
onPublish,
|
onPublish,
|
||||||
unPublish,
|
unPublish,
|
||||||
|
onDuplicate,
|
||||||
onPublishAndNew,
|
onPublishAndNew,
|
||||||
|
onPublishAndDuplicate,
|
||||||
currentStatus,
|
currentStatus,
|
||||||
isNewEntry,
|
isNewEntry,
|
||||||
useOpenAuthoring,
|
useOpenAuthoring,
|
||||||
@ -447,11 +459,18 @@ class EditorToolbar extends React.Component {
|
|||||||
onClick={onPublish}
|
onClick={onPublish}
|
||||||
/>
|
/>
|
||||||
{collection.get('create') ? (
|
{collection.get('create') ? (
|
||||||
|
<>
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
label={t('editor.editorToolbar.publishAndCreateNew')}
|
label={t('editor.editorToolbar.publishAndCreateNew')}
|
||||||
icon="add"
|
icon="add"
|
||||||
onClick={onPublishAndNew}
|
onClick={onPublishAndNew}
|
||||||
/>
|
/>
|
||||||
|
<DropdownItem
|
||||||
|
label={t('editor.editorToolbar.publishAndDuplicate')}
|
||||||
|
icon="add"
|
||||||
|
onClick={onPublishAndDuplicate}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
</ToolbarDropdown>
|
</ToolbarDropdown>
|
||||||
)}
|
)}
|
||||||
@ -470,11 +489,11 @@ class EditorToolbar extends React.Component {
|
|||||||
dropdownTopOverlap="40px"
|
dropdownTopOverlap="40px"
|
||||||
dropdownWidth="150px"
|
dropdownWidth="150px"
|
||||||
renderButton={() => (
|
renderButton={() => (
|
||||||
<UnpublishButton>
|
<PublishedButton>
|
||||||
{isPersisting
|
{isPersisting
|
||||||
? t('editor.editorToolbar.unpublishing')
|
? t('editor.editorToolbar.unpublishing')
|
||||||
: t('editor.editorToolbar.published')}
|
: t('editor.editorToolbar.published')}
|
||||||
</UnpublishButton>
|
</PublishedButton>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
@ -483,6 +502,11 @@ class EditorToolbar extends React.Component {
|
|||||||
iconDirection="right"
|
iconDirection="right"
|
||||||
onClick={unPublish}
|
onClick={unPublish}
|
||||||
/>
|
/>
|
||||||
|
<DropdownItem
|
||||||
|
label={t('editor.editorToolbar.duplicate')}
|
||||||
|
icon="add"
|
||||||
|
onClick={onDuplicate}
|
||||||
|
/>
|
||||||
</ToolbarDropdown>
|
</ToolbarDropdown>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -26,6 +26,7 @@ describe('Editor', () => {
|
|||||||
changeDraftFieldValidation: jest.fn(),
|
changeDraftFieldValidation: jest.fn(),
|
||||||
collection: fromJS({ name: 'posts' }),
|
collection: fromJS({ name: 'posts' }),
|
||||||
createDraftFromEntry: jest.fn(),
|
createDraftFromEntry: jest.fn(),
|
||||||
|
createDraftDuplicateFromEntry: jest.fn(),
|
||||||
createEmptyDraft: jest.fn(),
|
createEmptyDraft: jest.fn(),
|
||||||
discardDraft: jest.fn(),
|
discardDraft: jest.fn(),
|
||||||
entry: fromJS({}),
|
entry: fromJS({}),
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
DRAFT_CLEAR_ERRORS,
|
DRAFT_CLEAR_ERRORS,
|
||||||
DRAFT_LOCAL_BACKUP_RETRIEVED,
|
DRAFT_LOCAL_BACKUP_RETRIEVED,
|
||||||
DRAFT_CREATE_FROM_LOCAL_BACKUP,
|
DRAFT_CREATE_FROM_LOCAL_BACKUP,
|
||||||
|
DRAFT_CREATE_DUPLICATE_FROM_ENTRY,
|
||||||
ENTRY_PERSIST_REQUEST,
|
ENTRY_PERSIST_REQUEST,
|
||||||
ENTRY_PERSIST_SUCCESS,
|
ENTRY_PERSIST_SUCCESS,
|
||||||
ENTRY_PERSIST_FAILURE,
|
ENTRY_PERSIST_FAILURE,
|
||||||
@ -69,6 +70,16 @@ const entryDraftReducer = (state = Map(), action) => {
|
|||||||
state.set('fieldsErrors', Map());
|
state.set('fieldsErrors', Map());
|
||||||
state.set('hasChanged', true);
|
state.set('hasChanged', true);
|
||||||
});
|
});
|
||||||
|
case DRAFT_CREATE_DUPLICATE_FROM_ENTRY:
|
||||||
|
// Duplicate Entry
|
||||||
|
return state.withMutations(state => {
|
||||||
|
state.set('entry', fromJS(action.payload));
|
||||||
|
state.setIn(['entry', 'newRecord'], true);
|
||||||
|
state.set('mediaFiles', List());
|
||||||
|
state.set('fieldsMetaData', Map());
|
||||||
|
state.set('fieldsErrors', Map());
|
||||||
|
state.set('hasChanged', true);
|
||||||
|
});
|
||||||
case DRAFT_DISCARD:
|
case DRAFT_DISCARD:
|
||||||
return initialState;
|
return initialState;
|
||||||
case DRAFT_LOCAL_BACKUP_RETRIEVED: {
|
case DRAFT_LOCAL_BACKUP_RETRIEVED: {
|
||||||
|
@ -65,8 +65,10 @@ const en = {
|
|||||||
publish: 'Publish',
|
publish: 'Publish',
|
||||||
published: 'Published',
|
published: 'Published',
|
||||||
unpublish: 'Unpublish',
|
unpublish: 'Unpublish',
|
||||||
|
duplicate: 'Duplicate',
|
||||||
unpublishing: 'Unpublishing...',
|
unpublishing: 'Unpublishing...',
|
||||||
publishAndCreateNew: 'Publish and create new',
|
publishAndCreateNew: 'Publish and create new',
|
||||||
|
publishAndDuplicate: 'Publish and duplicate',
|
||||||
deleteUnpublishedChanges: 'Delete unpublished changes',
|
deleteUnpublishedChanges: 'Delete unpublished changes',
|
||||||
deleteUnpublishedEntry: 'Delete unpublished entry',
|
deleteUnpublishedEntry: 'Delete unpublished entry',
|
||||||
deletePublishedEntry: 'Delete published entry',
|
deletePublishedEntry: 'Delete published entry',
|
||||||
|
@ -312,7 +312,6 @@ const components = {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&:last-of-type {
|
&:last-of-type {
|
||||||
border-bottom: 0;
|
border-bottom: 0;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user