diff --git a/src/actions/editorialWorkflow.js b/src/actions/editorialWorkflow.js index c1cc0ffa6328..4c11905aac10 100644 --- a/src/actions/editorialWorkflow.js +++ b/src/actions/editorialWorkflow.js @@ -1,4 +1,5 @@ import uuid from 'uuid'; +import Immutable from 'immutable'; import { actions as notifActions } from 'redux-notifications'; import { closeEntry } from './editor'; import { BEGIN, COMMIT, REVERT } from 'redux-optimist'; @@ -33,6 +34,17 @@ export const UNPUBLISHED_ENTRY_PUBLISH_REQUEST = 'UNPUBLISHED_ENTRY_PUBLISH_REQU export const UNPUBLISHED_ENTRY_PUBLISH_SUCCESS = 'UNPUBLISHED_ENTRY_PUBLISH_SUCCESS'; export const UNPUBLISHED_ENTRY_PUBLISH_FAILURE = 'UNPUBLISHED_ENTRY_PUBLISH_FAILURE'; +export const UNPUBLISHED_ENTRIES_PUBLISH_REQUEST = 'UNPUBLISHED_ENTRIES_PUBLISH_REQUEST'; +export const UNPUBLISHED_ENTRIES_PUBLISH_SUCCESS = 'UNPUBLISHED_ENTRIES_PUBLISH_SUCCESS'; +export const UNPUBLISHED_ENTRIES_PUBLISH_FAILURE = 'UNPUBLISHED_ENTRIES_PUBLISH_FAILURE'; + +export const UNPUBLISHED_ENTRY_REGISTER_DEPENDENCY = 'UNPUBLISHED_ENTRY_REGISTER_DEPENDENCY'; +export const UNPUBLISHED_ENTRY_UNREGISTER_DEPENDENCY = 'UNPUBLISHED_ENTRY_UNREGISTER_DEPENDENCY'; + +export const UNPUBLISHED_ENTRY_DEPENDENCIES_REQUEST = 'UNPUBLISHED_ENTRY_DEPENDENCIES_REQUEST'; +export const UNPUBLISHED_ENTRY_DEPENDENCIES_SUCCESS = 'UNPUBLISHED_ENTRY_DEPENDENCIES_SUCCESS'; +export const UNPUBLISHED_ENTRY_DEPENDENCIES_FAILURE = 'UNPUBLISHED_ENTRY_DEPENDENCIES_FAILURE'; + /* * Simple Action Creators (Internal) */ @@ -180,16 +192,78 @@ function unpublishedEntryPublishError(collection, slug, transactionID) { }; } +function unpublishedEntriesPublishRequest(entries, transactionID) { + return { + type: UNPUBLISHED_ENTRIES_PUBLISH_REQUEST, + payload: { entries }, + optimist: { type: BEGIN, id: transactionID }, + }; +} + +function unpublishedEntriesPublished(entries, transactionID) { + return { + type: UNPUBLISHED_ENTRIES_PUBLISH_SUCCESS, + payload: { entries }, + optimist: { type: COMMIT, id: transactionID }, + }; +} + +function unpublishedEntriesPublishError(entries, transactionID) { + return { + type: UNPUBLISHED_ENTRIES_PUBLISH_FAILURE, + payload: { entries }, + optimist: { type: REVERT, id: transactionID }, + }; +} + +function unpublishedEntryRegisterDependency(field, collection, slug) { + return { + type: UNPUBLISHED_ENTRY_REGISTER_DEPENDENCY, + payload: { field, collection, slug }, + }; +} + +function unpublishedEntryUnregisterDependency(field) { + return { + type: UNPUBLISHED_ENTRY_UNREGISTER_DEPENDENCY, + payload: { field }, + }; +} + +function unpublishedEntryDependenciesRequest(collection, slug) { + return { + type: UNPUBLISHED_ENTRY_DEPENDENCIES_REQUEST, + payload: { collection, slug }, + }; +} + +function unpublishedEntryDependenciesSuccess(collection, slug, dependencies) { + return { + type: UNPUBLISHED_ENTRY_DEPENDENCIES_SUCCESS, + payload: { collection, slug, dependencies }, + }; +} + +function unpublishedEntryDependenciesError(collection, slug, error) { + return { + type: UNPUBLISHED_ENTRY_DEPENDENCIES_FAILURE, + payload: { collection, slug, error }, + }; +} + /* * Exported Thunk Action Creators */ +export const registerUnpublishedEntryDependency = unpublishedEntryRegisterDependency; +export const unregisterUnpublishedEntryDependency = unpublishedEntryUnregisterDependency; + export function loadUnpublishedEntry(collection, slug) { return (dispatch, getState) => { const state = getState(); const backend = currentBackend(state.config); dispatch(unpublishedEntryLoading(collection, slug)); - backend.unpublishedEntry(collection, slug) + return backend.unpublishedEntry(collection, slug) .then(entry => dispatch(unpublishedEntryLoaded(collection, entry))) .catch((error) => { if (error instanceof EditorialWorkflowError && error.notUnderEditorialWorkflow) { @@ -312,3 +386,158 @@ export function publishUnpublishedEntry(collection, slug) { }); }; } + +export function publishUnpublishedEntries(entries) { + return (dispatch, getState) => { + const state = getState(); + const backend = currentBackend(state.config); + const transactionID = uuid.v4(); + + dispatch(unpublishedEntriesPublishRequest(entries, transactionID)); + backend.publishUnpublishedEntries(entries) + .then(() => { + dispatch(unpublishedEntriesPublished(entries, transactionID)); + }) + .catch((error) => { + dispatch(notifSend({ + message: `Failed to merge: ${ error }`, + kind: 'danger', + dismissAfter: 8000, + })); + dispatch(unpublishedEntriesPublishError(entries, transactionID)); + }); + }; +} + +const getDepsPath = dep => [ + "entities", + dep, + "metaData", + "dependencies", +]; + +const getEventualDependencies = (paths, loadedDeps, state, dispatch) => + // Filter paths to remove those we've already checked. This + // prevents traverse from loading posts we don't need or looping + // infinitely over cyclic dependencies. + paths.filter(path => !loadedDeps.includes(path)).map((path) => { + const [pathCollectionName, pathSlug] = path.split("."); + const pathCollection = state.collections.get(pathCollectionName); + // Wait for the entry to load + return dispatch(loadUnpublishedEntry(pathCollection, pathSlug)) + // Return the path at the end so we can use it in .thens later + .then(() => path); + }); + +const pathHasDependencies = (state, path) => { + if (!state.editorialWorkflow.hasIn(path) || + state.editorialWorkflow.getIn(path) === null) { + return false; + } + + if (state.editorialWorkflow.getIn(path).size === 0) { + return false; + } + + return true; +}; + +const reducePromises = (promises, fn, initPromise) => { + // If the array is empty and we aren't given an init value, we don't + // have anything to return. + if (promises.length === 0 && initPromise === undefined) { + throw new Error("Reduce of empty promise array with no initial value."); + } + + // If we weren't given an init value, then the init value should be + // the first item in `promises` + const [initValue, skipFirstPromise] = (initPromise !== undefined) + ? [initPromise, false] + : [promises[0], true]; + + // If we are using the first promise as our init value, we need to + // remove it from the promises we'll reduce over. + const promisesToReduce = skipFirstPromise + ? promises.slice(1) + : promises; + + return promisesToReduce.reduce((accumulatedPromises, currentPromise) => + Promise.all([accumulatedPromises, currentPromise]).then( + (([accumulated, current]) => fn(accumulated, current))), + initValue); +}; + +const traverse = (collectedDeps, path, getState, dispatch) => { + const state = getState(); + + if (collectedDeps.get(path) === true) { + return Promise.resolve(collectedDeps); + } + + // Add this entry to the dependency list + const newDeps = collectedDeps.set(path, true); + const newDepsPromise = Promise.resolve(newDeps); + + // Get the full state path to this entries dependencies + const depsPath = getDepsPath(path); + + // If the entry has no dependencies, return the collected dependency + // list (including the current entry) + if (!pathHasDependencies(state, depsPath)) { + return newDepsPromise; + } + + const theseDependencies = state.editorialWorkflow.getIn(depsPath); + + // Gets a list of promises for all unrecorded dependencies. Each + // promise resolves once its entry is loaded. + const eventualDeps = getEventualDependencies(theseDependencies, collectedDeps, state, dispatch); + + // Reduce over the list of dependency promises. allDepsPromise is + // the accumulation value. Each time we reduce, we call traverse to + // get the dependencies of the current dependency, then continue to + // the next one. This makes traversal recurse until all the + // dependencies are collected. + return reducePromises( + eventualDeps, + (deps, dep) => traverse(deps, dep, getState, dispatch), + newDepsPromise + ); +}; + +export function getUnpublishedEntryDependencies(collection, slug) { + return (dispatch, getState) => { + dispatch(unpublishedEntryDependenciesRequest(collection, slug)); + + // Begin traversal + return traverse(new Immutable.Map(), `${ collection }.${ slug }`, getState, dispatch) + .then((dependencyMap) => { + const state = getState(); + const dependencies = dependencyMap.keySeq().toList(); + + // Remove any dependencies which are already published + const filteredDependencies = dependencies.filter( + dep => state.editorialWorkflow.hasIn(["entities", dep])); + return dispatch(unpublishedEntryDependenciesSuccess(collection, slug, filteredDependencies)); + }) + .catch(err => dispatch(unpublishedEntryDependenciesError(collection, slug, err))); + }; +} + +export function publishUnpublishedEntryAndDependencies(collection, slug) { + return (dispatch, getState) => dispatch(getUnpublishedEntryDependencies(collection, slug)) + .then(({ payload }) => { + if (payload.dependencies.size === 1) { + return dispatch(publishUnpublishedEntry(collection, slug)); + } + + const confirmationMessage = `\ +This entry has dependencies, and cannot be published on its own. Publish all the following posts? +${ payload.dependencies.join("\n") }`; + + if (window.confirm(confirmationMessage)) { + return dispatch(publishUnpublishedEntries(payload.dependencies.map( + dep => dep.split(".")))); + } + }); +} diff --git a/src/backends/backend.js b/src/backends/backend.js index 49ed91e967c0..348f5e1f0cfc 100644 --- a/src/backends/backend.js +++ b/src/backends/backend.js @@ -148,6 +148,9 @@ class Backend { persistEntry(config, collection, entryDraft, MediaFiles, options) { const newEntry = entryDraft.getIn(["entry", "newRecord"]) || false; + const maybeDeps = entryDraft.getIn(["entry", "dependencies"]); + const dependencies = (maybeDeps !== undefined) ? maybeDeps.toJS() : null; + const parsedData = { title: entryDraft.getIn(["entry", "data", "title"], "No Title"), description: entryDraft.getIn(["entry", "data", "description"], "No Description!"), @@ -184,7 +187,7 @@ class Backend { const collectionName = collection.get("name"); return this.implementation.persistEntry(entryObj, MediaFiles, { - newEntry, parsedData, commitMessage, collectionName, mode, ...options, + newEntry, parsedData, commitMessage, collectionName, mode, dependencies, ...options, }); } @@ -200,6 +203,10 @@ class Backend { return this.implementation.publishUnpublishedEntry(collection, slug); } + publishUnpublishedEntries(entries) { + return this.implementation.publishUnpublishedEntries(entries); + } + deleteUnpublishedEntry(collection, slug) { return this.implementation.deleteUnpublishedEntry(collection, slug); } diff --git a/src/backends/github/API.js b/src/backends/github/API.js index aad6cea9e44b..d006be0edcad 100644 --- a/src/backends/github/API.js +++ b/src/backends/github/API.js @@ -1,5 +1,6 @@ import LocalForage from "localforage"; import { Base64 } from "js-base64"; +import Immutable from "immutable"; import _ from "lodash"; import AssetProxy from "../../valueObjects/AssetProxy"; import { SIMPLE, EDITORIAL_WORKFLOW, status } from "../../constants/publishModes"; @@ -258,6 +259,7 @@ export default class API { collection: options.collectionName, title: options.parsedData && options.parsedData.title, description: options.parsedData && options.parsedData.description, + dependencies: options.dependencies, objects: { entry: { path: entry.path, @@ -288,6 +290,7 @@ export default class API { pr: updatedPR, title: options.parsedData && options.parsedData.title, description: options.parsedData && options.parsedData.description, + dependencies: options.dependencies, objects: { entry: { path: entry.path, @@ -330,6 +333,61 @@ export default class API { .then(() => this.deleteBranch(`cms/${ contentKey }`)); } + publishUnpublishedEntries(entries) { + const entriesMetadataPromise = Promise.all(entries.map(([collection, slug]) => { + const contentKey = slug; + return this.retrieveMetadata(contentKey); + })); + + const entryObjectsPromise = entriesMetadataPromise + .then(metadata => + metadata.map(m => [ + { + ...m.objects.entry, + mode: "100644", + type: "blob", + }, + ...(m.objects.files ? m.objects.files.map(file => ({ + sha: file.sha, + path: (file.path[0] === "/" ? file.path.slice(1) : file.path), + mode: "100644", + type: "blob", + })) : []), + ])) + // Flatten list of objects + .then(objectCollections => [].concat.apply([], objectCollections)); + + const entryPrsPromise = entriesMetadataPromise + .then(metadata => metadata.map(m => m.pr.head)); + + const baseShaPromise = this.getBranch("master") + .then(({ commit }) => commit.sha); + + const treePromise = Promise.all([entryObjectsPromise, baseShaPromise]) + .then(([entryObjects, baseSha]) => this.createTree(baseSha, entryObjects)); + + const commitPromise = Promise.all([baseShaPromise, treePromise, entryPrsPromise]) + .then(([baseSha, tree, entryPrs]) => + this.commit("Publish posts", { sha: tree.sha }, entryPrs.concat([baseSha]))); + + const pushCommitPromise = commitPromise + .then(commit => this.patchRef("heads", "master", commit.sha)); + + const deletePrBranchesPromise = pushCommitPromise + .then(pushCommit => Promise.all(entries.map(entry => this.deleteBranch(`cms/${ entry[1] }`)))); + + return deletePrBranchesPromise; + } + + createTree(baseSha, treeObjects) { + return this.request(`${ this.repoURL }/git/trees`, { + method: "POST", + body: JSON.stringify({ + base_tree: baseSha, + tree: treeObjects, + }), + }); + } createRef(type, name, sha) { return this.request(`${ this.repoURL }/git/refs`, { @@ -484,9 +542,11 @@ export default class API { }); } - commit(message, changeTree) { + commit(message, changeTree, parentArray=[]) { const tree = changeTree.sha; - const parents = changeTree.parentSha ? [changeTree.parentSha] : []; + const parentSha = changeTree.parentSha ? [changeTree.parentSha] : []; + const parents = (parentSha !== [] && parentArray.indexOf(parentSha[0] !== 0)) + ? parentArray.concat(parentSha) : parentArray; return this.request(`${ this.repoURL }/git/commits`, { method: "POST", body: JSON.stringify({ message, tree, parents }), diff --git a/src/backends/github/implementation.js b/src/backends/github/implementation.js index 87818864f032..acd588aae2b4 100644 --- a/src/backends/github/implementation.js +++ b/src/backends/github/implementation.js @@ -141,4 +141,8 @@ export default class GitHub { publishUnpublishedEntry(collection, slug) { return this.api.publishUnpublishedEntry(collection, slug); } + + publishUnpublishedEntries(entries) { + return this.api.publishUnpublishedEntries(entries); + } } diff --git a/src/components/ControlPanel/ControlPane.js b/src/components/ControlPanel/ControlPane.js index 31ab81b614d9..421dddfc3385 100644 --- a/src/components/ControlPanel/ControlPane.js +++ b/src/components/ControlPanel/ControlPane.js @@ -24,7 +24,17 @@ export default class ControlPane extends Component { }; controlFor(field) { - const { entry, fieldsMetaData, fieldsErrors, getAsset, onChange, onAddAsset, onRemoveAsset } = this.props; + const { + entry, + fieldsMetaData, + fieldsErrors, + getAsset, + onChange, + onAddAsset, + onRemoveAsset, + onAddDependency, + onRemoveDependency, + } = this.props; const widget = resolveWidget(field.get('widget')); const fieldName = field.get('name'); const value = entry.getIn(['data', fieldName]); @@ -51,6 +61,8 @@ export default class ControlPane extends Component { onValidate={this.props.onValidate.bind(this, fieldName)} onAddAsset={onAddAsset} onRemoveAsset={onRemoveAsset} + onAddDependency={onAddDependency} + onRemoveDependency={onRemoveDependency} getAsset={getAsset} ref={this.processControlRef.bind(this, fieldName)} /> @@ -90,4 +102,6 @@ ControlPane.propTypes = { onChange: PropTypes.func.isRequired, onValidate: PropTypes.func.isRequired, onRemoveAsset: PropTypes.func.isRequired, + onAddDependency: PropTypes.func, + onRemoveDependency: PropTypes.func, }; diff --git a/src/components/EntryEditor/EntryEditor.js b/src/components/EntryEditor/EntryEditor.js index ac68bffce15c..3c2a643bba00 100644 --- a/src/components/EntryEditor/EntryEditor.js +++ b/src/components/EntryEditor/EntryEditor.js @@ -37,6 +37,8 @@ class EntryEditor extends Component { onValidate, onAddAsset, onRemoveAsset, + onAddDependency, + onRemoveDependency, onCancelEdit, } = this.props; @@ -65,6 +67,8 @@ class EntryEditor extends Component { onValidate={onValidate} onAddAsset={onAddAsset} onRemoveAsset={onRemoveAsset} + onAddDependency={onAddDependency} + onRemoveDependency={onRemoveDependency} ref={c => this.controlPaneRef = c} // eslint-disable-line /> @@ -107,6 +111,8 @@ EntryEditor.propTypes = { onValidate: PropTypes.func.isRequired, onPersist: PropTypes.func.isRequired, onRemoveAsset: PropTypes.func.isRequired, + onAddDependency: PropTypes.func, + onRemoveDependency: PropTypes.func, onCancelEdit: PropTypes.func.isRequired, }; diff --git a/src/components/Widgets/ControlHOC.js b/src/components/Widgets/ControlHOC.js index 910735199732..896f591280df 100644 --- a/src/components/Widgets/ControlHOC.js +++ b/src/components/Widgets/ControlHOC.js @@ -15,6 +15,8 @@ class ControlHOC extends Component { onAddAsset: PropTypes.func.isRequired, onRemoveAsset: PropTypes.func.isRequired, getAsset: PropTypes.func.isRequired, + onAddDependency: PropTypes.func, + onRemoveDependency: PropTypes.func, }; processInnerControlRef = (wrappedControl) => { @@ -80,7 +82,18 @@ class ControlHOC extends Component { }; render() { - const { controlComponent, field, value, metadata, onChange, onAddAsset, onRemoveAsset, getAsset } = this.props; + const { + controlComponent, + field, + value, + metadata, + onChange, + onAddAsset, + onRemoveAsset, + getAsset, + onAddDependency, + onRemoveDependency, + } = this.props; return React.createElement(controlComponent, { field, value, @@ -88,6 +101,8 @@ class ControlHOC extends Component { onChange, onAddAsset, onRemoveAsset, + onAddDependency, + onRemoveDependency, getAsset, forID: field.get('name'), ref: this.processInnerControlRef, diff --git a/src/components/Widgets/RelationControl.js b/src/components/Widgets/RelationControl.js index fa6b3c94e44c..99c2091d63c3 100644 --- a/src/components/Widgets/RelationControl.js +++ b/src/components/Widgets/RelationControl.js @@ -15,6 +15,8 @@ function escapeRegexCharacters(str) { class RelationControl extends Component { static propTypes = { onChange: PropTypes.func.isRequired, + onAddDependency: PropTypes.func, + onRemoveDependency: PropTypes.func, forID: PropTypes.string.isRequired, value: PropTypes.node, field: PropTypes.node, @@ -60,6 +62,11 @@ class RelationControl extends Component { onSuggestionSelected = (event, { suggestion }) => { const value = this.getSuggestionValue(suggestion); + + if (this.props.onAddDependency) { + this.props.onAddDependency(this.props.field.get('name'), suggestion.collection, suggestion.slug); + } + this.props.onChange(value, { [this.props.field.get('collection')]: { [value]: suggestion.data } }); }; diff --git a/src/containers/EntryPage.js b/src/containers/EntryPage.js index d25ed026a5ef..9d9acff1e3a0 100644 --- a/src/containers/EntryPage.js +++ b/src/containers/EntryPage.js @@ -34,6 +34,7 @@ class EntryPage extends React.Component { loadEntry: PropTypes.func.isRequired, persistEntry: PropTypes.func.isRequired, removeAsset: PropTypes.func.isRequired, + registerDependency: PropTypes.func, closeEntry: PropTypes.func.isRequired, openSidebar: PropTypes.func.isRequired, fields: ImmutablePropTypes.list.isRequired, @@ -91,6 +92,8 @@ class EntryPage extends React.Component { changeDraftFieldValidation, addAsset, removeAsset, + onAddDependency, + onRemoveDependency, closeEntry, } = this.props; @@ -114,6 +117,8 @@ class EntryPage extends React.Component { onValidate={changeDraftFieldValidation} onAddAsset={addAsset} onRemoveAsset={removeAsset} + onAddDependency={onAddDependency} + onRemoveDependency={onRemoveDependency} onPersist={this.handlePersistEntry} onCancelEdit={this.handleCloseEntry} /> diff --git a/src/containers/editorialWorkflow/EntryPageHOC.js b/src/containers/editorialWorkflow/EntryPageHOC.js index c13a7b923747..5a42e8d7be03 100644 --- a/src/containers/editorialWorkflow/EntryPageHOC.js +++ b/src/containers/editorialWorkflow/EntryPageHOC.js @@ -2,7 +2,12 @@ import React from 'react'; import { connect } from 'react-redux'; import { EDITORIAL_WORKFLOW } from '../../constants/publishModes'; import { selectUnpublishedEntry, selectEntry } from '../../reducers'; -import { loadUnpublishedEntry, persistUnpublishedEntry } from '../../actions/editorialWorkflow'; +import { + loadUnpublishedEntry, + persistUnpublishedEntry, + registerUnpublishedEntryDependency, + unregisterUnpublishedEntryDependency, +} from '../../actions/editorialWorkflow'; export default function EntryPageHOC(EntryPage) { @@ -43,6 +48,13 @@ export default function EntryPageHOC(EntryPage) { returnObj.persistEntry = (collection) => { dispatch(persistUnpublishedEntry(collection, unpublishedEntry)); }; + returnObj.onAddDependency = (field, collection, slug) => { + dispatch(registerUnpublishedEntryDependency(field, collection, slug)); + }; + + returnObj.onRemoveDependency = (field) => { + dispatch(unregisterUnpublishedEntryDependency(field)); + }; } return { diff --git a/src/containers/editorialWorkflow/UnpublishedEntriesPanel.js b/src/containers/editorialWorkflow/UnpublishedEntriesPanel.js index ddb263c5a80d..45c0277997d9 100644 --- a/src/containers/editorialWorkflow/UnpublishedEntriesPanel.js +++ b/src/containers/editorialWorkflow/UnpublishedEntriesPanel.js @@ -2,11 +2,12 @@ import React, { Component, PropTypes } from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { OrderedMap } from 'immutable'; import { connect } from 'react-redux'; -import { +import { loadUnpublishedEntries, updateUnpublishedEntryStatus, publishUnpublishedEntry, - deleteUnpublishedEntry + publishUnpublishedEntryAndDependencies, + deleteUnpublishedEntry } from '../../actions/editorialWorkflow'; import { selectUnpublishedEntriesByStatus } from '../../reducers'; import { EDITORIAL_WORKFLOW, status } from '../../constants/publishModes'; @@ -69,6 +70,6 @@ function mapStateToProps(state) { export default connect(mapStateToProps, { loadUnpublishedEntries, updateUnpublishedEntryStatus, - publishUnpublishedEntry, + publishUnpublishedEntry: publishUnpublishedEntryAndDependencies, deleteUnpublishedEntry, })(unpublishedEntriesPanel); diff --git a/src/reducers/editorialWorkflow.js b/src/reducers/editorialWorkflow.js index a5249dab750e..d2750360ac4b 100644 --- a/src/reducers/editorialWorkflow.js +++ b/src/reducers/editorialWorkflow.js @@ -11,6 +11,8 @@ import { UNPUBLISHED_ENTRY_STATUS_CHANGE_REQUEST, UNPUBLISHED_ENTRY_STATUS_CHANGE_SUCCESS, UNPUBLISHED_ENTRY_PUBLISH_REQUEST, + UNPUBLISHED_ENTRIES_PUBLISH_REQUEST, + UNPUBLISHED_ENTRY_REGISTER_DEPENDENCY, } from '../actions/editorialWorkflow'; import { CONFIG_SUCCESS } from '../actions/config'; @@ -76,6 +78,14 @@ const unpublishedEntries = (state = null, action) => { // Update Optimistically return state.deleteIn(['entities', `${ action.payload.collection }.${ action.payload.slug }`]); + case UNPUBLISHED_ENTRIES_PUBLISH_REQUEST: + // Update Optimistically + return state.withMutations((map) => { + action.payload.entries.forEach((entry) => { + map.deleteIn(['entities', `${ entry[0] }.${ entry[1] }`]); + }); + }); + default: return state; } diff --git a/src/reducers/entryDraft.js b/src/reducers/entryDraft.js index f19ca72e8180..bcd696d8912a 100644 --- a/src/reducers/entryDraft.js +++ b/src/reducers/entryDraft.js @@ -13,6 +13,8 @@ import { UNPUBLISHED_ENTRY_PERSIST_REQUEST, UNPUBLISHED_ENTRY_PERSIST_SUCCESS, UNPUBLISHED_ENTRY_PERSIST_FAILURE, + UNPUBLISHED_ENTRY_REGISTER_DEPENDENCY, + UNPUBLISHED_ENTRY_UNREGISTER_DEPENDENCY, } from '../actions/editorialWorkflow'; import { ADD_ASSET, @@ -73,6 +75,12 @@ const entryDraftReducer = (state = Map(), action) => { case REMOVE_ASSET: return state.update('mediaFiles', list => list.filterNot(path => path === action.payload)); + case UNPUBLISHED_ENTRY_REGISTER_DEPENDENCY: + return state.setIn(['entry', 'dependencies', action.payload.field], + `${ action.payload.collection }.${ action.payload.slug }`); + case UNPUBLISHED_ENTRY_UNREGISTER_DEPENDENCY: + return state.deleteIn(['entry', 'dependencies', action.payload.field]); + default: return state; }