Editorial Workflow skeleton
This commit is contained in:
parent
b0e62d1ca9
commit
f0e608a209
@ -1,6 +1,8 @@
|
||||
import yaml from 'js-yaml';
|
||||
import _ from 'lodash';
|
||||
import { currentBackend } from '../backends/backend';
|
||||
import { authenticate } from '../actions/auth';
|
||||
import * as publishModes from '../constants/publishModes';
|
||||
import * as MediaProxy from '../valueObjects/MediaProxy';
|
||||
|
||||
export const CONFIG_REQUEST = 'CONFIG_REQUEST';
|
||||
@ -70,9 +72,9 @@ function parseConfig(data) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!('publish_workflow' in config)) {
|
||||
if (!('publish_mode' in config) || _.values(publishModes).indexOf(config.publish_mode) === -1) {
|
||||
// Make sure there is a publish workflow mode set
|
||||
config['publish_workflow'] = 'simple';
|
||||
config.publish_mode = publishModes.SIMPLE;
|
||||
}
|
||||
|
||||
if (!('public_folder' in config)) {
|
||||
|
53
src/actions/editorialWorkflow.js
Normal file
53
src/actions/editorialWorkflow.js
Normal file
@ -0,0 +1,53 @@
|
||||
import { currentBackend } from '../backends/backend';
|
||||
import { EDITORIAL_WORKFLOW } from '../constants/publishModes';
|
||||
/*
|
||||
* Contant Declarations
|
||||
*/
|
||||
export const UNPUBLISHED_ENTRIES_REQUEST = 'UNPUBLISHED_ENTRIES_REQUEST';
|
||||
export const UNPUBLISHED_ENTRIES_SUCCESS = 'UNPUBLISHED_ENTRIES_SUCCESS';
|
||||
export const UNPUBLISHED_ENTRIES_FAILURE = 'UNPUBLISHED_ENTRIES_FAILURE';
|
||||
|
||||
|
||||
/*
|
||||
* Simple Action Creators (Internal)
|
||||
*/
|
||||
function unpublishedEntriesLoading() {
|
||||
return {
|
||||
type: UNPUBLISHED_ENTRIES_REQUEST
|
||||
};
|
||||
}
|
||||
|
||||
function unpublishedEntriesLoaded(entries, pagination) {
|
||||
return {
|
||||
type: UNPUBLISHED_ENTRIES_SUCCESS,
|
||||
payload: {
|
||||
entries: entries,
|
||||
pages: pagination
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function unpublishedEntriesFailed(error) {
|
||||
return {
|
||||
type: UNPUBLISHED_ENTRIES_FAILURE,
|
||||
error: 'Failed to load entries',
|
||||
payload: error.toString(),
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Exported Thunk Action Creators
|
||||
*/
|
||||
export function loadUnpublishedEntries() {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
if (state.publish_mode !== EDITORIAL_WORKFLOW) return;
|
||||
|
||||
const backend = currentBackend(state.config);
|
||||
dispatch(unpublishedEntriesLoading());
|
||||
backend.unpublishedEntries().then(
|
||||
(response) => dispatch(unpublishedEntriesLoaded(response.entries, response.pagination)),
|
||||
(error) => dispatch(unpublishedEntriesFailed(error))
|
||||
);
|
||||
};
|
||||
}
|
@ -17,7 +17,6 @@ export const DRAFT_CREATE_EMPTY = 'DRAFT_CREATE_EMPTY';
|
||||
export const DRAFT_DISCARD = 'DRAFT_DISCARD';
|
||||
export const DRAFT_CHANGE = 'DRAFT_CHANGE';
|
||||
|
||||
|
||||
export const ENTRY_PERSIST_REQUEST = 'ENTRY_PERSIST_REQUEST';
|
||||
export const ENTRY_PERSIST_SUCCESS = 'ENTRY_PERSIST_SUCCESS';
|
||||
export const ENTRY_PERSIST_FAILURE = 'ENTRY_PERSIST_FAILURE';
|
||||
|
@ -3,7 +3,6 @@ import GitHubBackend from './github/implementation';
|
||||
import NetlifyGitBackend from './netlify-git/implementation';
|
||||
import { resolveFormat } from '../formats/formats';
|
||||
import { createEntry } from '../valueObjects/Entry';
|
||||
import { SIMPLE, EDITORIAL } from './constants';
|
||||
|
||||
class LocalStorageAuthStore {
|
||||
storageKey = 'nf-cms-user';
|
||||
@ -65,9 +64,9 @@ class Backend {
|
||||
return this.entryWithFormat(collection)(newEntry);
|
||||
}
|
||||
|
||||
entryWithFormat(collection) {
|
||||
entryWithFormat(collectionOrEntity) {
|
||||
return (entry) => {
|
||||
const format = resolveFormat(collection, entry);
|
||||
const format = resolveFormat(collectionOrEntity, entry);
|
||||
if (entry && entry.raw) {
|
||||
entry.data = format && format.fromFile(entry.raw);
|
||||
}
|
||||
@ -75,6 +74,15 @@ class Backend {
|
||||
};
|
||||
}
|
||||
|
||||
unpublishedEntries(page, perPage) {
|
||||
return this.implementation.unpublishedEntries(page, perPage).then((response) => {
|
||||
return {
|
||||
pagination: response.pagination,
|
||||
entries: response.entries.map(this.entryWithFormat('editorialWorkflow'))
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
slugFormatter(template, entry) {
|
||||
var date = new Date();
|
||||
return template.replace(/\{\{([^\}]+)\}\}/g, function(_, name) {
|
||||
@ -93,16 +101,6 @@ class Backend {
|
||||
});
|
||||
}
|
||||
|
||||
getPublishMode(config) {
|
||||
const publish_workflows = [SIMPLE, EDITORIAL];
|
||||
const mode = config.get('publish_workflow');
|
||||
if (publish_workflows.indexOf(mode) !== -1) {
|
||||
return mode;
|
||||
} else {
|
||||
return SIMPLE;
|
||||
}
|
||||
}
|
||||
|
||||
persistEntry(config, collection, entryDraft, MediaFiles) {
|
||||
const newEntry = entryDraft.getIn(['entry', 'newRecord']) || false;
|
||||
|
||||
@ -132,7 +130,7 @@ class Backend {
|
||||
collection.get('label') + ' “' +
|
||||
entryDraft.getIn(['entry', 'data', 'title']) + '”';
|
||||
|
||||
const mode = this.getPublishMode(config);
|
||||
const mode = config.get('publish_mode');
|
||||
|
||||
const collectionName = collection.get('name');
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import LocalForage from 'localforage';
|
||||
import MediaProxy from '../../valueObjects/MediaProxy';
|
||||
import { Base64 } from 'js-base64';
|
||||
import { EDITORIAL } from '../constants';
|
||||
import { EDITORIAL_WORKFLOW } from '../../constants/publishModes';
|
||||
|
||||
const API_ROOT = 'https://api.github.com';
|
||||
|
||||
@ -169,7 +169,7 @@ export default class API {
|
||||
.then(branchData => this.updateTree(branchData.commit.sha, '/', fileTree))
|
||||
.then(changeTree => this.commit(options.commitMessage, changeTree))
|
||||
.then((response) => {
|
||||
if (options.mode && options.mode === EDITORIAL) {
|
||||
if (options.mode && options.mode === EDITORIAL_WORKFLOW) {
|
||||
const contentKey = options.collectionName ? `${options.collectionName}-${entry.slug}` : entry.slug;
|
||||
const branchName = `cms/${contentKey}`;
|
||||
return this.createBranch(branchName, response.sha)
|
||||
@ -177,6 +177,7 @@ export default class API {
|
||||
type: 'PR',
|
||||
status: 'draft',
|
||||
branch: branchName,
|
||||
collection: options.collectionName,
|
||||
title: options.parsedData.title,
|
||||
description: options.parsedData.description,
|
||||
objects: files.map(file => file.path)
|
||||
|
@ -62,4 +62,11 @@ export default class GitHub {
|
||||
persistEntry(entry, mediaFiles = [], options = {}) {
|
||||
return this.api.persistFiles(entry, mediaFiles, options);
|
||||
}
|
||||
|
||||
unpublishedEntries() {
|
||||
return Promise.resolve({
|
||||
pagination: {},
|
||||
entries: []
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import LocalForage from 'localforage';
|
||||
import MediaProxy from '../../valueObjects/MediaProxy';
|
||||
import { Base64 } from 'js-base64';
|
||||
import { EDITORIAL } from '../constants';
|
||||
import { EDITORIAL_WORKFLOW } from '../../constants/publishModes';
|
||||
|
||||
export default class API {
|
||||
constructor(token, url, branch) {
|
||||
@ -161,7 +161,7 @@ export default class API {
|
||||
.then(branchData => this.updateTree(branchData.commit.sha, '/', fileTree))
|
||||
.then(changeTree => this.commit(options.commitMessage, changeTree))
|
||||
.then((response) => {
|
||||
if (options.mode && options.mode === EDITORIAL) {
|
||||
if (options.mode && options.mode === EDITORIAL_WORKFLOW) {
|
||||
const contentKey = options.collectionName ? `${options.collectionName}-${entry.slug}` : entry.slug;
|
||||
return this.createBranch(`cms/${contentKey}`, response.sha)
|
||||
.then(this.storeMetadata(contentKey, { status: 'draft' }))
|
||||
|
@ -57,4 +57,5 @@ export default class TestRepo {
|
||||
mediaFiles.forEach(media => media.uploaded = true);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
// Create/edit workflows
|
||||
export const SIMPLE = 'simple';
|
||||
export const EDITORIAL = 'editorial';
|
||||
export const EDITORIAL_WORKFLOW = 'editorial_workflow';
|
@ -2,6 +2,7 @@ import React, { PropTypes } from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { connect } from 'react-redux';
|
||||
import { loadEntries } from '../actions/entries';
|
||||
import { loadUnpublishedEntries } from '../actions/editorialWorkflow';
|
||||
import { selectEntries } from '../reducers';
|
||||
import { Loader } from '../components/UI';
|
||||
import EntryListing from '../components/EntryListing';
|
||||
@ -9,7 +10,7 @@ import EntryListing from '../components/EntryListing';
|
||||
class DashboardPage extends React.Component {
|
||||
componentDidMount() {
|
||||
const { collection, dispatch } = this.props;
|
||||
|
||||
dispatch(loadUnpublishedEntries);
|
||||
if (collection) {
|
||||
dispatch(loadEntries(collection));
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import YAMLFrontmatter from './yaml-frontmatter';
|
||||
|
||||
export function resolveFormat(collection, entry) {
|
||||
export function resolveFormat(collectionOrEntity, entry) {
|
||||
return new YAMLFrontmatter();
|
||||
}
|
||||
|
37
src/reducers/editorialWorkflow.js
Normal file
37
src/reducers/editorialWorkflow.js
Normal file
@ -0,0 +1,37 @@
|
||||
import { Map, List, fromJS } from 'immutable';
|
||||
import {
|
||||
UNPUBLISHED_ENTRIES_REQUEST, UNPUBLISHED_ENTRIES_SUCCESS
|
||||
} from '../actions/editorialWorkflow';
|
||||
|
||||
const unpublishedEntries = (state = Map({ entities: Map(), pages: Map() }), action) => {
|
||||
switch (action.type) {
|
||||
case UNPUBLISHED_ENTRIES_REQUEST:
|
||||
return state.setIn(['pages', 'isFetching'], true);
|
||||
|
||||
case UNPUBLISHED_ENTRIES_SUCCESS:
|
||||
const { entries, pages } = action.payload;
|
||||
return state.withMutations((map) => {
|
||||
entries.forEach((entry) => (
|
||||
map.setIn(['entities', `${entry.metadata.status}.${entry.slug}`], fromJS(entry).set('isFetching', false))
|
||||
));
|
||||
map.set('pages', Map({
|
||||
...pages,
|
||||
ids: List(entries.map((entry) => entry.slug))
|
||||
}));
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export const selectUnpublishedEntry = (state, status, slug) => (
|
||||
state.getIn(['entities', `${status}.${slug}`], null)
|
||||
);
|
||||
|
||||
export const selectUnpublishedEntries = (state, status) => {
|
||||
const slugs = state.getIn(['pages', 'ids']);
|
||||
return slugs && slugs.map((slug) => selectUnpublishedEntry(state, status, slug));
|
||||
};
|
||||
|
||||
|
||||
export default unpublishedEntries;
|
@ -7,13 +7,16 @@ const entries = (state = Map({ entities: Map(), pages: Map() }), action) => {
|
||||
switch (action.type) {
|
||||
case ENTRY_REQUEST:
|
||||
return state.setIn(['entities', `${action.payload.collection}.${action.payload.slug}`, 'isFetching'], true);
|
||||
|
||||
case ENTRY_SUCCESS:
|
||||
return state.setIn(
|
||||
['entities', `${action.payload.collection}.${action.payload.entry.slug}`],
|
||||
fromJS(action.payload.entry)
|
||||
);
|
||||
|
||||
case ENTRIES_REQUEST:
|
||||
return state.setIn(['pages', action.payload.collection, 'isFetching'], true);
|
||||
|
||||
case ENTRIES_SUCCESS:
|
||||
const { collection, entries, pages } = action.payload;
|
||||
return state.withMutations((map) => {
|
||||
@ -25,6 +28,7 @@ const entries = (state = Map({ entities: Map(), pages: Map() }), action) => {
|
||||
ids: List(entries.map((entry) => entry.slug))
|
||||
}));
|
||||
});
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import auth from './auth';
|
||||
import config from './config';
|
||||
import editor from './editor';
|
||||
import entries, * as fromEntries from './entries';
|
||||
import editorialWorkflow, * as fromEditorialWorkflow from './editorialWorkflow';
|
||||
import entryDraft from './entryDraft';
|
||||
import collections from './collections';
|
||||
import medias, * as fromMedias from './medias';
|
||||
@ -12,18 +13,27 @@ const reducers = {
|
||||
collections,
|
||||
editor,
|
||||
entries,
|
||||
editorialWorkflow,
|
||||
entryDraft,
|
||||
medias
|
||||
};
|
||||
|
||||
export default reducers;
|
||||
|
||||
/*
|
||||
* Selectors
|
||||
*/
|
||||
export const selectEntry = (state, collection, slug) =>
|
||||
fromEntries.selectEntry(state.entries, collection, slug);
|
||||
|
||||
|
||||
export const selectEntries = (state, collection) =>
|
||||
fromEntries.selectEntries(state.entries, collection);
|
||||
|
||||
export const selectUnpublishedEntry = (state, status, slug) =>
|
||||
fromEditorialWorkflow.selectUnpublishedEntry(state.editorialWorkflow, status, slug);
|
||||
|
||||
export const selectUnpublishedEntries = (state, status) =>
|
||||
fromEditorialWorkflow.selectUnpublishedEntries(state.editorialWorkflow, status);
|
||||
|
||||
export const getMedia = (state, path) =>
|
||||
fromMedias.getMedia(state.medias, path);
|
||||
|
Loading…
x
Reference in New Issue
Block a user