Merge pull request #293 from Benaiah/label-cards-in-editorial-workflow

Add meta info to cards in editorial workflow
This commit is contained in:
Shawn Erquhart 2017-03-20 20:30:09 -04:00 committed by GitHub
commit 96d6242b78
9 changed files with 116 additions and 13 deletions

View File

@ -124,7 +124,15 @@ class Backend {
.then(loadedEntries => loadedEntries.filter(entry => entry !== null)) .then(loadedEntries => loadedEntries.filter(entry => entry !== null))
.then(entries => ( .then(entries => (
entries.map((loadedEntry) => { entries.map((loadedEntry) => {
const entry = createEntry(loadedEntry.metaData.collection, loadedEntry.slug, loadedEntry.file.path, { raw: loadedEntry.data }); const entry = createEntry(
loadedEntry.metaData.collection,
loadedEntry.slug,
loadedEntry.file.path,
{
raw: loadedEntry.data,
isModification: loadedEntry.isModification,
}
);
entry.metaData = loadedEntry.metaData; entry.metaData = loadedEntry.metaData;
return entry; return entry;
}) })
@ -138,7 +146,14 @@ class Backend {
unpublishedEntry(collection, slug) { unpublishedEntry(collection, slug) {
return this.implementation.unpublishedEntry(collection, slug) return this.implementation.unpublishedEntry(collection, slug)
.then((loadedEntry) => { .then((loadedEntry) => {
const entry = createEntry("draft", loadedEntry.slug, loadedEntry.file.path, { raw: loadedEntry.data }); const entry = createEntry(
"draft",
loadedEntry.slug,
loadedEntry.file.path,
{
raw: loadedEntry.data,
isModification: loadedEntry.isModification,
});
entry.metaData = loadedEntry.metaData; entry.metaData = loadedEntry.metaData;
return entry; return entry;
}) })

View File

@ -1,7 +1,7 @@
import LocalForage from "localforage"; import LocalForage from "localforage";
import { Base64 } from "js-base64"; import { Base64 } from "js-base64";
import _ from "lodash"; import _ from "lodash";
import { filterPromises } from "../../lib/promiseHelper"; import { filterPromises, resolvePromiseProperties } from "../../lib/promiseHelper";
import AssetProxy from "../../valueObjects/AssetProxy"; import AssetProxy from "../../valueObjects/AssetProxy";
import { SIMPLE, EDITORIAL_WORKFLOW, status } from "../../constants/publishModes"; import { SIMPLE, EDITORIAL_WORKFLOW, status } from "../../constants/publishModes";
import { APIError, EditorialWorkflowError } from "../../valueObjects/errors"; import { APIError, EditorialWorkflowError } from "../../valueObjects/errors";
@ -159,20 +159,29 @@ export default class API {
} }
readUnpublishedBranchFile(contentKey) { readUnpublishedBranchFile(contentKey) {
let metaData; const metaDataPromise = this.retrieveMetadata(contentKey)
const unpublishedPromise = this.retrieveMetadata(contentKey) .then(data => (data.objects.entry.path ? data : Promise.reject(null)));
.then((data) => { return resolvePromiseProperties({
metaData = data; metaData: metaDataPromise,
if (data.objects.entry.path) { fileData: metaDataPromise.then(
return this.readFile(data.objects.entry.path, null, data.branch); data => this.readFile(data.objects.entry.path, null, data.branch)),
} isModification: metaDataPromise.then(
return Promise.reject(null); data => this.isUnpublishedEntryModification(data.objects.entry.path, this.branch)),
}) })
.then(fileData => ({ metaData, fileData }))
.catch(() => { .catch(() => {
throw new EditorialWorkflowError('content is not under editorial workflow', true); throw new EditorialWorkflowError('content is not under editorial workflow', true);
}); });
return unpublishedPromise; }
isUnpublishedEntryModification(path, branch) {
return this.readFile(path, null, branch)
.then(data => true)
.catch((err) => {
if (err.message && err.message === "Not Found") {
return false;
}
throw err;
});
} }
listUnpublishedBranches() { listUnpublishedBranches() {

View File

@ -99,6 +99,7 @@ export default class GitHub {
file: { path }, file: { path },
data: data.fileData, data: data.fileData,
metaData: data.metaData, metaData: data.metaData,
isModification: data.isModification,
}); });
sem.leave(); sem.leave();
} }
@ -127,6 +128,7 @@ export default class GitHub {
file: { path: data.metaData.objects.entry.path }, file: { path: data.metaData.objects.entry.path },
data: data.fileData, data: data.fileData,
metaData: data.metaData, metaData: data.metaData,
isModification: data.isModification,
}; };
}); });
} }

View File

@ -2,12 +2,14 @@
--defaultColor: #333; --defaultColor: #333;
--defaultColorLight: #eee; --defaultColorLight: #eee;
--backgroundColor: #fff; --backgroundColor: #fff;
--backgroundColorShaded: #eee;
--shadowColor: rgba(0, 0, 0, .25); --shadowColor: rgba(0, 0, 0, .25);
--infoColor: #69c; --infoColor: #69c;
--successColor: #1c7; --successColor: #1c7;
--warningColor: #fa0; --warningColor: #fa0;
--errorColor: #f52; --errorColor: #f52;
--borderRadius: 2px; --borderRadius: 2px;
--borderRadiusLarge: 10px;
--topmostZindex: 99999; --topmostZindex: 99999;
--foregroundAltColor: #fff; --foregroundAltColor: #fff;
--backgroundAltColor: #272e30; --backgroundAltColor: #272e30;

View File

@ -3,8 +3,11 @@ import { DragSource, DropTarget, HTML5DragDrop } from 'react-simple-dnd';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { Link } from 'react-router'; import { Link } from 'react-router';
import moment from 'moment'; import moment from 'moment';
import pluralize from 'pluralize';
import { capitalize } from 'lodash'
import { Card, CardTitle, CardText, CardActions } from 'react-toolbox/lib/card'; import { Card, CardTitle, CardText, CardActions } from 'react-toolbox/lib/card';
import Button from 'react-toolbox/lib/button'; import Button from 'react-toolbox/lib/button';
import UnpublishedListingCardMeta from './UnpublishedListingCardMeta.js';
import { status, statusDescriptions } from '../../constants/publishModes'; import { status, statusDescriptions } from '../../constants/publishModes';
import styles from './UnpublishedListing.css'; import styles from './UnpublishedListing.css';
@ -68,6 +71,7 @@ class UnpublishedListing extends React.Component {
const slug = entry.get('slug'); const slug = entry.get('slug');
const ownStatus = entry.getIn(['metaData', 'status']); const ownStatus = entry.getIn(['metaData', 'status']);
const collection = entry.getIn(['metaData', 'collection']); const collection = entry.getIn(['metaData', 'collection']);
const isModification = entry.get('isModification');
return ( return (
<DragSource <DragSource
key={slug} key={slug}
@ -77,6 +81,10 @@ class UnpublishedListing extends React.Component {
> >
<div className={styles.draggable}> <div className={styles.draggable}>
<Card className={styles.card}> <Card className={styles.card}>
<UnpublishedListingCardMeta
meta={capitalize(pluralize(collection))}
label={isModification ? "" : "New"}
/>
<CardTitle <CardTitle
title={entry.getIn(['data', 'title'])} title={entry.getIn(['data', 'title'])}
subtitle={`by ${ author }`} subtitle={`by ${ author }`}

View File

@ -0,0 +1,25 @@
@import '../UI/theme.css';
.cardMeta {
display: flex;
align-items: center;
justify-content: space-between;
height: 34px;
padding: 0 16px;
margin-bottom: -6px;
font-size: .75em;
text-transform: uppercase;
background: var(--backgroundColorShaded);
}
.meta {}
.label {
padding: 5px 8px 4px 8px;
border-radius: var(--borderRadiusLarge);
background: var(--backgroundAltColor);
color: var(--defaultColorLight)
}

View File

@ -0,0 +1,17 @@
import React, { PropTypes } from 'react';
import styles from './UnpublishedListingCardMeta.css';
const UnpublishedListingCardMeta = ({ meta, label }) =>
<div className={styles.cardMeta}>
<span className={styles.meta}>{meta}</span>
{(label && label.length > 0)
? <span className={styles.label}>{label}</span>
: ""}
</div>;
UnpublishedListingCardMeta.propTypes = {
meta: PropTypes.string.isRequired,
label: PropTypes.string,
};
export default UnpublishedListingCardMeta;

View File

@ -1,3 +1,23 @@
import { zipObject } from 'lodash';
export const filterPromises = (arr, filter) => export const filterPromises = (arr, filter) =>
Promise.all(arr.map(entry => filter(entry))) Promise.all(arr.map(entry => filter(entry)))
.then(bits => arr.filter(entry => bits.shift())); .then(bits => arr.filter(entry => bits.shift()));
export const resolvePromiseProperties = obj =>
(new Promise((resolve, reject) => {
// Get the keys which represent promises
const promiseKeys = Object.keys(obj).filter(
key => obj[key] instanceof Promise);
const promises = promiseKeys.map(key => obj[key]);
// Resolve all promises
Promise.all(promises)
.then(resolvedPromises => resolve(
// Return a copy of obj with promises overwritten by their
// resolved values
Object.assign(obj, zipObject(promiseKeys, resolvedPromises))))
// Pass errors to outer promise chain
.catch(err => reject(err));
}));

View File

@ -1,3 +1,5 @@
import { isBoolean } from "lodash";
export function createEntry(collection, slug = '', path = '', options = {}) { export function createEntry(collection, slug = '', path = '', options = {}) {
const returnObj = {}; const returnObj = {};
returnObj.collection = collection; returnObj.collection = collection;
@ -8,5 +10,8 @@ export function createEntry(collection, slug = '', path = '', options = {}) {
returnObj.data = options.data || {}; returnObj.data = options.data || {};
returnObj.label = options.label || null; returnObj.label = options.label || null;
returnObj.metaData = options.metaData || null; returnObj.metaData = options.metaData || null;
returnObj.isModification = isBoolean(options.isModification)
? options.isModification
: null;
return returnObj; return returnObj;
} }