diff --git a/src/actions/editorialWorkflow.js b/src/actions/editorialWorkflow.js index c7e73082..9b1cb70b 100644 --- a/src/actions/editorialWorkflow.js +++ b/src/actions/editorialWorkflow.js @@ -17,6 +17,9 @@ export const UNPUBLISHED_ENTRY_PERSIST_SUCCESS = 'UNPUBLISHED_ENTRY_PERSIST_SUCC export const UNPUBLISHED_ENTRY_STATUS_CHANGE_REQUEST = 'UNPUBLISHED_ENTRY_STATUS_CHANGE_REQUEST'; export const UNPUBLISHED_ENTRY_STATUS_CHANGE_SUCCESS = 'UNPUBLISHED_ENTRY_STATUS_CHANGE_SUCCESS'; +export const UNPUBLISHED_ENTRY_PUBLISH_REQUEST = 'UNPUBLISHED_ENTRY_PUBLISH_REQUEST'; +export const UNPUBLISHED_ENTRY_PUBLISH_SUCCESS = 'UNPUBLISHED_ENTRY_PUBLISH_SUCCESS'; + /* * Simple Action Creators (Internal) */ @@ -81,7 +84,6 @@ function unpublishedEntryPersistedFail(error) { }; } - function unpublishedEntryStatusChangeRequest(collection, slug, oldStatus, newStatus) { return { type: UNPUBLISHED_ENTRY_STATUS_CHANGE_REQUEST, @@ -96,6 +98,20 @@ function unpublishedEntryStatusChangePersisted(collection, slug, oldStatus, newS }; } +function unpublishedEntryPublishRequest(collection, slug, status) { + return { + type: UNPUBLISHED_ENTRY_PUBLISH_REQUEST, + payload: { collection, slug, status } + }; +} + +function unpublishedEntryPublished(collection, slug, status) { + return { + type: UNPUBLISHED_ENTRY_PUBLISH_SUCCESS, + payload: { collection, slug, status } + }; +} + /* * Exported Thunk Action Creators */ @@ -149,3 +165,15 @@ export function updateUnpublishedEntryStatus(collection, slug, oldStatus, newSta }); }; } + +export function publishUnpublishedEntry(collection, slug, status) { + return (dispatch, getState) => { + const state = getState(); + const backend = currentBackend(state.config); + dispatch(unpublishedEntryPublishRequest(collection, slug, status)); + backend.publishUnpublishedEntry(collection, slug, status) + .then(() => { + dispatch(unpublishedEntryPublished(collection, slug, status)); + }); + }; +} diff --git a/src/backends/backend.js b/src/backends/backend.js index db163b74..1cb9bc42 100644 --- a/src/backends/backend.js +++ b/src/backends/backend.js @@ -151,6 +151,10 @@ class Backend { return this.implementation.updateUnpublishedEntryStatus(collection, slug, newStatus); } + publishUnpublishedEntry(collection, slug, status) { + return this.implementation.publishUnpublishedEntry(collection, slug, status); + } + entryToRaw(collection, entry) { const format = resolveFormat(collection, entry); diff --git a/src/backends/github/API.js b/src/backends/github/API.js index 196150e8..fe2dc273 100644 --- a/src/backends/github/API.js +++ b/src/backends/github/API.js @@ -201,17 +201,24 @@ export default class API { if (!unpublished) { // Open new editorial review workflow for this entry - Create new metadata and commit to new branch + const contentKey = options.collectionName ? `${options.collectionName}-${entry.slug}` : entry.slug; + const branchName = `cms/${contentKey}`; + return this.getBranch() .then(branchData => this.updateTree(branchData.commit.sha, '/', fileTree)) .then(changeTree => this.commit(options.commitMessage, changeTree)) - .then((response) => { - const contentKey = options.collectionName ? `${options.collectionName}-${entry.slug}` : entry.slug; - const branchName = `cms/${contentKey}`; + .then(commitResponse => this.createBranch(branchName, commitResponse.sha)) + .then(branchResponse => this.createPR(options.commitMessage, branchName)) + .then((prResponse) => { return this.user().then(user => { return user.name ? user.name : user.login; }) .then(username => this.storeMetadata(contentKey, { type: 'PR', + pr: { + number: prResponse.number, + head: prResponse.head && prResponse.head.sha + }, user: username, status: status.first(), branch: branchName, @@ -223,9 +230,7 @@ export default class API { files: filesList }, timeStamp: new Date().toISOString() - })) - .then(this.createBranch(branchName, response.sha)) - .then(this.createPR(options.commitMessage, `cms/${contentKey}`)); + })); }); } else { // Entry is already on editorial review workflow - just update metadata and commit to existing branch @@ -272,6 +277,16 @@ export default class API { .then(updatedMetadata => this.storeMetadata(contentKey, updatedMetadata)); } + publishUnpublishedEntry(collection, slug, status) { + const contentKey = collection ? `${collection}-${slug}` : slug; + return this.retrieveMetadata(contentKey) + .then(metadata => { + const headSha = metadata.pr && metadata.pr.head; + const number = metadata.pr && metadata.pr.number; + return this.mergePR(headSha, number); + }); + } + createRef(type, name, sha) { return this.request(`${this.repoURL}/git/refs`, { method: 'POST', @@ -306,6 +321,16 @@ export default class API { }); } + mergePR(headSha, number) { + return this.request(`${this.repoURL}/pulls/${number}/merge`, { + method: 'PUT', + body: JSON.stringify({ + commit_message: 'Automatically generated. Merged on Netlify CMS.', + sha: headSha + }), + }); + } + getTree(sha) { return sha ? this.request(`${this.repoURL}/git/trees/${sha}`) : Promise.resolve({ tree: [] }); } diff --git a/src/backends/github/implementation.js b/src/backends/github/implementation.js index c744bc46..2d270261 100644 --- a/src/backends/github/implementation.js +++ b/src/backends/github/implementation.js @@ -102,4 +102,8 @@ export default class GitHub { updateUnpublishedEntryStatus(collection, slug, newStatus) { return this.api.updateUnpublishedEntryStatus(collection, slug, newStatus); } + + publishUnpublishedEntry(collection, slug, status) { + return this.api.publishUnpublishedEntry(collection, slug, status); + } } diff --git a/src/components/UnpublishedListing.css b/src/components/UnpublishedListing.css index db202e36..ebd7bcd6 100644 --- a/src/components/UnpublishedListing.css +++ b/src/components/UnpublishedListing.css @@ -26,7 +26,7 @@ width: 100% !important; margin: 7px 0; - & h1 { + & h2 { font-size: 17px; & small { font-weight: normal; @@ -38,6 +38,11 @@ font-size: 12px; margin-top: 5px; } + + & button { + margin: 10px 10px 0 0; + float: right; + } } diff --git a/src/components/UnpublishedListing.js b/src/components/UnpublishedListing.js index 8fa90bf2..9d09b6a0 100644 --- a/src/components/UnpublishedListing.js +++ b/src/components/UnpublishedListing.js @@ -5,7 +5,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import moment from 'moment'; import { Card } from './UI'; import { Link } from 'react-router'; -import { statusDescriptions } from '../constants/publishModes'; +import { status, statusDescriptions } from '../constants/publishModes'; import styles from './UnpublishedListing.css'; const CARD = 'card'; @@ -22,56 +22,52 @@ function Column({ connectDropTarget, status, isOver, children }) { ); } - const columnTargetSpec = { drop(props, monitor) { const slug = monitor.getItem().slug; const collection = monitor.getItem().collection; - const oldStatus = monitor.getItem().currentStatus; + const oldStatus = monitor.getItem().ownStatus; props.onChangeStatus(collection, slug, oldStatus, props.status); } }; - function columnCollect(connect, monitor) { return { connectDropTarget: connect.dropTarget(), isOver: monitor.isOver() }; } - - Column = DropTarget(CARD, columnTargetSpec, columnCollect)(Column); /* * Card DropTarget Component */ -function EntryCard({ connectDragSource, children }) { +function EntryCard({ slug, collection, ownStatus, onRequestPublish, connectDragSource, children }) { return connectDragSource(
Last updated: {timeStamp} by {entry.getIn(['metaData', 'user'])}