editor workflow ui adjustments
This commit is contained in:
parent
b6874152d9
commit
c84d538eb6
@ -67,6 +67,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bricks.js": "^1.7.0",
|
"bricks.js": "^1.7.0",
|
||||||
|
"dateformat": "^1.0.12",
|
||||||
"fuzzy": "^0.1.1",
|
"fuzzy": "^0.1.1",
|
||||||
"js-base64": "^2.1.9",
|
"js-base64": "^2.1.9",
|
||||||
"json-loader": "^0.5.4",
|
"json-loader": "^0.5.4",
|
||||||
|
@ -103,24 +103,12 @@ export default class API {
|
|||||||
}
|
}
|
||||||
|
|
||||||
retrieveMetadata(key) {
|
retrieveMetadata(key) {
|
||||||
const cache = LocalForage.getItem(`gh.meta.${key}`);
|
|
||||||
return cache.then((cached) => {
|
|
||||||
if (cached && cached.expires > Date.now()) { return cached.data; }
|
|
||||||
|
|
||||||
return this.request(`${this.repoURL}/contents/${key}.json`, {
|
return this.request(`${this.repoURL}/contents/${key}.json`, {
|
||||||
params: { ref: 'refs/meta/_netlify_cms' },
|
params: { ref: 'refs/meta/_netlify_cms' },
|
||||||
headers: { Accept: 'application/vnd.github.VERSION.raw' },
|
headers: { Accept: 'application/vnd.github.VERSION.raw' },
|
||||||
cache: 'no-store',
|
cache: 'no-store',
|
||||||
})
|
})
|
||||||
.then(response => JSON.parse(response))
|
.then(response => JSON.parse(response));
|
||||||
.then((result) => {
|
|
||||||
LocalForage.setItem(`gh.meta.${key}`, {
|
|
||||||
expires: Date.now() + 300000, // In 5 minutes
|
|
||||||
data: result,
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
}).catch(error => null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
readFile(path, sha, branch = this.branch) {
|
readFile(path, sha, branch = this.branch) {
|
||||||
@ -148,6 +136,10 @@ export default class API {
|
|||||||
}
|
}
|
||||||
|
|
||||||
readUnpublishedBranchFile(contentKey) {
|
readUnpublishedBranchFile(contentKey) {
|
||||||
|
const cache = LocalForage.getItem(`gh.unpublished.${contentKey}`);
|
||||||
|
return cache.then((cached) => {
|
||||||
|
if (cached && cached.expires > Date.now()) { return cached.data; }
|
||||||
|
|
||||||
let metaData;
|
let metaData;
|
||||||
return this.retrieveMetadata(contentKey)
|
return this.retrieveMetadata(contentKey)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
@ -156,6 +148,14 @@ export default class API {
|
|||||||
})
|
})
|
||||||
.then(file => {
|
.then(file => {
|
||||||
return { metaData, file };
|
return { metaData, file };
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
LocalForage.setItem(`gh.unpublished.${contentKey}`, {
|
||||||
|
expires: Date.now() + 300000, // In 5 minutes
|
||||||
|
data: result,
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,19 +191,24 @@ export default class API {
|
|||||||
if (options.mode && options.mode === EDITORIAL_WORKFLOW) {
|
if (options.mode && options.mode === EDITORIAL_WORKFLOW) {
|
||||||
const contentKey = options.collectionName ? `${options.collectionName}-${entry.slug}` : entry.slug;
|
const contentKey = options.collectionName ? `${options.collectionName}-${entry.slug}` : entry.slug;
|
||||||
const branchName = `cms/${contentKey}`;
|
const branchName = `cms/${contentKey}`;
|
||||||
return this.createBranch(branchName, response.sha)
|
return this.user().then(user => {
|
||||||
.then(this.storeMetadata(contentKey, {
|
return user.name ? user.name : user.login;
|
||||||
|
})
|
||||||
|
.then(username => this.storeMetadata(contentKey, {
|
||||||
type: 'PR',
|
type: 'PR',
|
||||||
status: status.DRAFT,
|
user: username,
|
||||||
|
status: status.first(),
|
||||||
branch: branchName,
|
branch: branchName,
|
||||||
collection: options.collectionName,
|
collection: options.collectionName,
|
||||||
title: options.parsedData.title,
|
title: options.parsedData && options.parsedData.title,
|
||||||
description: options.parsedData.description,
|
description: options.parsedData && options.parsedData.description,
|
||||||
objects: {
|
objects: {
|
||||||
entry: entry.path,
|
entry: entry.path,
|
||||||
files: mediaFiles.map(file => file.path)
|
files: mediaFiles.map(file => file.path)
|
||||||
}
|
},
|
||||||
|
timeStamp: new Date().toISOString()
|
||||||
}))
|
}))
|
||||||
|
.then(this.createBranch(branchName, response.sha))
|
||||||
.then(this.createPR(options.commitMessage, `cms/${contentKey}`));
|
.then(this.createPR(options.commitMessage, `cms/${contentKey}`));
|
||||||
} else {
|
} else {
|
||||||
return this.patchBranch(this.branch, response.sha);
|
return this.patchBranch(this.branch, response.sha);
|
||||||
|
29
src/components/UnpublishedListing.css
Normal file
29
src/components/UnpublishedListing.css
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
.column {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
text-align: center;
|
||||||
|
width: 28%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column:not(:last-child) {
|
||||||
|
margin-right: 8%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
width: 100% !important;
|
||||||
|
margin: 7px 0;
|
||||||
|
|
||||||
|
& h1 {
|
||||||
|
font-size: 17px;
|
||||||
|
& small {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& p {
|
||||||
|
color: #555;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
}
|
@ -1,29 +1,41 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import dateFormat from 'dateFormat';
|
||||||
import { Card } from './UI';
|
import { Card } from './UI';
|
||||||
import { statusDescriptions } from '../constants/publishModes';
|
import { statusDescriptions } from '../constants/publishModes';
|
||||||
|
import styles from './UnpublishedListing.css';
|
||||||
|
|
||||||
export default class UnpublishedListing extends React.Component {
|
export default class UnpublishedListing extends React.Component {
|
||||||
renderColumn(entries) {
|
renderColumns(entries, column) {
|
||||||
if (!entries) return;
|
if (!entries) return;
|
||||||
return (
|
|
||||||
<div>
|
if (!column) {
|
||||||
|
return entries.entrySeq().map(([currColumn, currEntries]) => (
|
||||||
|
<div key={currColumn} className={styles.column}>
|
||||||
|
<h3>{statusDescriptions.get(currColumn)}</h3>
|
||||||
|
{this.renderColumns(currEntries, currColumn)}
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
return <div>
|
||||||
{entries.map(entry => {
|
{entries.map(entry => {
|
||||||
return <Card key={entry.get('slug')}><h4>{entry.getIn(['data', 'title'])}</h4></Card>;
|
// 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');
|
||||||
|
return (
|
||||||
|
<Card key={entry.get('slug')} className={styles.card}>
|
||||||
|
<h1>{entry.getIn(['data', 'title'])} <small>by {author}</small></h1>
|
||||||
|
<p>Last updated: {timeStamp} by {entry.getIn(['metaData', 'user'])}</p>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>;
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { entries } = this.props;
|
const columns = this.renderColumns(this.props.entries);
|
||||||
const columns = entries.entrySeq().map(([key, currEntries]) => (
|
|
||||||
<div key={key}>
|
|
||||||
<h3>{statusDescriptions.get(key)}</h3>
|
|
||||||
{this.renderColumn(currEntries)}
|
|
||||||
</div>
|
|
||||||
));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -34,12 +46,5 @@ export default class UnpublishedListing extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
UnpublishedListing.propTypes = {
|
UnpublishedListing.propTypes = {
|
||||||
entries: ImmutablePropTypes.map,
|
entries: ImmutablePropTypes.orderedMap,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h3>Drafts</h3>
|
|
||||||
<card>Cool Recipe</card>
|
|
||||||
</div>
|
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import { Map } from 'immutable';
|
import { Map, OrderedMap } from 'immutable';
|
||||||
|
|
||||||
// Create/edit workflow modes
|
// Create/edit workflow modes
|
||||||
export const SIMPLE = 'simple';
|
export const SIMPLE = 'simple';
|
||||||
export const EDITORIAL_WORKFLOW = 'editorial_workflow';
|
export const EDITORIAL_WORKFLOW = 'editorial_workflow';
|
||||||
|
|
||||||
// Available status
|
// Available status
|
||||||
export const status = {
|
export const status = OrderedMap({
|
||||||
DRAFT: 'draft',
|
DRAFT: 'draft',
|
||||||
PENDING_REVIEW: 'pending_review',
|
PENDING_REVIEW: 'pending_review',
|
||||||
PENDING_PUBLISH: 'pending_publish',
|
PENDING_PUBLISH: 'pending_publish',
|
||||||
};
|
});
|
||||||
|
|
||||||
export const statusDescriptions = Map({
|
export const statusDescriptions = Map({
|
||||||
[status.DRAFT]: 'Draft',
|
[status.get('DRAFT')]: 'Draft',
|
||||||
[status.PENDING_REVIEW]: 'Waiting for Review',
|
[status.get('PENDING_REVIEW')]: 'Waiting for Review',
|
||||||
[status.PENDING_PUBLISH]: 'Waiting to go live',
|
[status.get('PENDING_PUBLISH')]: 'Waiting to go live',
|
||||||
});
|
});
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { Map } from 'immutable';
|
import { OrderedMap } from 'immutable';
|
||||||
import { init, loadUnpublishedEntries } from '../actions/editorialWorkflow';
|
import { init, loadUnpublishedEntries } from '../actions/editorialWorkflow';
|
||||||
import { selectUnpublishedEntries } from '../reducers';
|
import { selectUnpublishedEntries } from '../reducers';
|
||||||
import { EDITORIAL_WORKFLOW, status } from '../constants/publishModes';
|
import { EDITORIAL_WORKFLOW, status } from '../constants/publishModes';
|
||||||
import UnpublishedListing from '../components/UnpublishedListing';
|
import UnpublishedListing from '../components/UnpublishedListing';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
export default function EditorialWorkflow(WrappedComponent) {
|
export default function EditorialWorkflow(WrappedComponent) {
|
||||||
class EditorialWorkflow extends WrappedComponent {
|
class EditorialWorkflow extends WrappedComponent {
|
||||||
@ -45,11 +44,15 @@ export default function EditorialWorkflow(WrappedComponent) {
|
|||||||
const returnObj = { isEditorialWorkflow };
|
const returnObj = { isEditorialWorkflow };
|
||||||
|
|
||||||
if (isEditorialWorkflow) {
|
if (isEditorialWorkflow) {
|
||||||
returnObj.unpublishedEntries = _.reduce(status, (acc, currStatus) => {
|
/*
|
||||||
|
* Generates an ordered Map of the available status as keys.
|
||||||
|
* Each key containing a List of available unpubhlished entries
|
||||||
|
* Eg.: OrderedMap{'draft':List(), 'pending_review':List(), 'pending_publish':List()}
|
||||||
|
*/
|
||||||
|
returnObj.unpublishedEntries = status.reduce((acc, currStatus) => {
|
||||||
return acc.set(currStatus, selectUnpublishedEntries(state, currStatus));
|
return acc.set(currStatus, selectUnpublishedEntries(state, currStatus));
|
||||||
}, Map());
|
}, OrderedMap());
|
||||||
}
|
}
|
||||||
|
|
||||||
return returnObj;
|
return returnObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user