From f51525baaa647beb5752bfa943dda4655e120b1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Zen?= Date: Tue, 13 Sep 2016 03:59:48 -0300 Subject: [PATCH 1/3] edit unpublished content on EntryPage (through HOC) --- src/actions/editorialWorkflow.js | 30 ++++++++++++ src/backends/backend.js | 4 ++ src/backends/github/implementation.js | 8 ++++ src/components/UnpublishedListing.js | 4 +- src/containers/CollectionPage.js | 4 +- src/containers/EntryPage.js | 8 ++++ .../CollectionPageHOC.js} | 16 +++---- .../editorialWorkflow/EntryPageHOC.js | 46 +++++++++++++++++++ src/reducers/editorialWorkflow.js | 6 +-- src/routing/routes.js | 1 + 10 files changed, 113 insertions(+), 14 deletions(-) rename src/containers/{EditorialWorkflowHoC.js => editorialWorkflow/CollectionPageHOC.js} (76%) create mode 100644 src/containers/editorialWorkflow/EntryPageHOC.js diff --git a/src/actions/editorialWorkflow.js b/src/actions/editorialWorkflow.js index 5c906f1c..ee44f857 100644 --- a/src/actions/editorialWorkflow.js +++ b/src/actions/editorialWorkflow.js @@ -4,6 +4,10 @@ import { EDITORIAL_WORKFLOW } from '../constants/publishModes'; * Contant Declarations */ export const INIT = 'init'; + +export const UNPUBLISHED_ENTRY_REQUEST = 'UNPUBLISHED_ENTRY_REQUEST'; +export const UNPUBLISHED_ENTRY_SUCCESS = 'UNPUBLISHED_ENTRY_SUCCESS'; + export const UNPUBLISHED_ENTRIES_REQUEST = 'UNPUBLISHED_ENTRIES_REQUEST'; export const UNPUBLISHED_ENTRIES_SUCCESS = 'UNPUBLISHED_ENTRIES_SUCCESS'; export const UNPUBLISHED_ENTRIES_FAILURE = 'UNPUBLISHED_ENTRIES_FAILURE'; @@ -12,6 +16,21 @@ export const UNPUBLISHED_ENTRIES_FAILURE = 'UNPUBLISHED_ENTRIES_FAILURE'; /* * Simple Action Creators (Internal) */ + +function unpublishedEntryLoading(collection, slug) { + return { + type: UNPUBLISHED_ENTRY_REQUEST, + payload: { collection, slug } + }; +} + +function unpublishedEntryLoaded(entry) { + return { + type: UNPUBLISHED_ENTRY_SUCCESS, + payload: { entry } + }; +} + function unpublishedEntriesLoading() { return { type: UNPUBLISHED_ENTRIES_REQUEST @@ -49,6 +68,17 @@ export function init() { /* * Exported Thunk Action Creators */ + +export function loadUnpublishedEntry(collection, slug) { + return (dispatch, getState) => { + const state = getState(); + const backend = currentBackend(state.config); + dispatch(unpublishedEntryLoading(collection, slug)); + backend.unpublishedEntry(collection, slug) + .then((entry) => dispatch(unpublishedEntryLoaded(entry))); + }; +} + export function loadUnpublishedEntries() { return (dispatch, getState) => { const state = getState(); diff --git a/src/backends/backend.js b/src/backends/backend.js index 66fbd33c..c2c4d100 100644 --- a/src/backends/backend.js +++ b/src/backends/backend.js @@ -83,6 +83,10 @@ class Backend { }); } + unpublishedEntry(collection, slug) { + return this.implementation.unpublishedEntry(collection, slug).then(this.entryWithFormat(collection)); + } + slugFormatter(template, entry) { var date = new Date(); return template.replace(/\{\{([^\}]+)\}\}/g, function(_, name) { diff --git a/src/backends/github/implementation.js b/src/backends/github/implementation.js index a4540ce1..6ce1c865 100644 --- a/src/backends/github/implementation.js +++ b/src/backends/github/implementation.js @@ -90,4 +90,12 @@ export default class GitHub { }; }); } + + unpublishedEntry(collection, slug) { + return this.unpublishedEntries().then((response) => ( + response.entries.filter((entry) => ( + entry.metaData && entry.metaData.collection === collection.get('name') && entry.slug === slug + ))[0] + )); + } } diff --git a/src/components/UnpublishedListing.js b/src/components/UnpublishedListing.js index e43c8b07..af179aac 100644 --- a/src/components/UnpublishedListing.js +++ b/src/components/UnpublishedListing.js @@ -2,6 +2,7 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import dateFormat from 'dateFormat'; import { Card } from './UI'; +import { Link } from 'react-router' import { statusDescriptions } from '../constants/publishModes'; import styles from './UnpublishedListing.css'; @@ -22,9 +23,10 @@ export default class UnpublishedListing extends React.Component { // Look for an "author" field. Fallback to username on backend implementation; const author = entry.getIn(['data', 'author'], entry.getIn(['metaData', 'user'])); const timeStamp = dateFormat(Date.parse(entry.getIn(['metaData', 'timeStamp'])), 'longDate'); + const link = `/editorialworkflow/${entry.getIn(['metaData', 'collection'])}/${entry.getIn(['metaData', 'status'])}/${entry.get('slug')}`; return ( -

{entry.getIn(['data', 'title'])} by {author}

+

{entry.getIn(['data', 'title'])} by {author}

Last updated: {timeStamp} by {entry.getIn(['metaData', 'user'])}

); diff --git a/src/containers/CollectionPage.js b/src/containers/CollectionPage.js index 5790fbf0..96143bad 100644 --- a/src/containers/CollectionPage.js +++ b/src/containers/CollectionPage.js @@ -5,7 +5,7 @@ import { loadEntries } from '../actions/entries'; import { selectEntries } from '../reducers'; import { Loader } from '../components/UI'; import EntryListing from '../components/EntryListing'; -import EditorialWorkflow from './EditorialWorkflowHoC'; +import CollectionPageHOC from './editorialWorkflow/CollectionPageHOC'; class DashboardPage extends React.Component { componentDidMount() { @@ -48,7 +48,7 @@ DashboardPage.propTypes = { * Instead of checking the publish mode everywhere to dispatch & render the additional editorial workflow stuff, * We delegate it to a Higher Order Component */ -DashboardPage = EditorialWorkflow(DashboardPage); +DashboardPage = CollectionPageHOC(DashboardPage); function mapStateToProps(state, ownProps) { diff --git a/src/containers/EntryPage.js b/src/containers/EntryPage.js index 81c9947b..ecb2cda2 100644 --- a/src/containers/EntryPage.js +++ b/src/containers/EntryPage.js @@ -12,6 +12,7 @@ import { import { addMedia, removeMedia } from '../actions/media'; import { selectEntry, getMedia } from '../reducers'; import EntryEditor from '../components/EntryEditor'; +import EntryPageHOC from './editorialWorkflow/EntryPageHOC'; class EntryPage extends React.Component { constructor(props) { @@ -56,6 +57,7 @@ class EntryPage extends React.Component { const { entry, entryDraft, boundGetMedia, collection, changeDraft, addMedia, removeMedia } = this.props; + console.log(entry) if (entryDraft == null || entryDraft.get('entry') == undefined || entry && entry.get('isFetching')) { return
Loading...
; } @@ -100,6 +102,12 @@ function mapStateToProps(state, ownProps) { return { collection, collections, newEntry, entryDraft, boundGetMedia, slug, entry }; } +/* + * Instead of checking the publish mode everywhere to dispatch & render the additional editorial workflow stuff, + * We delegate it to a Higher Order Component + */ +EntryPage = EntryPageHOC(EntryPage); + export default connect( mapStateToProps, { diff --git a/src/containers/EditorialWorkflowHoC.js b/src/containers/editorialWorkflow/CollectionPageHOC.js similarity index 76% rename from src/containers/EditorialWorkflowHoC.js rename to src/containers/editorialWorkflow/CollectionPageHOC.js index 603f7819..590e1fb1 100644 --- a/src/containers/EditorialWorkflowHoC.js +++ b/src/containers/editorialWorkflow/CollectionPageHOC.js @@ -1,14 +1,14 @@ import React, { PropTypes } from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { OrderedMap } from 'immutable'; -import { init, loadUnpublishedEntries } from '../actions/editorialWorkflow'; -import { selectUnpublishedEntries } from '../reducers'; -import { EDITORIAL_WORKFLOW, status } from '../constants/publishModes'; -import UnpublishedListing from '../components/UnpublishedListing'; +import { init, loadUnpublishedEntries } from '../../actions/editorialWorkflow'; +import { selectUnpublishedEntries } from '../../reducers'; +import { EDITORIAL_WORKFLOW, status } from '../../constants/publishModes'; +import UnpublishedListing from '../../components/UnpublishedListing'; import { connect } from 'react-redux'; -export default function EditorialWorkflow(WrappedComponent) { - class EditorialWorkflow extends WrappedComponent { +export default function CollectionPageHOC(CollectionPage) { + class CollectionPageHOC extends CollectionPage { componentDidMount() { const { dispatch, isEditorialWorkflow } = this.props; @@ -32,7 +32,7 @@ export default function EditorialWorkflow(WrappedComponent) { } } - EditorialWorkflow.propTypes = { + CollectionPageHOC.propTypes = { dispatch: PropTypes.func.isRequired, isEditorialWorkflow: PropTypes.bool.isRequired, unpublishedEntries: ImmutablePropTypes.map, @@ -56,5 +56,5 @@ export default function EditorialWorkflow(WrappedComponent) { return returnObj; } - return connect(mapStateToProps)(EditorialWorkflow); + return connect(mapStateToProps)(CollectionPageHOC); } diff --git a/src/containers/editorialWorkflow/EntryPageHOC.js b/src/containers/editorialWorkflow/EntryPageHOC.js new file mode 100644 index 00000000..4d958a14 --- /dev/null +++ b/src/containers/editorialWorkflow/EntryPageHOC.js @@ -0,0 +1,46 @@ +import React from 'react'; +import { EDITORIAL_WORKFLOW } from '../../constants/publishModes'; +import { selectUnpublishedEntry } from '../../reducers'; +import { loadUnpublishedEntry } from '../../actions/editorialWorkflow'; +import { connect } from 'react-redux'; + +export default function EntryPageHOC(EntryPage) { + class EntryPageHOC extends React.Component { + render() { + return ; + } + } + + function mapStateToProps(state, ownProps) { + const publish_mode = state.config.get('publish_mode'); + const isEditorialWorkflow = (publish_mode === EDITORIAL_WORKFLOW); + const unpublishedEntry = ownProps.route && ownProps.route.unpublishedEntry === true; + + const returnObj = {}; + if (isEditorialWorkflow && unpublishedEntry) { + const status = ownProps.params.status; + const slug = ownProps.params.slug; + const entry = selectUnpublishedEntry(state, status, slug); + returnObj.entry = entry; + + returnObj.persistEntry = () => { + // TODO - for now, simply ignore + }; + } + return returnObj; + } + + function mapDispatchToProps(dispatch, ownProps) { + const unpublishedEntry = ownProps.route && ownProps.route.unpublishedEntry === true; + const returnObj = {}; + if (unpublishedEntry) { + // Overwrite loadEntry to loadUnpublishedEntry + returnObj.loadEntry = (collection, slug) => { + dispatch(loadUnpublishedEntry(collection, slug)); + }; + } + return returnObj; + } + + return connect(mapStateToProps, mapDispatchToProps)(EntryPageHOC); +} diff --git a/src/reducers/editorialWorkflow.js b/src/reducers/editorialWorkflow.js index 95cf9e28..fb5a96f5 100644 --- a/src/reducers/editorialWorkflow.js +++ b/src/reducers/editorialWorkflow.js @@ -27,9 +27,9 @@ const unpublishedEntries = (state = null, action) => { } }; -export const selectUnpublishedEntry = (state, status, slug) => ( - state.getIn(['entities', `${status}.${slug}`]) -); +export const selectUnpublishedEntry = (state, status, slug) => { + return state && state.getIn(['entities', `${status}.${slug}`]); +}; export const selectUnpublishedEntries = (state, status) => { if (!state) return; diff --git a/src/routing/routes.js b/src/routing/routes.js index 59f2aa7f..6dc9f55e 100644 --- a/src/routing/routes.js +++ b/src/routing/routes.js @@ -12,6 +12,7 @@ export default ( + From 165c758bb9944b095ec11f922058b9f3f16d8c0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Zen?= Date: Tue, 13 Sep 2016 04:09:52 -0300 Subject: [PATCH 2/3] Updated reducer for editorial workflow --- src/actions/editorialWorkflow.js | 14 +++++++------- src/containers/EntryPage.js | 2 +- src/containers/editorialWorkflow/EntryPageHOC.js | 3 ++- src/reducers/editorialWorkflow.js | 13 ++++++++++++- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/actions/editorialWorkflow.js b/src/actions/editorialWorkflow.js index ee44f857..36187bdf 100644 --- a/src/actions/editorialWorkflow.js +++ b/src/actions/editorialWorkflow.js @@ -17,17 +17,17 @@ export const UNPUBLISHED_ENTRIES_FAILURE = 'UNPUBLISHED_ENTRIES_FAILURE'; * Simple Action Creators (Internal) */ -function unpublishedEntryLoading(collection, slug) { +function unpublishedEntryLoading(status, slug) { return { type: UNPUBLISHED_ENTRY_REQUEST, - payload: { collection, slug } + payload: { status, slug } }; } -function unpublishedEntryLoaded(entry) { +function unpublishedEntryLoaded(status, entry) { return { type: UNPUBLISHED_ENTRY_SUCCESS, - payload: { entry } + payload: { status, entry } }; } @@ -69,13 +69,13 @@ export function init() { * Exported Thunk Action Creators */ -export function loadUnpublishedEntry(collection, slug) { +export function loadUnpublishedEntry(collection, status, slug) { return (dispatch, getState) => { const state = getState(); const backend = currentBackend(state.config); - dispatch(unpublishedEntryLoading(collection, slug)); + dispatch(unpublishedEntryLoading(status, slug)); backend.unpublishedEntry(collection, slug) - .then((entry) => dispatch(unpublishedEntryLoaded(entry))); + .then((entry) => dispatch(unpublishedEntryLoaded(status, entry))); }; } diff --git a/src/containers/EntryPage.js b/src/containers/EntryPage.js index ecb2cda2..1693b587 100644 --- a/src/containers/EntryPage.js +++ b/src/containers/EntryPage.js @@ -57,7 +57,7 @@ class EntryPage extends React.Component { const { entry, entryDraft, boundGetMedia, collection, changeDraft, addMedia, removeMedia } = this.props; - console.log(entry) + if (entryDraft == null || entryDraft.get('entry') == undefined || entry && entry.get('isFetching')) { return
Loading...
; } diff --git a/src/containers/editorialWorkflow/EntryPageHOC.js b/src/containers/editorialWorkflow/EntryPageHOC.js index 4d958a14..1840c210 100644 --- a/src/containers/editorialWorkflow/EntryPageHOC.js +++ b/src/containers/editorialWorkflow/EntryPageHOC.js @@ -35,8 +35,9 @@ export default function EntryPageHOC(EntryPage) { const returnObj = {}; if (unpublishedEntry) { // Overwrite loadEntry to loadUnpublishedEntry + const status = ownProps.params.status; returnObj.loadEntry = (collection, slug) => { - dispatch(loadUnpublishedEntry(collection, slug)); + dispatch(loadUnpublishedEntry(collection, status, slug)); }; } return returnObj; diff --git a/src/reducers/editorialWorkflow.js b/src/reducers/editorialWorkflow.js index fb5a96f5..81bdc980 100644 --- a/src/reducers/editorialWorkflow.js +++ b/src/reducers/editorialWorkflow.js @@ -1,6 +1,6 @@ import { Map, List, fromJS } from 'immutable'; import { - INIT, UNPUBLISHED_ENTRIES_REQUEST, UNPUBLISHED_ENTRIES_SUCCESS + INIT, UNPUBLISHED_ENTRY_REQUEST, UNPUBLISHED_ENTRY_SUCCESS, UNPUBLISHED_ENTRIES_REQUEST, UNPUBLISHED_ENTRIES_SUCCESS } from '../actions/editorialWorkflow'; const unpublishedEntries = (state = null, action) => { @@ -8,6 +8,17 @@ const unpublishedEntries = (state = null, action) => { case INIT: // Editorial workflow must be explicitly initiated. return Map({ entities: Map(), pages: Map() }); + + case UNPUBLISHED_ENTRY_REQUEST: + return state.setIn(['entities', `${action.payload.status}.${action.payload.slug}`, 'isFetching'], true); + + case UNPUBLISHED_ENTRY_SUCCESS: + return state.setIn( + ['entities', `${action.payload.status}.${action.payload.entry.slug}`], + fromJS(action.payload.entry) + ); + + case UNPUBLISHED_ENTRIES_REQUEST: return state.setIn(['pages', 'isFetching'], true); From 911e3f7077066b63f7dd7f3c415ceeabed0742fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Zen?= Date: Tue, 13 Sep 2016 04:29:30 -0300 Subject: [PATCH 3/3] initialize editorialWorkflow state after config loaded & parsed --- src/actions/editorialWorkflow.js | 12 ------------ .../editorialWorkflow/CollectionPageHOC.js | 3 +-- src/reducers/editorialWorkflow.js | 16 +++++++++++----- 3 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/actions/editorialWorkflow.js b/src/actions/editorialWorkflow.js index 36187bdf..32322840 100644 --- a/src/actions/editorialWorkflow.js +++ b/src/actions/editorialWorkflow.js @@ -3,8 +3,6 @@ import { EDITORIAL_WORKFLOW } from '../constants/publishModes'; /* * Contant Declarations */ -export const INIT = 'init'; - export const UNPUBLISHED_ENTRY_REQUEST = 'UNPUBLISHED_ENTRY_REQUEST'; export const UNPUBLISHED_ENTRY_SUCCESS = 'UNPUBLISHED_ENTRY_SUCCESS'; @@ -55,16 +53,6 @@ function unpublishedEntriesFailed(error) { }; } -/* - * Exported simple Action Creators - */ -export function init() { - return { - type: INIT - }; -} - - /* * Exported Thunk Action Creators */ diff --git a/src/containers/editorialWorkflow/CollectionPageHOC.js b/src/containers/editorialWorkflow/CollectionPageHOC.js index 590e1fb1..4894e79a 100644 --- a/src/containers/editorialWorkflow/CollectionPageHOC.js +++ b/src/containers/editorialWorkflow/CollectionPageHOC.js @@ -1,7 +1,7 @@ import React, { PropTypes } from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { OrderedMap } from 'immutable'; -import { init, loadUnpublishedEntries } from '../../actions/editorialWorkflow'; +import { loadUnpublishedEntries } from '../../actions/editorialWorkflow'; import { selectUnpublishedEntries } from '../../reducers'; import { EDITORIAL_WORKFLOW, status } from '../../constants/publishModes'; import UnpublishedListing from '../../components/UnpublishedListing'; @@ -13,7 +13,6 @@ export default function CollectionPageHOC(CollectionPage) { componentDidMount() { const { dispatch, isEditorialWorkflow } = this.props; if (isEditorialWorkflow) { - dispatch(init()); dispatch(loadUnpublishedEntries()); } super.componentDidMount(); diff --git a/src/reducers/editorialWorkflow.js b/src/reducers/editorialWorkflow.js index 81bdc980..beee2071 100644 --- a/src/reducers/editorialWorkflow.js +++ b/src/reducers/editorialWorkflow.js @@ -1,14 +1,20 @@ import { Map, List, fromJS } from 'immutable'; +import { EDITORIAL_WORKFLOW } from '../constants/publishModes'; import { - INIT, UNPUBLISHED_ENTRY_REQUEST, UNPUBLISHED_ENTRY_SUCCESS, UNPUBLISHED_ENTRIES_REQUEST, UNPUBLISHED_ENTRIES_SUCCESS + UNPUBLISHED_ENTRY_REQUEST, UNPUBLISHED_ENTRY_SUCCESS, UNPUBLISHED_ENTRIES_REQUEST, UNPUBLISHED_ENTRIES_SUCCESS } from '../actions/editorialWorkflow'; +import { CONFIG_SUCCESS } from '../actions/config'; const unpublishedEntries = (state = null, action) => { switch (action.type) { - case INIT: - // Editorial workflow must be explicitly initiated. - return Map({ entities: Map(), pages: Map() }); - + case CONFIG_SUCCESS: + const publish_mode = action.payload && action.payload.publish_mode; + if (publish_mode === EDITORIAL_WORKFLOW) { + // Editorial workflow state is explicetelly initiated after the config. + return Map({ entities: Map(), pages: Map() }); + } else { + return state; + } case UNPUBLISHED_ENTRY_REQUEST: return state.setIn(['entities', `${action.payload.status}.${action.payload.slug}`, 'isFetching'], true);